【问题标题】:Weird functioning of read() in socket programming套接字编程中 read() 的奇怪功能
【发布时间】:2014-11-03 15:36:57
【问题描述】:

我有以下服务器代码。我从一个网站上拿这个来学习套接字编程。

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

#define BUFFER_SIZE 356
#define READ_SIZE 255

void error(const char *msg)
{
    std::cerr << msg;
    exit(1);
}

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[BUFFER_SIZE];
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     if(argc < 2) error("ERROR, no port provided\n");
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) error("ERROR opening socket\n");


     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");
     listen(sockfd,5);
     clilen = sizeof(cli_addr);
     while(true){
        newsockfd = accept(sockfd, 
                    (struct sockaddr *) &cli_addr, 
                    &clilen);
        if (newsockfd < 0) 
             error("ERROR on accept");
        bzero(buffer,BUFFER_SIZE);
        n = read(newsockfd,buffer,READ_SIZE);    
        printf("Here is the message: %s\n",buffer);
        std::cout << "hellow" << "\n";
        char *message = "HTTP/1.1 200 OK\r\n\r\n<html><body><h1>Hello. Please don't close</h1></body></html>";
        n = write(newsockfd,message,strlen(message));
        if (n < 0) error("ERROR writing to socket");
        close(newsockfd);
     }
     close(sockfd);
     return 0; 
}

问题似乎与read() 函数有关。当我在特定端口上运行此服务器并使用 Firefox 作为客户端时,浏览器报告Connection was reset

这是关闭连接时tcpdump 的输出:

15:29:44.315802 IP (tos 0x0, ttl 64, id 7572, offset 0, flags [DF], proto TCP (6), length 52)
    localhost.8882 > localhost.36360: Flags [R.], cksum 0xfe28 (incorrect -> 0xebb8), seq 80, ack 291, win 350, options [nop,nop,TS val 3928986 ecr 3928985], length 0

所以服务器直接发送 RST 标志,不遵循 FIN/ACK 程序。

但是,如果我将 READ_SIZE 的值从 255 更改为 BUFFER_SIZE-1,则代码可以正常工作。

这是来自tcpdump 的新跟踪,对应于连接关闭:

15:30:21.353901 IP (tos 0x0, ttl 64, id 3241, offset 0, flags [DF], proto TCP (6), length 52)
    localhost.8882 > localhost.36437: Flags [F.], cksum 0xfe28 (incorrect -> 0x20b7), seq 80, ack 302, win 350, options [nop,nop,TS val 3938245 ecr 3938245], length 0
15:30:21.354071 IP (tos 0x0, ttl 64, id 38322, offset 0, flags [DF], proto TCP (6), length 52)
    localhost.36437 > localhost.8882: Flags [F.], cksum 0xfe28 (incorrect -> 0x20be), seq 302, ack 81, win 342, options [nop,nop,TS val 3938245 ecr 3938245], length 0
15:30:21.354093 IP (tos 0x0, ttl 64, id 3242, offset 0, flags [DF], proto TCP (6), length 52)
    localhost.8882 > localhost.36437: Flags [.], cksum 0xfe28 (incorrect -> 0x20b6), ack 303, win 350, options [nop,nop,TS val 3938245 ecr 3938245], length 0

为什么read() 函数会导致发送 RST 标志?为什么通过增加阅读量来解决问题?

注意:这种情况每次都会发生。这不是由于一些随机中断。

编辑:按照 Aif 的建议,我尝试多次调用 read()。这也带来了类似的问题。以下是循环

        while(true){
            std::cout << toread << "\n";
            readed = read(newsockfd, a, std::min(toread, 1));
        //  readed = read(newsockfd, a, 1);
            std::cout << readed << "\n";
            if(readed < 0){std::cout << "error reading"; exit(-1);}
            if(readed == 0) break;
            a += readed;
            toread -= readed;
            if(toread == 0) break;
        }

