先看代码。(完整的代码可到下载)
server.c
-
int main(void)
-
{
-
int listenfd;
-
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
-
ERR_EXIT("socket error");
-
-
struct sockaddr_in servaddr;
-
memset(&servaddr, 0, sizeof(servaddr));
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_port = htons(5188);
-
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
-
int on = 1;
-
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
-
ERR_EXIT("setsockopt error");
-
-
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
-
ERR_EXIT("bind error");
-
-
if (listen(listenfd, SOMAXCONN) < 0)
-
ERR_EXIT("listen error");
-
-
while (1) {
-
int conn;
-
struct sockaddr_in peeraddr;
-
socklen_t peerlen = sizeof(peeraddr);
-
if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
-
ERR_EXIT("accept error");
-
printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
-
-
while (1) {
-
char recvbuf[1024];
-
int readlen, writelen;
-
-
readlen = recv(conn, recvbuf, sizeof(recvbuf), 0);
-
if (readlen <= 0)
-
break;
-
writelen = send(conn, recvbuf, readlen, 0);
-
if (writelen < readlen)
-
break;
-
}
-
printf("close\n");
-
close(conn);
-
}
-
close(listenfd);
-
-
return 0;
-
}
client.c
-
int main(int argc, const char *argv[])
-
{
-
int sock;
-
int size = 1024;
-
char *buffer;
-
-
if (argc > 1) {
-
size = atoi(argv[1]);
-
printf("size = %d\n", size);
-
}
-
buffer = malloc(size);
-
if (buffer == NULL)
-
ERR_EXIT("malloc error");
-
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
-
ERR_EXIT("socket error");
-
-
struct sockaddr_in servaddr;
-
memset(&servaddr, 0, sizeof(servaddr));
-
servaddr.sin_family = AF_INET;
-
servaddr.sin_port = htons(5188);
-
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
-
-
if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
-
ERR_EXIT("connect error");
-
-
int writelen, readlen;
-
-
printf("write...\n");
-
writelen = send(sock, buffer, size, 0);
-
-
printf("read...\n");
-
while (readlen < writelen) {
-
int templen = recv(sock, buffer, size, 0);
-
if (templen <= 0) {
-
ERR_EXIT("read error");
-
}
-
readlen += templen;
-
}
-
-
printf("close\n");
-
close(sock);
-
-
return 0;
-
}
代码很简单,服务器以1024字节为单位接收客户端发送过来的数据,并马上发送回去。客户端发送指定大小的数据,并等待接收。
这个程序初看起来没有问题。
我们来运行一下:
# ./client
write...
read...
close
确实没问题。
再来改变下客户端发送的数据大小:
# ./client 5000000
size = 5000000
write...
read...
close
# ./client 50000000
size = 50000000
write...
我们发现,当客户端发送数据量设置为50000000时,客户端和服务器都阻塞了!
用netstat命令看一下:
# netstat anp | grep 5188
tcp 6072740 2607544 localhost:5188 localhost:60860 ESTABLISHED
tcp 953090 4163264 localhost:60860 localhost:5188 ESTABLISHED
其中,第2列表示接收队列中剩余数据大小,第3列表示发送队列中剩余数据大小。多次运行该命令,这两列的数字不会变。这说明数据已不再进行收发。
先画个简图:
C S
send
recv 1024B
send 1024B
recv 1024B
send 1024B
...
recv
这个问题产生的原因是这样的:
客户端调用send发送数据时,需要先把数据拷贝到发送缓冲区。对于阻塞IO,当发送缓冲区已满,还有数据未拷贝时,会一直等待。
上面的例子中,客户端需要发送大量数据,此数据量大于发送缓冲区的大小,于是,客户端阻塞。服务器每接收1024字节就往发送缓冲区中填1024字节数据,此时由于客户端未调用recv函数,服务器端的发送缓冲区不能减少。当服务器的发送缓冲区满,客户端数据还无法都填入发送缓冲区时,程序就走不下去了。
可以使用命令sysctl 查看发送缓冲区的大小。
# sysctl -A|grep tcp_wmem
net.ipv4.tcp_wmem = 4096 16384 4194304
3个数字分别表示,为TCP连接分配的最小内存,缺省内存和最大内存。
如何避免这个问题?
1. 修改发送缓冲区大小
但这个方法治标不治本,当数据量更大时又有同样问题。
2. 阻塞IO改成非阻塞IO
这样send不会阻塞,当send返回失败时,可以先调用recv把对方的发送缓冲区清空。
3. 修改收发协议
在数据头部增加数据长度信息,服务器先接收头部信息,根据长度分配buffer,再一次接收此长度的数据。
阅读(4359) | 评论(1) | 转发(0) |