我有一个可以处理多个客户端的 SFML 服务器的以下代码。服务器功能在不同的线程中运行,以便它可以并行获取用户输入并进行相应的操作。服务器可以打印客户端列表或显示最后收到的消息。客户端还接收用户输入并将数据发送到服务器。请暂时忽略用户输入,因为我相信这工作正常。下面的功能有什么问题吗?当我尝试打印客户端时,服务器不显示任何输出。即使我将套接字和侦听器设置为非阻塞,它也不会连接。可能是什么错误?
服务器-
struct tcpMessage
{
unsigned char nVersion;
unsigned char nType;
unsigned short nMsgLen;
char chMsg[1000];
};
void runTcpServer(unsigned short port, std::atomic<bool>& running, std::atomic<int>& command)
{
// Create a server socket to accept new connections
sf::TcpListener listener;
// Listen to the given port for incoming connections
if (listener.listen(port) != sf::Socket::Done)
return;
sf::SocketSelector selector;
std::list<sf::TcpSocket*> clients;
selector.add(listener);
tcpMessage msg;
std::string userInput;
while (running)
{
// Make the selector wait for data on any socket
if (selector.wait())
{
// Test the listener
if (selector.isReady(listener))
{
// The listener is ready: there is a pending connection
sf::TcpSocket* client = new sf::TcpSocket;
if (listener.accept(*client) == sf::Socket::Done)
{
// Add the new client to the clients list
clients.push_back(client);
// Add the new client to the selector so that we will
// be notified when he sends something
selector.add(*client);
}
else
{
// Error, we won't get a new connection, delete the socket
delete client;
}
}
else
{
// The listener socket is not ready, test all other sockets (the clients)
for (std::list<sf::TcpSocket*>::iterator it = clients.begin(); it != clients.end(); ++it)
{
sf::TcpSocket& client = **it;
if (selector.isReady(client))
{
// The client has sent some data, we can receive it
sf::Packet packet;
if (client.receive(packet) == sf::Socket::Done)
{
packet >> msg;
if (msg.nVersion == char(102))
{
if (msg.nType == char(77))
{
for (std::list<sf::TcpSocket*>::iterator it1 = clients.begin(); it1 != clients.end(); ++it1)
{
if (it1 == it)
continue;
else
{
sf::TcpSocket& c = **it1;
c.send(packet);
}
}
}
else if (msg.nType == char(20))
{
strcpy(msg.chMsg, strrev(msg.chMsg));
sf::Packet packetReversed;
packetReversed << msg;
client.send(packetReversed);
}
}
}
}
}
}
}
if (command == 1)
{
long unsigned int number = clients.size();
std::cout << "Number of Clients: " << number << std::endl;
for (std::list<sf::TcpSocket*>::iterator it = clients.begin(); it != clients.end(); ++it)
{
sf::TcpSocket& client = **it;
std::cout << "IP Address: " << client.getRemoteAddress()
<< " | Port: " << client.getRemotePort() << std::endl;
}
command = 0;
}
else if (command == 2)
{
std::cout << msg.nVersion << " " << msg.nType << " " << msg.nMsgLen << " " << msg.chMsg;
command = 0;
}
}
}
int main()
{
unsigned short port = 50000;
std::atomic<bool> running{ true };
std::atomic<int> command{ 0 };
std::thread serverThread(runTcpServer, port, std::ref(running), std::ref(command));
std::string userInput;
do
{
std::cout << "Please enter command: ";
std::cin >> userInput;
if (userInput == "clients")
command = 1;
else if (userInput == "msg")
command = 2;
} while (userInput != "exit");
running = false;
serverThread.join();
}
客户-
void runTcpClient(unsigned short port, tcpMessage& msgOut, std::atomic<int>& inputComplete,
std::atomic<bool>& running)
{
// Ask for the server address
sf::IpAddress server;
// Create a socket for communicating with the server
sf::TcpSocket socket;
socket.setBlocking(false);
const char out[] = "lmao";
socket.send(out, sizeof(out));
if (socket.connect(server, port) != sf::Socket::Done)
{
std::cout << "Could not connect to server " << server << std::endl;
}
while (running)
{
if (inputComplete == 2)
{
sf::Packet packetOut;
packetOut << msgOut;
socket.send(packetOut);
std::cout << "Message sent to the server: \"" << msgOut.chMsg << "\"" << std::endl;
inputComplete = 0;
}
// Receive a message from the server
sf::Packet packetIn;
if (socket.receive(packetIn) == sf::Socket::Done)
{
tcpMessage msgIn;
packetIn >> msgIn;
// std::cout << "Message received from the server: \"" << msgIn.chMsg << "\"" << std::endl;
std::cout << "Received Msg Type: " << msgIn.nType << "; Msg: " << msgIn.chMsg << std::endl;
}
}
if (running == false)
socket.disconnect();
int main(int argc, char* argv[])
{
std::string ip = argv[1];
unsigned short port = static_cast<unsigned short>(std::stoi(argv[2]));
std::atomic<bool> running{ true };
std::atomic<int> inputComplete{ 0 };
std::string input, strCommand;
tcpMessage msgOut;
long lVersion, lType;
char lMsg[1000];
/*
msgOut.nVersion = char(102);
msgOut.nType = char(20);
msgOut.nMsgLen = 67;
strcpy(msgOut.chMsg, "LMAO");
*/
std::thread clientThread(runTcpClient, port, std::ref(msgOut), std::ref(inputComplete), std::ref(running));
do
{
std::cout << "Please enter command: ";
std::getline(std::cin, input);
std::istringstream iss(input);
if (iss >> strCommand)
{
if (strCommand == "v")
{
iss >> lVersion;
msgOut.nVersion = static_cast<unsigned char>(lVersion);
inputComplete++;
}
if (strCommand == "t")
{
iss >> lType;
msgOut.nType = static_cast<unsigned char>(lType);
iss >> lMsg;
strcpy(msgOut.chMsg, lMsg);
msgOut.nMsgLen = static_cast<unsigned short>(strlen(lMsg));
inputComplete++;
}
if (strCommand == "q")
{
running = false;
}
}
} while (input != "q");
clientThread.join();
服务器不显示任何输出,因为它正忙于在
if (selector.wait())
内等待,当使用默认超时参数调用时,它会无限期地等待(docs)(顺便说一句,在这种情况下它总是返回true
)。此外,您以不同步的方式从不同线程访问 std::cout
,特别是下一个 "Please enter command: "
消息可以(并且很可能)在上一个命令的输出完成之前输出(如果您的 serverThread
必须处理命令) .
如果你想正确处理命令,你有2个选择:
serverThread
中处理命令并输出,但是你应该为等待函数提供一个合理的超时参数(如selector.wait(sf::seconds(0.1f))
),这样它就不会阻塞你的serverThread
无限期地等待选择器并给它一个处理command
的机会。此外,为了避免同步问题,例如在前一个命令完成之前在主线程中提示输入新命令,您必须在主线程中等待来自 serverThread
的信号,该信号表示 command
处理结束。您可以使用 std::mutex
和 std::condition_variable
来完成此操作,如下所示:
void runTcpServer(unsigned short port, std::atomic<bool>& running, std::atomic<int>& command,
std::condition_variable& cv, std::mutex& command_mutex)
{
// ...
while (running)
{
if (selector.wait(sf::seconds(0.1f))) // Wait at most 100ms
{
// ...
}
if (command == 0) // No command
continue;
// Command exists, so process it
{
std::unique_lock lock{command_mutex};
if (command == 1)
{
// ...
}
else if (command == 2)
{
// ...
}
else
assert(false); // Unidentified command, shouldn't happen according to the main thread logic
command = 0; // We don't need separate boolean varialbe to signify the command is finished, just set command to 0
}
cv.notify_one(); // Notify the main thread that the command is processed
}
}
int main()
{
unsigned short port = 50000;
std::atomic<bool> running{ true };
std::atomic<int> command{ 0 };
std::condition_variable cv;
std::mutex command_mutex;
std::thread serverThread(runTcpServer, port, std::ref(running), std::ref(command),
std::ref(cv), std::ref(command_mutex));
std::string userInput;
do
{
std::cout << "Please enter command: ";
std::cin >> userInput;
if (userInput == "clients")
command = 1;
else if (userInput == "msg")
command = 2;
else
continue; // Unidentified command, don't bother the serverThread
std::unique_lock lock(command_mutex);
// Wait for command == 0. Note that it will be rechecked either
// due to cv.notify_one() from serverThread or possibly during spurious wakeups
cv.wait(lock, [&]() {return command == 0; });
} while (userInput != "exit");
running = false;
serverThread.join();
}
clients
、msg
以及可能的其他数据必须可以从主系统访问。您可以在 main
函数中创建它们,然后再次将 std::ref
作为参数传递给 runTcpServer
,但此时最好创建一个类(我们称之为 TcpServer
)来保存共享状态和必要的同步基元。这次 std::mutex
足以保护对共享资源的访问,无需 std::conditional_variable
或担心需要不同步的控制台 IO(因为它仅通过一个线程完成):
class Server
{
unsigned short port{ 50000 };
std::atomic<bool> running{ true };
std::vector<sf::TcpSocket*> clients;
tcpMessage msg;
std::mutex m;
void runTcpServer()
{
sf::TcpListener listener;
if (listener.listen(port) != sf::Socket::Done)
return;
sf::SocketSelector selector;
selector.add(listener);
while (running)
{
// We don't need to do anything other then process incoming packets here,
// but we still set reasonable timeout to periodically wakeup
// to check whether running == false and the thread needs to stop
if (selector.wait(sf::seconds(0.1f)))
{
// Block resource mutex while handling incoming packet
std::lock_guard lock{m};
// ...
}
}
}
public:
void runMain()
{
std::thread serverThread(&runTcpServer, this);
std::string userInput;
do
{
std::cout << "Please enter command: ";
std::cin >> userInput;
// Block resource mutex while handling command
std::lock_guard lock{m};
if (userInput == "clients")
{
// ...
}
else if (userInput == "msg")
{
// ...
}
} while (userInput != "exit");
running = false;
serverThread.join();
}
};
int main()
{
Server server;
server.runMain();
return 0;
}