我每次都读取 1 个字节。现在,这带来了一个非常奇怪的问题。它只读取 294 个字节,然后无限期地停止。我不知道程序是如何得出这个数字的。很快,即使这种重复阅读也不起作用。有什么想法吗?

【问题讨论】:

  • 提供完整的源码让我们测试,会更容易。
  • 为什么要调用两次 read 并把第一个返回值扔掉?

标签: sockets


【解决方案1】:

编辑:

  • 检查read()的返回值
  • 迭代读取以防您收到的内容多于您正在阅读的内容,并将整个内容保存在更大的缓冲区中。

哦,顺便说一句,这是做套接字的旧方法,你现在应该使用getaddrinfo。关于套接字编程的(非常非常)好的教程,我推荐Beej's guide to skcet programming

编辑 2:

在减小缓冲区大小时确实有一些我不明白的地方。无论如何,我对您的代码进行了一些更正,使其正常工作。

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>                       // Use errno

#define BUFFER_SIZE 10                   // Very small buffer, to test the behaviour
#define READ_SIZE 255

void error(const char *msg)
{
    std::cerr << msg;
    exit(1);
}

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[BUFFER_SIZE];
     std::string msg;                                // Use a string for the "large" buffer
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     if(argc < 2) error("ERROR, no port provided\n");
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) error("ERROR opening socket\n");


     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");
     listen(sockfd,5);
     clilen = sizeof(cli_addr);
     while(1){
       int pkt = 0;
       newsockfd = accept(sockfd, 
              (struct sockaddr *) &cli_addr, 
              &clilen);
       if (newsockfd < 0) 
     error("ERROR on accept");
       bzero(buffer,BUFFER_SIZE);
       //n = read(newsockfd,buffer,sizeof(buffer));
       do 
       {
           n = recv(newsockfd, buffer, sizeof(buffer), MSG_DONTWAIT);  // Use recv instead of read
           if (n>0) 
           {
              buffer[n] = 0;
              msg += buffer;                    // Actually increase the "large" buffer
              pkt++;
           }
       } while (n > 0 || ((n == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)) && pkt == 0));

       if (n<0)
       {

          if ((errno != EAGAIN) && (errno != EWOULDBLOCK))
              error("Reading error\n");
          else
              std::cout << "Noting left to read" << std::endl;
       }

       std::cout << "Here is the message: " << msg << std::endl;
       std::cout << "hellow" << "\n";
       char *message = "HTTP/1.1 200 OK\r\n\r\n<html><body><h1>Hello. Please don't close</h1></body></html>";
       n = write(newsockfd,message,strlen(message));
       if (n < 0) error("ERROR writing to socket");
       else std::cout << "Wrote " << n << " bytes" << std::endl;
       close(newsockfd);
     }
     close(sockfd);
     return 0; 
}

主要变化有:

  • 我使用字符串来存储整个客户端请求,没有任何分配问题。
  • 我使用recv 而不是read 来简化非阻塞行为的使用
  • 循环条件曾经是 errno = eagain 或 errno = willblock,但默认的“rst”行为迫使我计算接收到的数据包的数量。

希望这会有所帮助。

【讨论】:

  • 无论如何,bzero 都是不必要的浪费。读取后的 buffer[n]='\0' 也可以。
  • 确实如此,但我只是从他的来源那里得到的。
  • ..这很糟糕,因为“BUFFERSIZE”的#define 可以防止这样的错误。幻数:(
  • 抱歉无效,但那是buffer[356]。这是我在这里复制代码时的错误。原来是 356。为了确认,我再次进行了相同的测试:(
  • @hatter 摆脱愚蠢的数字。为 BUFFERSIZE 输入 #define 并读取 BUFERSIZE-1 字节。这个 255/355/256/356 只会让你和其他人感到困惑,而且会浪费贡献者的时间来查看不是你实际运行的代码:(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 2017-10-23
  • 2015-04-24
  • 2011-03-28
相关资源
最近更新 更多