2016-04-28 17 views
1

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

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专家,也不是我自称的,我只是在学习,所以请帮助我。我做错了什么,如何在不改变(太多)我的程序行为的情况下避免这种情况?

+0

默认情况下,请记住'recv'无限期地阻止。你可以用'setsockopt'设置超时时间。 – chrisd1100

+2

另外,你需要管理循环内的'fd_set'数据,你不需要注意,Linux的['select'教程](http://linux.die.net/man/2/select_tut)指出:“由于select()修改了它的文件描述符集合,因此如果在循环中使用该调用,那么在每次调用之前必须重新初始化这些集合。” – Myst

+0

P.S.如果使用linux,比'select'更喜欢'epoll'。如果使用BSD,比'select'更喜欢'kqueue'。 Windows有重叠IO,Solaris有'evpoll' ...考虑使用抽象库(即'libev'),如果你的代码需要在不同的系统上运行......祝你好运! – Myst

回答

1

要使用select作为正确的IO多路复用设施,您需要正确维护FD_SET。由于每次select返回时,FD_SET只包含准备进行操作的fds,这意味着您必须重新调用FD_SET,然后每次调用select

你的代码还有一个问题,你不能只在循环中的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的改进,尝试像epoll在Linux上,它保持状态的你,这样你就不需要再装像select那样的所有fds都可以。