通过TCP套接字发送char缓冲区不完整

时间:2014-06-28 15:22:25

标签: unix tcp sockets c

我正在学习如何在C中处理套接字和TCP连接。我有一个应用程序(很长的一个),它基本上发送和接收char数组,系统调用从服务器写入客户端和反之亦然(当然是两个独立的C应用程序)。只要我使用本地连接,在同一台PC上,在终端上运行服务器而在另一台PC上运行客户端,一切正常,数据到达目的地。但是,如果我尝试在一台计算机上使用服务器而在另一台计算机上使用另一台计算机上的客户端,则向客户端传递一个类似于192.168.1.X的地址(从运行服务器的计算机上获取),之后连接建立后,我收到一个错误,告诉我预期的字节数(我在发送真实的char []之前传递的)没有到达。如果我在我的PC上尝试服务器,而另一个客户端在不同的提供商上使用不同的线路,那么同样的事情。

我缺少一些东西,是否有顺序发送一堆字节的限制?

弹出错误的代码。

服务器端:

r=htonl(lghstr);
w=write(myFd,&r,sizeof(int));//writes the number of incoming bytes
if(w<0) perror("writeServer4"),exit(-1);
w=write(myFd,tmp->string,lghstr);
if(w<0) perror("writeServer5"),exit(-1);
if(w!=lghstr) perror("ERROR");

客户端

rC=read(fdc,&cod,sizeof(int));//read incoming number of bytes
lghstr=ntohl(cod);
if(rC<0) perror("readClient3"),exit(-1);
rC=read(fdc,dest,lghstr);
if(rC<0) perror("readClient4"),exit(-1);
if(rC!=lghstr) perror("error : "), printf("didn't read the right number of bytes"),exit(-1);

现在这种情况基本上重复了很多次,甚至说了300次,并且程序无法正常工作。

3 个答案:

答案 0 :(得分:2)

你在一边做ntohl()而不是另一边。这可能是用错误的值解释字节。

你应该printf()两边的字节,看看int正在评估什么。

编辑:我确信这是记录的编程错误。

如果我不得不猜测,我会说出于某些原因你不会与另一方同步。你说这个'大约300次'。

尝试在协议中添加一个魔术整数。

以下是按此顺序发送的客户端示例。

  1. 一个始终不变的魔术整数。
  2. 即将发送的长度字节。
  3. 要发送的字节。
  4. 这使用分散收集机制(它更适合序列化)但除此之外,它实际上正在做同样的事情,作为客户端,只需添加一个魔术值。

    当接收方接收到数据时,它可以通过检查数字的来源来验证数据是否按正确的顺序进行验证。如果魔法是错误的,则意味着客户端或服务器在位置上丢失了流。

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/uio.h>
    #include <err.h>
    
    #include <time.h>
    
    #define MAGIC 0xDEADBEEFLU
    #define GARBAGE_MAX 65536
    
    const int iterations = 3000;
    
    char * create_garbage_buf(
        void)
    {
      int rc = -1;
      int fd = -1;
      char *buf = NULL;
    
      buf = malloc(GARBAGE_MAX);
      if (!buf)
        err(1, "Cannot allocate buf");
    
      fd = open("/dev/urandom", O_RDONLY);
      if (fd < 0)
        err(1, "Cannot open urandom");
    
      rc = read(fd, buf, GARBAGE_MAX);
      if (rc < 0)
        err(1, "Cannot read from urandom");
      else if (rc != GARBAGE_MAX)
        errx(1, "Expected %d bytes, but got %d reading from urandom",
             GARBAGE_MAX, rc);
    
      close(fd);
      return buf;
    }
    
    int main() {
      int fd, offset, i, rc;
      uint32_t magic = MAGIC;
      uint32_t blen = 0;
      char *buf = NULL;
      struct iovec vecs[3];
    
      /* Seed poor random number generator */
      srand(time(NULL));
    
      /* Use a file for demonstration, but a socket will do just fine */
      fd = open("/dev/null", O_WRONLY);
    
      /* Create some garbage to send */
      buf = create_garbage_buf();
    
      if (fd < 0)
        err(1, "Cannot open file");
    
      /* The first vector, is always the magic */
      vecs[0].iov_len = sizeof(uint32_t);
      vecs[0].iov_base = &magic;
      for (i=0; i < iterations; i++) {
        /* The second vector represents lengh of what we send
         * in this demonstration it is a number between 0 and
         * GARBAGE_MAX/2.
         */
         blen = rand() % (GARBAGE_MAX / 2);
         vecs[1].iov_len = sizeof(uint32_t);
         vecs[1].iov_base = &blen;
         /* The last record is the data to send. Its another random
          * number between 0 and GARBAGE_MAX which represents the offset
          * in our garbage data to send */
         offset = rand() % (GARBAGE_MAX / 2);
         vecs[2].iov_len = blen;
         vecs[2].iov_base = &buf[offset];
    
         rc = writev(fd, vecs, 3);
         if (rc < 0)
           err(1, "Could not write data");
         if (rc != (sizeof(uint32_t)*2 + blen))
           errx(1, "Did not write proper number of bytes to handle");
    
         printf("Wrote %u bytes from offset %u in garbage\n", blen, offset);
      }
    
      free(buf);
      printf("Done!\n");
      return 0;
    }
    

答案 1 :(得分:2)

这是问题所在:

rC=read(fdc,dest,lghstr);
...
if(rC!=lghstr) perror("error : ")

套接字编程的#1谬误是期望recv()read()将返回与另一方发出的写入/发送调用相对应的完全相同的字节数。

实际上,部分数据非常可能和预期。简单的解决方法是循环读取/ recv,直到获得预期的确切字节数:

size_t count = 0;
while (count < lghstr)
{
    ssize_t readresult = read(fdc, dest+count, lghstr-count);
    if (readresult == -1)
    {
      // socket error - handle appropriately (typically, just close the connection)
    }
    else if (readresult == 0)
    {
      // The other side closed the connection - handle appropriately (close the connection)
    }
   else
    {
        count += readresult;
    }
}

循环的另一种替代方法是使用带套接字的MSG_WAITALL标志。这意味着,使用recv()而不是read()。您仍然需要处理错误案例。

rc = recv(fdc, dest, lghstr, MSG_WAITALL);
if (rc == -1)
{
  // socket error
}
else if (rc == 0)
{
  // socket closed by remote
}
else if (rc < lghstr)
{
   // the other side likely closed the connection and this is residual data (next recv will return 0)
}

答案 2 :(得分:1)

仔细阅读read() / write()的文档,并了解这两个函数不一定是read() / write()字节,因为它们被告知,但很少。因此,围绕此类调用进行循环计数,直到读取/写入所有预期数据为止,这是一个好主意,而不是说必不可少。

例如,如何编写此内容,您可能希望查看此答案:https://stackoverflow.com/a/24260280/694576并阅读此答案:https://stackoverflow.com/a/20149925/694576