c - 服务器程序卡在 read() 函数上,但 write() 函数在客户端程序中运行良好

标签 c linux sockets network-programming

<分区>

我正在使用 C 中的套接字编程方法开发一个简单的聊天室程序。我实现了客户端可以连接到服务器并向服务器发送消息,并且服务器应该向其他服务器分发消息。

但是当我尝试测试我的程序并尝试让两个客户端通信时,奇怪的事情发生了。如果我按照以下步骤操作,就会发生奇怪的事情。

  1. 运行服务器程序并等待连接。
  2. 打开一个客户端程序并连接到服务器,然后发送两条消息,都成功。
  3. 打开另一个客户端,同理,第一个客户端可以完美接收到第二个客户端发送的消息。
  4. 但是当我切换到第一台服务器并尝试编写新消息并发送到要分发的服务器时,它失败了。并且第二个客户端和服务器都无法接收到此消息。
  5. 经过一些调试,服务器程序似乎停留在 read() 函数上。 顺便说一句,我在不同终端的Uubuntu18.04 虚拟机上完成了所有这些操作。

更多要点:

  1. 我已经尝试调试并打印我能得到的所有值,客户端程序的 write() 函数似乎工作正常,但服务器程序停止在 Receive 中的第二个函数 read()线程。
  2. 我认为这可能是关于 read() 函数的阻塞状态的问题,但我是网络编程的新手,不太明白。

这是我的问题的截图(聊天室:左边是服务器,右边是两个客户端):

Chat-room:Left is a server, right are two clients

这里是我的client.c和server.c的代码,我已经指出了问题发生的位置。

客户端.C

/* tcp-client.c */
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

//send the message to the server any time terminal get input
void*  Send(void* Socked)
{
    char sender[80];
    //save the socked into a int pointer
    int *SockedCopy = Socked;
    while(fgets(sender, sizeof(sender), stdin)){
        //whenever enter a string, send it
        int messageSize = strlen(sender) + 1;
        write(*SockedCopy, &messageSize, sizeof(int));
        int i = write(*SockedCopy, sender, messageSize);      //both work when sending message
        printf("write over! the write() returns %d\n", i);
        //check whether this is a quit message
        if(strcmp(sender, "q!") == 0)
            exit(1);

    }
}


//receive message from server
void* Receive(void* Socked)
{
    int *SockedCopy = Socked;
    char Receiver[80];

    while(1){
        //read message continuosly
        int reveiverEnd = 0;
        reveiverEnd  = read (*SockedCopy, Receiver, 1000);
        Receiver[reveiverEnd] = '\0';   
        fputs(Receiver, stdout);
        Receiver[0] = '\0';
    }
}

int main ()
{
    int sockfd, n;
    pthread_t threadSend;
    pthread_t threadReceive;
    struct sockaddr_in serv, cli;
    char rec[1000];
    char send[80];

    //input UserName
    printf("Input Username:" );
    fgets(send, sizeof(send), stdin);
    send[strlen(send) - 1] = '\0';
    int MessageSize = strlen(send);

    //create socked
    sockfd = socket (PF_INET, SOCK_STREAM, 0);

    //create server information
    bzero (&serv, sizeof (serv));
    serv.sin_family = PF_INET;
    serv.sin_port = htons (8888);
    serv.sin_addr.s_addr = inet_addr ("127.0.0.1" /*local machine*/);

    //connect to the server
    connect (sockfd, (struct sockaddr *) &serv, sizeof (struct sockaddr));

    //send the user name to the server
    write(sockfd, &MessageSize, sizeof(int));
    write (sockfd, send, sizeof(send));

    //get successfully connecting message
    n = read (sockfd, rec, 1000);//n marks real length
    rec[n] = '\0';  
    fputs(rec, stdout);

    send[0] = '\0';

    //open send thread  
    pthread_create(&threadSend, 0, Send, &sockfd);

    //open receiving message thread
    pthread_create(&threadReceive, 0, Receive, &sockfd);

    //close socked and close two threads
    for(int i = 0; i < 100; ++i)
        sleep(100000);
    pthread_exit(&threadSend);
    pthread_exit(&threadReceive);
    close(sockfd);

    return 0;
}

服务器.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <unistd.h>
#include <unistd.h>

pthread_t thread;
pthread_t threadClient[100];
int ServerSock;

//Every client's information
typedef struct
{
    int sock;
    char UserName[16]; 
    struct sockaddr address;
    int addr_len;
} connection_t;
static connection_t conn[100];

//this function distributes the messsage/status of single client to the other
//Info is the message needed to be distributed
int SendInfo(void* Info)
{
    char *info = Info;
    for(int i = 0; i < 100; ++i)
        //send to the client that exists and doesn't quit room
        if(conn[i].addr_len != -1 && conn[i].addr_len != 0){
            if(send (conn[i].sock, info , strlen(info) + 1, 0) == -1)
                printf("error occured, send to %s fail", conn[i].UserName);
            printf("send %s to %s successfully!\n", info, conn[i].UserName);
        }   
    return 0;   
}


