c-Linux TCP套接字:客户端已经发送了数据,但服务器仍然阻止读取()



我有一个在Linux上使用TCP套接字的简单客户端-服务器示例。服务器侦听环回地址。客户端连接到服务器并发送一些整数加上一个"0";结束";标记数据结束的字符串。服务器读取这些数字,将它们全部相加,然后返回总数。然而,我的服务器有时会阻塞read(),即使客户端已经成功发送了所有数据。

这是代码:

server.c:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BACKLOG 5
int main(int argc, char *argv[]) {
struct sockaddr_in addr;
int down_flag = 0;
int result = 0;
int ret = 0;
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0) {
perror("Create server socket error: %sn");
return 0;
}
/* Bind socket to loopback address */
memset((void *) &addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) {
perror("Bind server socket failed");
goto _exit;
}
if (listen(sfd, BACKLOG) == -1) {
perror("Listen failed");
goto _exit;
}
ssize_t num_rd = 0;
char buf[100] = {0};
for (;;)
{
printf("Waiting to accept a connection...n");
int cfd = accept(sfd, NULL, NULL);
printf("Accepted socket fd = %dn", cfd);
result = 0;
while ((num_rd = read(cfd, buf, sizeof(buf))) > 0) {
/* Ensure the buffer is 0-terminated */
buf[sizeof(buf) - 1] = 0;
printf("Read data: %sn", buf);
/* Handle commands */
if (!strncmp(buf, "DOWN", sizeof(buf))) {
down_flag = 1;
break;
}
if (!strncmp(buf, "END", sizeof(buf))) {
break;
}
/* Add received summand */
result += atoi(buf);
}
if (-1 == num_rd) {
perror("Read error");
}
/* Send result */
sprintf(buf, "%d", result);
ret = write(cfd, buf, sizeof(buf));
if (-1 == ret) {
perror("Write errorn");
goto _exit;
}
close(cfd);
/* Quit on DOWN command */
if (down_flag) {
break;
}
}
_exit:
close(sfd);
return 0;
}

client.c:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
struct sockaddr_in addr;
int ret;
int data_socket;
char buf[100] = {0};
int i = 0;
data_socket = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == data_socket) {
perror("Create client socket error");
exit(EXIT_FAILURE);
}
/* Connect to server socket */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ret = connect(data_socket, (const struct sockaddr *) &addr, sizeof(addr));
if (-1 == ret) {
perror("Connect error");
exit(EXIT_FAILURE);
}
/* Send arguments */
for (i = 1; i < argc; i++) {
ret = write(data_socket, argv[i], strlen(argv[i]) + 1);
if (-1 == ret) {
perror("Write error");
break;
}
}
strcpy(buf, "END");
ret = write(data_socket, buf, strlen(buf) + 1);
printf("write %s to socket, ret = %dn", buf, ret);
if (-1 == ret) {
perror("Write to socket error");
exit(EXIT_FAILURE);
}
/* Read the result */
memset(buf, 0, sizeof(buf));
ret = read(data_socket, buf, sizeof(buf));
if (-1 == ret) {
perror("Read from client socket error");
exit(EXIT_FAILURE);
}
buf[sizeof(buf) - 1] = 0;
printf("Result = %sn", buf);
close(data_socket);
exit(EXIT_SUCCESS);
}

运行客户端几次,服务器会在某些时候阻塞read()调用:

$ for i in {1..100}; do ./client 3 4 5 6; done
write END to socket, ret = 4
Result = 18
write END to socket, ret = 4

服务器输出:

$ ./server
Waiting to accept a connection...
Accepted socket fd = 4
Read data: 3
Read data: 4
Read data: 5
Read data: 6
Read data: END
Waiting to accept a connection...
Accepted socket fd = 4
Read data: 3

服务器正在阻止while ((num_rd = read(cfd, buf, sizeof(buf))) > 0)线路。

