我目前正在使用套接字进行多人游戏,我在登录时遇到了一些问题。
这是服务器功能 - 处理来自用户的传入消息的线程:
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函数首先接收&#34; StartLogIn&#34;但是直到下一个循环才进入if。因为它再次循环,它变为&#34; taigi100&#34; (我使用的用户名)然后它输入if if甚至它不应该
。解决这个问题的方法是建立一个send-recv系统,以便在得到一些反馈之前不发送任何其他内容。
我想知道是否还有其他快速方法可以解决这个问题以及为什么会出现这种奇怪的行为。
答案 0 :(得分:3)
嗯,它充满了错误。
您过度使用新[]。好吧不是错误,但你没有删除任何这些,你可以使用本地堆栈缓冲区空间或vector< char >
您需要始终检查对recv
的任何调用的结果,因为您无法保证收到您期望的字节数。您指定的数字是缓冲区的大小,而不是您希望获得的字节数。
strcmp如果字符串匹配则返回0,如果不匹配则返回非零(实际上是1或-1,具体取决于它们是否更少或更大)。但似乎你使用非零来表示相等。
不确定stringc是什么。某种从宽字符串到字符串的转换?在任何情况下,我认为发送是const正确的,所以不需要抛弃常量。
send的第3个参数是您要发送的字节数,而不是缓冲区的容量。用户名和密码可能不是256个字节。您需要将它们作为“数据包”发送,因此接收方知道它们正在获取什么,并且知道它们何时收到完整数据包。例如发送一个像“User = vandamon \ 0”这样的字符串。 (你需要检查它的返回值)
答案 1 :(得分:0)
因为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()
来等待或忙等待数据。 UGH。
无论如何,您首先使用短reinterpret_cast<char *>
和预期== sizeof(short)
的地址进行调用。然后你new[]
有足够的空间,并再次呼叫以获得有效载荷。请注意缺少尾随NUL字符,除非您明确发送它们,我的代码没有。