2014-09-30 33 views
0

我目前正在使用套接字进行多人游戏,并且在登录时遇到了一些问题。C++套接字,发送和recv不同步

这里的服务器功能 - 线程从用户收到的消息涉及:

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”,但不会输入if,直到下一个循环结束。因为它再次循环它变成“taigi100”(我使用的用户名),然后它进入如果它甚至不应该。

解决此问题的一种方法是制作send-recv系统,以便在获得一些反馈之前不发送任何其他内容。

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

+0

在实施它之前,请大步退一步并记录协议。这将为您节省大量未来的痛苦。如果您不确定如何执行此操作,请查看TCP上分层的其他协议(例如SMTP,HTTP或IRC)的文档。 – 2014-09-30 16:36:25

回答

3

那么它充满了错误。

  • 您过度使用新的[]。确定不是一个错误,但你不删除任何这些,你可以使用任何本地堆栈缓冲区空间或vector<char>

  • 你需要经常检查,recv任何调用的结果,你不能保证收到的号码你期待的字节数。您指定的数字是缓冲区的大小,而不是您期望获得的字节数。

  • STRCMP返回0,如果字符串匹配,非零值,如果他们不(实际上是1或-1取决于他们是否比较少或更大)。但看起来你使用非零来表示平等。

  • 不确定stringc是什么。从宽字符串到字符串的某种转换?在任何情况下,我认为发送是常量正确的,所以不需要将常量抛弃。发送的

  • 第三个参数是要发送,而不是你的缓冲区的容量的字节数。用户名和密码可能不是256字节。你需要把它们发送一个“数据包”,但这样的接收器知道他们做了什么,并知道什么时候他们已经收到一个完整的数据包。例如发送一个字符串,如“User = vandamon \ 0”。 (而你需要太检查它的返回值)

+0

我还会添加“尝试发送比可用数据更多的数据”,“不检查'send'的返回值,这可能会部分成功,并且在使用流套接字时,”对'send'的单个调用可能无法映射给'recv'打个电话 – Hasturkun 2014-09-30 15:29:10

+0

上帝,我好傻,忘了!在strcmp ... – 2014-09-30 15:57:41

0

因为send()recv()通话可能不匹配,两个非常好的习惯进入是:(1)由一个固定大小的长度preceed所有可变长度的数据,和(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)。然后你有足够的空间,然后再次调用以获得有效载荷。请注意缺少尾部NUL字符,除非您明确地发送它们,而我的代码不会。