【问题标题】:C UDP Client-Server stuck comunicationC UDP客户端-服务器卡住通信
【发布时间】:2021-12-20 02:16:30
【问题描述】:

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

你应该开发一个私人聊天环境来交流 主机之间的短信。消息加密是可选的,但不是必需的。 该项目应由2个主要模块组成:

  • 服务器:接收每条消息并将其存储在一种聊天数据库中。 一个非常幼稚的数据库将包含在 User 结构中, 包含所有内容(登录凭据、聊天...)。 每个Chat 结构都包含实际消息。
  • 客户端:提供了一个非常简单的界面来选择一个 接收我们的消息,然后编写消息的内容。 需要登录。如果接收者未订阅,则返回错误。

该项目应该至少有 3 个用户进行测试:)

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

这是服务器中的代码:

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] = '\0';
    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 仓库的链接:https://gitlab.com/antonio_ciprani/so-progetto-20_21

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

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

【问题讨论】:

  • sendto 返回错误会发生什么(当它返回 -1 时)?
  • 我还建议您花时间学习如何调试您的程序。就像使用 debugger 来逐句执行代码,同时监控变量及其值。
  • @Someprogrammerdude 您是否暗示冻结是由于对 sendto 结果缺乏控制而发生的?我也尝试使用 Valgrind 和 gdb,但我无法理解问题到底发生在哪里,仅在这两个函数中
  • 如果sendto失败一次返回-1,会继续失败返回-1,导致死循环。这会让你的程序看起来像是“卡住了”。
  • @Someprogrammerdude 谢谢,我已经按照您的建议添加了一个 break 语句以防出现 -1 结果并且工作正常,直到我取消注释客户端中收件箱线程的代码并且尽快我已经完成了一切都停止了工作,我检查并添加了对 sendtos 和 recvfroms 的控制但似乎没有工作,你可能知道为什么会这样吗?

标签: c udp client-server


【解决方案1】:

我还注意到,当我在客户端中引入一个单独的线程来处理收件箱的消息时,情况变得更糟,比以前更频繁地卡住了。

确实,在程序中使用线程弊大于利。尤其是在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] = '\0';
            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,以便在等待消息时允许来自另一个客户端的传入命令。

【讨论】:

  • 好吧,我有点猜到他们在竞争,但我认为我真的需要一个单独的线程来处理inbox,否则我将无法异步接收来自其他用户的消息,你呢?知道在不发生这种情况的情况下实现reciving 的方法吗?
  • 第二个选项是显示存储的消息,异步我的意思是你可以在任何时候收到消息,你也可以在给其他人写消息或在菜单选项中空闲时接收消息。英语不是我的主要语言,希望我能正确解释
  • 是的,我真的对不同的方法很感兴趣,我并不专注于使用多线程,唯一重要的是使用 C 实现我需要的最终结果
  • 非常感谢,您现在无法想象您对我的帮助有多大。它工作得很好,我会考虑如何在你答案的最后部分实施建议。非常感谢,再次感谢您
  • 哦,是的,现在我真的明白了,我什至无法想到。你为我打开了一个世界再次感谢您的进一步解释。你说得很清楚,对我帮助很大,可能比你想象的要多:D
猜你喜欢
  • 2012-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多