c++ - 使用 select() 管理多个套接字
问题描述
我无法让 select() 在处理多个客户端并从它们接收消息时正常工作而不会阻塞每个操作。
我了解到 select 确定一个或多个套接字的状态,因此每次接受连接时,我都会将套接字放入集合中,使用宏来确定哪个已准备好读取。我观察到我的代码从未进入 accept_clients() 函数。我希望有人查看我的代码并帮助使其正常工作。
这是我的代码。
#pragma comment(lib, "ws2_32.lib")
#include <WinSock2.h>
#include <stdio.h>
#include <signal.h>
#include <conio.h>
#include <assert.h>
#include <Windows.h>
#include <iostream>
//sockets
#define CLIENT_CON 10
#define CLIENT_DIS 20
#define BF_SZ 100
#define MAX_CONS 5
#define TIMEVAL_SEC 0
#define TIMEVAL_USEC 10
SOCKET sock, clien;
int PR_CONS = 0;
struct _client
{
bool con; // Set true if a client is connected
sockaddr_in addr; // Client info like ip address
SOCKET cs; // Client socket
//fd_set set; // used to check if there is data in the socket
int i; // any piece of additional info
};
_client client[10];
fd_set sset;
int accept(_client*);
int recv(_client*, char*, int);
void Server_Status(int );
void accept_clients();
void recv_client();
int main() {
//int res;
int i = 1;
int port = 5150;
WSADATA ws;
printf("\t Echo Server (Multiple client support)\n");
sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_addr.S_un.S_addr = INADDR_ANY;
ser.sin_port = htons(port);
WSAStartup(MAKEWORD(2,2),&ws);
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&i,sizeof(i));
bind(sock,(SOCKADDR*)&ser, sizeof(ser));
listen(sock,5);
printf("listening \n");
unsigned long b= 1;
ioctlsocket(sock,FIONBIO,&b);
for(int i = 0; i < MAX_CONS; i++) {
client[i].con = false;
}
while(true) {
accept_clients();
recv_client();
}
}
int accept(_client* x) {
x->i = sizeof(sockaddr_in);
int isClient = 0;
unsigned long b= 1;
FD_ZERO(&sset);
FD_SET(sock, &sset);
isClient = select(0, &sset, NULL,NULL, 0);
if(isClient > 0 && PR_CONS < MAX_CONS) {
x->cs = accept(sock, (SOCKADDR*)&x->addr, &x->i);
ioctlsocket(x->cs,FIONBIO,&b);
if(x->cs != INVALID_SOCKET ) {
x->con = true;
FD_ZERO(&sset);
FD_SET(x->cs,&sset);
return true;
}
}
//printf("failed to accept client");
return false;
}
int recv(_client *x, char* buffer, int sz) {
int r;
/*timeval timeOut;
timeOut.tv_sec = TIMEVAL_SEC;
timeOut.tv_usec = TIMEVAL_USEC;*/
r = select(0,&sset, NULL,NULL,0);
if(r > 0) {
if((FD_ISSET(x->cs,&sset)) ) {
x->i = recv(x->cs,buffer,sz, 0);
if(x->i > 0) {
Server_Status(CLIENT_DIS);
FD_CLR(x->cs,&sset);
return 0;
}
return true;
}
}
return false;
}
void accept_clients() {
for(int i = 0; i < MAX_CONS; i++) {
if(!client[i].con) {
if(accept(&client[i])) {
Server_Status(CLIENT_CON);
}
}
}
}
void Server_Status(int msg) {
if(msg == CLIENT_CON) {
PR_CONS++;
printf("client has connected");
}
else if(msg == CLIENT_DIS) {
PR_CONS--;
printf("client has disconnected");
}
else {
printf("we got unknown message");
}
}
void recv_client() {
printf("problem?");
char buffer[BF_SZ];
for(int i = 0; i < MAX_CONS; i++) {
if(client[i].con) {
if(recv(&client[i],buffer, BF_SZ)) {
std::cout << buffer << std::endl;
}
}
}
}
解决方案
除非客户端的数量是固定的(总是 MAX_CONS)客户端,并且在它们全部连接之前无法交换任何内容),否则您正在滥用 select。
比较常见的用法是(伪代码)
s = listening_socket
put s in a fd_set
loop on select
if s is ready for read:
new_con = accept
store new_con in a client array
put s in the fd_set
else
find one element of the read-ready set other than s (*)
recv data from that client
process data
(*) 您可以从第一个元素开始,在这种情况下,第一个客户端将具有稍高的优先级,或者在最后一个使用的元素之后开始并在最后一个之后换行(循环)以给予所有客户端相同的优先级
推荐阅读
- macos - 我的 Mac USB 3.0 拇指驱动器上的 NTFS 文件传输非常慢
- node.js - 节点:被其他 lambda 函数调用时请求上下文
- html - 如何在包含图像的两个 div 元素之间显示文本段落?
- php - 正则表达式没有返回正确的值
- rust - 当转换可能失败时,如何选择性地标准化值?
- azure - Azure Redis 缓存异地复制
- android - Android 存储 RSA 公钥
- flutter - 如何在下拉菜单上应用过滤器
- docker - 如何让 docdb 模拟器 docker 容器运行?
- javascript - 如果您不先执行 alert(),则使用 Xrm.WebApi.createRecord 创建事件会失败