C UDP 客户端-服务器卡住通信

C UDP Client-Server stuck comunication

为了我的一门大学课程,我必须用 C 语言实现一个使用 UDP Client-Server 的简单聊天程序,这是老师发给我们的描述:

You should develop a private chat environment to exchange text messages between hosts. Message encryption is optional but not required. The project should be composed by 2 main modules:

  • Server: receives and stores each message in a sort of chat database. A very naive database would consist in a User struct, that contains everything (login credentials, chats, ...). Each Chat structure contains the actual messages.
  • Client: provides a very simple interface to select a receiver for our message and then write the content of the message. Login is required. If a receiver is not subscribed returns an error.

The project should be tested with at least 3 users :)

我设法实现了身份验证阶段,但是在尝试实现消息交换阶段时我遇到了困难。当我尝试将在线用户的 linked_list 从服务器发送到客户端时,执行会冻结,不仅如此,它还会产生一些随机行为,有时在第一次尝试时卡住,有时在第二次尝试时卡住,依此类推。我还注意到,当我在客户端中引入一个单独的线程来处理消息的收件箱时,情况变得更糟,比以前更容易卡住。我会将负责发送和接收在线用户的函数代码以及 link 添加到我的 git 存储库中,如果您需要,可以在其中找到完整代码。

这是服务器中的代码:

void Send_list(ListHead* head, int sockfd,struct sockaddr_in cliaddr, int size){
int written_bytes;
int len = sizeof(cliaddr);
char username[50];


if(head->size == 1){
    return;
}

ListItem* aux = head->first;
for(int i=0;i<size;i++){
    memset(username,0,sizeof(username));
    UserListItem* uitem = (UserListItem*) aux;
    strcpy(username,uitem->user.username);
    do{
        written_bytes = 0;
        written_bytes = sendto(sockfd,(const char *)username,strlen(username),0,(const struct sockaddr*)&cliaddr,len); 
    }while(written_bytes != strlen(username));
    printf("\nusername mandato: %s",username);
    printf("\n");
    if(aux->next){
        aux = aux->next;
    }
  }
}

这是客户端中的代码:

int recv_list(int sockfd,struct sockaddr_in  servaddr, ListHead* head,int size, char username[50]){

char onuser[50];
int len = sizeof(servaddr);
int read_bytes;
int idx = 1;   

if(size == 1){
    return 1;
}

for(int i=0;i<size;i++){
    
    read_bytes = recvfrom(sockfd,(char *)onuser,sizeof(onuser),0,(struct sockaddr*)&servaddr,&len );
    onuser[read_bytes] = '[=11=]';
    if(List_find_by_username(head,onuser) == 0 || strcmp(onuser,username)){
        UserListItem* uitem = malloc(sizeof(UserListItem));
        memset(uitem,0,sizeof(UserListItem));
        UList_init(uitem,onuser);
        uitem->idx = idx++;        
        ListItem* result = List_insert(head,head->last,(ListItem*)uitem);
        assert(result);
    }
    memset(onuser,0,sizeof(onuser));   
    
}


UserList_print(head);
return 0; 
}

这是我的 git 回购的 link:https://gitlab.com/antonio_ciprani/so-progetto-20_21

我在基于 Ubuntu 的系统中工作。

