服务器/客户端模型阻止I / O多路复用

时间:2016-04-28 02:13:12

标签: c sockets io client-server multiplexing

我不熟悉C语言中的套接字编程,我想编写一个服务器 - 客户端原型,其中服务器将充当多个用户的一种文件托管服务器(使用TCP协议)。由于服务器和客户端都需要能够从stdin获取命令,因此我使用了select()函数。这是我程序的两个部分的代码,问题如下:
由于服务器和客户端都需要能够从stdin获取命令并处理它们,并且服务器必须发送对客户端请求的响应,所以程序在某一点阻塞,服务器发送它并且#39;仅在客户端发送另一个请求后才响应。我认为这是因为在 server.c 文件中同时使用send()和recv(),但我不完全确定。

server.c

while(1) {
  if (select(maxfd+1,&tmpfds,NULL,NULL,NULL) == -1) {
    error("Err in select");
  }
  for(i = 0; i < maxfd; i++) {
    if(FD_ISSET(i,&tmpfds) {
      if (i == listenfd) {
         < add new client to list >
      }
      else if (i == 0) { /* keyboard input */
        < parse server commands >
      }
      else {
        /* This is where I think my problem is*/
        recv(i,buffer,BUFLEN,0);
        process(buffer);
        send(i,buffer,BUFLEN,0);
      }
  }
}

client.c

 while(1) {
   if(select(fdmax+1,&tmpfds,NULL,NULL,NULL) == -1) {
     error("Err in select");
   }
   if (FD_ISSET(0,&tmpfds)) {
     fgets(buffer,BUFLEN,stdin);
     process_request(buffer);
     send(serverfd,buffer,BUFLEN,0);
   }
   else if (FD_ISSET(serverfd,&tmpfds)) {
     recv(serverfd,buffer,BUFLEN,0);
     process_response(buffer); 
   }
}

另外,请不要对我的编码风格或C习惯做出严厉的评论。我在任何情况下都不是C专家,也不是我自称的,我只是在学习,所以请帮助我。我做错了什么,如何在不改变(过多)程序行为的情况下避免这种情况?

1 个答案:

答案 0 :(得分:1)

要使用select作为正确的IO多路复用工具,您需要正确维护FD_SET。由于每次select返回,FD_SET仅包含准备进行操作的fds,这意味着您必须在每次调用FD_SET之前重新设置select。< / p>

您的代码中还有另一个问题,您不能只在循环中的FD_SET中添加新客户端,您需要保存它,然后在开头重新安装它们。

此外,您不需要检查集合中的每个FD,因为select将返回准备用于IO的fd数。

尝试以下更改:

int clients[MAX_CLIENTS] = {0};
int I;
int maxfd;
int server_sock = <the listening fd>;
FD_SET readfds;
int ret;
while(1) {
    // Setup SD_SET each time calling select
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);
    maxfd = STDIN_FILENO;
    FD_SET(server_sock, &readfds);
    maxfd = max(maxfd, server_sock);
    for (I = 0; I < MAX_CLIENTS; I++) {
        if (clients[I] >= 0) {
            FD_SET(clients[I], &readfds);
            maxfd = max(maxfd, clients[I]);
    }

    if ((ret = select(maxfd+1,&readfds,NULL,NULL,NULL)) == -1) {
        error("Err in select");
    }
    for(i = 0; i < maxfd && ret; i++, ret--) {
        if(FD_ISSET(i, &readfds) {
            if (i == listenfd) {
                 // < add new client to clients array
            }
            else if (i == STDIN_FILENO) { /* keyboard input */
                 //  < parse server commands >
            }
            else {
                  // one of the client is ready
                  int nread = recv(i,buffer,BUFLEN,0);
                  if (nread == 0) {
                       // client is closed, remove I from clients array
                       continue;
                  }
                  process(buffer);
                  send(i,buffer,BUFLEN,0);
            }
        }
    }
}

最后但并非最不重要的是,作为对select的改进,请在Linux上尝试类似epoll的内容,它会为您保留状态,以便您不需要重新安装所有fds像select那样。