11

我有一个客户端和一个服务器。我的客户端中有两个 read(),服务器代码中有两个 write()。服务器在第一次 write() 时向客户端发送数据,客户端读取并存储到缓冲区,但它不会停止读取,它会继续读取服务器的第二次 write(),因为在我的客户端中,我将其设置为在流中读取 255(据我了解)。我放了 255,因为我不知道第一次 write() 的数据数据大小有多长。我该如何解决?

客户:

n = read(sockfd,buffer,255);
if (n < 0) 
     error("ERROR reading from socket");
      printf("%s\n",buffer);

 n = read(sockfd,buffer,255);
if (n < 0) 
     error("ERROR reading from socket");
      printf("%s\n",buffer);

服务器:

  n = write(newsockfd,datasize,strlen(datasize));
if (n < 0) error("ERROR writing to socket");

n = write(newsockfd,data,255);
if (n < 0) error("ERROR writing to socket");
4

4 回答 4

14

您正在体验的是 TCP 的工作原理。如果服务器write()在客户端调用之前多次调用read(),则read()可以接收之前写入的所有内容,最多可以达到您指定的最大缓冲区大小。TCP 没有消息边界的概念,就像 UDP 一样。没有什么不妥。你只需要考虑它,仅此而已。

如果您需要知道一条消息在哪里结束,而下一条消息在哪里开始,那么您只需要框定您的消息。有几种不同的方法可以做到这一点。

  1. 在发送实际数据之前发送数据长度,以便客户端知道要读取多少数据,例如:

    服务器:

    int datalen = ...; // # of bytes in data
    int tmp = htonl(datalen);
    n = write(newsockfd, (char*)&tmp, sizeof(tmp));
    if (n < 0) error("ERROR writing to socket");
    n = write(newsockfd, data, datalen);
    if (n < 0) error("ERROR writing to socket");
    

    客户:

    int buflen;
    n = read(sockfd, (char*)&buflen, sizeof(buflen));
    if (n < 0) error("ERROR reading from socket");
    buflen = ntohl(buflen);
    n = read(sockfd, buffer, buflen);
    if (n < 0) error("ERROR reading from socket");
    else printf("%*.*s\n", n, n, buffer);
    
  2. 用实际数据中没有出现的分隔符包装数据,然后客户端可以继续阅读并查找这些分隔符。使用对您的数据有意义的任何分隔符(STX/ETX、换行符、特殊保留字符等):

    服务器:

    char delim = '\x2';
    n = write(newsockfd, &delim, 1);
    if (n < 0) error("ERROR writing to socket");
    n = write(newsockfd, data, datalen);
    if (n < 0) error("ERROR writing to socket");
    delim = '\x3';
    n = write(newsockfd, &delim, 1);
    if (n < 0) error("ERROR writing to socket");
    

    客户:

    char tmp;
    
    do
    {
        n = read(sockfd, &tmp, 1);
        if (n < 0) error("ERROR reading from socket");
    
        if (tmp != '\x2')
            continue;
    
        buflen = 0;
    
        do
        {
            n = read(sockfd, &tmp, 1);
            if (n < 0) error("ERROR reading from socket");
    
            if (tmp == '\x3')
                break;
    
            // TODO: if the buffer's capacity has been reached, either reallocate the buffer with a larger size, or fail the operation...
            buffer[buflen] = tmp;
            ++buflen;
        }
        while (1);
    
        printf("%*.*s\n", buflen, buflen, buffer);
        break;
    }
    while (1);
    
于 2013-10-01T23:15:39.817 回答
3

您不能假设一次读取将完全读取一次写入所写的内容。TCP 是一种字节流协议。没有消息边界。read() 可以读取最少一个字节和您提供的长度,具体取决于已到达的数据,您在发送端或接收端都无法控制。您也无法控制 TCP 是否将传出的写入合并到一个段中。

如果你想要消息,你必须自己实现它们,例如行,长度字前缀,类型长度值,STX/ETX,XML,...

注意当您遇到错误时,不要只打印您自己设计的消息。打印错误。在这种情况下,调用 'perror()',或者用 'strerror' 组成一个格式化的字符串。

于 2013-10-01T23:03:53.967 回答
2

您对data在发送实际长度之前发送长度有正确的想法data,但是您发送datasize的格式错误。将其作为 ascii 字符串发送意味着 的长度datasize将取决于 的长度data

例如:

  • 如果data长度为 5 个字节,则为datasize“5”。
  • 如果data长度为 100 字节,则为datasize“100”。

不幸的是,当涉及到序列化时data,这将不起作用,datasize必须始终占用相同数量的字节。您需要将其作为整数写入套接字,并在另一端作为整数再次读取。然后将这个确切字节数的数据写入套接字,并在另一端读取这个确切字节数的数据:

例如:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

void send(int sock)
{
    const char* msg = "this is a message!";
    uint16_t len = strlen(msg); 
    uint16_t networkLen = htons(len); // convert to network byte order

    write(sock, &networkLen, sizeof(networkLen));
    write(sock, msg, len);    
}

void receive(int sock)
{
    char msg[1024];

    uint16_t networkLen;
    read(sock, &networkLen, sizeof(networkLen));

    uint16_t len = ntohs(networkLen); // convert back to host byte order
    read(sock, msg, sizeof(msg) - 1);

    msg[len] = '\0';

    printf("%u %s\n", len, msg);
}

int main(int argc, char** argv)
{
    int sockets[2];
    pipe(sockets);

    send(sockets[1]);
    receive(sockets[0]);
}
于 2013-10-01T23:20:05.040 回答
0

套接字 == 字节流,它不会进行一些打包等。因此,如果服务器应该向客户端发送 2 个数据包,您需要做一些事情以便客户端可以区分它们中的每一个。例如,如果您决定服务器应发送 2 个数据包,每个数据包 255 字节,则接收一个数据包的客户端程序将如下所示:

int count = 0;
while (count < 255) {
    n = read(sockfd, buffer + count, 255 - count);
    if (n < 0) {
         error("ERROR reading from socket");
          printf("%s\n",buffer);
         return;
    } 
    count += n;
}
// here buffer has 255 bytes for the packet

该程序具有循环,因为您可以在读取结果中接收 0..255 之间的任何数字,如果套接字关闭,则为负数。您可以对第二个数据包执行相同的操作。如果您的数据包大小不同,那么您必须从服务器告诉客户端数据包大小是多少。您可以在前 2 个字节中发送数据包的长度,并在上面的代码中使用数字而不是 255 常量。

于 2013-10-01T23:12:34.020 回答