【光棍节热身技术分享】:让你大开眼界的TCP连接问题。
这两天没事看《TCP/IP协议详解-卷1》,把那些TFTP,FTP,TELNET,RPC/NFS都看了一下。
终于又长见识了,一句话总结: 一个TCP连接是由4元组:
[remote IP,remote port,local IP,local port] 唯一决定的。
人人皆知的做法:
服务器:bind在固定端口PORT等待连接。
客户端:connect服务器的固定端口PORT。
结果:客户端内核自动分配local port,服务端accept得到连接SOCKET,并且这些SOCKET的端口都是PORT。
长久以来的疑惑:尼玛所有的客户端都往服务端的PORT上发包,凭什么就知道送给哪个SOCKET呢?
解惑:一个TCP连接是由4元组:
[remote IP,remote port,local IP,local port] 唯一决定的。
服务端IP/PORT都一样,但客户端使用的PORT各不相同,所以服务端各SOCKET对应唯一的TCP连接。
这个概念是在看FTP得时候学到的:
FTP服务器提供端口20做控制命令,FTP客户端创建SOCKET BIND(0)在临时端口PORT做数据通道等待连接,并将IP/PORT发往服务器20端口,服务器创建一个SOCKET,bind在21端口向客户端PORT发起主动连接。
当大量客户端请求数据时,服务器创建无数个SOCKET,都bind在21端口,然后向不同的客户端发起主动连接,通过SO_REUSEADDR选项可以实现。
这是不是很郁闷,一台服务器上bind一堆20端口,然后向外connect不同的客户端(和TCP accept类似)。
这是因为虽然服务器的一堆SOCKET都在20端口,但remote ip/port是不同的,所以4元组不同就是不同的链接。
讲解完毕,如果对remote ip/port 和 local ip/port 还没深刻理解的盆友需要先去理解一下再来理解这个。
(UDP无连接,如果你让N个UDP SOCKET bind在同一个PORT,那外边来的包注定不知道给哪个SOCKET,这就是有连接和无连接的区别。 有连接是4元组,无连接是2元组)
极限测试代码如下,linux:
#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/wait.h>#include <unistd.h>#define BASE_PORT 15555 #define SRV_IP "127.0.0.1"int main(){ for(int i=0;i<10;++i) { pid_t pid; if( (pid=fork())>0 ) { printf("child server %d is serving now ! \n",pid); } else if(pid==0) { struct sockaddr_in srvAddr; srvAddr.sin_family=AF_INET; srvAddr.sin_port=htons(BASE_PORT+i); srvAddr.sin_addr.s_addr=inet_addr(SRV_IP); int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd==-1) { perror("socket"); exit(127); } int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))==-1) { perror("setsockopt"); exit(127); } if(bind(listenfd,(struct sockaddr*)&srvAddr,sizeof(srvAddr))==-1) { perror("bind"); exit(127); } if(listen(listenfd,5)==-1) { perror("listen"); exit(127); } char buffer[100]; struct sockaddr_in cliAddr; socklen_t len=sizeof(cliAddr); int clifd=accept(listenfd,(struct sockaddr*)&cliAddr,&len); if(clifd==-1) { perror("clifd"); exit(127); } send(clifd,"done",5,0); close(clifd); close(listenfd); exit(0); } else { exit(127); } } for(int i=0;i<10;++i) { pid_t pid=wait(NULL); printf("server pid %d exit !\n",pid); } return 0;}
#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/wait.h>#include <unistd.h>#define BASE_PORT 15555 #define SRV_IP "127.0.0.1"int main(){ struct sockaddr_in cliAddr; cliAddr.sin_family=AF_INET; cliAddr.sin_port=htons(19999); cliAddr.sin_addr.s_addr=inet_addr(SRV_IP); for(int i=0;i<10;++i) { pid_t pid; if( (pid=fork())>0 ) { printf("child cilent %d is running now ! \n",pid); } else if(pid==0) { struct sockaddr_in srvAddr; srvAddr.sin_family=AF_INET; srvAddr.sin_port=htons(BASE_PORT+i); srvAddr.sin_addr.s_addr=inet_addr(SRV_IP); int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd==-1) { perror("socket"); exit(127); } int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))==-1) { perror("setsockopt"); exit(127); } if(bind(listenfd,(struct sockaddr*)&cliAddr,sizeof(cliAddr))==-1) { perror("bind"); exit(127); } char buffer[100]; int ret=connect(listenfd,(struct sockaddr*)&srvAddr,sizeof(srvAddr)); if(ret==-1) { perror("connect"); exit(127); } int n=recv(listenfd,buffer,100,0); buffer[n]='\0'; printf("client %d recv str from port %d : %s \n",i,BASE_PORT+i,buffer); close(listenfd); exit(0); } else { exit(127); } } return 0;}
owenliang@linux-7lsl:~/csdn/src/4yuan> child server 7595 is serving now ! child server 7596 is serving now ! child server 7597 is serving now ! child server 7598 is serving now ! child server 7599 is serving now ! child server 7600 is serving now ! child server 7601 is serving now ! child server 7602 is serving now ! child server 7603 is serving now ! child server 7604 is serving now ! ./clientchild cilent 7606 is running now ! child cilent 7607 is running now ! child cilent 7608 is running now ! child cilent 7609 is running now ! child cilent 7610 is running now ! child cilent 7611 is running now ! child cilent 7612 is running now ! child cilent 7613 is running now ! child cilent 7614 is running now ! child cilent 7615 is running now ! owenliang@linux-7lsl:~/csdn/src/4yuan> server pid 7601 exit !client 6 recv str from port 15561 : done server pid 7602 exit !client 7 recv str from port 15562 : done server pid 7600 exit !client 5 recv str from port 15560 : done server pid 7603 exit !client 8 recv str from port 15563 : done server pid 7599 exit !server pid 7604 exit !client 9 recv str from port 15564 : done server pid 7598 exit !client 3 recv str from port 15558 : done server pid 7597 exit !client 2 recv str from port 15557 : done client 4 recv str from port 15559 : done server pid 7596 exit !client 1 recv str from port 15556 : done server pid 7595 exit !client 0 recv str from port 15555 : done