Socket编程实践(6) --TCP服务端注意事项

僵尸进程处理

1)通过忽略SIGCHLD信号,避免僵尸进程

    在server端代码中添加

    signal(SIGCHLD, SIG_IGN);

 

2)通过wait/waitpid方法,解决僵尸进程

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. signal(SIGCHLD,onSignalCatch);  
  2.   
  3. void onSignalCatch(int signalNumber)  
  4. {  
  5.     wait(NULL);  
  6. }  

3) 如果多个客户端同时关闭, 问题描述如下面两幅图所示:


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** client端实现的测试代码**/  
  2. int main()  
  3. {  
  4.     int sockfd[50];  
  5.     for (int i = 0; i < 50; ++i)  
  6.     {  
  7.         if ((sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1)  
  8.             err_exit("socket error");  
  9.   
  10.         struct sockaddr_in serverAddr;  
  11.         serverAddr.sin_family = AF_INET;  
  12.         serverAddr.sin_port = htons(8001);  
  13.         serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  14.         if (connect(sockfd[i], (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1)  
  15.             err_exit("connect error");  
  16.     }  
  17.     sleep(20);  
  18. }  

在客户运行过程中按下Ctrl+C,则可以看到在server端启动50个子进程,并且所有的客户端全部一起断开的情况下,产生的僵尸进程数是惊人的(此时也证明了SIGCHLD信号是不可靠的)!


解决方法-将server端信号捕捉函数改造如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void sigHandler(int signo)  
  2. {  
  3.     while (waitpid(-1, NULL, WNOHANG) > 0)  
  4.         ;  
  5. }  

waitpid返回值解释:

  on  success,  returns the process ID of the child whose state has changed(返回已经结束运行

的子进程的PID); if WNOHANG was specified and one or more child(ren) specified by pid exist, 

but have not yet changed state, then 0 is returned(如果此时尚有好多被pid参数标识的子进程存在

且没有结束的迹象, 返回0).  On error, -1 is returned.

 

地址查询API

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <sys/socket.h>  
  2. int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //获取本地addr结构  
  3. int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //获取对方addr结构  
  4.   
  5. int gethostname(char *name, size_t len);  
  6. int sethostname(const char *name, size_t len);  
  7.   
  8. #include <netdb.h>  
  9. extern int h_errno;  
  10. struct hostent *gethostbyname(const char *name);  
  11.   
  12. #include <sys/socket.h>       /* for AF_INET */  
  13. struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);  
  14. struct hostent *gethostent(void);  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //hostent结构体  
  2. struct hostent  
  3. {  
  4.     char  *h_name;            /* official name of host */  
  5.     char **h_aliases;         /* alias list */  
  6.     int    h_addrtype;        /* host address type */  
  7.     int    h_length;          /* length of address */  
  8.     char **h_addr_list;       /* list of addresses */  
  9. }  
  10. #define h_addr h_addr_list[0] /* for backward compatibility */  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /**获取本机IP列表**/  
  2. int gethostip(char *ip)  
  3. {  
  4.     struct hostent *hp = gethostent();  
  5.     if (hp == NULL)  
  6.         return -1;  
  7.   
  8.     strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));  
  9.     return 0;  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     char host[128] = {0};  
  15.     if (gethostname(host, sizeof(host)) == -1)  
  16.         err_exit("gethostname error");  
  17.   
  18.     cout << "host-name: " << host << endl;  
  19.     struct hostent *hp = gethostbyname(host);  
  20.     if (hp == NULL)  
  21.         err_exit("gethostbyname error");  
  22.   
  23.     cout << "ip list: " << endl;  
  24.     for (int i = 0; hp->h_addr_list[i] != NULL; ++i)  
  25.     {  
  26.         cout << '\t'  
  27.              << inet_ntoa(*(struct in_addr*)hp->h_addr_list[i]) << endl;  
  28.     }  
  29.   
  30.     char ip[33] = {0};  
  31.     gethostip(ip);  
  32.     cout << "local-ip: " << ip << endl;  
  33. }  

