【问题标题】:managing multiple socket using select()使用 select() 管理多个套接字
【发布时间】:2018-10-25 07:50:55
【问题描述】:

我在让 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;
            }
        }
    }

}

【问题讨论】:

  • select的第一个参数不应该是0,应该是至少linux实现上最大fd的个数
  • @Tyker winsock 是基于 Windows 的
  • accept_clients 连续多次调用accept。每次,select() 都会阻塞等待新连接到达侦听套接字。我想你没有 MAX_CONS 客户端都在尝试连接,所以其中一个 select() 调用永远阻塞。
  • @IgorTandetnik 所以我应该删除 recv_accept 中的循环并将代码粘贴到其中的 recv 函数中。
  • 你应该把你等待某事发生的所有套接字传递给select()——而不是一个接一个。这意味着侦听套接字以及先前已接受但尚未关闭的所有客户端套接字。当select() 返回时,它会告诉您哪些套接字有操作或错误未决 - 执行这些操作/处理这些错误,然后再次调用select()

标签: c++ sockets select winsock


【解决方案1】:

除非客户端的数量是固定的(总是 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

(*) 您可以从第一个元素开始,在这种情况下,第一个客户端将具有稍高的优先级,或者在最后使用的元素之后开始并在最后一个元素之后换行(循环)以给予所有客户端相同的优先级

【讨论】:

  • from msdn select 将通知 readfds 如果“如果已调用侦听并且连接挂起,则接受将成功。数据可供读取(如果启用 SO_OOBINLINE,则包括 OOB 数据)。连接已关闭/重置/终止。”在这种情况下,挂起的连接和准备接收的数据都会通知 readfds 宏,您的回答没有显示您如何区分每个操作我在这里缺少什么?
  • @Dota: if s is ready for read 应该用 C 语言翻译成if (FD_ISSET(s, &amp;rd_set))
  • 但是 s 正在监听套接字它不会准备好接收数据?所以要处理准备接收的数据,我需要一组套接字并循环遍历它们以检查哪个客户端套接字已准备好读取?
  • 对于监听套接字,“准备接收”意味着“准备接受”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-23
  • 2023-03-14
  • 1970-01-01
  • 2013-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多