【发布时间】:2019-04-14 15:26:17
【问题描述】:
出于学习目的,我正在制作自己的 TCP Socket 类。
该类旨在处理多个客户端。每个客户端都存储在vector 中。当客户端断开连接时,我遇到了从向量中正确删除客户端的问题。
如何在与vector 断开连接时正确删除客户端以及如何相应地处理传入数据? (见其他分支)。
目前,控制台在断开连接时收到std::cout else case 的垃圾邮件。
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 案例中:
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;
}
}
}
}
【问题讨论】:
-
啊,好吧。以“良好”方式断开连接的套接字将变得可读,
read(或recv)返回零。它有据可查。 -
您已经知道如何检查套接字是否可读。您可以使用被动侦听套接字来执行此操作。正如我所说,如果
read或recv返回零,那么这与“连接结束”相同(对应于文件的“文件结束”)。互联网上肯定有数以百万计的示例和教程。快速搜索会更早告诉您。 -
@Kyu96:“您将迭代集合中的所有套接字,检查哪个是可读的(如何?)” - 您的
select()调用要求可读性,因此它将修改readfds的条目以删除所有不可读的套接字。因此,您只需迭代您的clients列表,在每个列表上调用FD_ISSET(),就像您对master_socket所做的那样。 “然后删除它?” - 一旦你确定给定的客户端是否可读,你就可以recv()来自该客户端的数据,如果recv失败或返回0,@987654337 @ 该客户端并将其从clients列表中删除,否则根据需要对数据进行操作。 -
@RemyLebeau 感谢您的建议。请看我的编辑。我设法在断开连接时删除客户端并处理传入数据。我想知道是否还有一些我遗漏的东西,需要完成的重要检查或其他我尚未考虑的事情。我想确保我没有遗漏任何重要的东西;)
-
@Kyu96: 1) 你的
clients列表中不应该有值为-1 的项目。如果是这样,您的代码中还有其他错误。 2)不要在你的循环中使用at(),这是浪费开销。请改用operator[]。 3) 如果您想在循环中修改clients,请不要在每次循环迭代时增加j,否则您将在删除客户端时跳过客户端。否则,使用迭代器而不是索引,因为erase()将迭代器返回到列表中的下一个元素。 4) 你没有处理recv()错误返回 -1 的情况。你也需要close()这些客户。