编辑:我的问题是为什么read()会阻塞。则read()将阻塞直到有至少1个字节的数据要从套接字中读取。在这种情况下,客户端发送的数据比服务器读取的数据多,所以我认为有可用的数据可以从套接字中读取。那么为什么read()仍然阻塞呢?

问题的核心是,代码测试缓冲区中的第一条消息,忽略了同一缓冲区可能包含多条消息、部分消息或任何其他组合的可能性(请参见edit(。因此,消息END有时被忽略,并且read循环从未终止。

该代码假设单个read将接收到write调用所发送的内容。

这是非常不准确的,很少是真的,而且可能只有在客户端和服务器都在同一台机器上时才有效。

单个read可能同时读取2个write调用,或者它可能读取半个write调用,然后在稍后读取另外1.5个write调用…

TCP/IP(与UDP不同(是一种流协议,不知道消息边界。


编辑

为了澄清(如评论中所要求的(,假设调用read收集以下数据"1234EN"(下一个read将收集"D"(。。。这个程序做什么?

另一种可能的情况是一次性读取所有CCD_ 19。即buf包含字符串"3456END"

此时循环中发生了什么?

在此示例场景中,if语句(strncmp(buf, "END", sizeof(buf))始终为false(且不安全(,导致服务器从不中断while(read)循环。

由于while循环继续,当没有可用的数据时,服务器将尝试另一个read,导致服务器阻塞,直到客户端发送额外的数据。

null终止符需要紧跟在读取的数据之后,而不仅仅是在缓冲区的末尾。否则,当您尝试将缓冲区视为字符串时,前面的内容将成为字符串的一部分。

buf[num_read] = '';

为了防止在缓冲区外写入,在调用read():时,需要从缓冲区大小中减去1

while ((num_rd = read(cfd, buf, sizeof(buf)-1)) > 0) {

然后,服务器会阻塞,因为它无法识别使其脱离读取循环的END消息,并且没有其他内容可供读取。它将阻塞,直到有数据要读取或EOF。当客户端关闭连接时,它会得到EOF,但直到服务器发送结果,客户端才会这样做。

然而,我认为你的整个设计可能注定要失败。TCP是一个流协议,它没有消息边界。客户端中对write()的每次调用并不一定会在服务器中产生一个read(),连续的写入可以(通常也会(连接起来。你需要重新设计你的协议来处理这个问题。

最直接的方法是使用poll()

upd:
我认为其他答案中写的东西的真正原因。你想在有小溪的地方用管道
但是
你可以试试这个

SOCKET q = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bool c = 1;
setsockopt(q, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));

最后

#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>    //socket
#include <netdb.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <netinet/tcp.h>
#include <string.h>
#include <poll.h>
int main(int argc , char *argv[])
{
struct sockaddr_in addr = { AF_INET , htons( 8888 ) /* btc port */ , htonl(INADDR_LOOPBACK) };;
int down_flag = 0;
int result = 0;
int ret = 0;
bool c = !false;
enum { buf_size = 1025 };
char buf[buf_size] = {0};
int bc = 1024;    
int data_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt(data_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&c, sizeof(c));
setsockopt(data_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));
setsockopt(data_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&c, sizeof(c));
setsockopt(data_socket, SOL_SOCKET, SO_REUSEPORT, (char *)&c, sizeof(c));
setsockopt(data_socket, SOL_SOCKET, SO_SNDBUF, (int *)&bc, sizeof(bc));
setsockopt(data_socket, SOL_SOCKET, SO_RCVBUF, (int *)&bc, sizeof(bc));
ret = connect(data_socket, (const struct sockaddr *) &addr, sizeof(addr));
if (-1 == ret) {
perror("Connect error");
exit(EXIT_FAILURE);
}
setsockopt(data_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));    /* Send arguments */
struct pollfd pfd[1];
int nready;
pfd[0].fd = data_socket;
pfd[0].events = POLLOUT;
for (int k = 1; k < argc; k++)
{
nready = poll(pfd, 1, 15 * 1000);
// if((pfd[0].revents & (POLLOUT|POLLHUP))) printf("tray n" );
memset(buf, 0, sizeof(buf));
strcpy(buf, argv[k]);
ret = write(data_socket, buf , 99);
if (-1 == ret) {
perror("Write error");
break;
}
}
memset(buf, 0, sizeof(buf));
strcpy(buf, "END");
nready = poll(pfd, 1, 15 * 1000);
ret = write(data_socket, buf, 99);
printf("write %s to socket, ret = %dn", buf, ret);
if (-1 == ret) {
perror("Write to socket error");
exit(EXIT_FAILURE);
}
/* Read the result */
memset(buf, 0, sizeof(buf));
pfd[0].events = POLLIN;
nready = poll(pfd, 1, 15 * 1000);
ret = read(data_socket, buf, 99);
if (-1 == ret) {
perror("Read from client socket error");
exit(EXIT_FAILURE);
}
buf[sizeof(buf) - 1] = '';
printf("Result = %10sn", buf);
close(data_socket);
exit(EXIT_SUCCESS);
}

#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>    //socket
#include <netdb.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <netinet/tcp.h>
#include <string.h>
#include <poll.h>

int main(int argc , char *argv[])
{
struct sockaddr_in addr = { AF_INET , htons( 8888 ) /* btc port */ , htonl(INADDR_LOOPBACK) };;
int down_flag = 0;
int result = 0;
int ret = 0;
bool c = !false;
int bc = 1024;    
int sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sfd < 0) {
perror("Create server socket error: %sn");
return 0;
}
setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));
setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&c, sizeof(c));
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&c, sizeof(c));
setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (char *)&c, sizeof(c));
setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (int *)&bc, sizeof(bc));
setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (int *)&bc, sizeof(bc));
/* Bind socket to loopback address */
if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) {
perror("Bind server socket failed");
goto _exit;
}
setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof(c));
if (listen(sfd, 128) == -1) {
perror("Listen failed");
goto _exit;
}
ssize_t num_rd = 0;
enum { buf_size = 1025 };
char buf[buf_size] = {0};
struct pollfd pfd[1] = {{0}};
int nready;
for (;;)
{
printf("Waiting to accept a connection...n");
int cfd = accept(sfd, NULL, NULL);
printf("Accepted socket fd = %dn", cfd);
result = 0;
pfd[0].fd = cfd;
//      pfd[0].fd = sfd;
pfd[0].events = POLLIN;
while (!false) {
memset(buf, 0, sizeof(buf));
nready = poll(pfd, 1, 15 * 1000);
num_rd = read(cfd, buf, 99);
if (num_rd <= 0) break;
buf[sizeof(buf) - 1] = '';
printf("Read data: %sn", buf);
/* Handle commands */
if (!strncmp(buf, "DOWN", strlen(buf))) {
down_flag = 1;
break;
}
if (!strncmp(buf, "END", strlen(buf))) {
break;
}
int temp = 0;
int f = sscanf(buf, "%d", &temp);
if (f != 1)
{
printf("and then n" );
return (0);
}
result = result + temp;
}
if (-1 == num_rd) {
perror("Read error");
}
memset(buf, 0, sizeof(buf));
sprintf(buf, "%d", result);
pfd[0].events = POLLOUT;
nready = poll(pfd, 1, 15 * 1000);
ret = write(cfd, buf, 99);
if (-1 == ret) {
perror("Write errorn");
goto _exit;
}
close(cfd);
/* Quit on DOWN command */
if (down_flag) {
break;
}
}
_exit:
close(sfd);
return 0;
}

这里的所有东西都有一个尺寸。在一次写入之后,read_buffer应该已满,所以其他的等待直到读取完成

运行:
./serv
for i in {1..100}; do ./client 3 4 5 6; done

也在github上:
https://github.com/alexeyneu/BlockZero/tree/master/onemore

edit:
使用1kb/命令看起来可以抵御一些大的传输。这是SO_XXX缓冲的最小值

最新更新