在TCP连接中,当双方同时发送时,数据包丢失

在TCP连接中,当双方同时发送时,数据包丢失,c,sockets,networking,tcp,C,Sockets,Networking,Tcp,我正在使用tcp连接进行编码。但我观察到,当双方发送/接收消息时,数据包都会丢失。我不知道这里出了什么问题 下面是一个最起码的例子。我使用gcc(没有标志/选项)进行编译。 在for循环中,双方每次都向对方发送128字节的消息。它包含一个序列num(第一个字节)。但似乎有时发送的信息总是丢失 服务器: #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <

我正在使用tcp连接进行编码。但我观察到,当双方发送/接收消息时,数据包都会丢失。我不知道这里出了什么问题

下面是一个最起码的例子。我使用gcc(没有标志/选项)进行编译。 在for循环中,双方每次都向对方发送128字节的消息。它包含一个序列num(第一个字节)。但似乎有时发送的信息总是丢失

服务器:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>
#include <stdlib.h>
void error(const char *msg)
{
    printf("%s\n",msg);
    exit(0);
}
FILE* server_open_socket(int portno)
{
    int sockfd, newsockfd;
    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    int on=1;
    if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
    {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }
    if (bind(sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0)
        error("ERROR on binding");
    listen(sockfd,5);
    clilen = sizeof(cli_addr);
    newsockfd = accept(sockfd,
                       (struct sockaddr *) &cli_addr,
                       &clilen);
    if (newsockfd < 0)
        error("ERROR on accept");
    close(sockfd);
    FILE *stream;
    stream = fdopen(newsockfd, "wb+");
    printf("connected Server at %d\n",portno);
    return stream;
}
int main()
{
    FILE *S;
    S=server_open_socket(12345);
    char sbuffer[128],rbuffer[128];
    for(int i=0;i<128;i++)
        sbuffer[i]=0;
    for(int i=0;i<256;i++)
    {
        rbuffer[0]=0;
        sbuffer[0]=1+i%255;
        int a=fwrite(sbuffer,sizeof(unsigned char),128,S);
        int b=fread(rbuffer,sizeof(unsigned char),128,S);
        if((a!=128)||(b!=128))
            printf("Network error!\n");
        if(rbuffer[0]!=sbuffer[0])
        {
            printf("msgerror! %d %d\n",rbuffer[0],sbuffer[0]);
            exit(0);
        }
   }

}
客户:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>
#include <stdlib.h>
void error(const char *msg)
{
    printf("%s\n",msg);
    exit(0);
}
FILE* client_open_socket(int portno)
{
    int sockfd;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");
    server = gethostbyname("localhost");//gethostbyname(argv[1]);
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
          (char *)&serv_addr.sin_addr.s_addr,
          server->h_length);
    serv_addr.sin_port = htons(portno);
    while(connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) == -1) {
        sleep(1000);
    }
    FILE *stream;
    stream = fdopen(sockfd, "wb+");
    printf("connected Clinet at %d\n",portno);
    return stream;
}
int main()
{
    FILE *S;
    S=client_open_socket(12345);
    char sbuffer[128],rbuffer[128];
    for(int i=0;i<128;i++)
        sbuffer[i]=0;
    for(int i=0;i<256;i++)
    {
        rbuffer[0]=0;
        sbuffer[0]=1+i%255;
        int a=fwrite(sbuffer,sizeof(unsigned char),128,S);
        int b=fread(rbuffer,sizeof(unsigned char),128,S);
        if((a!=128)||(b!=128))
            printf("Network error!\n");
        if(rbuffer[0]!=sbuffer[0])
        {
            printf("msgerror! %d %d\n",rbuffer[0],sbuffer[0]);
            exit(0);
        }
    }
}
msgerror! 3 2
似乎缺少来自服务器的以“2”开头的消息

如果我让双方按顺序发送消息(服务器发送然后侦听,客户端侦听然后发送),这个问题似乎就消失了。但我也不知道为什么


当涉及多个参与方时,例如,三台机器/主机/参与方,每台机器/主机/参与方对另一方都有一条不同的消息:总共6条消息。我该如何处理这样的任务?

你完全把stdio库搞糊涂了

不能在同一套接字上同时在两个方向使用缓冲流。缓冲流机制的设计考虑了一个特定的模型:磁盘上普通文件的模型。插座不是那样的。也就是说,该库是为一种模型设计的,其中“写入”端和“读取”端共享一个底层文件指针,并且您可以同时读取和写入数据的同一区域

考虑一下,如果您在磁盘上操作一个普通文件,那么这将如何工作,并将其与套接字进行对比。您可以从“文件”中读取128个字节。这会导致库实际读取(如在
read(2)
系统调用中)至少128个字节,但它会尝试读取比正常情况下更多的字节,直到达到某个较大的缓冲区大小。然后,它会将128字节传输到缓冲区,并将其内部文件偏移量设置为128。如果然后写入128字节,我相信stdio库将覆盖其内部缓冲区中从偏移量128开始的所有字节,长度为128

如果它最初读取的数据超过128字节,那么它现在需要重新定位底层文件指针,然后才能将数据“刷新到磁盘”(即调用
write(2)
)。我不确定它到底会写多少,但它会尝试在其内部缓冲区中保持数据的一致视图——但它再次假设了一个与双向套接字数据流完全不同的模型。因此,在执行写入之前,它将尝试重新定位文件指针以准备更新文件,因为“文件”实际上是一个套接字,
lseek(2)
系统调用失败(这解释了您看到的“网络错误”)

您可以在套接字上创建两个单独的缓冲流,一个用于读取(
fdopen(sockfd,“rb”)
),另一个用于写入(
fdopen(sockfd,“wb”)
)。我不知道库标准化是否能保证这一点——可能不能——但由于每个缓冲流都是完全独立的,而且您只从一个流读取数据,而只向另一个流写入数据,因此库不需要以影响两个方向的方式调整其文件偏移量,该库可以将每个“流”几乎完全视为磁盘上正在缓慢增长的文件


如果您尝试这样做,则需要添加一些
fflush(3)
调用,以强制库实际将缓冲数据发送到对等方。因为否则,它不知道它需要这样做。

注意:'SOCK_STREAM'是写入还是读取失败?您永远不会检查
fdopen()
@jwdonahue的结果如果我不检查消息的正确性,程序可以继续,直到其中一方脱机(另一方给出“fread错误”)。因此我认为fdopen()不会失败。如果不检查返回值,就不知道是否出了问题。这是非常弱的代码。您还应该独立检查写入和读取结果,以便调试此问题。非常感谢。你的回答使我摆脱了这个问题。
msgerror! 3 2