當我們用socket進行編程的時候,細節上都是選擇一個 ,`AF_INET`再根據相應的類型填充地址,其實根據通信需求,有幾種簡單的服務模型可供選用,掌握了這些框架再結合socket高度的抽象,可以為我們編寫簡單的伺服器程式提供指導 迴圈服務 用戶請求服務需要排隊,伺服器一次只能服務一個客戶,服務完 ...
當我們用socket進行編程的時候,細節上都是選擇一個AF_LOCAL
,AF_INET
再根據相應的類型填充地址,其實根據通信需求,有幾種簡單的服務模型可供選用,掌握了這些框架再結合socket高度的抽象,可以為我們編寫簡單的伺服器程式提供指導
迴圈服務
用戶請求服務需要排隊,伺服器一次只能服務一個客戶,服務完才能對下一個客戶進行服務。ATM機就是這個1vs1模型。udp伺服器也經常使用這個模型
//模型偽代碼
main{
//獲得偵聽文件描述符
listenfd=socket();
//準備地址addr
//(listenfd,100);
while(1){將伺服器的addr和listenfd綁定
bind(listenfd,addr)
//開始偵聽,設置緩衝隊列長度
listen(listenfd,100);
while(1){
//如果偵聽到任務就獲取sockfd
int sockfd = accept(listenfd); //listenfd預設是阻塞IO,沒任務時進程會sleep
//通信...
close(sockfd);
}
}
多進程併發服務
多進程只是放listen到請求的時候,不是在原進程中處理請求,而是在子進程中處理,父進程繼續偵聽請求。多進程的好處父進程不必等待子進程處理完一個請求才能獲取下一個請求,而是只要有請求就fork一個子進程處理,這樣就可以實現併發伺服器。多進程併發的更實際的方案是使用進程池來實現。
//模型偽代碼
main{
//獲得偵聽文件描述符
listenfd=socket();
//準備地址addr
//將伺服器的addr和listenfd綁定
bind(listenfd,addr)
//開始偵聽,設/準備地址add置緩衝隊列長度
listen(listenfd,100);
while(1){
//如果偵聽到任務就獲取sockfd
int sockfd = accept(listenfd); //listenfd預設是阻塞IO,沒任務時進程會sleep
pid=fork();
if(0 == pid){
//子進程一定要首先關閉listenfd,防止和父進程一起偵聽
//也可以在父進程socket(,,SOCK_CLOEXEC);
close(listenfd);
while(1){
ret=read(sockfd);
if(0 == ret) break;
//通信...
}
exit(0);
}
}
}
多線程併發服務
使用多線程實現併發和多進程類似,但是創建一個線程的開銷比創建一個進程小得多,需要註意的是使用多線程需要做好對臨界資源的保護。實際操作經常使用線程池來實現多線程併發服務。
//模型偽代碼
void* communicate(void* arg){
int sockfd=(int)arg;
while(1){
ret=read(sockfd);
if(0 == ret) break;
//通信...
}
}
main{
//獲得偵聽文件描述符
listenfd=socket();
//準備地址addr
//將伺服器的addr和listenfd綁定
bind(listenfd,addr)
//開始偵聽,設置緩衝隊列長度
listen(listenfd,100);
while(1){
//如果偵聽到任務就獲取sockfd
//listenfd預設是阻塞IO,沒任務時進程會sleep
int sockfd = accept(listenfd);
pthread_create(tid,communicate,(void*)&sockfd);
}
}
}
I/O多路復用併發服務
I/O多路復用實現併發服務我已經在Linux I/O多路復用一文中舉出了詳細的例子,其實質就是將udp伺服器的迴圈模型用在tcp上。
//模型偽代碼
main{
//獲得偵聽文件描述符
listenfd=socket();
//準備地址addr
//將伺服器的addr和listenfd綁定
bind(listenfd,addr)
//開始偵聽,設置緩衝隊列長度
listen(listenfd,100);
//準備偵聽對象,和相應的觸發事件的集合
monitor_set_1[i]={fds...} //監控I/O有數據流入
monitor_set_2[i]={fds...} //監控I/O變得可寫
while(1){
//監控對象,如果有事件發生就返回
poll(monitor_set_1,monitor_set_2)
for(n=0;n<maxfd;n++){ //poll返回,說明有(一些)事件被觸發,依次處理這些觸發了事件的文件描述符
if(一個fd有數據流入){
if(是listenfd有數據流入){
//獲取sockfd
int sockfd = accept(listenfd)
//將這個sockfd加入監聽有數據流入可寫的集合
add_mem(moniter_set_1,sockfd)
}else { //不是listenfd有數據流入,而是之前加入的sockfd有數據流入
//讀取信息
read(fd,&msg)
//將其挪入加入監控可寫事件的集合
add_mem(monitor_set_1,fd)
}
}else{ //一個fd變得可寫了
//寫入信息
write(fd,&msg)
//將其挪入監控可讀的集合
add_mem(monitor_set_1,fd)
}
}
}
}
}