读书人

【光棍节热身技术分享】:让你大开眼界

发布时间: 2012-02-19 19:43:39 作者: rapoo

【光棍节热身技术分享】:让你大开眼界的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:

C/C++ code
#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;}


C/C++ code

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


第一个是服务器,第二个客户端。

首先启动服务器创建10个server在不同端口服务。
然后启动客户端创建10个client在同一个端口去connect 10个不同的服务器。

服务器立即回送done给客户端,发现数据可以正确的送给对应的客户端。

C/C++ code
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  



[解决办法]

[解决办法]
高级玩意 哥暂时看不懂,肯定是暂时的
[解决办法]
探讨

占位,学习!

[解决办法]
跟能发300分贴子的同学学习总是好的。
[解决办法]
很我的帖子,值得顶一下。
[解决办法]
TCP/IP协议详解中的代码都是linux下面的
代码很有艺术
[解决办法]
能来个Windows系统的吗?
[解决办法]
谢谢分享细节~顶起~
[解决办法]
关于四元组的,很多学习socket的书籍也有介绍的
[解决办法]
探讨

能来个Windows系统的吗?

[解决办法]
只能mark下。。
[解决办法]
TCP/IP协议详解 的确非常不错,可以没空读完!帮顶中!
[解决办法]

[解决办法]
探讨

关于四元组的,很多学习socket的书籍也有介绍的

[解决办法]
能力有限没看懂

读书人网 >C++

热点推荐