管理客户端在 C 套接字中突然断开连接的好方法
A good way to manage client abruptly disconnecting in C sockets
在我目前正在小组中做的一个项目中,我们必须从头开始构建一个使用套接字(Linux)的纸牌游戏。我们还必须建立一个每个玩家都可以使用的聊天室。
目前为止,一切都好。聊天是使用三个单独的线程实现的,一个接收传入连接(最多 50 个)并将其存储在客户端列表中,一个持续等待来自所有连接的客户端的消息,另一个在每次客户端发送消息时创建,将该消息发送到客户端列表中的所有客户端。所有这些都有效,除非单个客户端断开连接。
当客户端断开连接时,我设法使服务器保持活动状态(使用 SIGPIPE 的 sig 处理程序),但现在,当客户端断开连接时,我不断收到错误错误文件描述符。但这不是唯一的问题,因为服务器不断接收空消息并将其发送到其余客户端,有效地在几毫秒内用空消息淹没整个聊天。
我相信,如果我能在服务器端解决问题,客户端就不会有任何问题。
所以我的问题是:在我的情况下,管理错误文件描述符的正确方法(或任何方法)是什么。我已经尝试关闭套接字 FD 并在客户端列表中将值设置为 -1,但这会产生更多问题并且没有解决初始问题。
如有必要,这是代码。最重要的功能(用于聊天)是客户端的reception_thread、chat_thread、receive_string、send_string和connect_to_chat。
这是客户端:
//includes
const int PORT = 2477;
const int CHAT_PORT = 2478;
#define DEBUG
//error()
// Sets up the connection to the server.
//connect_to_server()
int connect_to_chat(char * hostname)
{
#ifdef DEBUG
printf("[DEBUG] Initiating connection to chat server.n");
#endif
struct sockaddr_in serv_addr;
struct hostent *server;
// Get a socket.
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("Error opening socket for server.");
// Get the address of the server.
server = gethostbyname(hostname);
if (server == NULL) {
fprintf(stderr, "ERROR, no such hostn");
exit(0);
}
// Zero out memory for server info.
memset(&serv_addr, 0, sizeof (serv_addr));
// Set up the server info.
serv_addr.sin_family = AF_INET;
memmove(server->h_addr, &serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(CHAT_PORT);
// Make the connection.
if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
error("Error connecting to chat server");
#ifdef DEBUG
printf("[DEBUG] Connected to server.n");
#endif
return sockfd;
}
//-------------------------------- Messages ------------------------------------
// Bunch of send/recv functions that are not important to chat
int send_string(int sockfd, std::string myString)
{
#ifdef DEBUG
printf("[DEBUG] Sending string: %s.n", myString.c_str());
#endif
//send size
uint32_t stringLen = myString.size();
uint32_t sendLen = htonl(stringLen);
int n = send(sockfd, &sendLen, sizeof (uint32_t), 0);
if (n < 0) {
error("Error sending message (string size). Removing client from list.");
return -1;
}
//send string
n = send(sockfd, myString.c_str(), stringLen, 0);
if (n < 0) {
error("Error sending message (string). Removing client from list.");
return -1;
}
return 0;
}
std::string receive_string(int sockfd)
{
//get string length
uint32_t stringLen;
int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
if (n < 0) {
perror("Error receiving message(string size).");
}
stringLen = ntohl(stringLen);
std::vector<uint8_t> buffer;
buffer.resize(stringLen, 0x00);
//get string
n = recv(sockfd, &(buffer[0]), stringLen, 0);
if (n < 0) {
perror("Error receiving message(string).");
}
std::string returnString;
returnString.assign(reinterpret_cast<const char*> (&(buffer[0])), buffer.size()); //might be a bad idea, but it works
#ifdef DEBUG
printf("[DEBUG] Received message: %sn", returnString.c_str());
#endif
return returnString;
}
//----------------------------- Printing functions------------------------------
void print_menu_guest()
{
// some visual function
}
void print_menu_user()
{
// some visual function
}
void print_info()
{
std::cout << " No information available on the game yet." << std::endl;
}
//---------------------------- Account functions -------------------------------
// Not necessary for chat functions
//--------------------------- Chat thread functions ----------------------------
void reception_thread(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Reception thread started.n");
#endif
std::string stringToPrint;
while (1) {
stringToPrint = receive_string(sockfd);
std::cout << stringToPrint << std::endl;
}
}
void chat_thread(int sockfd, char* host)
{
#ifdef DEBUG
printf("[DEBUG] Chat thread started.n");
#endif
std::string myString, myUsername, blank;
std::cout << "Enter your username (NO SPACES): ";
std::cin >> myUsername;
myUsername += ": ";
int chat_sockfd = connect_to_chat(host);
std::thread reception_thr(reception_thread, chat_sockfd);
reception_thr.detach();
while (1) {
getline(std::cin, myString);
if (!myString.empty()) {
if (myString != "/quit") {
send_string(chat_sockfd, (myUsername + myString));
}
else {
printf("On peut pas encore quitter :( ");
}
}
}
}
//---------------------- Menu management functions -----------------------------
// Main menu function
//---------------------------- Main function -----------------------------------
int main(int argc, char** argv)
{
/* Make sure host and port are specified. */
if (true) {
char* hostname = "localhost";
/* Connect to the server. */
int sockfd = connect_to_server(hostname);
#ifdef DEBUG
printf("[DEBUG] Client ID: Not yet implemented. ");
#endif
login_prompt(sockfd);
user_menu_loop(sockfd);
}
return 0;
}
这是服务器:它最重要的功能(用于聊天)是setup_user_fetcher、message_receiver、send_string_to_all、receive_string、send_string、get_chat_user、setup_chat_listener。
// Bunch of includes
const int PORT = 2477;
const int CHAT_PORT = 2478;
const int BACKLOG = 10;
const int MAX_CLIENTS = 20;
int clients_list[50] = {-1};
#define DEBUG
void error(const char *msg)
{
perror(msg);
}
/* Catch Signal Handler functio */
void signal_callback_handler(int signum){
printf("Caught signal SIGPIPE %dn",signum);
}
//-------------------------- Server set-up functions ---------------------------
// Not necessary for chat
//--------------------------- Chat server functions ---------------------------
int setup_chat_listener()
{
int sockfd;
struct sockaddr_in serv_addr;
// Get a socket to listen on
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening listener socket.");
// Zero out the memory for the server information
memset(&serv_addr, 0, sizeof (serv_addr));
// set up the server info
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(CHAT_PORT);
// Bind the server info to the listener socket.
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
error("Error binding listener socket.");
#ifdef DEBUG
printf("[DEBUG] Chat listener set.n");
#endif
// Return the socket number.
return sockfd;
}
int get_chat_user(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Getting chat user.n");
#endif
struct sockaddr_in their_addr;
socklen_t sin_size;
if (listen(sockfd, BACKLOG) < 0) {
perror("Error while listening.");
exit(EXIT_FAILURE);
}
sin_size = sizeof (struct sockaddr_in);
// Mise a zero de la memoire pour le client.
memset(&their_addr, 0, sin_size);
int new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
if (new_fd < 0)
error("Error while accepting.");
printf("Chat server: Connection received from: %sn",
inet_ntoa(their_addr.sin_addr));
return new_fd;
}
int send_string(int sockfd, std::string myString)
{
#ifdef DEBUG
printf("[DEBUG] Sending string to client %d.n", sockfd);
#endif
uint32_t stringLen = myString.size();
uint32_t sendLen = htonl(stringLen);
int n = send(sockfd, &sendLen, sizeof (uint32_t), 0);
if (n < 0) {
error("Error sending message (string size). Removing client from list.");
return -1;
}
//send string
n = send(sockfd, myString.c_str(), stringLen, 0);
if (n < 0) {
error("Error sending message (string). Removing client from list.");
return -1;
}
return 0;
}
std::string receive_string(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Receiving string.n");
printf("Current chat user sockfd: %dn", sockfd);
#endif
uint32_t stringLen;
int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
#ifdef DEBUG
printf("[DEBUG] String size received: %d.n", stringLen);
#endif
if (n < 0) {
perror("Error receiving message(string size).");
}
stringLen = ntohl(stringLen);
std::vector<uint8_t> buffer;
buffer.resize(stringLen, 0x00);
//get string
n = recv(sockfd, &(buffer[0]), stringLen, 0);
if (n < 0) {
perror("Error receiving message(string).");
close(sockfd);
}
std::string returnString;
returnString.assign(reinterpret_cast<const char*> (&(buffer[0])), buffer.size()); //might be a bad idea, but it works
#ifdef DEBUG
printf("[DEBUG] Received message: %sn", returnString.c_str());
#endif
return returnString;
}
void send_string_to_all(std::string myString)
{
#ifdef DEBUG
printf("[DEBUG] Sending string to all clients.n");
#endif
int n;
for (int i = 0; i < 50; ++i) {
if (clients_list[i] != -1) {
n = send_string(clients_list[i], myString);
if (n < 0) {
close(clients_list[i]);
clients_list[i] = -1;
}
}
}
}
void message_receiver(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Setting up message receiver.n");
printf("Current chat user sockfd: %d", sockfd);
#endif
std::string message;
int n;
while (1) {
message = receive_string(sockfd);
std::thread t1(send_string_to_all, message);
t1.detach();
}
}
//------------------------------------------------------------------------------
// Bunch of send/recv functions, not necessary to chat
//----------------------------Account Functions---------------------------------
// Not necessary to chat
//------------------------------------------------------------------------------
// Main menu function
void setup_user_fetcher(int lis_chat_sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Gotta catch'em all.n");
#endif
while (1) {
int chat_user_sockfd = get_chat_user(lis_chat_sockfd);
for (int i = 0; i < 50; ++i)
if (clients_list[i] == -1) {
clients_list[i] = chat_user_sockfd;
break;
}
std::thread message_receiver_thread(message_receiver, chat_user_sockfd);
message_receiver_thread.detach();
}
}
int main(int argc, char** argv)
{
signal(SIGPIPE, signal_callback_handler);
int lis_sockfd = setup_listener();
int lis_chat_sockfd = setup_chat_listener();
std::thread chat_thread(setup_user_fetcher, lis_chat_sockfd);
chat_thread.detach();
while (1) {
int user_sockfd = get_user(lis_sockfd);
int* user_sockfd_ptr = (int*) malloc(sizeof (int));
memset(user_sockfd_ptr, 0, sizeof (int));
user_sockfd_ptr[0] = user_sockfd;
#ifdef DEBUG
printf("[DEBUG] Starting main menu...n");
#endif
pthread_t thread;
int result = pthread_create(&thread, NULL, main_menu,
(void *) user_sockfd_ptr);
if (result) {
printf("Thread creation failed with return code %dn", result);
exit(-1);
}
#ifdef DEBUG
printf("[DEBUG] New main menu thread started.n");
#endif
}
close(lis_sockfd);
pthread_exit(NULL);
return 0;
}
如果要重现错误,可以使用以下行编译代码
g++ client.cpp -o client -std=c++14 -pthread
g++ server.cpp -o server -std=c++14 -pthread
并在没有任何参数的情况下运行两者。客户端设置为在"本地主机"上连接。
如果有人能帮助我解决这个问题,我会很高兴。
我重新评论摆脱SIGPIPE
信号本身。
signal(SIGPIPE, SIG_IGN);
现在,被杀死的套接字上的write()s
将简单地返回 -1。处理起来应该比异步信号更容易。
如果出于其他原因需要SIGPIPE
,请将 write()
s 替换为 sendto()
s,并带有 MSG_NOSIGNAL
选项。有关详细信息,请参阅sendto(2)
手册页。
你有 UB。 如果读取的字节数0
,&(buffer[0])
将失败(我相信如果客户端断开连接,就会发生这种情况)。在构建字符串之前,您应该测试0
并尽早返回。
此外,在发现错误后您不会返回,因此在发生错误时从错误数据构建字符串。
也许更像:
std::string receive_string(int sockfd)
{
uint32_t stringLen;
int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
if (n < 0) {
close(sockfd);
// exit early
throw std::runtime_error("Error receiving message(string size): "
+ std::string(std::strerror(errno)));
}
// test for zero
if(!n)
return {}; // empty string
stringLen = ntohl(stringLen);
std::vector<uint8_t> buffer(stringLen);
// buffer.resize(stringLen, 0x00);
//get string
n = recv(sockfd, &(buffer[0]), stringLen, 0);
if (n < 0) {
close(sockfd);
// exit early
throw std::runtime_error("Error receiving message(string): "
+ std::string(std::strerror(errno)));
}
// only build string if no errors
return {buffer.begin(), buffer.begin() + n};
}
- 当套接字连接断开时检测C/C++Unix
- 升压信号2将插槽传递到成员功能以断开连接
- 视频在唤醒其他线程时输入设备断开连接
- 以编程方式重新连接断开的 VHD 链
- SNMP 代理在单元测试期间断开连接
- 断开连接后重新连接boost beast(asio)websocket和http连接时出错
- 如何从Qt绑定到在Windows 7 / 8 / 10中连接/断开USB设备事件
- C++套接字客户端断开连接
- 如何在 Windows 进程回调中断开连接时获取设备信息(硬件 ID)
- 如何在 Grpc 中使用双向流时检测(物理)断开连接
- 对等方断开连接后未释放 SSL 内存
- SIGPIPE C++确定哪个 TCP 套接字已断开连接
- C++中断开连接的记录集
- QJSONRPC 中的客户端连接/断开连接事件
- 来自 boost::asio::async_write 的 WriteHandler 在连接断开时无法正常工作(防火墙/手动断开网络)
- 增强信号:在类接口中公开信号本身或连接/断开方法
- Win32确定何时连接/断开键盘
- hiredis客户端,连接断开时发生redisAsyncFree错误
- 在不轮询GetMessage()的情况下检测USB连接/断开连接
- Mysql c++连接器,如何保持连接存活和重新连接,如果连接断开