首页 文章

C套接字,发送和接收不同步

提问于
浏览
0

我目前正在使用套接字进行多人游戏,我在登录时遇到了一些问题 .

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

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甚至它不应该 .

解决这个问题的方法是创建一个send-recv系统,以便在得到一些反馈之前不发送任何其他内容 .

我想知道是否还有其他快速方法可以解决这个问题以及为什么会出现这种奇怪的行为 .

2 回答

  • 0

    好吧,它充满了错误 .

    • 你过度使用新[] . 好吧不是一个错误,但你没有删除任何这些,你可以使用本地堆栈缓冲区空间或 vector< char >

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

    如果字符串匹配,

    • strcmp返回0,如果不匹配则返回非零(实际上是1或-1,具体取决于它们是更少还是更大) . 但似乎你使用非零来表示相等 .

    • 不确定是什么字符串 . 某种从宽字符串到字符串的转换?在任何情况下,我认为send是const-correct所以不需要抛弃constness .

    • send的第3个参数是您要发送的字节数,而不是缓冲区的容量 . 用户名和密码可能不是256个字节 . 您需要将它们作为“数据包”发送,因此接收方知道它们正在获取什么,并且知道它们何时收到完整数据包 . 例如发送一个像“User = vandamon \ 0”这样的字符串 . (你还需要检查它的返回值)

  • 3

    因为 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<char *> 和期望== sizeof(short) 的地址来调用它 . 然后你 new[] 足够的空间,并再次调用以获得有效载荷 . 请注意缺少尾随NUL字符,除非您明确发送它们,我的代码没有 .

相关问题