C++套接字客户端断开连接

C++ sockets client disconnect

本文关键字:连接 断开 客户端 套接字 C++      更新时间:2023-10-16

出于学习目的,我正在制作自己的TCP Socket类。 该类用于处理多个客户端。每个客户端都存储在一个vector中。我遇到了在客户端断开连接时从矢量中正确删除客户端的问题。 如何在断开与vector的连接时正确删除客户端,以及如何相应地处理传入数据?(请参阅其他分支)。 目前,控制台在断开连接时收到带有 else 案例std::cout的垃圾邮件。

bool socks::start() {
if (listen(this->master_socket, this->backlog) !=0){
std::cerr << "Failed to start listening." << std::endl;
return false;
}
std::cout << "Listening for connections on port " << this->listening_port << std::endl;
int max_sd;
addrlen = sizeof(address);
while (true) {
//clear the socket set
FD_ZERO( & readfds);
//add master socket to set
FD_SET(master_socket, & readfds);
max_sd = master_socket;
// Add child sockets to set
for (int i = 0; i < this->clients.size();
i++){
//socket descriptor
int sd = clients[i];
// If valid socket descriptor then add to read list
if (sd > 0)
FD_SET(sd, & readfds);
//highest file descriptor number, need it for the select function
if (sd > max_sd)
max_sd = sd;
}
// Wait indefinitely for an activity on one of the sockets
int activity = select(max_sd + 1, & readfds, NULL, NULL, NULL);
if ((activity < 0) && (errno != EINTR)) {
std::cerr << "select() failed" << std::endl;
return false;
}
// Handle incoming connections
if (FD_ISSET(master_socket, & readfds)){
if ((new_socket = accept(master_socket, (struct sockaddr *) & address,(socklen_t *) & addrlen)) <0){
std::cerr << "Failed to accept incoming connection." << std::endl;
return false;
}
// Information about the new connection
std::cout << "New connection : "
<< "[SOCKET_FD : " << new_socket
<< " , IP : " << inet_ntoa(address.sin_addr)
<< " , PORT : " << ntohs(address.sin_port)
<< "]" << std::endl;
// Add connection to vector
this->clients.push_back(new_socket);
}
// Hande client disconnections / incoming data?
else{
std::cout << "Disconnect??? Or what happens here?" << std::endl;
}
}
}

编辑:我将其添加到其他情况下:

else {
for (int j = 0; j < this->clients.size(); ++j) {
if (this->clients.at(j) == -1) {
continue; // eventually vector.erase() ?
}
if (FD_ISSET(this->clients.at(j), &this->readfds)) {
char buf[256];
ssize_t rc = recv(this->clients.at(j), buf, 256, 0);
if (rc == 0) {
std::cout << "Client disconnected! [SOCKET_FD: "
<< this->clients.at(j) << "]"
<< std::endl;
close(this->clients.at(j));
this->clients.erase(this->clients.begin() + j);
} else {
std::cout << "Client " << this->clients.at(j)
<< " sent: " << buf << std::endl;
}
}
}
}

您的select()调用仅要求可读套接字,因此在退出时它将修改您的readfds以删除所有不可读的套接字。 因此,您只需要遍历clients列表,在每个套接字上调用FD_ISSET(),就像处理master_socket一样。 无论如何,您都不应该在else块中进行迭代,因为侦听套接字可能会在已建立的客户端也接收数据的同时接收新的入站客户端。

确定给定客户端是否可读后,可以从该客户端recv()数据,如果recv调用返回 -1(错误)或 0(对等方正常断开连接),则close()该客户端并将其从clients列表中删除。 否则,请根据需要对数据执行操作。

其他需要考虑的事项:

  1. 您的clients列表中不应包含值为 -1 的项目。 如果是这样,您的代码就有更大的问题需要修复。

  2. 不要在循环中使用clients.at(),它只是浪费了开销。 请改用列表的operator[]

  3. 如果要在循环访问clients列表时对其进行修改,请不要在每次循环迭代时递增j,否则每当擦除客户端时都会跳过客户端。 否则,请使用迭代器而不是索引,因为erase()将迭代器返回到列表中的下一个元素。 无论如何,请考虑使用迭代器,因为您要擦除的是迭代器,而不是索引。

  4. 您没有处理recv()错误时可能返回 -1 的情况。您需要close()并删除失败的客户端,而不仅仅是断开连接的客户端。

  5. 您假设recv()返回以 null 结尾的数据,即使发送方实际发送以 null 结尾的数据,也不能保证这一点。 TCP 是一种流式传输,任何给定的读取都可能返回比请求的字节少的字节数。您必须注意recv()的返回值以了解实际接收了多少字节,否则可能会超出缓冲区的范围。

尝试更多类似的东西:

