【问题标题】:how to handle packets in multi-threaded server client program?如何在多线程服务器客户端程序中处理数据包?
【发布时间】:2011-05-09 15:14:48
【问题描述】:

我目前有一个可以运行的客户端应用程序,但它是单线程的。

我的数据包如下所示:|"

“|”用作我的数据的分隔符。

后面总是 4 位数字。

看起来像:||||||

我创建数据包的代码是:

_snprintf_s(data_buffer, WS_MAX_DATA_PACKET_SIZE, 
               WS_MAX_DATA_PACKET_SIZE - 1,
               "%s%d%s%d%s%d%s%s%s%d%s", 
               WS_PACKET_SEP, pkt->transaction_id, 
               WS_PACKET_SEP, pkt->command, 
               WS_PACKET_SEP, pkt->bufsize, 
               WS_PACKET_SEP, pkt->buf, 
               WS_PACKET_SEP, pkt->checksum, WS_PACKET_SEP);

buf_len = strlen(data_buffer);

_snprintf_s(send_buffer, WS_MAX_DATA_PACKET_SIZE, 
            WS_MAX_DATA_PACKET_SIZE - 1, "%04d%s%s", 
            buf_len, WS_PACKET_SEP, data_buffer);

buf_len = strlen(send_buffer);
// Send buffer
bytes_sent = send(ConnectSocket, send_buffer, buf_len, 0);

客户端线程向服务器发送命令,然后调用 GetIncomingPackets() 函数。在 GetIncomingPackets() 中,我调用 recv() 来获取 5 个字节,这应该是其余数据包的 len,我解析这 5 个字节并验证它们是否符合我的预期格式。然后我将前 4 个字节转换为整数 x。然后我再次调用 recv() 以获取更多 x 字节,然后将它们解析到我的数据包结构中。

当我添加另一个线程来做同样的事情(发送和接收命令)时,就会出现问题。 我启动我的应用程序并触发 2 个线程并发送它们以发送不同的命令并等待响应。当线程调用 GetIncomingPackets() 时,我返回的数据是无效的。我期望的前 5 个字节有时会丢失,而我只得到以下 5 个字节,因此我无法获得我的 数据包。

我什至在我的 GetIncomingPackets() 中的 2 个 recv() 调用之间添加了一个临界区块,因此在获取完整数据包时,踏板不会相互中断。 没有一些额外的错误检查代码,函数看起来像这样

#define WS_SIZE_OF_LEN_PACKET 5
bool GetIncomingPackets(SOCKET sd, dev_sim_packet_t *pkt )
{
    char len_str_buf[WS_SIZE_OF_LEN_PACKET + 1] = {0};      // + 1 for NULL char
    char data_buf[WS_MAX_DATA_PACKET_SIZE + 1] = {0};
    int ret = 0;
    int data_len = 0;

    EnterCriticalSection( &recv_critical_section );
    nReadBytes = WS_RecvAll(sd, len_str_buf, WS_SIZE_OF_LEN_PACKET );
    ret = WS_VerifyLenPacket(len_str_buf);
    // Convert data packet lenght string received to int 
    data_len = WS_ConvertNumberFromString(len_str_buf, WS_SIZE_OF_LEN_PACKET );   
    // Get data from packet
    nReadBytes = WS_RecvAll(sd, data_buf, data_len);
    LeaveCriticalSection( &recv_critical_section  );
    ret = ParseMessager(data_buf, data_len, pkt);
}

我的问题是,什么可能导致此问题,我该如何解决?或者有更好的方法来做我想做的事情。我试图使其成为多线程的原因是因为我的应用程序将与其他 2 个源进行通信,并且我希望有一个线程来处理来自任一源的每个请求。

在此先感谢您,如果我没有很好地解释某些事情,请随时提出任何问题。

这是 WS_RecvAll() 的代码。缓冲区是在 GetIncomingPackets() 中声明的静态缓冲区,如下所示:

  char data_buf[WS_MAX_DATA_PACKET_SIZE + 1] = {0};   // + 1 for NULL char


int WS_RecvAll(SOCKET socket_handle, char* buffer, int size)
{
    int ret = 0;
    int read = 0;
    int i = 0;
    char err_buf[100] = {0};
    while(size)
    {
        ret = recv(socket_handle, &buffer[read], size, 0);
        if (ret == SOCKET_ERROR)
        {
            printf("***ERROR***: recv failed, error = %d\n", WSAGetLastError());
            return WS_ERROR_RECV_FAILED;
        }
        if (ret == 0) {
            break;
        }
        read += ret;
        size -= ret;
     }
     return read;
 }

【问题讨论】:

  • 客户端和服务器之间使用的协议是什么? (即 TCP 还是 UDP?)
  • 发布 WS_RecvAll 的代码。
  • WS_RecvAll 如何分配data_buf?
  • 我现在发布了代码 WS_RecvAll 作为问题的一部分。
  • 在上面的 _snprintf_s 语句中,您正在使用 %s 格式化程序格式化消息。但是如果 pkt->buf 是二进制数据,那么任何超过第一个 nul 字节的数据都会被截断。

标签: c windows multithreading winsock client-server


【解决方案1】:

调试 MT 问题非常困难,尤其是在一次删除时,但如果您使用的是静态缓冲区,则不应该:

 LeaveCriticalSection( &recv_critical_section  );
 ret = ParseMessager(data_buf, data_len, pkt);

是:

 ret = ParseMessager(data_buf, data_len, pkt);
 LeaveCriticalSection( &recv_critical_section  );

为什么在任何情况下都使用静态缓冲区?

【讨论】:

  • 好的,它没有被声明为静态的。我的意思是,我没有使用 malloc 分配它。我使用恒定大小的缓冲区来接收我的数据,因为我总是知道我可以接收的最大数据是多少。那有意义吗?还是你认为我应该每次都动态分配缓冲区?
  • @emge 这是有道理的。不要将动态分配添加到您的困境中。不过,我仍然会尝试移动 LeaveCriticalSection 调用。
  • @emge 实际上,我只是重新阅读了您的代码,除非我错过了什么,否则您正在静态分配缓冲区 - 即不在函数内。
  • 好吧,我不确定你认为我应该怎么做?我没有在任何地方使用关键字静态。
  • @emge 在函数之外定义的任何东西都是静态的。移动函数内部的缓冲区定义,或移动 LCS 调用。
【解决方案2】:

我很想知道您是否在两个线程中使用了相同的 socked 描述符来连接服务器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-28
    • 2014-12-21
    • 2011-04-28
    • 1970-01-01
    • 2016-01-10
    • 1970-01-01
    • 2020-10-15
    相关资源
    最近更新 更多