套接字聯網API之二 select作用和案例
這個系列的上一篇文章講了套接字聯網API在服務端和客戶端的幾個主要函式。 這篇文章用來實現,順便說一下select模型,也在下面的程式碼中用到。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
select模型出現的真正原因在網上一直找不到,經過仔細思考其實只有一個,就是解決非同步的問題;而且這個select是要結合多執行緒的。下面對這些進行說明。
1) 當使用阻塞 I/0時, 當阻塞在輸入的時候無法進行讀取的監聽;
2) 當使用非阻塞I/O時,會在讀取的監聽上消耗CPU的資源。 其實這種情況即使忽略消耗的CPU資源,也是不能實現非同步的。
3) 這時可能會考慮到使用多執行緒,就是讓讀取的監聽 和 輸入放在不同的執行緒中。
比如採用select監聽一個套接字, 如果返回一個結果,就開闢一個新執行緒用來進行互動,假設這個新的執行緒阻塞在輸入上,監聽執行緒還是能夠監聽的,如果收到了一個FIN,能夠及時的終止這個新的執行緒。 這就是select的作用,但是是不是每個連線都要設定一個select? 不用的,之需要在全域性的主執行緒設定一個select就可以了,每當一個連線建立,就信開闢一個執行緒,主執行緒仍然在監聽;而它又收到監聽描述符上的資訊後(比如FIN報文),就會對該描述符對應的執行緒進行處理。
下面是select的一個模型,分為服務端和客戶端,有詳細的註釋。
服務端流程如下: 只發了一個檔案,目的是弄清流程
#include <stdio.h> #include <stdlib.h> #include "server.h" #include "database.h" #include "userdata.h" #include "struct.h" #include "thread.h" int main() { int x = 0; //關於線上使用者變數 userlist_init(&L); pthread_t hThread[FD_SETSIZE]; //套接字變數 struct message ma; int i,maxi; //maxi代表儲存最大已連線描述符的序號 fd_set allset,curset; int readyfd,maxfd; //select返回的已準備好的描述符 int client[FD_SETSIZE];//連線描述符陣列 int ser_sockfd,cli_sockfd,confd; int ser_length,cli_length; struct sockaddr_in server_address; struct sockaddr_in client_address; //先連上資料庫 連線的mysql的資料庫,使用者名稱和密碼均為majintao my_sql=mysql_init(NULL); if(NULL == my_sql) { printf("mysql init error\n"); return mysqlinit; } my_sql=mysql_real_connect(my_sql,"localhost","majintao","majintao","chatroom",0,NULL,0); if(NULL == my_sql) { printf("connect error\n"); return mysqlconnect; } //建立一個未命名的套接字 ser_sockfd=socket(AF_INET, SOCK_STREAM,0); if(ser_sockfd < 0) { printf("create socket error\n"); return 0; } bzero(&server_address,sizeof(server_address)); server_address.sin_family=AF_INET; server_address.sin_addr.s_addr=htonl(INADDR_ANY); //暫定 server_address.sin_port=htons(9734);//暫定 int bindfd=bind(ser_sockfd,(struct sockaddr*)&server_address,sizeof(server_address)); if(bindfd < 0) { printf("bind error\n"); return 0; } //客戶端資料處理 bzero(&client_address,sizeof(client_address)); //cl_echo(ser_sockfd,(struct sockaddr*)&server_address,sizeof(server_address)); listen(ser_sockfd,LISTENQ); FD_ZERO(&allset); FD_SET(ser_sockfd,&allset); maxfd=ser_sockfd; //初始化描述符陣列 for(i=0;i<FD_SETSIZE;i++) { client[i]=-1; } //開始select監聽套接字 curset=allset; for(;;) { // <span style="color:#ff0000;">readyfd=select(maxfd+1,&curset,NULL,NULL,NULL);</span> if(FD_ISSET(ser_sockfd,&curset)) { cli_length=sizeof(client_address); if(-1 == (confd=accept(ser_sockfd,(struct sockaddr*)&client_address,&cli_length) ) ) { printf("accept error\n"); exit(1); } //把描述符放到合適的位置 for(i =0;i<FD_SETSIZE;i++) { if(client[i] <= 0) { client[i] = confd; break; } } //客戶端連線太多 if(i == FD_SETSIZE) { printf("too many clients\n"); exit(1); } //重新設定 FD_SET(confd,&allset); //如果已連線描述符大於監聽描述符,更改 //if(confd > maxfd) //{ // maxfd = confd; // } //更新描述符所在序號的最大值 if(i > maxi) { maxi = i; } //如果只有一個監聽描述符,就不檢查已連線描述符了 /*if(--readyfd <= 0) { continue; }*/ } //fd_set //對每一個已經連線描述符做處理 for(i = 0;i <=maxi; i++) { if( (testfd = client[i]) < 0) continue; if( FD_ISSET(testfd,&allset) ) { int tThread; //這個時候建立一個執行緒,意思為每個已經連線的描述符建立一個執行緒 <span style="color:#ff0000;">tThread = pthread_create(&hThread[x++], NULL, thread_ser,&testfd);</span> if(tThread != 0) { printf("Thread create failed\n"); return -1; } } client[i] = -1; //保證不會重複建立執行緒 if(--readyfd <= 0) break; }//第二個for }//最外層for迴圈 }//main
客戶端沒有使用select,就不介紹了。