【问题标题】:C socket programming - Read and Write of TCP Server and Client out of syncC socket编程——TCP Server和Client的读写不同步
【发布时间】:2021-12-03 01:26:31
【问题描述】:

我有一个简单的 C 语言服务器和客户端。

我要实现的功能的步骤是:

  1. 服务器向客户端发送一个字符“%”并打印在客户端的屏幕上。

  2. 客户端然后输入一条消息,该消息将发送到服务器并打印在服务器的屏幕上,上面写着“客户端:(来自客户端的消息)”。

  3. 服务器最终向客户端发送一条“收到消息”的消息,并将其打印在客户端的屏幕上。

  4. 返回步骤 1。

我为客户端添加了一个名为 valid 的指示器,用于告诉服务器缓冲区(来自客户端的消息)何时真正有效,以防止 客户端写入 和 服务器读取不同步。

但是,我得到的结果是这样的:

提前谢谢你!

客户端代码:

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

void error(const char *msg){
    perror(msg);
    exit(1);  //terminate program
}

int main(int argc, char *argv[]){   
    
    int sockfd/*(file descriptor)*/, portno, n;
    char buffer[255];
    int valid = 0;
    
    struct sockaddr_in serv_addr, cli_addr;
    
    
    portno = 1234;
    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(1234);
    if( connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0 ){
        error("Connection failed");
    }
    
    
S:  bzero(buffer, 255);
    n = read(sockfd, buffer, 255);
    if(n < 0){
        error("Error reading from socket");
    }
    printf("%s", buffer);

    bzero(buffer, 255);
    fgets(buffer, 255, stdin);
    
    // valid is set to 1 and sent to server 
    // when the message in buffer is actually typed in and meant to be sent to the server 
    valid = 1;
    write(sockfd, &valid, sizeof(valid));
    write(sockfd, buffer, strlen(buffer));
    if(strncmp("exit", buffer, 4) == 0){
        goto Q;
    }

    bzero(buffer, 255);
    n = read(sockfd, buffer, 255);
    if(n < 0){
        error("Error reading from socket");
    }
    printf("%s", buffer);
    goto S;
    
Q:  printf("You have chosen to exit. Exit successful\n");
    close(sockfd);
    
    return 0;
}

服务器代码:

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

void error(const char *msg){
    perror(msg);
    exit(1);  //terminate program
}

int main(int argc, char *argv[]){
    
    int sockfd/*(file descriptor)*/, newsockfd, portno, n;
    char buffer[255];
    int valid = 0;

    int count = 0;
    
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t clilen;
    
    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(1234);
    
    if(bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
        error("Building failed");
    }
    
    listen(sockfd, 5); //integer stands for max clients
    clilen = sizeof(cli_addr);
    
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    
    if(newsockfd < 0){
        error("Error on accept");
    }
    
    int num1, num2, ans, choice;
    
S:  
    valid = 0;
    bzero(buffer, 255);
    if(count == 0){
        count++;
        strcpy(buffer, "********************************\n** Welcome to the BBS server. **\n********************************\n%");
    }else{
        strcpy(buffer, "% ");
    }
    n = write(newsockfd, buffer, strlen(buffer));
    if(n < 0){
        error("Error writing to socket");
    }
    
M:
    // check if the message from client (buffer) is valid
    read(newsockfd, &valid, sizeof(int));
    // if not, return to M
    if(valid == 0){
        goto M;
    }
    // continue if valid
    bzero(buffer, 255);
    read(newsockfd, buffer, strlen(buffer));
    if(strncmp("exit", buffer, 4) == 0){
        goto Q;
    }
    printf("Client: %s\n", buffer);
    

    bzero(buffer, 255);
    strcpy(buffer, "Message received.\n");
    n = write(newsockfd, buffer, strlen(buffer));
    if(n < 0){
        error("Error writing to socket\n");
    }
    goto S;
    
    
Q:  close(newsockfd);
    close(sockfd);
    
    return 0;
}

【问题讨论】:

  • printf() 之后使用fflush(stdout);。输出是行缓冲的,因此如果它不以换行符结尾,您将看不到任何内容。
  • 截图中有Command received,但我在代码中没有看到。因此,这与产生该输出的代码不同。
  • 我刚刚编辑更新了正确的照片,谢谢提醒。
  • 不要用goto 语句构造循环。 goto 有几个合理的用例,但它们并不常见,循环不是其中之一。请改用forwhiledo ... while 语句,必要时使用break 和/或continue。有时将您的代码分解为多个函数也会有所帮助。
  • 我尝试按照您的建议使用 fflush(stdout),但是在客户端进行一次写入后,服务器上仍然出现 3 个“客户端:”,客户端上出现了 2 个“收到​​消息”。

标签: c sockets server client


【解决方案1】:

您的代码有很多问题,但导致您询问的特定不当行为的问题来自服务器中的此代码:

    bzero(buffer, 255);
    read(newsockfd, buffer, strlen(buffer));

缓冲区刚刚清零,您可以确信strlen(buffer) 将返回0。因此read() 实际上并不传输任何字节。你可能想要sizeof 而不是strlen

    bzero(buffer, 255);
    read(newsockfd, buffer, sizeof(buffer));

代码中立即明显的其他问题包括

  • 假设双方的write() 呼叫将与另一方的read() 呼叫一对一配对以传输完整的消息。您正在使用流套接字,因此实际上没有消息边界。通常会看到read 接收来自多个writes 的字节,有时来自一个write 的字节会分成两个甚至更多reads。因此,

    • 如果您想以完整消息为单位交换数据,那么您需要在流之上添加一些东西,使参与者能够识别消息边界。

    • 每一方都必须准备好接收部分消息。

    • 您传输的valid 信号值不足以确保客户端和服务器保持同步。

  • readwrite 的调用不能保证传输指定的全部字节数。如果要传输特定数量的字节,则必须使用这些函数的返回值来确定您是否确实这样做了,如果没有,还需要传输多少字节。

  • 客户端和服务器在每个read 处接收到完整大小的缓冲区,并且他们假设缓冲区内容此后可以作为字符串处理。但是,如果一个read 实际上读取了最大字节数(并且没有嵌入的空字节),那么缓冲区将不会被终止,并且其内容可以被视为字符串的假设将导致未定义的行为。

【讨论】:

  • 感谢您指出有关 strlen 和 sizeof 的问题。我也会弄清楚如何正确使用读写功能。
最近更新 更多