TCP端口复用 用于TCP穿透之中。
为了验证真实性,写了一个小例子。
试验了一个端口是否可以同时进行监听接收和发送 ,创建了四个进程。
原理:
1.设置::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));
2. ::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof (opt)); 这个设置可有可无
3.9997端口 同时绑定一个ip的两个socket,进行:一个listen,一个connect 9995
4.9995 listen,9996 connect9997
5.结果:9997 这个端口同时进行listen 和 connect。
注意:
操作系统如何区分一个socket的呢?
那就是:发送方IP、发送方Port、接收方IP、接收方Port、通信协议(Tcp/Udp),这也被称为五元素。
由这五个元素,我们就能知道为什么Tcp服务端的socket的端口号都相同而且能准确收发数据。
举个列子,如果在一个客端端程序中创建两个socket,如下表所示。
SOCKET 本方IP 本方Port 目的IP 目的Port 协议
sokcet1 127.0.0.1 8000 192.168.1.1 9000 Tcp
socket2 127.0.0.1 8000 192.168.1.1 10000
Tcp
因为目的Port不一致,所以操作系统能够区分两个socket,所以两个socket均能正常通信。
但是如果这五个元素都一直,则采用Tcp协议进行connect时,就会出现连接错误,因为五元素出现了重复,操作系统不能区别两个socket。
也就是说,只要五元素不完全一致,操作系统就能区分socket。
进行收发的时候,双方都会各自创建两个socket,一个用于收一个用于发,这就会导致,通信时候,五元素会重复。所以,
一个端口接收的地址和 其发送的端口,目标地址不能相同。
问题: 9998 -》9996 listen的socket 和 9998 -》9996 connect的socket 相同 。所以这个理论有待考究。
但是TCP通信是双向的 两个socket建立连接之后 便不再去管listen或connect。
代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 100
int main(int argc, char ** argv) {
pid_t pp = fork();
if (pp == 0) {
unsigned short port = 9997;
unsigned short port2 = 9995;
//socket
int accept_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); //创建socket,文件描述符是accept_fd
int accept_fd2 = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); //创建socket,文件描述符是accept_fd
//端口重用,快速重启服务
int opt = 1;
::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
::setsockopt(accept_fd2, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
::setsockopt(accept_fd2, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
//bind
sockaddr_in addr;
memset(&addr, 0, sizeof (sockaddr_in));
addr.sin_family = AF_INET; //选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址
addr.sin_port = htons(port); //inet_pton:将“点分十进制” -> “二进制整数”
::inet_pton(AF_INET, "192.168.23.232", &addr.sin_addr); //bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
sockaddr_in addr2;
memset(&addr2, 0, sizeof (sockaddr_in));
addr2.sin_family = AF_INET; //选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址
addr2.sin_port = htons(port2); //inet_pton:将“点分十进制” -> “二进制整数”
::inet_pton(AF_INET, "192.168.23.232", &addr2.sin_addr); //bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
if (0 != ::bind(accept_fd, (sockaddr*) & addr, (socklen_t)sizeof (sockaddr_in)))//即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
{//addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同(IPV4/6)
perror("bind 1failed"); //通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
return false;
}
if (0 != ::bind(accept_fd2, (sockaddr*) & addr, (socklen_t)sizeof (sockaddr_in)))//即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
{//addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同(IPV4/6)
perror("bind1 failed"); //通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
return false;
}
pid_t p1 = fork();
if (p1 == 0) {
if (0 != ::listen(accept_fd, SOMAXCONN)) //要监听的描述字,最大连接数。listen是被动的,等待客户的请求。
{
perror("listen failed");
return false;
}
printf("lisen 2885 port ok\n");
socklen_t addrlen = (socklen_t)sizeof (addr);
while (1) {
memset(&addr, 0, sizeof (sockaddr_in));
int client_fd = ::accept(accept_fd, (sockaddr*) & addr, &addrlen);
char* a = inet_ntoa(addr.sin_addr);
printf("accept,%d,%s,%d\n", client_fd, a, addr.sin_port);
//失败
if (client_fd < 0)
printf("not ok\n");
}
sleep(10);
} else {
sleep(3);
//printf("gogogo1\n");
//连接
int ret = ::connect(accept_fd2, (sockaddr*) & addr2, (socklen_t)sizeof (sockaddr_in));
char* a = inet_ntoa(addr2.sin_addr);
printf(" connect 2855 ret =%d,%hu,%s\n", ret, addr2.sin_port, a);//9995
}
sleep(10);
close(accept_fd2);
close(accept_fd);
} else {
unsigned short port = 9995;
unsigned short port2 = 9997;
unsigned short port3 = 9996;
//socket
int accept_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); //创建socket,文件描述符是accept_fd
int accept_fd2 = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); //创建socket,文件描述符是accept_fd
//端口重用,快速重启服务
int opt = 1;
::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
::setsockopt(accept_fd2, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
::setsockopt(accept_fd2, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof (opt)); //accept_fd套接字描述符/ SO_REUSEADDR,打开或关闭地址复用功能。/
//bind
sockaddr_in addr;
memset(&addr, 0, sizeof (sockaddr_in));
addr.sin_family = AF_INET; //选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址
addr.sin_port = htons(port); //inet_pton:将“点分十进制” -> “二进制整数”
::inet_pton(AF_INET, "192.168.23.232", &addr.sin_addr); //bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
sockaddr_in addr2;
memset(&addr2, 0, sizeof (sockaddr_in));
addr2.sin_family = AF_INET; //选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址
addr2.sin_port = htons(port2); //inet_pton:将“点分十进制” -> “二进制整数”
::inet_pton(AF_INET, "192.168.23.232", &addr2.sin_addr); //bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
sockaddr_in addr3;
memset(&addr3, 0, sizeof (sockaddr_in));
addr3.sin_family = AF_INET; //选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址
addr3.sin_port = htons(port3); //inet_pton:将“点分十进制” -> “二进制整数”
::inet_pton(AF_INET, "192.168.23.232", &addr3.sin_addr); //bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
if (0 != ::bind(accept_fd, (sockaddr*) & addr, (socklen_t)sizeof (sockaddr_in)))//即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
{//addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同(IPV4/6)
perror("bind 2failed"); //通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
return false;
}
if (0 != ::bind(accept_fd2, (sockaddr*) & addr3, (socklen_t)sizeof (sockaddr_in)))//即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
{//addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同(IPV4/6)
perror("bind 2failed"); //通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
return false;
}
pid_t p = fork();
if (p == 0) {
if (0 != ::listen(accept_fd, SOMAXCONN)) //要监听的描述字,最大连接数。listen是被动的,等待客户的请求。
{
perror("listen failed");
return false;
}
printf("lisen 3367 port ok\n");
socklen_t addrlen = (socklen_t)sizeof (addr);
while (1) {
memset(&addr, 0, sizeof (sockaddr_in));
int client_fd = ::accept(accept_fd, (sockaddr*) & addr, &addrlen);
char* a = inet_ntoa(addr.sin_addr);
printf("accept,%d,%s,%d\n", client_fd, a, addr.sin_port);
//失败
if (client_fd < 0)
printf("not ok\n");
}
sleep(10);
} else {
sleep(3);
int ret = ::connect(accept_fd2, (sockaddr*) & addr2, (socklen_t)sizeof (sockaddr_in));
char* a = inet_ntoa(addr2.sin_addr);
printf(" connect 3367 ret =%d,%hu,%s\n", ret, addr2.sin_port, a);
}
sleep(10);
close(accept_fd2);
close(accept_fd);
}
}