【Linux】【网络】不同子网下的客户端和服务器通信
前两天在进行socket()网络编程并进行测试时,发现在不同wifi下两个电脑无法进行连接,大概去查找了如何解决 看到可以使用
frp 这个快速反向代理实现。
- frp 可让您将位于 NAT 或防火墙后面的本地服务器暴露到互联网。它目前支持TCP和UDP,以及HTTP和HTTPS协议,允许通过域名将请求转发到内部服务。
- github官网:https://github.com/fatedier/frp?tab=readme-ov-file#tcp-stream-multiplexing
先提一些基础概念,为什么在不同wifi下无法进行通信:
不同wifi下无法进行通信原因
1公网 IP与 内网 IP
IP地址是网络中的唯一标识符,用于区分网络中不同设备的位置。
- 公网 IP地址由互联网服务提供商(ISP)分配给用户,可以唯一地标识全球设备,以实现互联网上的通信。
- 内网 IP地址旨在用于组织内部网络,不会在全球互联网上路由,从而确保内部网络的安全。
1.1公网 IP
定义:公网 IP 是由互联网服务提供商(ISP)分配给你设备的 IP 地址,它是唯一的,且在互联网上是可以直接访问的。
作用:公网 IP 用于设备之间的通信,允许设备通过互联网相互访问。例如,当你访问一个网站时,浏览器会使用你的公网 IP 发送请求。
- 可以直接在互联网上被访问。
- 每个公网 IP 是唯一的,全球只有一个设备可以使用一个特定的公网 IP 地址。
- 公网 IP 的分配是有限的,IP 地址资源稀缺。
1.2 内网 IP
定义:内网 IP 是在局域网(LAN)内部使用的 IP 地址,它们不能在互联网上直接访问。内网设备可以通过 NAT(网络地址转换)技术与公网通信,但公网设备无法直接访问内网设备。
作用:内网 IP 用于局域网内设备之间的通信,通常由路由器通过 DHCP(动态主机配置协议)分配。
- 只能在局域网内使用,无法直接访问互联网。
- 由于内网 IP 地址的重复性,多个局域网可以使用相同的内网 IP 地址。
- 内网 IP地址范围是预定义的,常见的内网 IP 地址段有:
Class A:10.0.0.0 ~ 10.255.255.255
Class B:172.16.0.0 ~ 172.31.255.255
Class C:192.168.0.0 ~ 192.168.255.255
1.3区分公网 IP 和内网 IP
可以通过以下几个步骤来分辨一个 IP 地址是公网 IP 还是内网 IP:
-
a. 查看 IP 地址是否在内网地址范围内
内网 IP 地址有固定的地址段,如果某个 IP 地址属于以下任意一个范围,那么它就是内网 IP:
10.0.0.0 ~ 10.255.255.255 (Class A)
172.16.0.0 ~ 172.31.255.255 (Class B)
192.168.0.0 ~ 192.168.255.255 (Class C) -
b. 检查是否可通过外部访问
如果你能够直接访问某个 IP 地址并且该地址位于互联网,那么它是公网 IP。
内网 IP 地址只能在同一局域网内使用,无法通过互联网直接访问。
解决方案
拥有一台有公网IP的云服务器作为中转站,将局域网下的电脑将数据信息发送给中转的服务器,然后这个中转的服务器将收到的数据转给另外一台电脑,这样就可以实现两台电脑之间的互相通信。
原因:我们可以实现在局域网下的通信而不能在不是同一局域网下的通信是因为,不同的私网之间是无法通信的,我们使用的192.168.x.x都是私网,但是所有的私网却都可以和公网ip直接通信的。所以。想要在两个私网之间通信的话,我们就需要多一个步骤,也就是需要一个公网的IP作为中转站。
1云服务器
我的是阿里云服务器 当然只要是云服务器都可以
2 frp
2.1 云服务器上的配置
github下载frp网站:https://github.com/fatedier/frp/releases?page=5
因为我参考的文档使用的是0.33.0版本 我也就用了这个
ps:需要注意你的云服务器架构是什么
- ARM架构下arm版本
- x86_64架构选择amd 版本 我是这个版本的
可以使用命令下载:
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz
然后解压缩:
tar xzvf frp_0.33.0_linux_amd64.tar.gz
将解压后的文件重命名:
mv frp_0.33.0_linux_amd64 frp
查看文件内容:
cd frp
ls
frp 默认给出两个服务端配置文件,一个是简版的 frps.ini,另一个是完整版本 frps_full.ini。我们这里通过简版的 frps.ini配置,快速的搭建起一个 frp服务端。
查看frps.ini的配置:
cat frps.ini
#输出
[common]
bind_port = 7000
由于默认配置中监听的是 7000 端口,但是用户可根据自己实际情况修改,我这里就没有修改了
启动frp服务端:
./frps -c ./frps.ini
输出:
root@iZ2vc4j4f5dy4g5cif3dnxZ:~/frp_set/frp# ./frps -c ./frps.ini
2025/02/20 14:13:02 [I] [service.go:178] frps tcp listen on 0.0.0.0:7000
2025/02/20 14:13:02 [I] [root.go:209] start frps success
2025/02/20 14:13:09 [I] [service.go:432] [e17730b293054812] client login info: ip [223.104.11.108:15309] version [0.33.0] hostname [] os [linux] arch [amd64]
2.2 云服务器上打开对应端口
可参考这个:https://blog.csdn.net/aa390481978/article/details/96837655
服务器配置
同样先下载,再解压,然后修改配置文件,之后启动
下载:
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz
解压缩:
tar xzvf frp_0.33.0_linux_amd64.tar.gz
将解压后的文件重命名:
mv frp_0.33.0_linux_amd64 frp
修改查看frpc.ini的配置:
[common]
server_addr = your_server_ip # 公网服务器 IP 地址
server_port = 7000 # FRP 服务器端口
[ssh]
type = tcp
local_ip = 192.168.x.x # 局域网电脑的 IP 地址
local_port = 23 # 局域网中要转发的服务端口(例如 SSH)
remote_port = 6001 # 在公网服务器上暴露的端口
server_addr是你服务器的公网ip
启动frp服务端:
./frpc -c ./frpc.ini
输出:
025/02/20 14:17:46 [I] [service.go:282] [c78168a348dd212e] login to server success, get run id [c78168a348dd212e], server udp port [0]
2025/02/20 14:17:46 [I] [proxy_manager.go:144] [c78168a348dd212e] proxy added: [ssh]
2025/02/20 14:17:46 [I] [control.go:179] [c78168a348dd212e] [ssh] start proxy success
客户端配置
同样先下载,再解压,然后修改配置文件,之后启动
下载:
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz
解压缩:
tar xzvf frp_0.33.0_linux_amd64.tar.gz
将解压后的文件重命名:
mv frp_0.33.0_linux_amd64 frp
修改查看frpc.ini的配置:
[common]
server_addr = your_server_ip # 公网服务器 IP 地址
server_port = 7000 # FRP 服务器端口
[ssh]
type = tcp
local_ip = 192.168.x.x # 局域网电脑的 IP 地址
local_port = 22 # 局域网中要转发的服务端口(例如 SSH)
remote_port = 6000 # 在公网服务器上暴露的端口
server_addr是你服务器的公网ip
启动frp服务端:
./frpc -c ./frpc.ini
输出:
注意事项
- 1 客户端和服务器的local_port = 22,remote_port = 6000 要设置为不一致的
- 2 客户端和服务器的名称也要设置为不一致的 ssh和ssh1
- 3 云服务器打开对应端口
ps:这边1,2设置为一致的是会导致 端口号 名称被占用的问题 但是按理说不应该 这边后续可以再试试
[W] [control.go:177] [0a1ee9193b4f3b0e] [ssh] start error: proxy name [ssh] is already in use
[W] [control.go:177] [0b9fe4453b7ae8ea] [ssh1] start error: port already used
3测试连接
配置完成后 测试客户端和服务器能否连接成功
下面是我的测试代码:
3.1 服务器
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
const char* host_ip = "192.168.xx.xx"; // A 局域网服务器的内网 IP 地址
int host_port = 23; // 局域网服务端口
// 创建 socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
std::cerr << "Socket creation failed!" << std::endl;
return -1;
}
sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(host_port);
server_address.sin_addr.s_addr = inet_addr(host_ip);
// 绑定并监听
if (bind(server_socket, (sockaddr*)&server_address, sizeof(server_address)) < 0) {
std::cerr << "Binding failed!" << std::endl;
return -1;
}
if (listen(server_socket, 5) < 0) {
std::cerr << "Listening failed!" << std::endl;
return -1;
}
std::cout << "Server A is waiting for connections..." << std::endl;
// 接受连接
sockaddr_in client_address;
socklen_t client_len = sizeof(client_address);
int client_socket = accept(server_socket, (sockaddr*)&client_address, &client_len);
if (client_socket < 0) {
std::cerr << "Connection acceptance failed!" << std::endl;
return -1;
}
std::cout << "Connection established with " << inet_ntoa(client_address.sin_addr) << std::endl;
char buffer[1024];
int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); // 接收数据
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
std::cout << "Received from client: " << buffer << std::endl;
}
const char* response = "Hello, Client B!";
send(client_socket, response, strlen(response), 0); // 发送响应
// 关闭连接
close(client_socket);
close(server_socket);
return 0;
}
3.2 客户端
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
const char* server_ip = "xx.xx.xx.xx"; // 云服务器的公网 IP
int server_port = 6001; // FRP 映射的端口
// 创建 socket
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
std::cerr << "Socket creation failed!" << std::endl;
return -1;
}
sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(server_port);
server_address.sin_addr.s_addr = inet_addr(server_ip);
// 连接到服务器
if (connect(client_socket, (sockaddr*)&server_address, sizeof(server_address)) < 0) {
std::cerr << "Connection failed!" << std::endl;
return -1;
}
const char* message = "Hello, Server A!";
send(client_socket, message, strlen(message), 0); // 发送数据
char buffer[1024];
int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); // 接收数据
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
std::cout << "Received from server: " << buffer << std::endl;
}
// 关闭连接
close(client_socket);
return 0;
}
连接结果
注意事项
- 服务器绑定的端口是23 小于1024 要切换到管理员状态 否则会绑定失败
参考文章:
https://blog.csdn.net/weixin_44917390/article/details/106685219
https://blog.csdn.net/weixin_51354739/article/details/144422320
https://blog.csdn.net/qq_34623639/article/details/140506034
下个文章说一下frp具体是如何实现能够将客户端的连接准确转发给服务器的