博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

浅析阻塞IO导致的程序永久阻塞 不错

Posted on 2016-03-24 10:43  bw_0927  阅读(447)  评论(0)    收藏  举报

http://www.zyfforlinux.cc/2015/06/16/%E6%B5%85%E6%9E%90%E9%98%BB%E5%A1%9EIO%E5%AF%BC%E8%87%B4%E7%9A%84%E7%A8%8B%E5%BA%8F%E6%B0%B8%E4%B9%85%E9%98%BB%E5%A1%9E/

 

About

阻塞IO导致程序永久阻塞?咋一看这个问题感觉不应该会出现这种情况的啊。但是下面我会
通过实验来验证这个问题,最后结合代码来分析产生这个问题的原因,这也很好的证明了阻塞
IO会带来的一些潜在问题,现代的主流网络编程还是IO复用+非阻塞IO才是王道。

Practice

实验环境:
OS:Centos6.5 x86_64

在这里使用一个简单的echo程序来完成这个实验,通过不断增加发送的数据量来完成。最后
导致程序永久阻塞。先简单的来看下这个echo程序。

服务器端代码如下(省略了部分细节代码):

 

//打印客户端的地址信息,并派生一个子进程来处理业务逻辑
while(1){
int fd = accept(sockfd,NULL,NULL);
if(fd == -1){
ERR_EXIT("accept client");
}
else
{
getpeername(fd,(struct sockaddr *)&caddr,&addrlen);
cout << inet_ntoa(caddr.sin_addr) << endl;
pid_t pid;
if((pid = fork()) == -1)
cout << "fork error" << endl;
else if(pid > 0){
close(fd);
cout << pid << endl;
continue;
}
else if(pid == 0)
{
childfunc(fd);
exit(EXIT_SUCCESS);
}
}
}
// 子进程负责处理主要业务逻辑
int childfunc(int fd)
{
int n = 0;
int ret = 0;
char buf[4096] = {0};
//接收指定大小的内容
while((n = recv(fd,buf,sizeof(buf),MSG_WAITALL)) > 0) //服务器收到多少就发多少
{
cout << n << endl;
//写入指定大小的内容
ret = writen(fd,buf,n);
if (ret < n)
break;
}
close(fd);
}




服务器端比较简单,就是来一个链接accept并打印其地址,然后接收指定大小的数据,然后
再写回去,逻辑就这么简单。

客户端代码(主要部分):

int sockfd = MakeConnect("127.0.0.1",8080);
string message(atoi(argv[1]),'s'); //够到指定大小的报文
int ret = writen(sockfd,(void*)message.c_str(),message.size()); //一次性发送所有数据
cout << "send data size:"<< ret << endl;
std::vector<char> receive(atoi(argv[1]));
ret = readn(sockfd,receive.data(),receive.size());
if(ret >0){
cout << "receive:" << ret << endl;
}else if(ret == 0){
cout << "server close" << endl;
}else if(ret < 0){
cout << "recv error" << endl;
}
close(sockfd);




客户端这个部分只是单单去连接服务器,然后写入用户输入大小的数据,然后等待接收全部
数据。

下面我将在本机开始测试阻塞IO的数据发送,分别从不同大小的数据开始测试

 

[root@localhost network-programing]# ./echocli 4096
send data size:4096
receive:4096
[root@localhost network-programing]# ./echocli 40960
send data size:40960
receive:40960
[root@localhost network-programing]# ./echocli 409600
send data size:409600
receive:409600
[root@localhost network-programing]# ./echocli 4096000
send data size:4096000
receive:4096000
[root@localhost network-programing]# ./echocli 40960000 //这里阻塞了

 

查看下套接字的状态和内核队列的情况如下:

[root@localhost ~]# netstat -tnp|grep echo
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 222144 1551264 127.0.0.1:39708 127.0.0.1:8080 ESTABLISHED 38095/./echocli
tcp 2094933 1344320 127.0.0.1:8080 127.0.0.1:39708 ESTABLISHED 38096/./echoserv



这里我们会发现其内核的发送队列和接收队列存有大量数据,并且一直保持不变。其实
这个永久阻塞的问题主要就是内核的发送队列和接收队列的问题。下面会对这个问题
做一个分析。

Analysis

 

简要的分析图示如下:

阻塞的原因在于,客户端不停的发送大量数据,直到大量数据发送完才开始接收数据,然后
服务器端不停的去接收数据,接收了一点数据后就开始发送,但是发送只是将其放在内核的
发送队列上,客户端一直不接收导致发送队列一会就满了,此后服务器端接收到的数据再
write的时候将会阻塞,阻塞后服务器端也不再接收数据了,那么客户端仍在不停的发送
数据,但是这时服务器端已经不再接收数据了,很快客户端的内核发送队列就满了,此后
客户端就阻塞在write上了。这样就导致了永久阻塞的问题。