我正在尝试用 C++ 和 SFML(图形和套接字)制作一个简单的多人游戏示例。我目前的目标是制作一个演示,您可以在其中连接到服务器,当您这样做时,您会收到服务器上所有其他“玩家”的位置。
我想尝试使用非阻塞 UDP,因为非阻塞 UDP 可能会在“真实”游戏中使用。
到目前为止,我没有实时同步,只是试图将服务器上当前所有“玩家”的状态发送到新客户端(请原谅代码的状态,它在“调整”/“扔东西”在墙上看看有什么棒”模式)。
我的“协议”是这样的:
RequestConnect
Acknowlege
Acknowlege
客户端进入阻塞模式以获得可靠的读取这一次我的问题在
RequestConnect
和 Acknowlege
之间,服务器在非阻塞模式下检查每个帧是否有来自 UDP 套接字的连接(注意:这使它有点复杂,但我在其中留下了一些评论以显示一些我尝试过的东西,我也可以参考它们):
std::vector<Request<>> RecieveRequests(
unsigned short int port//,
//const std::atomic<bool>& keepReading,
//const std::atomic<bool>& gameRunning
)
{
sf::UdpSocket listener;
listener.setBlocking(false);
listener.bind(port);
std::vector<Request<>> queue;
//while(keepReading.load() == true)
for(size_t ii = 0; ii < 1000; ++ii)
{
sf::IpAddress client{};
sf::Packet incomingPacket;
Request<> request;
sf::Socket::Status recieveStatus = listener.receive(incomingPacket, client, port);
//count += 1;
if(client == sf::IpAddress::None)
return queue;
//break;
std::cout << "Recieved a request from " << client << "\n";
incomingPacket >> request;
if(recieveStatus == sf::Socket::Status::Done)
{
std::cout << "Reciving was successful processing\n";
std::cout << "Got header: " << std::string_view{request.header} << "\n";
queue.push_back(request);
}
else if(recieveStatus == sf::Socket::Status::Partial)
std::cerr << "Recieved only PARTIAL packet\n";
else {
std::cerr << "ERROR: BAD CONNECTION\n";
return queue;
//break;
}
}
//std::cout << "Listened for: " << count << " requestes\n";
return queue;
}
一条消息似乎没有传到服务器,所以我重复客户端的消息:
template<size_t ColorCountParameterConstant>
std::optional<Game> Connect(
sf::IpAddress server,
unsigned short port,
const std::array<sf::Color, ColorCountParameterConstant>& playerColorList,
float retryDelayInSeconds = .001f
)
{
sf::Clock clock;
bool acknowleged = false;
sf::UdpSocket handshakeSocket;
sf::UdpSocket acknowlegeSocket;
while(acknowleged == false)
{
sf::Time startTime = clock.getElapsedTime();
sf::Packet connectionRequest;
Request<> request;
std::strcpy(request.header, Request<>::requestConnect);
request.address = sf::IpAddress::getLocalAddress();
connectionRequest << request;
handshakeSocket.setBlocking(true);
for(size_t ii = 0; ii < 1000; ++ii) {
auto status = handshakeSocket.send(connectionRequest, server, port);// != sf::Socket::Done) {
std::cout << status << "\n";
}
//if(status != sf::Socket::Done) {
// std::cerr << "ERROR: Failed to SEND connection request to server!\n";
// return std::nullopt;
//}
//std::cout << "Done sending request\n";
handshakeSocket.setBlocking(false);
sf::Packet acknowlegement;
//while((clock.getElapsedTime() - startTime).asSeconds() < retryDelayInSeconds);
//for(size_t ii = 0; ii < 1000; ++ii)
//handshakeSocket.receive(acknowlegement, server, port);
Request<> acknowlegementReply;
acknowlegement >> acknowlegementReply;
if(std::string_view{acknowlegementReply.header} == std::string_view{Request<>::acknowlege})
{
std::cout << "GOT AWKNOWLEGEMENT!!!\n";
if(std::string_view{acknowlegementReply.header} == std::string_view{Request<>::acknowlege}) {
acknowleged = true;
std::cout << "AWKNOWLEGED WAITING FOR REPLY\n";
}
}
//else
//std::cout << "Did not recieve acknowlegement\n";
//while((clock.getElapsedTime() - startTime).asSeconds() < retryDelayInSeconds);
}
sf::Packet initialData;
if(handshakeSocket.receive(initialData, server, port) != sf::Socket::Done) {
std::cerr << "ERROR: Never RECIEVE handshake from server!\n";
return std::nullopt;
}
std::cout << "Recieved handshake\n";
InitialHandshake initialHandShake;
initialData >> initialHandShake;
return GameFromHandshake(initialHandShake, port, playerColorList);
}
基本上我在这里开火 (
send
) RequestConnect
(无论是阻塞模式还是非阻塞模式)然后立即尝试 read
Acknowlege
,但是我注意到一些事情:
Done
我认为根据 SFML 的doc/source,[我认为这可能是在这种情况下定义的实现]),当我发送后没有阅读Disconnected
我认为)for(size_t ii = 0; ii < 1000; ++ii)
服务器寻找连接,连接需要几秒钟(这正常吗?)这与阻塞调用非常相似,因为我可以使用确定性定时选择器因此,如果我只能在状态为0时在服务器上接收连接,并且在向服务器发送消息后没有立即读取时我只能获得状态0,并且我需要进行非阻塞读取(因为如果我不这样做,并且服务器没有收到我的
RequestConnection
我需要再次发送),那么我将无法收到确认请求并且不知道何时等待服务器发送游戏数据状态。
我一直在寻找使用非阻塞 UDP 套接字写入的问题,然后阅读获取错误代码,但我还没有发现其他人有同样的问题。
现在对于程序的这一部分(共享初始状态),使用阻塞套接字来设置游戏状态和“热加入”可能没问题。但是,尝试共享播放器状态时并不好,我需要发送和接收播放器状态并知道每一帧所有播放器的所有输入,这需要发送和接收。它不会花费很长时间,(就像没有
for(size_t ii = 0; ii < 1000; ++ii)
中的RecieveRequests
一样。我不确定这种延迟是否是问题的症状,但我认为这可能是出于某种原因。
我的问题是
SFML 的 UDP sockets 的源代码是相对简单的,似乎主要是对一些平台差异的抽象。
注意:我想指出的一点注释掉的代码是:
while((clock.getElapsedTime() - startTime).asSeconds() < retryDelayInSeconds);
我有两个,我把它们放在我做的地方,因为我认为套接字在读取时没有完成写入,反之亦然,或者资源没有被操作系统释放。所有这一切都使得在未接收时连接到服务器需要更长的时间并且不能解决问题。
您可能还会注意到
const std::atomic<bool>& keepReading
我曾经RecieveRequests
在一个单独的线程上运行,因为它似乎在阻塞模式下工作,但不幸的是因为线程需要加入我无法在a之前修改游戏状态客户端连接,或之后(除非我告诉它在客户端连接后也停止)而无需进行更大量的修改以解决阻塞套接字。
谢谢!
更新:
我试过了
for(
sf::Socket::Status status;
(status = handshakeSocket.send(connectionRequest, server, port)) != sf::Socket::Done;
std::cout << status
);
和
for(
sf::Socket::Status status;
(status = handshakeSocket.receive(acknowlegement, server, port)) != sf::Socket::Done;
std::cout << status
);
它似乎通过了发送,但随后接收并打印了一堆 1(我认为是 NotReady),如果我尝试创建第二个套接字,则会出现错误 4(断开连接)。