1

我正在编写一个需要可靠 UDP 的实时游戏,因此我选择使用 UDT(基于 UDP 的数据传输协议 - http://sourceforge.net/projects/udt/)。

客户端(在浏览器上)通过 CGI 脚本向我的服务器发送实时消息。问题是有一些消息正在丢失,我不知道为什么,因为服务器说它已成功将所有消息发送到相应的客户端,但有时客户端没有收到消息。

在我的调试文件中,我发现当客户端没有收到消息时,它的脚本说:

error in recv();
recv: Connection was broken.

我想获得一些关于服务器如何知道客户端是否收到消息的帮助;我应该从客户端发送 NACK 或其他东西吗?我认为 UDT 应该为我做到这一点。有人可以澄清这种情况吗?

我的代码的通信部分的相关部分如下所示,并带有一些注释:

服务器的相关代码:

//...

void send_msg_in(player cur, char* xml){
/*this function stores the current message, xml, in a queue if xml!=NULL, and sends the 1st message of the queue to the client*/
/*this function is called when the player connects with the entering xml=NULL to get the 1st message of the queue,
or with xml!=NULL when a new message arrives: in this case the message is stored in the queue, and then the message will be sent in the appropriate time, i.e. the messages are ordered.*/
    char* msg_ptr=NULL;

    if (xml!=NULL){         //add the message to a queue (FIFO), the cur.xml_msgs
        msg_ptr=(char*) calloc(strlen(xml)+1, sizeof(char));
        strcpy(msg_ptr, xml);
        (*(cur.xml_msgs)).push(msg_ptr);
    }                       //get the 1st message of the queue
    if (!(*(cur.xml_msgs)).empty()){
        xml=(*(cur.xml_msgs)).front();
    }

    if (cur.get_udt_socket_in()!=NULL){
        UDTSOCKET cur_udt = *(cur.get_udt_socket_in());
//      cout << "send_msg_in(), cur_udt: " << cur_udt << endl;

        //send the "xml", i.e. the 1st message of the queue...
        if (UDT::ERROR == UDT::send(cur_udt, xml, strlen(xml)+1, 0)){
            UDT::close(cur_udt);
            cur.set_udt_socket_in(NULL);
        }
        else{    //if no error this else is reached
            cout << "TO client:\n" << xml << "\n";    /*if there is no error,
                             i.e. on success, the server prints the message that was sent.*/
                             //  / \
                             // /_!_\
                             /*the problem is that
                             the messages that are lost don't appear on the client side,
                             but they appear here on the server! */
            if (((string) xml).find("<ack.>")==string::npos){
                UDT::close(cur_udt);
                cur.set_udt_socket_in(NULL);          //close the socket
            }
            (*(cur.xml_msgs)).pop();
        }
    }
}

//...

客户相关代码:

//...
#define MSGBUFSIZE 1024
char msgbuf[MSGBUFSIZE];
UDTSOCKET client;
ofstream myfile;

//...

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

    // connect to the server, implict bind
    if (UDT::ERROR == UDT::connect(client, (sockaddr*)&serv_addr, sizeof(serv_addr))){
        cout << "error in connect();" << endl;
        return 0;
    }

    myfile.open("./log.txt", ios::app);

    send(xml);
    char* cur_xml;

    do{
        cur_xml = receive();                //wait for an ACK or a new message...
        myfile << cur_xml << endl << endl;  //  / \
                                            /* /_!_\ the lost messages don't appear on the website
                                            neither on this log file.*/
    } while (((string) cur_xml).find("<ack.>")!=string::npos);
    cout << cur_xml << endl;

    myfile.close();
    UDT::close(client);
    return 0;
}


char* receive(){
    if (UDT::ERROR == UDT::recv(client, msgbuf, MSGBUFSIZE, 0)){
                    //  / \
                    /* /_!_\ when a message is not well received
                    this code is usually reached, and an error is printed.*/
        cout << "error in recv();" << endl;
        myfile << "error in recv();" << endl;
        myfile << "recv: " << UDT::getlasterror().getErrorMessage() << endl << endl;
        return 0;
    }
    return msgbuf;
}


void* send(string xml){
    if (UDT::ERROR == UDT::send(client, xml.c_str(), strlen(xml.c_str())+1, 0)){
        cout << "error in send();" << endl;
        myfile << "error in send();" << endl;
        myfile << "send: " << UDT::getlasterror().getErrorMessage() << endl << endl;
        return 0;
    }
}

感谢您的任何帮助!

PS。在找到链接http://udt.sourceforge.net/udt4/doc/opt.htm后,我尝试增加 close() 的逗留时间,将以下内容添加到服务器的代码中:

    struct linger l;
    l.l_onoff  = 1;
    l.l_linger = ...;  //a huge value in seconds...
    UDT::setsockopt(*udt_socket_ptr, 0, UDT_LINGER, &l, sizeof(l));

