c - TCP连接双方同时发送时丢包

标签 c sockets networking tcp

我正在使用 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);
        }
    }
}

这是可能的输出:

服务器:

Network error!
msgerror! 0 3

客户:

msgerror! 3 2

似乎缺少来自服务器的以“2”开头的消息。

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

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

最佳答案

你完全混淆了 stdio 库。

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

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

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

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

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

关于c - TCP连接双方同时发送时丢包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49392844/

相关文章:

windows - 如何关闭 Windows 机器上的套接字?

c - 尝试释放时程序崩溃()

c - 从文本文件中读取结构

c - GCC 中不需要指定哪些类型的库?

.net - 在 onconnect 例程中调用 winform 时出错

c - 在socket编程c中,为什么我们使用memset将值0填充到结构体中?

c - Mlt 框架 - 32 位 Windows 构建无法正常工作

python - 指纹识别远程操作系统 : Python

C:关于 Beej 的网络指南的问题......这里有一个假设吗?

java - 连接到局域网