gpt4 book ai didi

c++ - 管理 C 套接字中客户端突然断开连接的好方法

转载 作者:太空宇宙 更新时间:2023-11-04 04:46:05 26 4
gpt4 key购买 nike

在我目前正在小组中进行的一个项目中,我们必须从头开始构建一个使用套接字(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 host\n");
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: %s\n", 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 %d\n",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: %s\n",
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: %d\n", 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: %s\n", 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 %d\n", 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

并在不带任何参数的情况下运行两者。客户端设置为连接“localhost”。

如果有人能帮助我解决这个问题,我会非常高兴。

最佳答案

我建议去掉 SIGPIPE 信号本身。

signal(SIGPIPE, SIG_IGN);

现在,被杀死的套接字上的 write()s 将简单地返回 -1。处理这个问题应该比异步信号更容易。

如果出于其他原因需要 SIGPIPE,请将 write() 替换为带有 MSG_NOSIGNAL 选项的 sendto()。有关详细信息,请参阅 sendto(2) 手册页。

关于c++ - 管理 C 套接字中客户端突然断开连接的好方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35641530/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com