bool socks::start() {
if (listen(master_socket, backlog) < 0) {
std::cerr << "Failed to start listening." << std::endl;
return false;
}
std::cout << "Listening for connections on port " << listening_port << std::endl;
fd_set readfds;
char buf[256];
while (true) {
//clear the socket set
FD_ZERO(&readfds);
//add master socket to set
FD_SET(master_socket, &readfds);
int max_sd = master_socket;
// Add child sockets to set
for (size_t i = 0; i < clients.size(); ++i) {
//socket descriptor
int sd = clients[i];    
FD_SET(sd, &readfds);
//highest file descriptor number, need it for the select function
if (sd > max_sd)
max_sd = sd;
}
// Wait indefinitely for an activity on one of the sockets
int activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
if (activity < 0) {
if (errno == EINTR) continue;
std::cerr << "select() failed" << std::endl;
return false;
}
// Handle incoming connections
if (FD_ISSET(master_socket, &readfds)) {
sockaddr_in address;
socklen_t addrlen = sizeof(address);
int new_socket = accept(master_socket, (sockaddr *) &address, &addrlen);
if (new_socket < 0) {
std::cerr << "Failed to accept incoming connection." << std::endl;
return false;
}
// Information about the new connection
std::cout << "New connection : "
<< "[SOCKET_FD : " << new_socket
<< " , IP : " << inet_ntoa(address.sin_addr)
<< " , PORT : " << ntohs(address.sin_port)
<< "]" << std::endl;
// Add connection to vector
clients.push_back(new_socket);    
}
// Handle client disconnections / incoming data?
size_t j = 0;
while (j < clients.size()) {
int sd = clients[j];
if (FD_ISSET(sd, &readfds)) {
ssize_t rc = recv(sd, buf, sizeof(buf), 0);
if (rc <= 0) {
std::cout << "Client " << (rc < 0) ? "read error" : "disconnected" << "! [SOCKET_FD: " << sd << "]" << std::endl;
close(sd);
clients.erase(clients.begin() + j);
continue;
}
std::cout << "Client " << sd << " sent: ";
std::cout.write(buf, rc);
std::cout << std::endl;
}
++j;
}
}
return true;
}

请注意,select()一次可以处理的最大套接字数。 如果最终的客户端数超过了select()的处理能力,则必须将列表分解为对select()的多个调用(可能在工作线程中调用它们以进行并行处理),或者切换到(e)poll()

bool socks::start() {
if (listen(master_socket, backlog) < 0) {
std::cerr << "Failed to start listening." << std::endl;
return false;
}
std::cout << "Listening for connections on port " << listening_port << std::endl;
std::vector<pollfd> readfds;
char buf[256];
pollfd pfd;
//add master socket to set
pfd.fd = master_socket;
pfd.events = POLLIN;
pfd.revents = 0;
readfds.push_back(pfd);
while (true) {
// Wait indefinitely for an activity on one of the sockets
int activity = poll(&readfds[0], readfds.size(), -1);
if (activity < 0) {
if (errno == EINTR) continue;
std::cerr << "poll() failed" << std::endl;
return false;
}
// Handle incoming connections, client disconnections, and incoming data
size_t j = 0;
while (j < readfds.size()) {
if (readfds[j].revents == 0) {
++j;
continue;
}
int sd = readfds[j].fd;
if (readfds[j].revents & POLLIN) {
if (sd == master_socket) {
sockaddr_in address;
socklen_t addrlen = sizeof(address);
int new_socket = accept(master_socket, (struct sockaddr *) &address, &addrlen);
if (new_socket < 0) {
std::cerr << "Failed to accept incoming connection." << std::endl;
return false;
}
// Information about the new connection
std::cout << "New connection : "
<< "[SOCKET_FD : " << new_socket
<< " , IP : " << inet_ntoa(address.sin_addr)
<< " , PORT : " << ntohs(address.sin_port)
<< "]" << std::endl;
// Add connection to vectors
clients.push_back(new_socket);    
pfd.fd = new_socket;
pfd.events = POLLIN | POLLRDHUP;
pfd.revents = 0;
readfds.push_back(pfd);
}
else {
ssize_t rc = recv(sd, buf, sizeof(buf), 0);
if (rc > 0) {
std::cout << "Client " << sd << " sent: ";
std::cout.write(buf, rc);
std::cout << std::endl;
}
else if (rc == 0) {
readfds[j].revents |= POLLHUP;
} else {
readfds[j].revents |= POLLERR;
}
}
}
if (readfds[j].revents != POLLIN) {
if (sd == master_socket) {
...
}
else {
std::cout << "Client " << (readfds[j].revents & POLLERR) ? "read error" : "disconnected" << "! [SOCKET_FD: " << sd << "]" << std::endl;
close(sd);
clients.erase(std::find(clients.begin(), clients.end(), sd));
readfds.erase(readfds.begin() + j);
continue;
}
}
++j;
}
}
return true;
}