TCP/IP 客户端发送
TCP/IP client send
以下是我的 C/S 代码和输出,仅供测试。
我有问题。
很明显,我只是通过客户端发送了两次。但是,服务器刚收到 3 条消息。第二个似乎是 NULL。
#define MP_MAXLINE 4096
/* client */
int main(int ac, char *av[])
{
char buf[MP_MAXLINE];
int clifd;
struct sockaddr_in cliaddr;
if((clifd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
perror("socket error");
memset(&cliaddr, 0, sizeof(struct sockaddr_in));
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(atoi(av[2]));
inet_pton(AF_INET, av[1], &cliaddr.sin_addr);
if(connect(clifd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)))
perror("can not connect to server");
printf("enter data to send: ");
while(fgets(buf, MP_MAXLINE, stdin) != NULL){
write(clifd, buf, sizeof(buf));
printf("enter data to send: ");
}
}
/* server */
static void cli_service(int clifd){
int lclifd, rcv_cnt = 0;
char data[MP_MAXLINE];
for(;;){
if(read(clifd, data, sizeof(data)) == 0)
exit(1);
rcv_cnt++;
printf("cnt: %d, data: %s", rcv_cnt, data);
}
}
int main(int ac, char *av[])
{
...
for(;;){
clifd = accept(svrfd, (struct sockaddr *)&cliaddr, &clisocklen);
if((clipid = fork()) < 0){
printf("fork error\n");
continue;
}
else if(clipid == 0){
close(svrfd);
cli_service(clifd);
}
close(clifd);
continue;
}
}
客户端的输出在这里:
输入要发送的数据:1111
输入要发送的数据:2222
输入要发送的数据:
服务器的输出在这里:
cnt: 1, 数据: 1111
cnt:2,数据:cnt:3,数据:2222
发生的事情是你即将发现 TCP 是一个 stream-oriented 协议,这意味着它保证你发送的字节将被正确传递并且in-order,但是字节到单个 write()
/read()
调用的映射是任意的——也就是说,每次调用 read()
写入接收器数组的字节数]绝对不保证与发送程序先前传递给write()
调用的字节数相同。
所以在您的客户端中,每次调用 write()
都会发送 4096 个字节,其中前几个字节将包含用户输入的以 0 结尾的文本字符串,其余部分将是未初始化的数据(即它可以是任何东西,因为你从未写入数组的那部分)。
然后在你的服务器中,你正在调用 read()
,它读取了一些字节,但你不知道它读取了多少字节,因为你所做的只是检查是否 read()
returned 0 与否。所以你不知道 read() 是将 4096 字节的数据放入你的 data
数组还是一些更小的字节数;如果它是一些较小的数字,那么您的接收代码现在是 out-of-sync 和您的发送代码,因为随后对 read()
的调用可能会将未初始化的数据写入您的 data
数组,用户的下一个字符串位于数组中间的某处。
当然,这不是有用的行为。为了修复它,您需要做的是密切注意 read()
编辑的值 return;如果它大于零但小于 sizeof(data)
,则您需要以某种方式处理它。处理它的一种方法是计算 data
数组中当前有多少有效字节,并且始终 read(clifd, data+numValidBytes, sizeof(data)-numValidBytes)
直到 (numValidBytes == sizeof(data))
... 然后才解析数据和将 numValidBytes
设置回零。另一个更简单的选择是调用 recv()
而不是 read()
,并在最后一个参数中设置 MSG_WAITALL —— 在这种情况下,recv()
将承诺不会 return 直到它读取所有 sizeof(data)
个字节,然后您就不必自己处理 partial-reads。
以下是我的 C/S 代码和输出,仅供测试。
我有问题。
很明显,我只是通过客户端发送了两次。但是,服务器刚收到 3 条消息。第二个似乎是 NULL。
#define MP_MAXLINE 4096
/* client */
int main(int ac, char *av[])
{
char buf[MP_MAXLINE];
int clifd;
struct sockaddr_in cliaddr;
if((clifd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
perror("socket error");
memset(&cliaddr, 0, sizeof(struct sockaddr_in));
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(atoi(av[2]));
inet_pton(AF_INET, av[1], &cliaddr.sin_addr);
if(connect(clifd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)))
perror("can not connect to server");
printf("enter data to send: ");
while(fgets(buf, MP_MAXLINE, stdin) != NULL){
write(clifd, buf, sizeof(buf));
printf("enter data to send: ");
}
}
/* server */
static void cli_service(int clifd){
int lclifd, rcv_cnt = 0;
char data[MP_MAXLINE];
for(;;){
if(read(clifd, data, sizeof(data)) == 0)
exit(1);
rcv_cnt++;
printf("cnt: %d, data: %s", rcv_cnt, data);
}
}
int main(int ac, char *av[])
{
...
for(;;){
clifd = accept(svrfd, (struct sockaddr *)&cliaddr, &clisocklen);
if((clipid = fork()) < 0){
printf("fork error\n");
continue;
}
else if(clipid == 0){
close(svrfd);
cli_service(clifd);
}
close(clifd);
continue;
}
}
客户端的输出在这里:
输入要发送的数据:1111
输入要发送的数据:2222
输入要发送的数据:
服务器的输出在这里:
cnt: 1, 数据: 1111
cnt:2,数据:cnt:3,数据:2222
发生的事情是你即将发现 TCP 是一个 stream-oriented 协议,这意味着它保证你发送的字节将被正确传递并且in-order,但是字节到单个 write()
/read()
调用的映射是任意的——也就是说,每次调用 read()
写入接收器数组的字节数]绝对不保证与发送程序先前传递给write()
调用的字节数相同。
所以在您的客户端中,每次调用 write()
都会发送 4096 个字节,其中前几个字节将包含用户输入的以 0 结尾的文本字符串,其余部分将是未初始化的数据(即它可以是任何东西,因为你从未写入数组的那部分)。
然后在你的服务器中,你正在调用 read()
,它读取了一些字节,但你不知道它读取了多少字节,因为你所做的只是检查是否 read()
returned 0 与否。所以你不知道 read() 是将 4096 字节的数据放入你的 data
数组还是一些更小的字节数;如果它是一些较小的数字,那么您的接收代码现在是 out-of-sync 和您的发送代码,因为随后对 read()
的调用可能会将未初始化的数据写入您的 data
数组,用户的下一个字符串位于数组中间的某处。
当然,这不是有用的行为。为了修复它,您需要做的是密切注意 read()
编辑的值 return;如果它大于零但小于 sizeof(data)
,则您需要以某种方式处理它。处理它的一种方法是计算 data
数组中当前有多少有效字节,并且始终 read(clifd, data+numValidBytes, sizeof(data)-numValidBytes)
直到 (numValidBytes == sizeof(data))
... 然后才解析数据和将 numValidBytes
设置回零。另一个更简单的选择是调用 recv()
而不是 read()
,并在最后一个参数中设置 MSG_WAITALL —— 在这种情况下,recv()
将承诺不会 return 直到它读取所有 sizeof(data)
个字节,然后您就不必自己处理 partial-reads。