【问题标题】:UDP Chat Server using message structs使用消息结构的 UDP 聊天服务器
【发布时间】:2017-03-29 04:08:15
【问题描述】:

我一直在尝试建立一个简单的 UDP 聊天服务器来学习如何制作我自己的聊天室,对于初学者来说,我只是掌握了与共享相同 IP/端口的客户端运行 UDP 服务器的窍门。服务器。客户端以结构的形式发送数据。 服务器应该有 3 种可能性,一个新的客户端加入,它通过匹配命令 JOIN 来识别,或者客户端通过匹配命令 QUIT 退出,或者通过不匹配这两者来识别消息......

这些命令存储在客户端以(ID,COM​​MAND,域/端口)的形式发送的结构中。 除非它是一条消息,否则它将是(ID、域/端口、消息)。我已经在我的代码中处理了。

客户端 100% 工作,因为它作为源提供给我,但我不能使用它,因为它是可执行的,所以问题出在我的服务器上。我制作了一个调试工具来检查出了什么问题,似乎当客户加入并且我向该客户确认我已接受他时,客户从未收到回确认,那是因为我没有格式化它正确。 它应该类似于Received: <1 JOIN loki> 然后Sent: <1 JOIN loki> 其中带括号的3个变量是结构部分..但它没有正确存储作为域/端口部分的第二个字符串......所以当我运行调试时工具我得到Received: <1 JOIN>SENT <1 JOIN>。 那么为什么它会像我将结构解析到我的缓冲区结构然后发送它那样做呢。为什么它没有正确捕获域部分?

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define LOCAL_PORT 2317
#define MAX_LEN 255

struct message {

    int   cid;                    /* Connection ID */

    char  str1[MAX_LEN];         /* JOIN, QUIT, or client-domain-name */

    char  str2[MAX_LEN];        /* user-string or client-domain-name */

};



int rcv_cid;

int rc,sd;

struct message my_msg, rcv_msg;

char rcv_str1[MAX_LEN];

char rcv_str2[MAX_LEN];




int main(int argc, char* argv[]) {

    struct sockaddr_in  servAddr;
     int servLen = sizeof(servAddr);
    int IDlist [] = {0,0,0,0,0,0,0,0,0,0} ;
    int i=0;  int port;
    /* Socket Creation */
    if(argc < 3){
        printf("usage : ./chatServer <port#> <debug_option:0 or 1>\n");
        exit(1);
    }
    sd = socket(AF_INET, SOCK_DGRAM , 0);
    if(sd<0) {
        printf("%s , cannot open socket \n",argv[0]);
        exit(1);
    }

     /* bind argv[1] server port */
     servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /* bind argv[1] which is what the user provides */
        port = atoi(argv[1]);
    servAddr.sin_port = htons(port);

       rc= bind(sd, (struct sockaddr *) &servAddr , sizeof (servAddr));
if(rc <0){
    printf("%s: cannot bind port number %d \n", argv[0], port);
    exit (1);
}
printf("%s: waiting for data on port UDP %d \n", argv[0], port);

while(1) {
    /* grab incoming request */
    rc = recvfrom(sd, &rcv_msg, MAX_LEN, 0,   (struct sockaddr *) &servAddr, &servLen);
   printf(" %s \n" , rcv_msg.str2);
    if(atoi(argv[2]) == 1){
       printf("DEBUG: Receiving <%d %s %s>\n",rcv_msg.cid,rcv_msg.str1,rcv_msg.str2);
   }
    if(strcmp(rcv_msg.str1,"join") == 0 ) {

        for( i =1; i< 11 ; i ++){
            if(IDlist[i] ==  rcv_msg.cid ){
                rcv_msg.cid = i;
                IDlist[i] = rcv_msg.cid;
                break;}
        }



    /*         for(j = 0, j < 10 ; j ++){                     */
    /*          if(IDlist[j]  !=  IDlist [i] ){              */

    my_msg.cid = i;
    strcpy(my_msg.str1, "JOIN");
    strcpy(my_msg.str2, rcv_msg.str2);    

    if(atoi(argv[2]) == 1){
            printf("DEBUG: Sending <%d %s %s>\n",my_msg.cid,my_msg.str1,my_msg.str2);
                                }

    rc = sendto(sd, &my_msg, sizeof(my_msg)+1, 0,
            (struct sockaddr *) &servAddr,
            sizeof(servAddr));
    /* compose a temp struct , then send it to each ID on the list except the one we just added */
    }


if(strcmp(rcv_msg.str1,"QUIT") == 0 ) {

    for( i=0;i<10 ; i++){
        if(rcv_msg.cid == IDlist[i])
            IDlist [i] = 0;
    }
    my_msg.cid =  rcv_msg.cid;
    strcpy(my_msg.str1, "QUIT");
    strcpy(my_msg.str2, rcv_msg.str2);    /*my name Nodname contains domain and port number built into C library */

if(atoi(argv[2]) == 1){
                    printf("DEBUG: Sending <%d %s %s>\n",my_msg.cid,my_msg.str1,my_msg.str2);
                                                        }


    rc = sendto(sd, &my_msg, sizeof(my_msg)+1, 0,
            (struct sockaddr *) &servAddr,
             sizeof(servAddr));
 }
 else {

my_msg.cid =  rcv_msg.cid;
strcpy(my_msg.str1,  rcv_msg.str1);
strcpy(my_msg.str2, rcv_msg.str2);    /*my name Nodname contains domain and port number built into C library */

  if(atoi(argv[2]) == 1){
                printf("DEBUG: Sending <%d %s %s>\n",my_msg.cid,my_msg.str1,my_msg.str2);
                                                    }


rc = sendto(sd, &my_msg, sizeof(my_msg)+1, 0,
        (struct sockaddr *) &servAddr,
        sizeof(servAddr));

                   }

           }

        return 0;
      }