TCP协议的11种状态 


1.如下图(客户端与服务器都在本机:双方(server的子进程,与client)链接已经建立(ESTABLISHED),等待通信)

 

2.最先close的一端,会进入TIME_WAIT状态; 而被动关闭的一端可以进入CLOSE_WAIT状态 (下图,server端首先关闭)

 

3.TIME_WAIT 时间是2MSL(报文的最长存活周期的2倍) 

  原因:(ACK y+1)如果发送失败可以重发, 因此如果server端不设置地址重复利用的话, 服务器在短时间内就无法重启;

    服务器端处于closed状态,不等于客户端也处于closed状态。

(下图, client先close, client出现TIME_WAIT状态)

 

4.TCP/IP协议的第1种状态:图上只包含10种状态,还有一种CLOSING状态

产生CLOSING状态的原因:

Server端与Client端同时关闭(同时调用close,此时两端同时给对端发送FIN包),将产生closing状态,最后双方都进入TIME_WAIT状态(如下图)。


SIGPIPE信号

往一个已经接收FIN的套接中写是允许的,接收到FIN仅仅代表对方不再发送数据;但是在收到RST段之后,如果还继续写,调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略即可。

    signal(SIGPIPE, SIG_IGN); 

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 测试: 在Client发送每条信息都发送两次 
  2. 当Server端关闭之后Server端会发送一个FIN分节给Client端, 
  3. 第一次消息发送之后, Server端会发送一个RST分节给Client端,  
  4. 第二次消息发送(调用write)时, 会产生SIGPIPE信号; 
  5. 注意: Client端测试代码使用的是下节将要介绍的Socket库 
  6. **/  
  7. void sigHandler(int signo)  
  8. {  
  9.     if (SIGPIPE == signo)  
  10.     {  
  11.         cout << "receive SIGPIPE = " << SIGPIPE << endl;  
  12.         exit(EXIT_FAILURE);  
  13.     }  
  14. }  
  15. int main()  
  16. {  
  17.     signal(SIGPIPE, sigHandler);  
  18.     TCPClient client(8001, "127.0.0.1");  
  19.     try  
  20.     {  
  21.         std::string msg;  
  22.         while (getline(cin, msg))  
  23.         {  
  24.             client.send(msg);  
  25.             client.send(msg);   //第二次发送  
  26.             msg.clear();  
  27.             client.receive(msg);  
  28.             client.receive(msg);  
  29.             cout << msg << endl;  
  30.             msg.clear();  
  31.         }  
  32.     }  
  33.     catch (const SocketException &e)  
  34.     {  
  35.         cerr << e.what() << endl;  
  36.     }  
  37. }  

close与shutdown的区别

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <unistd.h>  
  2. int close(int fd);  
  3.   
  4. #include <sys/socket.h>  
  5. int shutdown(int sockfd, int how);  

shutdown的how参数

SHUT_RD

关闭读端

SHUT_WR

关闭写端

SHUT_RDWR

读写均关闭

1.close终止了数据传送的两个方向;

  而shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向。

2.shutdown how=SHUT_WR(关闭写端)可以保证对等方接收到一个EOF字符(FIN段),而不管是否有其他进程已经打开了套接字(shutdown并没采用引用计数)。

  而close需要等待套接字引用计数减为0时才发送FIN段。也就是说直到所有的进程都关闭了该套接字。

 

示例分析:

   客户端向服务器按照顺序发送:FIN E D C B A, 如果FIN是当client尚未接收到ABCDE之前就调用close发送的, 那么client端将永远接收不到ABCDE了, 而通过shutdown函数, 则可以有选择的只关闭client的发送端而不关闭接收端, 则client端还可以接收到ABCDE的信息;


/**测试: 实现与上面类似的代码(使用close/shutdown)两种方式实现 **/

完整源代码请参照:

http://download.csdn.net/detail/hanqing280441589/8486517

注意: 最好读者需要有select的基础, 没有select基础的读者可以参考<Socket编程实践(8)>相关部分

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值