使用阻塞io简单的实现一个回显功能,程序被阻塞死锁,部分代码如附录,分析记录过程及原因
- 服务端,常见socket简单封装了下
Bind
Accept
read
write(回写read的内容) - 客户端
connect
write
read(读服务端传回的内容)
当client传送的字节数过大时,客户端和服务端程序同时被阻塞了。
3. 分析一下原因:
问题出在服务端?客户端?客户端发送一个大点的数据没问题,问题在于服务端,没有读到完整的客户端的请求,服务端阻塞在上述2过程里,而客户端还有未发送完的数据,阻塞在1过程,没法去执行read,所以导致了死锁的过程
实际上系统内核有发送缓冲区和接收缓冲区,当使用阻塞io读写的数据超过了缓冲区大小,会导致服务端写满接收缓冲区后,一直阻塞等待客户端读取,然而此时客户端正在等待往服务度端接收缓冲区写。
-
验证
此时查看缓冲区
可以查看内核配置的缓冲区大小与这个数值刚好匹配 -
解决方案
客户端发送一个报文长度的字段,服务端接收到保存。客户端发送数据,服务端读取完所有数据,处理完毕后再发送给客户端长度以及回报。 -
总结
阻塞io发送较大数据,一旦阻塞,很难解锁,可以考虑分包策略
附录代码
client.cc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
//#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string>
#include <vector>
#include <sys/types.h>
#include <memory>
#define SER_PORT 9527
int sys_err(const char* s)
{
perror(s);
exit(0);
}
int main(int argc , char* argv[])
{
int fd;
fd = socket(AF_INET, SOCK_STREAM,0);
if(fd == -1)
{
sys_err("socket error");
}
struct sockaddr_in ser_addr;
//inet_pton(AF_INET,"127.0.0.1", &ser_addr.sin_addr.s_addr);
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(SER_PORT);
int ret = connect(fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
if(ret != 0)
{
sys_err("conn error");
}
printf("cli conneted\n");
int len = atoi(argv[1]);
printf("len is : %d \n", len);
std::string message(len, 'S');
if(write(fd, message.c_str(), message.size())< 0)
{
sys_err("write error");
}
//int num = read(fd, buf, sizeof(buf));
//注意buf长度为获取的真正的长度,否则会乱码
//write(STDOUT_FILENO, buf, sizeof(buf));
std::vector<char> receiv(len);
int nr = read(fd, receiv.data(), len);
printf("read %d bytes \n", nr);
printf("close client\n");
close(fd);
}
server.cc
#include <thread>
#include "wrapper.h"
#define SER_PORT 9527
int main()
{
int fd = Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(SER_PORT);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(ser_addr);
//bind
Bind(fd, (struct sockaddr*)&ser_addr, len);
Listen(fd);
int count = 0;
while(true)
{
//accetp客户端的连接
struct sockaddr_in cli_addr;
socklen_t cli_addr_len = sizeof(cli_addr);
memset(&cli_addr, 0, sizeof(cli_addr));
int cfd = Accept(fd, (struct sockaddr*)&(cli_addr), &cli_addr_len);
if(cfd == -1)
{
sys_err("accect error");
}
printf("cfd is :%d \n", cfd);
std::thread thr([&count, cfd](){
int nr ;
char buf[BUFSIZ];
count++;
bzero(buf, sizeof(buf));
int i = 0;
while ((nr = read(cfd, buf, sizeof(buf))) > 0)
{
//printf("read bytes: %d\n", nr);
//printf("start read %d bytes\n", ret);
int nw = write(cfd, buf, nr);
/*if(nw < nr)
{
break;
}*/
printf("thread : %d, read %d times , %d bytes \n", count, i++,nr);
}
//close(cfd);
});
thr.detach();
}
}