网络编程(28)—— socket的close函数以及shutdown函数在多进程服务器中的不同表现(一)
2016-12-06 20:35
381 查看
这几天遇到了一个很奇怪的问题,编写了一个读写IO分离的多进程socket客户端,主要功能是在成功connect服务端后,fork一个子进程,在子进程中不断的提示用户输入字符串(输入Q或者q会退出程序),然后发给服务端。然后再父进程中不断的读,读取从客户端发送过来的数据。代码如下:
正常编译上述代码,成功生成可执行文件,然后运行客户端,输入q却无法退出程序:
按照我的设想,在标准输入q后,子进程(write_handling)会结束死循环,然后回到主程序代码执行 close(sock)代码,然后客户端发送一个EOF结束符给服务端表明要断开连接,服务端接收到EOF后,也会close客户端的socket,并发送一个EOF结束符,而父进程(read_handling)读到EOF后也会结束死循环,返回主函数执行close(sock)代码,OK,程序完美结束。
但是现实时,无论我输入q还是Q,都无法结束程序。为了查看子进程是否正常结束,我输入ps au查看当前系统中的所有进程:
请注意红色方框圈出的部分,上面的为主进程,STAT为S+表示正常运行,而下面的红色圈出进程为子进程,STAT为Z+,很明显改进程已经结束,但是由于父进程没有结束而成了僵尸进程。但是这说明我输入q之后成功结束了子进程的死循环,然后成功结束了进程。
但问题是:结束了子进程说明执行了close(sock)代码,但是为什么没有close成功?
后面参考一些资料,发现了其中的原因:
我们平时在客户端或者服务端操作的其实是socket描述符,它相当于一个指针,指向真正的socket,是指针就可以复制,它可以存在多个socket描述符指向同一个socket,在上面的客户端中,子进程会复制一个socket文件描述符,此时的状态是两个socket文件描述同时指向一个socket,如下图所示:
如果单纯close一个socket描述,不会真正的关闭改socket,只有所有的socket描述符,才会真正的关闭socket,因此在上述情景中,我们在子进程中执行close(sock)并不会真正的关闭改socket,因此此时父进程仍然有一个socket描述符指向改socket。
那么怎么解决这个问题?
答案是用shutdown()函数,用shutdown函数关闭socket描述符时,不管还有没有别描述符指向改socket,客户端都会发送一个EOF表示输出关闭。把write_handling函数中shutdown前面的注释去掉,发现程序就能正常关闭。
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
Git clone
git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL28
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/socket.h> #include<arpa/inet.h> #include<signal.h> #include<string.h> #define BUF_SIZE 1024 void error_handling(char* message); void read_handling(int sock); void write_handling(int sock); int main(int argc,char* argv[]) { int sock; struct sockaddr_in addr; pid_t pid; if(argc!=3) { printf("Usage %s<address> <port>\n",argv[0]); exit(1); } sock=socket(AF_INET,SOCK_STREAM,0); if(sock==-1) error_handling("socket() error"); memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr(argv[1]); addr.sin_port=htons(atoi(argv[2])); if(connect(sock,(structsockaddr*)&addr,sizeof(addr))==-1) error_handling("connect() error"); pid=fork(); if(pid==0) { write_handling(sock); } else { read_handling(sock); } close(sock); return 0; } //写进程 void write_handling(int sock) { char buf[BUF_SIZE]; while(1) { memset(buf,0,BUF_SIZE); fputs("Input:",stdout); fgets(buf,BUF_SIZE,stdin); if(!strcmp(buf,"q\n")|| !strcmp(buf,"Q\n")) { //shutdown(sock,SHUT_WR); return; } write(sock,buf,strlen(buf)); } } //读进程 void read_handling(int sock) { int str_len; char buf[BUF_SIZE]; while(1) { str_len=read(sock,buf,BUF_SIZE); if(str_len<=0) return; buf[str_len]=0; printf("themessage from server:%s\n",buf); } } void error_handling(char* message) { fputs(message,stderr); fputc('\n',stderr); exit(1); }
正常编译上述代码,成功生成可执行文件,然后运行客户端,输入q却无法退出程序:
[Hyman@Hyman-PC multiProcess]$ ./clnt127.0.0.1 9190 Input:q
按照我的设想,在标准输入q后,子进程(write_handling)会结束死循环,然后回到主程序代码执行 close(sock)代码,然后客户端发送一个EOF结束符给服务端表明要断开连接,服务端接收到EOF后,也会close客户端的socket,并发送一个EOF结束符,而父进程(read_handling)读到EOF后也会结束死循环,返回主函数执行close(sock)代码,OK,程序完美结束。
但是现实时,无论我输入q还是Q,都无法结束程序。为了查看子进程是否正常结束,我输入ps au查看当前系统中的所有进程:
请注意红色方框圈出的部分,上面的为主进程,STAT为S+表示正常运行,而下面的红色圈出进程为子进程,STAT为Z+,很明显改进程已经结束,但是由于父进程没有结束而成了僵尸进程。但是这说明我输入q之后成功结束了子进程的死循环,然后成功结束了进程。
但问题是:结束了子进程说明执行了close(sock)代码,但是为什么没有close成功?
后面参考一些资料,发现了其中的原因:
我们平时在客户端或者服务端操作的其实是socket描述符,它相当于一个指针,指向真正的socket,是指针就可以复制,它可以存在多个socket描述符指向同一个socket,在上面的客户端中,子进程会复制一个socket文件描述符,此时的状态是两个socket文件描述同时指向一个socket,如下图所示:
如果单纯close一个socket描述,不会真正的关闭改socket,只有所有的socket描述符,才会真正的关闭socket,因此在上述情景中,我们在子进程中执行close(sock)并不会真正的关闭改socket,因此此时父进程仍然有一个socket描述符指向改socket。
那么怎么解决这个问题?
答案是用shutdown()函数,用shutdown函数关闭socket描述符时,不管还有没有别描述符指向改socket,客户端都会发送一个EOF表示输出关闭。把write_handling函数中shutdown前面的注释去掉,发现程序就能正常关闭。
[Hyman@Hyman-PC multiProcess]$ ./clnt 127.0.0.1 9190 Input:q [Hyman@Hyman-PC multiProcess]$
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
Git clone
git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL28
相关文章推荐
- 网络编程(29)—— socket的close函数以及shutdown函数在多进程服务器中的不同表现(二)
- socket编程:客户端与服务器间的连接以及各函数的用法
- 关于socket应用:一个不断监听一个进程的服务器以及发送信息的客户端 TCP的三次握手和四次挥手
- socket的accept函数解析以及服务器和多个客户端的端口问题
- socket的accept函数解析以及服务器和多个客户端的端口问题
- linux网络编程之一般应用采用的协议和不同套接字的地址结构以及用户进程和内核通过哪些函数传递套接字的地址结构
- socket编程:客户端与服务器间的连接以及各函数的用法
- linux局域网通讯源码(服务器多路复用和客户端多进程模式)(socket)客户端
- socket网络编程常用的结构及函数小结
- LR监控服务器以及在服务器上安装rstatd守护进程
- 网络编程socket之close与shutdown函数
- linux局域网通讯源码(服务器多路复用和客户端多进程模式)(socket)服务器端
- 用 Socket 和 Pcntl 实现一个多进程服务器(一)
- 进程间同步(互斥) 以及 同一DLL的不同进程copy共享全局变量
- (转)守护进程原理分析以及linux下的daemon函数使用
- php的POSIX 函数以及进程测试
- JavaMail:邮件发送以及sina、163、QQ服务器不同的解析结果
- JavaMail:邮件发送以及sina、163、QQ服务器不同的解析结果(附图)
- Linux下socket编程的辅助函数:select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET
- socket网络编程常用的结构及函数小结