linux非阻塞socket教程
from :http://blog.csdn.net/devday/article/details/5296621
本文並非解釋什麼是非阻塞socket,也不是介紹socket API的用法, 取而代替的是讓你感受實際工作中的程式碼編寫。雖然很簡陋,但你可以通過man手冊與其它資源非富你的程式碼。請注意本教程所說的主題,如果細說,內容可以達到一本書內容,你會發現本教程很有用。
本教程內容如下:
1. 改變一個阻塞的socket為非阻塞模式。
2. select模型
3. FD巨集
4. 讀寫函式
5. 寫一個非阻塞socket程式碼片
6. 整個程式碼
7.下一步
如果你在此有許多問題,那麼恭喜你,在初級階段,任何人都沒有剝奪你發現問題的權利。關於你所發現的問題,請不要猶豫email我。
你可以自由發表本教程到任何WWW或FTP網部,但無論如何也要保持原教程的原型。這樣我將會非常感謝你。
1.改變一個阻塞的socket為非阻塞模式
簡單的幾行程式碼就可以建立一個socket 並連線,看起來如此簡單。(你可以自己加入錯誤處理)
- s = socket(AF_INET, SOCK_STREAM, 0);
-
memset(&sin, 0, sizeof
- sin.sin_family = AF_INET;
- sin.sin_port = htons(port);
- sin.sin_addr.s_addr = inet_addr(hstname);
- if(sin.sin_addr.s_addr == INADDR_NONE) {
- connect(s, (struct sockaddr *)&sin, sizeof(sin))
有很多種方法可以設定socket為非阻塞模式, 我在unix下常用的方法如下:
- int x;
-
x=fcntl(s,F_GETFL,0);
- fcntl(s,F_SETFL,x | O_NONBLOCK);
到現在為此, 這個socket已為非阻塞模式了,我們可以把焦點放在如何用它了。 但是在接著寫程式碼之前,我們要看看我們將要用到的命令。
2.選擇模型
select這個方法用來檢測一個socket是否有資料到來或是是否有準備好的資料要傳送。宣告如下:
- select(s, &read_flags, &write_flags, &exec_flags, timer);
s socket的控制代碼
read_flags 讀描述字集合。檢查socket上是否有資料可讀。
write_flags 寫描述字集合。檢查socket上是否已有資料可傳送。
exec_flags 錯誤描述字集合。(本教程這兒不介紹)
timer 等待某個條件為真時超時時間。
在本教程中,我們將如下用:
- select(s, &read_flags, &write_flags, NULL, timer);
exec_flags引數設為null,因為在我們的程式中我們不需要關心exec事件。(解釋它,超出了本教程的範圍)
3.FD巨集
在select方法中的描述字集合是用來檢測socket上發生的讀取事件的,它用法如下:
- FD_ZERO(s, &write_flags) sets all associated flags
- in the socket to 0
- FD_SET(s, &write_flags) used to set a socket for checking
- FD_CLR (s, &write_flags) used to clear a socket from being checked
- FD_ISSET(s, &write_flags) used to query as to if the socket is ready
- for reading or writing.
如何用,我們在後面的程式碼中展示。
4.讀取方法
你應知道如何用下面的兩個方法,但是在非阻塞模式下,它們的用法有一點點不同。
- write(s,buffer,sizeof(buffer)) send the text in "buffer"
- read(s,buffer,sizeof(buffer)) read available data into "buffer"
當一個socket用非阻塞模式時,當呼叫這兩個方法的時候,它們將立即返回,比如,如果沒有資料的時候,它們將不會阻塞等待資料,而是返回一個錯誤。從現在開始,我們就要看程式碼了。
5.寫一個非阻塞socket程式碼片
首先宣告要用到的變數:
- fd_set read_flags,write_flags; // the flag sets to be used
- struct timeval waitd; // the max wait time for an event
- char buffer[8196]; // input holding buffer
- int stat; // holds return value for select();
我們程式執行的大部份時間都花費在不斷呼叫select(它將花費我們大部份CPU時間),至到有資料準備好讀或取。此時,timer就體現了它的意義。它決定select將等待多久。下面就是用法,請仔細分析:
- // Insert Code to create a socket
- while(1) // put program in an infinite loop of reading and writing data
- {
- waitd.tv_sec = 1; // Make select wait up to 1 second for data
- waitd.tv_usec = 0; // and 0 milliseconds.
- FD_ZERO(&read_flags); // Zero the flags ready for using
- FD_ZERO(&write_flags);
- // Set the sockets read flag, so when select is called it examines
- // the read status of available data.
- FD_SET(thefd, &read_flags);
- // If there is data in the output buffer to be sent then we
- // need to also set the write flag to check the write status
- // of the socket
- if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);
- // Now call select
- stat=select(s+1, &read_flags,&write_flags,(fd_set*)0,&waitv);
- if(stat < 0) { // If select breaks then pause for 5 seconds
- sleep(5); // then continue
- continue;
- }
- // Now select will have modified the flag sets to tell us
- // what actions can be performed
- // Check if data is available to read
- if (FD_ISSET(thefd, &read_flags)) {
- FD_CLR(thefd, &read_flags);
- // here is where you use the read().
- // If read returns an error then the socket
- // must be dead so you must close it.
- }
- //Check if the socket is prepared to accept data
- if (FD_ISSET(thefd, &write_flags)) {
- FD_CLR(thefd, &write_flags);
- // this means the socket is ready for you to use write()
- }
- // Now here you can put in any of the precedures that you want
- // to happen every 1 second or so.
- // now the loop repeats over again
補充:請確保只有在你有資料傳送的情況下才設定write_flag這個描述字集合,因為socket一量建立總是可寫的。也就是說,如果你設定了這個引數,select將不會等待,而是馬上返回並一直迴圈,它將搶佔CPU99%的利用率,這是不允許的。
6.整個程式碼
最後利用我們所學,寫一個簡單的客戶端。當然用非阻塞模式寫一個客戶端有點大采小用,這兒我們只是為了展示用法。更多示例請看第7節內容。
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <netinet/in.h>
- #include <netdb.h>
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <fcntl.h>
- // this routine simply converts the address into an
- // internet ip
- unsigned long name_resolve(char *host_name)
- {
- struct in_addr addr;
- struct hostent *host_ent;
- if((addr.s_addr=inet_addr(host_name))==(unsigned)-1) {
- host_ent=gethostbyname(host_name);
- if(host_ent==NULL) return(-1);
- memcpy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);
- }
- return (addr.s_addr);
- }
- // The connect routine including the command to set
- // the socket non-blocking.
- int doconnect(char *address, int port)
- {
- int x,s;
- struct sockaddr_in sin;
- s=socket(AF_INET, SOCK_STREAM, 0);
- x=fcntl(s,F_GETFL,0); // Get socket flags
- fcntl(s,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag
- memset(&sin, 0, sizeof(struct sockaddr_in));
- sin.sin_family=AF_INET;
- sin.sin_port=htons(port);
- sin.sin_addr.s_addr=name_resolve(address);
- if(sin.sin_addr.s_addr==NULL) return(-1);
- printf("ip: %s/n",inet_ntoa(sin.sin_addr));
- x=connect(s, (struct sockaddr *)&sin, sizeof(sin));
- if(x<0) return(-1);
- return(s);
- }
- int main (void)
- {
- fd_set read_flags,write_flags; // you know what these are
- struct timeval waitd;
- int thefd; // The socket
- char outbuff[512]; // Buffer to hold outgoing data
- char inbuff[512]; // Buffer to read incoming data into
- int err; // holds return values
- memset(&outbuff,0,sizeof(outbuff)); // memset used for portability
- thefd=doconnect("203.1.1.1",79); // Connect to the finger port
- if(thefd==-1) {
- printf("Could not connect to finger server/n");
- exit(0);
- }
- strcat(outbuff,"jarjam/n"); //Add the string jarjam to the output
- //buffer
- while(1) {
- waitd.tv_sec = 1; // Make select wait up to 1 second for data
- waitd.tv_usec = 0; // and 0 milliseconds.
- FD_ZERO(&read_flags); // Zero the flags ready for using
- FD_ZERO(&write_flags);
- FD_SET(thefd, &read_flags);
- if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);
- err=select(thefd+1, &read_flags,&write_flags,
- (fd_set*)0,&waitd);
- if(err < 0) continue;
- if(FD_ISSET(thefd, &read_flags)) { //Socket ready for reading
- FD_CLR(thefd, &read_flags);
- memset(&inbuff,0,sizeof(inbuff));
- if (read(thefd, inbuff, sizeof(inbuff)-1) <= 0) {
- close(thefd);
- break;
- }
- else printf("%s",inbuff);
- }
- if(FD_ISSET(thefd, &write_flags)) { //Socket ready for writing
- FD_CLR(thefd, &write_flags);
- write(thefd,outbuff,strlen(outbuff));
- memset(&outbuff,0,sizeof(outbuff));
- }
- // now the loop repeats over again
- }
- }
7.下一步
其它更多的示例程式碼從此教程中分離,以zip檔案的方式給出。為了更好的理解所學, 你最好參考一些結構更復雜,技術更強的程式碼: