【问题标题】:C++ Sockets, send and recv not in syncC ++套接字,发送和接收不同步
【发布时间】:2014-11-25 06:44:08
【问题描述】:

我目前正在使用套接字开发多人游戏,但在登录时遇到了一些问题。

这是服务器函数 - 处理来自用户的传入消息的线程:

void Server::ClientThread(SOCKET Connection)
{
char *buffer = new char[256];

while (true)
{
    ZeroMemory(buffer,256);
    recv(Connection, buffer, 256, 0);
    cout << buffer << endl;
    if (strcmp(buffer, "StartLogIn"))
    {
        char* UserName = new char[256];
        ZeroMemory(UserName, 256);
        recv(Connection, UserName, 256, 0);

        char* Password = new char[256];
        ZeroMemory(Password, 256);
        recv(Connection, Password, 256, 0);

        cout << UserName << "-" << Password << " + "<<  endl;
        if (memcmp(UserName, "taigi100", sizeof(UserName)))
        {
            cout << "SMB Logged in";
        }
        else
            cout << "Wrong UserName";
    }

    int error = send(Connection, "0", 1, 0);
//  error = WSAGetLastError();
    if (error == SOCKET_ERROR)
    {
        cout << "SMB D/Ced";
        ExitThread(0);
    }
}
}

这是将数据从客户端发送到服务器的函数:

if (LogInButton->isPressed())
{
    send(Srv->getsConnect(), "StartLogIn", 256, 0);
    const wchar_t* Usern = UserName->getText();
    const wchar_t* Passn = Password->getText();
    stringc aux = "";
    aux += Usern;
    char* User = (char*)aux.c_str();

    stringc aux2 = "";
    aux2 += Passn;
    char* Pass = (char*)aux2.c_str();

    if (strlen(User) > 0 && strlen(Pass) > 0)
    {
        send(Srv->getsConnect(), User, 256, 0);
        send(Srv->getsConnect(), Pass, 256, 0);
    }
}

我将尽可能简单地解释这一点。服务器端函数中 while(true) 的第一个 recv 函数首先接收“StartLogIn”,但直到下一个 while 循环才进入 if。因为它再次循环,所以它更改为“taigi100”(我使用的用户名),然后它进入 if 即使它不应该。

解决此问题的一种方法是创建一个发送-接收系统,以便在收到反馈之前不发送任何其他内容。

我想知道是否有任何其他快速解决此问题的方法以及为什么会发生这种奇怪的行为。

【问题讨论】:

  • 在实施协议之前,请退后一步并记录协议。这将在未来为您节省大量的痛苦。如果您不确定如何执行此操作,请查看位于 TCP 之上的其他协议(例如 SMTP、HTTP 或 IRC)的文档。

标签: c++ sockets networking


【解决方案1】:

因为send()recv() 调用可能不匹配,所以要养成两个非常好的习惯:(1)在所有可变长度数据之前一个固定大小的长度,以及(2)只发送所需的最低限度。

因此,您最初的send() 调用将如下所示:

char const * const StartLogin = "StartLogIn";
short const StartLoginLength = static_cast<short>(strlen(StartLogin));
send(Srv->getsConnect(), reinterpret_cast<char *>(&StartLoginLength), sizeof(short), 0);
send(Srv->getsConnect(), StartLogin, StartLoginLength, 0);

然后,相应的接收代码必须读取两个字节,并通过检查来自recv() 的返回值来保证它得到它们,如果接收不到足够的内容,则重试。然后它会循环第二次将那么多字节准确地读取到缓冲区中。

int guaranteedRecv(SOCKET s, char *buffer, int expected)
{
    int totalReceived = 0;
    int received;
    while (totalReceived < expected)
    {
        received = recv(s, &buffer[totalReceived], expected - totalReceived, 0);
        if (received <= 0)
        {
            // Handle errors
            return -1;
        }
        totalReceived += received;
    }
    return totalReceived;
}

请注意,这假定了一个阻塞套接字。如果没有数据可用,非阻塞将返回零,并且 errno / WSAGetLastError() 将显示 *WOULDBLOCK。如果你想走这条路,你必须专门处理这种情况,并找到一些方法来阻止直到数据可用。通过反复调用recv(),要么忙着等待数据。哎呀。

无论如何,您首先使用短地址reinterpret_cast&lt;char *&gt; 和预期 == sizeof(short) 调用它。然后你new[] 足够的空间,并再次调用以获取有效负载。请注意缺少尾随 NUL 字符,除非您明确发送它们,而我的代码没有。

【讨论】:

    【解决方案2】:

    它充满了错误。

    • 你过度使用 new[]。好的,不是错误,但您没有删除任何这些,您可以使用本地堆栈缓冲区空间或vector&lt; char &gt;

    • 您需要始终检查对recv 的任何调用的结果,因为不能保证您收到预期的字节数。您指定的数字是缓冲区的大小,而不是您期望获得的字节数。

    • 如果字符串匹配,strcmp 返回 0,如果不匹配则返回非零(实际上是 1 或 -1,取决于它们比较小于还是大于)。但您似乎使用非零来表示相等。

    • 不确定 stringc 是什么。从宽字符串到字符串的某种转换?无论如何,我认为 send 是 const 正确的,所以没有必要抛弃 constness。

    • send 的第三个参数是您发送的字节数,而不是缓冲区的容量。用户名和密码可能不是 256 字节。不过,您需要将它们作为“数据包”发送,以便接收者知道他们收到了什么,并知道他们何时收到了完整的数据包。例如发送类似“User=vandamon\0”的字符串。 (而且你还需要检查它的返回值)

    【讨论】:

    • 我还要添加“尝试发送比可用数据更多的数据”、“不检查 send 的返回值,这可能会部分成功”,以及在使用流套接字时“单个调用到 send 可能不会映射到对 recv 的单个调用"
    猜你喜欢
    • 2016-07-28
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    • 2021-07-08
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 2016-02-21
    相关资源
    最近更新 更多