出于学习目的,我正在创建自己的TCP Socket类。
该类旨在处理多个客户端。每个客户端都存储在vector
中。我遇到了在断开连接时从向量中正确删除客户端的问题。
如何在与vector
的断开连接上正确删除客户端,以及如何相应地处理传入数据? (见其他分支)。
目前,控制台因断开连接的其他情况的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;
}
}
}
}
答案 0 :(得分:3)
您的select()
调用仅询问可读套接字,因此退出时它将修改您的readfds
以删除所有不可读的套接字。因此,您只需遍历每个套接字上调用clients
的{{1}}列表,就像使用FD_ISSET()
一样。并且您不应该在master_socket
块中进行该迭代,因为侦听套接字可能在已建立的客户端也在接收数据的同时接收新的入站客户端。
一旦确定给定客户端是否可读,您就可以else
来自该客户端的数据,如果recv()
调用返回-1(错误)或0(对等端断开连接), recv
该客户端并将其从close()
列表中删除。否则,根据需要对数据采取行动。
需要考虑的其他事项:
您的clients
列表中绝不应包含值为-1的项目。如果是这样,那么您需要解决的代码存在更大的问题。
不要在你的循环中使用clients
,只是浪费了开销。请改为使用列表clients.at()
。
如果要在循环时修改operator[]
列表,请不要在每次循环迭代时增加clients
,否则每当擦除客户端时都会跳过客户端。否则,请使用迭代器而不是索引,因为j
会将迭代器返回到列表中的下一个元素。无论如何,请考虑使用迭代器,因为您正在擦除迭代器而不是索引。
您没有处理erase()
出错时可能返回-1的情况。您需要recv()
并删除失败的客户端,而不仅仅是断开连接的客户端。
您假设close()
返回以空值终止的数据,即使发件人实际发送以空值终止的数据,也无法保证。 TCP是一种流传输,任何给定的读取都可以返回比请求的更少的字节。您必须注意recv()
的返回值才能知道实际接收的字节数,否则您可能会超出缓冲区的范围。
尝试更像这样的事情:
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()
调用(可能在工作线程中调用它们进行并行处理),或者切换到{{ 1}}代替:
select()