//This function deals with single client, aim to receive message from this client
//and then send them to another using SendIinfo
void* Receive(void* clientnumber)
{
    int* Clientnumber = clientnumber;
    while(1)
    {
        //read the message from the client
        char *Buffer;
        int messageLen = 0;
        read(conn[*Clientnumber].sock, &messageLen, sizeof(int));
        printf("receive from %d\n", messageLen);
        if(messageLen > 0)
        {
            Buffer = (char *)malloc((messageLen+1)*sizeof(char));
            read(conn[*Clientnumber].sock, Buffer, messageLen);   // the program stucks here and don't know why

            if(Buffer[0] != ':') continue;
            Buffer[messageLen] = '\0';
            //whether the client want to quit
            if( Buffer[1] == 'q' && Buffer[2] == '!' )
            {
                //constitute quit message and delete this client
                char quit[] = " quit the chat room";
                char quitMessage[20];       
                quitMessage[0] = '\0';
                strcat(conn[*Clientnumber].UserName, quit); 
                SendInfo(quitMessage);
                conn[*Clientnumber].addr_len = -1;
                pthread_exit(&threadClient[*Clientnumber]);
            }
            else{
                //constitute the message
                char begin[] = " says";
                char messageDistribute[200];
                messageDistribute[0] = '\0';
                strcat(messageDistribute, conn[*Clientnumber].UserName);
                strcat(messageDistribute, begin);
                strcat(messageDistribute, Buffer);
                SendInfo(messageDistribute);
            }
            free(Buffer);
        }
        else
            continue;
    }
}



//aim to accept whenever there is a client trying to connect
void * process(void * ptr)
{
    pthread_t clientThread[100];
    char * buffer;
    int len;
    //the number of the client connecting now
    int clientNumber = 0;      
    long addr = 0;
    while(1){
        //waiting to be connected
        if(clientNumber < 100)
        {
            conn[clientNumber].sock = accept(ServerSock, &conn[clientNumber].address, &conn[clientNumber].addr_len);
        }
        else
            break;


        //the length of the message
        read(conn[clientNumber].sock, &len, sizeof(int));
        if (len > 0)
        {

            //multiple information of a client
            addr = (long)((struct sockaddr_in *)&conn[clientNumber].address)->sin_addr.s_addr;
            buffer = (char *)malloc((len+1)*sizeof(char));
            buffer[len] = 0;
            read(conn[clientNumber].sock, buffer, len);


            //send success message to the client
            send (conn[clientNumber].sock, "You have entered the chatroom, Start CHATTING Now!\n", 51, 0);


            //save client's nick name
            strcpy(conn[clientNumber].UserName, buffer);


            printf("User <%s> has entered the Chatroom!\n", conn[clientNumber].UserName);
            printf("There are %d people in Chatroom now!\n",clientNumber+1);
            free(buffer);

            //create a thread dealing the messages from a single client
            int number = clientNumber;
            pthread_create(&threadClient[clientNumber], 0, Receive, &number);
        }
        clientNumber += 1;

    }
    pthread_exit(0);
}

int main(int argc, char ** argv){
    struct sockaddr_in address;
    int port = 8888;
    connection_t * connection;

    //create socked
    ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //bind the socked to a port
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(port);
    if (bind(ServerSock, (struct sockaddr *)&address, sizeof(struct sockaddr_in)) < 0)
    {
        fprintf(stderr, "error: cannot bind socket to port %d\n", port);
        return -4;
    }


    //listen for connections
    listen(ServerSock, 100);
    printf("the server is ready and listening\n");


    //creating a thread dealing with connections
    pthread_create(&thread, 0, process, (void *)connection);


    //keep this program working
    for(int i = 0; i < 100; ++i)
        sleep(10000);


    //close socked and thread
    pthread_detach(thread);
    close(ServerSock);
    return 0;
}

这是一份学术作业。

最佳答案

您的问题根本不是网络问题,而是您将 clientnumber 参数传递给 Receive 线程的方式出错:

 if (len > 0)
 {
    [...]

    int number = clientNumber;
    pthread_create(&threadClient[clientNumber], 0, Receive, &number);
 }  // number gets destroyed here

请注意,在上面的代码中,number 是一个局部变量,这意味着它会在范围退出时立即销毁(即一旦执行到 } 行,上面)。

但是,您正在将指向 number 的指针(即 &number)传递给您的接收线程,并且您的接收线程会尝试使用该指针来查找有关客户。这意味着 Receive 线程正在取消引用悬空指针,这会调用未定义的行为。 (在这种情况下,我看到的是该程序运行了一小段时间,然后停止运行,因为 Clientnumber 指向的内存位置已被其他一些数据覆盖,因此下一次 Receive 线程试图取消引用指针时,它最终会看到错误的数据,因此在错误的套接字 fd 上调用 read() ...但是在原则上任何事情都可能发生,因为一旦你调用了未定义的行为,所有的赌注都没有了)

无论如何,解决该问题的一种简单方法是直接传递指向 connection_t 对象的指针,而不是传递索引,即:

pthread_create(&threadClient[clientNumber], 0, Receive, &conn[clientNumber] );

.... 然后您可以直接在您的Receive 线程中使用该指针:

void* Receive(void* arg)
{
   connection_t * conn = (connection_t *) arg; 

   [...]

   int r = read(conn->sock, &messageLen, sizeof(int));

这样做不会调用未定义的行为,因为 connection_t conn[100]; 数组被声明为静态/全局的,因此保证其中的对象不会被破坏直到进程退出。

如果您仍然需要从 Receive() 中访问 clientNumber 索引值,您可以将该值作为另一个字段添加到 connection_t 结构定义。

关于c - 服务器程序卡在 read() 函数上,但 write() 函数在客户端程序中运行良好,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56208447/

相关文章:

c - 在 gcc 3.4.3 中使用原子操作

php - 无法保存库存项目。请再试一次。 Magento 2.4.0

我可以使用 tun/tap 和原始套接字制作 "TCP packet modifier"吗?

c# - 异步返回服务器消息到客户端

c - 使用 fscanf 时出现段错误

c - 如何获取当前用户的用户名?

c - 不小心创建了巨大的文件

javascript - Node.js + Socket.io 如何从 PHP 接收数据

c - 运行客户端时为 "address not supported by protocol"

c - 什么最好使用 volatile?当我们在局部变量或形式变量中使用时会发生什么?