生活随笔
收集整理的這篇文章主要介紹了
UNIX网络编程笔记(6):I/O复用之select函数
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
上一講中我們正確處理了僵尸子進程,使得這個簡單的服務器更加健壯。不幸的是,這個程序仍然有問題。想象一下,如果一個客戶正在和一個服務器子進程連接建立完畢正在通話,而服務器子進程意外終止(比如kill),服務器TCP向客戶TCP發送一個FIN,但客戶端正在調用fgets函數等待用戶輸入字符而得不到這個FIN,直到套接字讀為止(可能過了很長時間)。
1、來看看問題是什么
首先啟動服務器與客戶,建立連接:
客戶輸入nihao后服務器正確回射,然后kill掉服務器子進程后客戶輸入and...,輸出錯誤:
服務器端重啟(上一講中解決的問題)。
這個過程發生了什么?
(1)當kill掉服務器子進程后,服務器父進程正確處理了SIGCHLD信號。服務器也向客戶發送一個FIN,客戶回應一個ACK,TCP連接終止的前半部分完成;
(2)客戶上沒有發生任何特殊的事,客戶TCP接收到服務器的FIN后回應一個ACK,但問題是客戶正阻塞在fgets上,等待用戶輸入;
(3)在客戶上再輸入and...,導致錯誤發生:str_cli調用write,客戶TCP接著把數據發送給服務器,但是客戶FIN的接收并沒有告知客戶TCP服務器進程已經終止。當服務器TCP接收到客戶的數據時,發送一個RST;
(4)但是客戶進程看不到這個RST,因為調用write后直接調用readline,并且由于之前接收的FIN,導致readline直接返回0(表示EOF)。但客戶沒有預期收到EOF,于是出錯;
(5)客戶終止,所有打開的描述符關閉;
問題的根本在于,當FIN到達套接字時,客戶正阻塞在fgets調用上。客戶實際上在應對兩個描述符:套接字和用戶輸入,它不能單純阻塞在某個特定的源上。select函數可以解決這個問題。
2、select函數
有些進程需要一種預先告知內核的能力,使得內核一旦發現進程指定的一個或多個I/O條件就緒時,它就通知進程。這個能力就是I/O復用。
select函數允許進程指示內核等待多個事件中的任何一個發生,并只在有一個或多個事件發生或經歷一段指定的時間后才喚醒它。
下面的例子指出select可以告知內核在什么條件下發生時返回:
- 集合{1,4,5}中的任何描述符準備好讀;
- 集合{2,7}中的任何描述符準備好寫;
- 集合{1,4}中的任何描述符由異常條件待處理;
- 已經過了12.8秒;
也就是說,調用select函數告知內核對哪些描述符(就讀、寫或異常條件)感興趣以及等待多長時間。描述符不局限于套接字,任何描述符都可以使用select。
函數定義如下,包含在<sys/select.h>頭文件中:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set * exceptset,const struct timeval *timeout);函數如果有描述符就緒就返回數量,超時返回0,出錯返回-1。
參數含義如下:
maxfdp1:指定待測試描述符的個數,通常是待測試的最大描述符加1;
中間三個參數指定內核測試讀、寫和異常條件的描述符,這里設置成NULL即可;
timeval:告知內核等待所指定的時間,有三種情況:
(1)永遠等待:設置為NULL;
(2)等待固定時間:在有一個描述符就緒后返回,但不超過參數所指定的時間;
(3)根本不等待:檢查描述符后立刻返回;
3、重寫客戶端中的str_cli函數
客戶的套接字上的三個條件:
(1)如果對端TCP發送數據,那么該套接字變為可讀,并且read返回一個大于0的值(即讀入數據的字節數);
(2)如果對端TCP發送一個FIN,那么該套接字變為可讀,read返回0(EOF);
(3)如果對端TCP發送一個RST(對端主機崩潰并重啟),那么該套接字變為可讀,并且read返回-1,errno中有錯誤碼;
下面是修改后的str_cli函數:
void str_cli(FILE *fp,int sockfd)
{int maxfdp1;fd_set rset;char sendline[MAXLINE],recvline[MAXLINE];FD_ZERO(&rset);int n;for(;;){FD_SET(fileno(fp),&rset);FD_SET(sockfd,&rset);maxfdp1=max(fileno(fp),sockfd)+1;if((n=select(maxfdp1,&rset,NULL,NULL,NULL))==0){printf("select timeout\n");return;}else if(n<0){printf("select error\n");return;}if(FD_ISSET(sockfd,&rset)){if(readline(sockfd,recvline,MAXLINE)==0){printf("str_cli:server terminated prematurely\n");return;}fputs(recvline,stdout);}if(FD_ISSET(fileno(fp),&rset)){if(fgets(sendline,MAXLINE,fp)==NULL)return;write(sockfd,sendline,strlen(sendline));}}
}函數首先檢查套接字,如果套接字是可讀的,那么就用readline讀入回射文本并輸出;如果標準輸入是可讀的,就調用fgets讀入一行,然后寫入套接字。
運行結果:
(1)服務器進程終止后客戶端直接終止:
(2)服務器端:
可以看到,程序運行良好。
總結
以上是生活随笔為你收集整理的UNIX网络编程笔记(6):I/O复用之select函数的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。