2016-04-28 16 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都可以。