C++套接字客户端断开连接
C++ sockets client disconnect
出于学习目的,我正在制作自己的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
列表中删除。 否则,请根据需要对数据执行操作。
其他需要考虑的事项:
-
您的
clients
列表中不应包含值为 -1 的项目。 如果是这样,您的代码就有更大的问题需要修复。 -
不要在循环中使用
clients.at()
,它只是浪费了开销。 请改用列表的operator[]
。 -
如果要在循环访问
clients
列表时对其进行修改,请不要在每次循环迭代时递增j
,否则每当擦除客户端时都会跳过客户端。 否则,请使用迭代器而不是索引,因为erase()
将迭代器返回到列表中的下一个元素。 无论如何,请考虑使用迭代器,因为您要擦除的是迭代器,而不是索引。 -
您没有处理
recv()
错误时可能返回 -1 的情况。您需要close()
并删除失败的客户端,而不仅仅是断开连接的客户端。 -
您假设
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;
}
- 当套接字连接断开时检测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++连接器,如何保持连接存活和重新连接,如果连接断开