【问题讨论】:

    标签: c networking server udp chat


    【解决方案1】:

    你只允许系统最多读取MAX_LEN字节:

    rc = recvfrom(sd, &rcv_msg, MAX_LEN, 0, 
        (struct sockaddr *)&servAddr, &servLen
    );
    

    但是rcv_msg 是这个大小的两倍多(它是2 * MAX_LEN + sizeof(int))。正确的应该是:

    rc = recvfrom(sd, &rcv_msg, sizeof(rcv_msg), 0,
        (struct sockaddr *)&servAddr, &servLen
    );
    

    另外servLen 必须有类型socklen_t 而不是int;不保证两种类型相同或可互换。

    而且这段代码也是错误的:

    rc = sendto(sd, &my_msg, sizeof(my_msg)+1, 0,
        (struct sockaddr *)&servAddr, sizeof(servAddr)
    );
    

    my_msg 正好是sizeof(my_msg) 大,那你为什么要告诉系统发送sizeof(my_msg)+1 字节呢? +1 在那里做什么?这一字节超出了结构的末尾,它不属于结构,该内存可能正在填充或已经属于不同的变量。您不应该在结构之后读取该字节,如果您不走运,这甚至可能会使您的程序崩溃。

    那么你需要小心大小写。在您的代码中,命令始终为大写(“JOIN”),但此代码行与小写字符串进行比较:

     if(strcmp(rcv_msg.str1,"join") == 0 ) {
    

    strcmp 区分大小写,strcmp("JOIN", "join") 不会返回0

    你有没有想过问题可能不是接收方没有正确接收消息而是发送方没有正确发送消息?我只有你的接收者代码,我没有任何发送者代码。您说客户端按照您在源代码中获得的方式工作,但这并不意味着它可以正常工作,或者以您认为的方式发送数据。在没有看到客户端如何发送数据的情况下,我无法真正告诉您问题的确切原因,而且我也没有测试用例来测试服务器代码。

    如果我解决了上述所有问题并让服务器向自身发送测试消息,这在我的系统上运行良好,您甚至可以直接在线测试该代码并且它也可以正常运行,请参见此处:

    http://rextester.com/MSH79937

    【讨论】:

    • @MohammedAl-Huneidi 然后客户端不会发送看起来像它认为的方式的数据。我在上面指出的您的代码中发现了另外两个错误,但是没有任何客户端代码,我无法构建测试用例并且没有测试用例,我无法告诉您问题出在哪里。
    • @MohammedAl-Huneidi 还添加了一个测试用例,您可以在其中直接在线测试修复的代码,它可以正常工作,所以如果您仍然有问题,您没有正确添加我指出的所有修复或者错误在客户端。
    • 感谢您在回复中澄清我的错误。问题是客户端是可执行文件。因此,除非我反编译它,否则我无法访问它的源代码。但是,当我在激活调试工具的情况下运行客户端时,它显示结构像这样发送 ,其中 Loki 是域名。保证结构遵循我上面在客户端中提到的格式。我的朋友使用了与我相同的可执行代码,他的程序运行良好,他声称他的结构和我的一模一样,这让我抓狂。
    • @MohammedAl-Huneidi 您是否在使用 Linux/UNIX/MacOS 系统?然后您可能可以tcpdump 消息:tcpdump -i INTF -X udp port 2317 之类的东西应该可以工作,其中 INTF 它是要监视的接口的名称(例如 Linux 上的 eth0 或 eth1,MacOS 上的 en0 或 en1 等)这将捕获所有 UDP来自/到端口 2317 的数据包到达选定的接口,并将它们作为十六进制转储转储到终端。按 CTRL+C 再次停止该命令。编辑您的问题并添加此类数据包的 hexdump,以便我们查看它的真实外观。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 2017-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多