但问题还是一样...

聚苯乙烯。服务器端通信的其他部分是:(注意:对我来说,它们并不那么相关)

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

    UDTSOCKET serv = UDT::socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in my_addr;
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    memset(&(my_addr.sin_zero), '\0', sizeof(my_addr.sin_zero));

    if (UDT::ERROR == UDT::bind(serv, (sockaddr*)&my_addr, sizeof(my_addr))){
        cout << "error in bind();";
        return 0;
    }

    UDT::listen(serv, 1);

    int namelen;
    sockaddr_in their_addr;

    while (true){
        UDTSOCKET recver = UDT::accept(serv, (sockaddr*)&their_addr, &namelen);
        if (UDT::ERROR == UDT::recv(recver, msgbuf, MSGBUFSIZE, 0)){
        //this recv() function is called only once for each aqccept(), because the clients call CGI scripts via a browser, they need to call a new CGI script with a new UDT socket for each request (this in in agreement to the clients' code presented before).
            cout << "error in recv();" << endl;
        }

        char* player_xml = (char*) &msgbuf;
        cur_result = process_request((char*) &msgbuf, &recver, verbose);    //ACK
    }
}


struct result process_request(char* xml, UDTSOCKET* udt_socket_ptr, bool verbose){
    //parse the XML...
    //...

    player* cur_ptr = get_player(me);   //searches in a vector of player, according to the string "me" of the XML parsing.

    UDTSOCKET* udt_ptr = (UDTSOCKET*) calloc(1, sizeof(UDTSOCKET));
    memcpy(udt_ptr, udt_socket_ptr, sizeof(UDTSOCKET));

    if (cur_ptr==NULL){
        //register the player:
        player* this_player = (player*) calloc(1, sizeof(player));
        //...
        }
    }
    else if (strcmp(request_type.c_str(), "info_waitformsg")==0){
        if (udt_ptr!=NULL){
            cur_ptr->set_udt_socket_in(udt_ptr);

            if (!(*(cur_ptr->xml_msgs)).empty()){
                send_msg_in(*cur_ptr, NULL, true);
            }
        }
    }
    else{           //messages that get instant response from the server.
        if (udt_ptr!=NULL){
            cur_ptr->set_udt_socket_out(udt_ptr);
        }
        if (strcmp(request_type.c_str(), "info_chat")==0){
            info_chat cur_info;
            to_object(&cur_info, me, request_type, msg_ptr);    //convert the XML string values to a struct
            process_chat_msg(cur_info, xml);
        }
    /*  else if (...){  //other types of messages...
        }*/
    }
}


void process_chat_msg(info_chat cur_info, char* xml_in){
    player* player_ptr=get_player(cur_info.me);

    if (player_ptr){
        int i=search_in_matches(matches, cur_info.match_ID);
        if (i>=0){
            match* cur_match=matches[i];
            vector<player*> players_in = cur_match->followers;
            int n=players_in.size();
            for (int i=0; i<n; i++){
                if (players_in[i]!=msg_owner){
                    send_msg_in(*(players_in[i]), xml, flag);
                }
            }
        }
    }
}
4

2 回答 2

1

查看http://sourceforge.net/p/udt/git/ci/master/tree/udt4/src/core.cpp上的 UDT 源代码,当任一布尔值时都会产生错误消息“连接已断开” flagsm_bBrokenm_bClosingistrue并且接收缓冲区中没有数据。

这些标志仅在少数情况下设置:

  • 在标记为“不应该发生;攻击或错误”的代码部分中(不太可能)
  • 在故意关闭或关闭操作中(不要在您的代码中看到这种情况)
  • 检查对等活动的计时器到期(可能的罪魁祸首)

在第 2593 行的源文件中,它说:

     // Connection is broken. 
     // UDT does not signal any information about this instead of to stop quietly.
     // Application will detect this when it calls any UDT methods next time.
     //
     m_bClosing = true;
     m_bBroken = true;

     // ...[code omitted]...

     // app can call any UDT API to learn the connection_broken error

查看send()调用,我没有看到它在返回之前等待来自对等方的 ACK 或 NAK 的任何地方,所以我认为send()服务器端的成功返回并不表示客户端成功接收到消息.

您没有在服务器端显示绑定到套接字并侦听来自客户端的响应的代码;如果问题存在,那么服务器可能会很高兴地发送消息,并且从不监听试图响应的客户端。

于 2013-09-12T16:00:00.753 回答
0

UDP 不是保证传输协议。主机会发送消息,但如果收件人没有收到,或者没有正确接收,则不会引发错误。因此,它通常用于需要速度而不是完美交付的应用程序,例如游戏。TCP 确实保证传递,因为它要求首先建立连接,并且每条消息都由客户端确认。

我鼓励您考虑是否真的需要保证接收该数据,如果需要,请考虑使用 TCP。

于 2013-09-05T23:00:10.687 回答