Hi,
I am currently learning networking with the SFML, so please bear with me if I'm doing something stupid, or if this situation is normal.
Long story short: it seems that sf::TcpListener fails to listen again to the same port after it was closed while a client was connected.
You will find below a code sample that reproduces the problem. It is a simplified excerpt of a larger code I'm working on, but all the listening, connecting and disconnecting is handled in the same way SFML wise (which I hope is robust and clean enough, please tell me if it's not!).
On the one hand, the server is programmed to accept any incoming TCP connection using a sf::TcpListener, and stores a list of connected clients (sf::TcpSockets), each registered in a common sf::TcpSelector. Whenever one of the client sends a packet, I'm asking the server to restart (this is just a test case) : I close all connections and listening, then start listening again on the same port.
On the other hand, the client simply connects to the server by using a sf::TcpSocket, and sends a dummy packet to the server on demand. When the packet is sent, knowing that the server is going to restart, it routinely checks for incoming packets from the server in order to notice when it actually goes down. Once that is done, it closes the connection and asks for a new one.
The server:
#include <SFML/Network.hpp>
#include <iostream>
#include <vector>
#include <memory>
int main() {
sf::TcpListener listener;
sf::SocketSelector selector;
std::vector<std::unique_ptr<sf::TcpSocket>> client_list;
while (true) {
std::cout << "starting server..." << std::endl;
std::size_t last_id = 0;
// Try to open the port
while (listener.listen(4444) != sf::Socket::Done) {
std::cout << "cannot listen to port" << std::endl;
std::cout << "try again (y/n) ? " << std::flush;
char r; std::cin >> r;
if (r == 'n') return 0;
}
std::cout << "connected!" << std::endl;
selector.add(listener);
// Start the main loop
bool run = true;
while (run) {
selector.wait(sf::milliseconds(10));
// Look for new clients
if (selector.isReady(listener)) {
std::unique_ptr<sf::TcpSocket> s(new sf::TcpSocket());
if (listener.accept(*s) == sf::Socket::Done) {
selector.add(*s);
client_list.push_back(std::move(s));
std::cout << "new client" << std::endl;
}
}
// Receive packets from each clients
auto iter = client_list.begin();
while (iter != client_list.end()) {
auto& socket = **iter;
if (selector.isReady(socket)) {
sf::Packet p;
switch (socket.receive(p)) {
case sf::Socket::Done :
run = false;
std::cout << "request restart" << std::endl;
break;
case sf::Socket::Disconnected :
case sf::Socket::Error :
socket.disconnect();
iter = client_list.erase(iter);
continue;
default : break;
}
}
++iter;
}
}
selector.clear();
client_list.clear();
listener.close();
std::cout << "server is down" << std::endl;
}
return 0;
}
The client:
#include <SFML/Network.hpp>
#include <iostream>
#include <string>
int main() {
sf::TcpSocket socket;
while (true) {
std::cout << "press [Enter] to connect: " << std::flush;
std::string line;
std::getline(std::cin, line);
// Try to connect
while (socket.connect(sf::IpAddress("127.0.0.1"), 4444, sf::seconds(2)) != sf::Socket::Done) {
std::cout << "cannot connect" << std::endl;
std::cout << "try again (y/n) ? " << std::flush;
char r; std::cin >> r;
if (r == 'n') return 0;
}
std::cout << "connected!" << std::endl;
std::cout << "press [Enter] to restart server: " << std::flush;
std::getline(std::cin, line);
// Send a dummy packet to tell the server to restart
std::uint8_t word = 0;
sf::Packet p; p << word;
if (socket.send(p) != sf::Socket::Done) {
std::cout << "could not send packet" << std::endl;
socket.disconnect();
continue;
}
std::cout << "packet sent, waiting to see server going down" << std::endl;
p.clear();
socket.setBlocking(false);
while (socket.receive(p) == sf::Socket::NotReady) {
sf::sleep(sf::milliseconds(50));
}
socket.setBlocking(true);
std::cout << "server is down" << std::endl;
socket.disconnect();
}
return 0;
}
Compiling:
g++ -std=c++0x server.cpp -lsfml-network -lsfml-system -o server
g++ -std=c++0x client.cpp -lsfml-network -lsfml-system -o client
Here is how to demonstrate the issue:
- In a first window, launch the server. If everything goes fine, it should print:
starting server...
connected!
- In a second window, launch the client, and press Enter to try to connect. If everything goes fine, it should print in the client window:
press [Enter] to connect
connected!
press [Enter] to restart server
and in the server window:
new client
- Still in the client window, press Enter once more to send the packet, then watch the two windows carefully. If everything goes fine, it should print fairly quickly in the client window:
packet sent, waiting to see server going down
server is down
press [Enter] to connect
and in the server window:
request restart
server is down
starting server...
- After a few seconds, the server will inform you that it failed to setup:
Failed to bind listener socket to port 4444
cannot listen to port
try again (y/n) ?
If you try again repeatedly, it should be able to start after some time (took me more than 40 seconds).
This issue doesn't show up if I try to listen to a different port after the restart.
On the other hand, it also happens in this more brutal situation :
- launch the server,
- launch the client and connect,
- kill the sever (Ctrl-C for example),
- while the client is still alive, launch the server again.
Now that is something I can understand, because killing the server in such a brutal way prevents the SFML (or myself) from properly closing the connections. But I don't get why it still happens if I do things cleanly.
Am I doing it wrong? Or is this an inherent limitation of networking?
Thank you for your help.