我真的希望有人能帮助我,因为这让我发疯:(

I also noticed that when I introduced a separated thread in the Client to handle the inbox of messages the situation got worst getting stuck more often then before.

确实,在您的程序中使用线程弊大于利。特别是在 main 循环中你 pthread_create 一个新的 reciving 线程,它与主线程竞争传入消息,扰乱了 recv_list 的过程。最好不要在你的项目中使用线程 - 你会避免很多问题。

我们先写两个辅助函数:

void store(thread_args_t *targs, Message *msg)
{   // code taken from your function "reciving"
    if (!strcmp(targs->user->username, msg->reciver))
    {
        Inbox *mitem = malloc(sizeof (Inbox));
        strcpy(mitem->msg.sender,  msg->sender);
        strcpy(mitem->msg.reciver, msg->reciver);
        strcpy(mitem->msg.data,    msg->data);
        ListItem *result =
            List_insert(targs->inbox, targs->inbox->last, (ListItem *)mitem);
        assert(result);
    }
}
char *input(thread_args_t *targs)
{   // wait for user input and store incoming messages
    fflush(stdout);
    fd_set fds, fdr;
    FD_ZERO(&fds);
    FD_SET(0, &fds);                // add STDIN to the fd set
    FD_SET(targs->sockfd, &fds);    // add socket to the fd set
    for (; ; )
    {
        if (fdr = fds, select(targs->sockfd+1, &fdr, NULL, NULL, NULL) < 0)
            perror("select"), exit(1);
        if (FD_ISSET(0, &fdr))
        {   // this is the user's input
            static char data[256];
            if (!fgets(data, sizeof data, stdin)) return NULL;  // no more user input
            data[strlen(data)-1] = '[=11=]';
            return data;
        }
        // if no user input, then there's a message
        Message msg;
        socklen_t len = sizeof targs->servaddr;
        if (recvfrom(targs->sockfd, &msg, sizeof msg, 0,
                     (struct sockaddr *)targs->servaddr, &len) < 0)
            perror("recvfrom"), exit(1);
        store(targs, &msg);
    }
}

现在您可以将 main 中的主循环体替换为:

        int ret, op;
        printf("\nPlease choose an option: ");
        printf("\n1.Send a message!");
        printf("\n2.Incoming messages!");
        printf("\n3.Logout!");
        printf("\nYour choice:\t");
        char *str = input(&targs);
        sscanf(str, "%d", &ret);
        printf("\nqua bro?\n");
        if (ret == 1)
        {
            printf("\nqua loz?\n");
            int res, read_bytes, size, id;
            op = 3;
            socklen_t len = sizeof servaddr;
            sendto(sockfd, &op, sizeof op, MSG_CONFIRM,
                   (struct sockaddr *)&servaddr, len);
            printf("\nqua shiiis?\n");
            // We cannot preclude that a message arrives here,
            // therefore we must handle that case.
            Message msg;
            while ((read_bytes = recvfrom(sockfd, &msg, sizeof msg, 0,
                                          (struct sockaddr *)&servaddr,
                                          &len)) == sizeof msg)
                store(&targs, &msg);
            if (read_bytes == -1) perror("recvfrom"), exit(1);
            size = *(int *)&msg;
            printf("\nqua ci siamo?\n");
            res = recv_list(sockfd, servaddr, &on_list, size, user.username);
            printf("\nqua?\n");
            if (res == 0)
            {
                printf("\nChoose whom you want to send a message to");
                printf("\nYour choice:\t");
                str = input(&targs);
                sscanf(str, "%d", &id);
                printf("\nWrite the message you want to send:\n");
                str = input(&targs);
                Init_msg(&msg, str, id, &on_list, user.username);
                int written_bytes = sendto(sockfd, &msg, sizeof msg, MSG_CONFIRM,
                                           (struct sockaddr *)&servaddr, len);
                if (written_bytes == -1) perror("sendto"), exit(1);
                // With your present server, a message cannot arrive here, but you
                // possibly will want to change that, so let's handle it already.
                while ((read_bytes = recvfrom(sockfd, &msg, sizeof msg, 0,
                                              (struct sockaddr *)&servaddr,
                                              &len)) == sizeof msg)
                    store(&targs, &msg);
                if (read_bytes == -1) perror("recvfrom"), exit(1);
                int sent = *(int *)&msg;
                if (sent == 0) printf("\nMessage sent!");
                else
                if (sent == 1) printf("\nUser is offline :(");
            }
            else
            if (res == 1) printf("\nNo online user :(");
        }
        else
        if (ret == 2) Print_msg(&inbox);

您可能想要改进的下一件事是修改服务器函数 Forward_message 以便它允许在等待消息时接收来自另一个客户端的传入命令。