Linux系統程式設計(35)—— socket程式設計之TCP伺服器的併發處理
我們知道,伺服器通常是要同時服務多個客戶端的,如果我們執行上一篇實現的server和client之後,再開一個終端執行client試試,新的client就不能能得到服務了。因為伺服器之支援一個連線。
網路伺服器通常用fork來同時服務多個客戶端,父程式專門負責監聽埠,每次accept一個新的客戶端連線就fork出一個子程式專門服務這個客戶端。但是子程式退出時會產生殭屍程式,父程式要注意處理SIGCHLD訊號和呼叫wait清理殭屍程式。
下面是程式碼框架:
listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, ...);
while (1) {
connfd= accept(listenfd, ...);
n= fork();
if(n == -1) {
perror("callto fork");
exit(1);
}else if (n == 0) {
close(listenfd);
while(1) {
read(connfd,...);
...
write(connfd,...);
}
close(connfd);
exit(0);
}else
close(connfd);
}
現在做一個測試,首先啟動server,然後啟動client,然後用Ctrl-C使server終止,這時馬上再執行server,結果是:
binderror: Address already in use
這是因為,雖然server的應用程式終止了,但TCP協議層的連線並沒有完全斷開,因此不能再次監聽同樣的server埠。server終止時,socket描述符會自動關閉併發FIN段給client,client收到FIN後處於CLOSE_WAIT狀態,但是client並沒有終止,也沒有關閉socket描述符,因此不會發FIN給server,因此server的TCP連線處於FIN_WAIT2狀態。
現在用Ctrl-C把client也終止掉,再觀察現象結果是:
binderror: Address already in useclient
終止時自動關閉socket描述符,server的TCP連線收到client發的FIN段後處於TIME_WAIT狀態。TCP協議規定,主動關閉連線的一方要處於TIME_WAIT狀態,等待兩個MSL(maximum segment lifetime)的時間後才能回到CLOSED狀態,因為我們先Ctrl-C終止了server,所以server是主動關閉連線的一方,在TIME_WAIT期間仍然不能再次監聽同樣的server埠。MSL在RFC1122中規定為兩分鐘,但是各作業系統的實現不同,在Linux上一般經過半分鐘後就可以再次啟動server了。
在server的TCP連線沒有完全斷開之前不允許重新監聽是不合理的,因為,TCP連線沒有完全斷開指的是connfd(127.0.0.1:8000)沒有完全斷開,而我們重新監聽的是listenfd(0.0.0.0:8000),雖然是佔用同一個埠,但IP地址不同,connfd對應的是與某個客戶端通訊的一個具體的IP地址,而listenfd對應的是wildcard address。解決這個問題的方法是使用setsockopt()設定socket描述符的選項SO_REUSEADDR為1,表示允許建立埠號相同但IP地址不同的多個socket描述符。在server程式碼的socket()和bind()呼叫之間插入如下程式碼:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));
select是網路程式中很常用的一個系統呼叫,它可以同時監聽多個阻塞的檔案描述符(例如多個網路連線),哪個有資料到達就處理哪個,這樣,不需要fork和多程式就可以實現併發服務的server。
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char **argv)
{
inti, maxi, maxfd, listenfd, connfd, sockfd;
intnready, client[FD_SETSIZE];
ssize_tn;
fd_setrset, allset;
charbuf[MAXLINE];
charstr[INET_ADDRSTRLEN];
socklen_tcliaddr_len;
structsockaddr_in cliaddr, servaddr;
listenfd= Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd,20);
maxfd= listenfd; /* initialize */
maxi= -1; /* indexinto client[] array */
for(i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
for( ; ; ) {
rset= allset; /* structure assignment */
nready= select(maxfd+1, &rset, NULL, NULL, NULL);
if(nready < 0)
perr_exit("selecterror");
if(FD_ISSET(listenfd, &rset)) { /* new client connection */
cliaddr_len= sizeof(cliaddr);
connfd= Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("receivedfrom %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr,str, sizeof(str)),
ntohs(cliaddr.sin_port));
for(i = 0; i < FD_SETSIZE; i++)
if(client[i] < 0) {
client[i]= connfd; /* save descriptor */
break;
}
if(i == FD_SETSIZE) {
fputs("toomany clients\n", stderr);
exit(1);
}
FD_SET(connfd,&allset); /* add newdescriptor to set */
if(connfd > maxfd)
maxfd= connfd; /* for select */
if(i > maxi)
maxi= i; /* max index in client[] array */
if(--nready == 0)
continue; /* no more readable descriptors */
}
for(i = 0; i <= maxi; i++) { /* check allclients for data */
if( (sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd, &rset)) {
if( (n = Read(sockfd, buf, MAXLINE)) == 0) {
/*connection closed by client */
Close(sockfd);
FD_CLR(sockfd,&allset);
client[i]= -1;
}else {
intj;
for(j = 0; j < n; j++)
buf[j]= toupper(buf[j]);
Write(sockfd,buf, n);
}
if(--nready == 0)
break; /* no more readable descriptors */
}
}
}
}
相關文章
- Linux Socket C語言網路程式設計:TCP SocketLinuxC語言程式設計TCP
- TCP併發伺服器的程式設計實現TCP伺服器程式設計
- python網路-Socket之TCP程式設計(26)PythonTCP程式設計
- Linux系統程式設計之訊號中斷處理(下)Linux程式設計
- Linux系統程式設計之訊號中斷處理(上)Linux程式設計
- Linux系統程式設計之程式介紹Linux程式設計
- socket程式設計在TCP中的應用程式設計TCP
- Windows Socket程式設計精華《TCP通訊伺服器》Windows程式設計TCP伺服器
- socket程式設計實現tcp伺服器_C/C++程式設計TCP伺服器C++
- 併發程式設計之volatile程式設計
- 併發程式設計之:Atomic程式設計
- 併發程式設計之:Lock程式設計
- 併發程式設計之:ForkJoin程式設計
- 併發程式設計之:ThreadLocal程式設計thread
- 併發程式設計之:CountDownLatch程式設計CountDownLatch
- 併發程式設計之:synchronized程式設計synchronized
- 併發程式設計之:JMM程式設計
- Linux系統程式設計之匿名管道Linux程式設計
- Linux系統程式設計——特殊程式之孤兒程式Linux程式設計
- 【Linux系統程式設計】libevent庫實現簡易tcp伺服器Linux程式設計TCP伺服器
- 網路程式設計之socket程式設計
- 從併發程式設計到分散式系統-如何處理海量資料(上)程式設計分散式
- 程式設計小技巧之 Linux 文字處理命令(二)程式設計Linux
- Python併發程式設計之從效能角度來初探併發程式設計(一)Python程式設計
- SOCKET程式設計程式設計
- 併發程式設計之 CAS 的原理程式設計
- linux非阻塞式socket程式設計之select()用法Linux程式設計
- Linux作業系統之Shell程式設計Linux作業系統程式設計
- Linux系統程式設計之檔案IOLinux程式設計
- Java併發程式設計之synchronizedJava程式設計synchronized
- Go 併發程式設計之 MutexGo程式設計Mutex
- 併發程式設計程式設計
- 基於TCP協議的Socket網路程式設計( )TCP協議程式設計
- (3)Tcp Socket程式設計的封裝類 TcpListener/TcpClientTCP程式設計封裝client
- 併發程式設計之:JUC併發控制工具程式設計
- 通過 Socket 實現 TCP 程式設計入門TCP程式設計
- linux網路程式設計中的errno處理Linux程式設計
- PHP回顧之socket程式設計PHP程式設計
- 玩轉 PHP 網路程式設計全套之 socket stream 程式設計PHP程式設計