socket例程

来源:互联网 发布:网上买菜 知乎 编辑:程序博客网 时间:2024/04/28 23:05

http://blog.csdn.net/stalendp/article/details/12272205


这篇文章将对一个简单的socket例程进行剖析。

代码

这里提供的例子就是一个简单的TCP client/server程序,client主动连接server,并从服务器中得到一条欢迎消息:“[server] welcome client!”。程序的流程参考如下:


参考代码如下:

server端:

[cpp] view plaincopy
  1. #include <sys/socket.h>  
  2. #include <netinet/in.h>  
  3. #include <arpa/inet.h>  
  4. #include <stdio.h>  
  5. #include <stdlib.h>  
  6. #include <unistd.h>  
  7. #include <errno.h>  
  8. #include <string.h>  
  9. #include <sys/types.h>  
  10.   
  11. int main(void)  
  12. {  
  13.     int listenfd = 0,connfd = 0;  
  14.     struct sockaddr_in serv_addr;  
  15.     char sendBuff[1025];  
  16.       
  17.     listenfd = socket(AF_INET, SOCK_STREAM, 0);  
  18.       
  19.     memset(&serv_addr, '0'sizeof(serv_addr));  
  20.     memset(sendBuff, '0'sizeof(sendBuff));  
  21.     serv_addr.sin_family = AF_INET;  
  22.     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  23.     serv_addr.sin_port = htons(55555);  
  24.       
  25.     bind(listenfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr));  
  26.         printf("server start successfully!\n");  
  27.       
  28.     if(listen(listenfd, 10) == -1){  
  29.         printf("Failed to listen\n");  
  30.         return -1;  
  31.     }  
  32.       
  33.     while(1) {  
  34.         connfd = accept(listenfd, (struct sockaddr*)NULL ,NULL); // accept awaiting request  
  35.           
  36.         strcpy(sendBuff, "[server] welcome client!");  
  37.         write(connfd, sendBuff, strlen(sendBuff));  
  38.           
  39.         close(connfd);  
  40.         sleep(1);  
  41.         }  
  42.       
  43.     return 0;  
  44. }  

客户端代码:

[cpp] view plaincopy
  1. #include <sys/socket.h>  
  2. #include <sys/types.h>  
  3. #include <netinet/in.h>  
  4. #include <netdb.h>  
  5. #include <stdio.h>  
  6. #include <string.h>  
  7. #include <stdlib.h>  
  8. #include <unistd.h>  
  9. #include <errno.h>  
  10. #include <arpa/inet.h>  
  11.   
  12. int main(void)  
  13. {  
  14.     int sockfd = 0,n = 0;  
  15.     char recvBuff[1024];  
  16.     struct sockaddr_in serv_addr;  
  17.       
  18.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0))< 0) {  
  19.         printf("\n Error : Could not create socket \n");  
  20.         return 1;  
  21.     }  
  22.       
  23.     serv_addr.sin_family = AF_INET;  
  24.     serv_addr.sin_port = htons(55555);  
  25.     serv_addr.sin_addr.s_addr = inet_addr("192.168.1.101");   
  26.     if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))<0)  
  27.     {  
  28.         printf("\n Error : Connect Failed \n");  
  29.         return 1;  
  30.     }  
  31.       
  32.     memset(recvBuff, '0' ,sizeof(recvBuff));  
  33.     while((n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0) {  
  34.         recvBuff[n] = 0;  
  35.         if(fputs(recvBuff, stdout) == EOF) {  
  36.             printf("\n Error : Fputs error");  
  37.         }  
  38.         printf("\n");  
  39.     }  
  40.       
  41.     if( n < 0) {  
  42.         printf("\n Read Error \n");  
  43.     }  
  44.       
  45.     return 0;  
  46. }  


原理

先谈谈服务器端的listen的原理,listen函数的声明如下:

[cpp] view plaincopy
  1. int listen(int sockfd, int backlog);  

其中backlog参数表明服务器内核能够同时承受的最大的连接数(这里的例子是10)。继续深入backlog。其实内核维护了两个队列:1)连接未完成队列(incomplete connection queue); 2) 连接完成队列(completed connection queue)。 "连接未完成队列"可以理解为空闲队列,当客户端到来,将占用一个 "连接未完成队列",与服务器进行3次握手成功后,将从“连接未完成队列”移动到“连接完成队列”中。如下图所示:


接下来讨论客户端和服务器端的三次握手连接。

上图描述的情况简述如下:在三次握手之前,服务器端进行了socket,bind和listen函数的调用,在内核中设立一个socket资源,并准备了相关的连接队列,等待客户端连接的到来;之后服务器就阻塞在accept函数上面。当客户端准备好要连接的服务信息(即为struct sockaddr_in结构体指定特定的值),并传给connect来实现和服务器的三次握手,并确立tcp连接(server端accept函数将返回,客户端connect将返回)。

建立tcp连接之后,就是数据的通讯了,在通讯结束后,要关闭连接,由于tcp是全双工的,要关闭读和写,每次操作要确认,所以关闭tcp需要四次数据包的传递。如下图:


实验

原理介绍清楚了,到了进行试验的时候了。这里,我将使用Wireshark工具在mac平台上进行试验,具体如下:

1. 工具的准备。Wireshark可以在官网上下载到。

2.  在mac运行,还需要设置一下权限,否则Wireshark无法访问网卡(参考《No Interfaces Available In Wireshark Mac OS X》)。在控制台运行如下命令:

[cpp] view plaincopy
  1. sudo chmod 644 /dev/bpf*  
3. 配置Wireshark;在Wireshark的工具栏点击,选择相应的网卡(这里走本地网卡,所以选择lo0),如下图:

4. 由于使用xcode进行调试代码,调试信息也是走本地网络的,所以,我要做一些过滤,以便更好地观察试验结果。具体在工具栏下面的Filter下,填写过滤条件:

[cpp] view plaincopy
  1. ip.addr==192.168.1.101  

这里表明将只显示目标或者源地址中包含192.168.1.101的数据包(调试信息,地址为127.0.0.1,所以可以被过滤掉)。

点击开始按钮,就可以对数据包进行监听了。

在这个试验中,我在xcode中设置了几个断点,具体如下:

服务器端:


客户端:


这里约定,服务器代码第36行记作s36, 客户端代码第25行记作c25。

确保WireShark启动的状态下,启动服务器端(这时会阻塞在s34,accept函数处),然后启动客户端。

1)当代码运行到c25行时,wireShark并没有纪录到任何信息。

2)当客户端运行到c33时(执行完connect函数后,客户端将阻塞在read函数上),服务器运行到s36。这样服务器和客户端建立了tcp连接,wireShark纪录到了三次握手的信息,如下:


3)当服务器端运行到s39,服务器端就向客户端发送了“[server] welcome client!”字符串,这时候客户端从c33唤醒,运行到c38(经过read的调用,程序从内核中取出数据,并打印,运行过c38后,客户端又被阻塞在c33行,等待服务器的消息)。这个时候,wireShark纪录到数据的传输,如下图:


4)当服务器端运行过s39,执行完close之后,客户端将从c33唤醒,这个时候的返回值为0,跳出循环到c41;wireShark纪录到结束的四次数据包交换,如下图:


ok,tcp的剖析暂时到这里,接下来我将写点select,poll等相关的nonblock的文章。


参考:

《Unix Networking Programming Volume 1, Third Edition》

https://langui.sh/2010/01/31/no-interfaces-available-in-wireshark-mac-os-x/

0 0