TCP端口复用

 

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);
	}



}

 

你可能感兴趣的:(C++)