伺服器程式設計入門(11)TCP併發回射伺服器實現
阿新 • • 發佈:2019-01-25
問題聚焦:
當客戶端阻塞於從標準輸入接收資料時,將讀取不到別的途徑發過來的必要資訊,如TCP發過來的FIN標誌。
因此,程序需要核心一旦發現程序指定的一個或多個IO條件就緒(即輸入已準備好被讀取,或者描述符已能承接更多的輸出),它就通知程序。
這個機制稱為I/O複用,這是由select, poll, epoll函式支援的。
編譯環境:
Ubuntu12.04 g++
需求描述:
步驟:
一個輸入操作通常包括兩個不同的階段:
客戶端:
問題: 1 監聽標準輸入的描述符? 解決:標準輸入描述符:0 2 當客戶端傳送所有訊息,即可關閉連線,但是如果這時候呼叫close方法,會導致接收不到仍在傳送過來的資訊。 方案:需要一種關閉TCP連線其中一半的方法,也即是說,我們想給伺服器傳送一個FIN,告訴它我們已經完成了資料傳送,但是仍然保持套接字描述符開啟以便讀取。 完成這個功能的函式為shutdown。 shutdown函式可以不管描述符的引用計數,就激發TCP的正常連線終止序列。 關閉一半的圖示: 函式宣告: #include <sys/socket.h> int shutdown(int sockfd, int howto); howto:取值SHUT_RD(關閉這一端的讀,不再讀取連線上的資料) SHUT_WR(關閉這一端的寫,不再往連線上寫資料) SHUT_RDWR(關閉這一端的讀和寫) 3 套接字描述符的第一個可用描述符是多少? 答案:3。0 1 2分別為標準輸入,標準輸出,標準錯誤輸出。 4 伺服器程序終止後的動作? 這裡需要知道的一點是,當伺服器程序一終止,就會對客戶程序傳送一個FIN訊號,這時套接字連線可讀,read返回0 參考資料: 《Linux高效能伺服器程式設計》 《UNIX網路程式設計 卷1:套接字聯網API(第3版)》
- 單程序,IO複用,實現多個連線同時監聽和收發資訊
- 當伺服器程序一終止,客戶就能馬上得到結果(select +shutdown實現)
- 當客戶端使用"exit"命令或者Cirl+C結束程序時,伺服器可以立即感應到,並關閉當前介面(select+close實現
步驟:
- 伺服器連線了第一個客戶,並收發訊息“hello world”
- 伺服器連線了第二個客戶,並收發訊息“hello select”
- 伺服器從第一個客戶收發訊息“hello world again”
- 伺服器從第二個客戶收發訊息“hello select again”
- 第二個客戶關閉連線
- 第一個客戶關閉連線
- 阻塞式IO
- 非阻塞式IO
- IO複用
- 訊號驅動式IO
- 非同步IO
一個輸入操作通常包括兩個不同的階段:
- 等待資料準備好
- 從核心向程序複製資料
- 等待資料從網路中到達,當所等待分組到達時,它被複制到核心中的某個緩衝區
- 把資料從核心緩衝區複製到應用程序緩衝區
- select主要通過維護兩個陣列,來實現埠的輪詢:
- client[]陣列,記錄有哪些連線已經建立
- rset[]陣列,記錄有註冊哪些埠,需要監聽
- 當rset陣列中註冊的埠被啟用,這時將埠號放到client陣列中,稍後遍歷client[]陣列,處理連線上的資料
#include "mtserver.h" int main(int argc, char* argv[]) { checkArgc(argc, 2); const char* ip = argv[1]; int port = atoi( argv[2] ); /* declare socket*/ int listenfd, connfd, sockfd; int ret; /* initialize listen socket*/ mySocket(listenfd); /* server address */ struct sockaddr_in servaddr; initSockAddr(servaddr, ip, port); /* bind */ myBind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); /* listen */ myListen(listenfd, 5); /* handle SIGCHLD signal*/ //signal(SIGCHLD, handle_sigchild); /* waiting for connecting */ pid_t chipid; socklen_t clilen; struct sockaddr_in cliaddr; /* select initialize */ int maxfd, maxi, i; bool toclose; int nready, client[FD_SETSIZE]; fd_set rset, allset; maxfd = listenfd; maxi = -1; for ( i=0; i < FD_SETSIZE; i++ ) client[i] = -1; FD_ZERO(&allset); FD_SET(listenfd, &allset); printf("Waiting for connecting...\n"); for(;;) { rset = allset; if ( (nready=select(maxfd+1, &rset, NULL, NULL, NULL)) < 0 ) { fprintf(stderr, "select failed.%s\n", strerror(errno)); continue; } /* handle listen fd and no recv or respond */ if (FD_ISSET(listenfd, &rset)) { clilen = sizeof(cliaddr); connfd = myAccept(listenfd, (struct sockaddr*)&cliaddr, &clilen); printf("Connection is established with sockfd: %d\n", connfd); for ( i = 0; i < FD_SETSIZE; i++) { if ( client[i] < 0 ) { client[i] = connfd; break; } } if (i == FD_SETSIZE) { fprintf(stderr, "too many clients\n" ); break; } FD_SET( connfd, &allset ); if ( connfd > maxfd ) { maxfd = connfd; } if ( i > maxi) { maxi = i; } if (--nready <= 0) { continue; } } /* handle accept fds(client[]) and handle recv or respond msg */ for ( i = 0; i <= maxi; i++) { if ( (sockfd = client[i]) < 0 ) continue; if ( FD_ISSET(sockfd, &rset) ) { if( (toclose = handle_recv(sockfd))) { printf("Client close this connection: %d\n" , sockfd); close(sockfd); FD_CLR(sockfd, &allset); client[i] = -1; } if (--nready <= 0) break; } } } } bool handle_recv(int connfd) { char recvbuf[BUFSIZE]; memset( recvbuf, '\0', BUFSIZE ); if ( recv(connfd, recvbuf,BUFSIZE,0) != 0) { if (!strcmp(recvbuf, "exit")) return true; fprintf(stderr,"recv msg: \"%s\" from connfd:%d\n", recvbuf, connfd); send(connfd, recvbuf, strlen(recvbuf), 0); fprintf(stderr,"send back: \"%s\" to connfd:%d\n\n", recvbuf, connfd); } else return true; return false; }
客戶端:
#include "mtclient.h"
int main(int argc, char* argv[])
{
checkArgc(argc, 2);
int port = atoi(argv[2]);
char* ip = argv[1];
int sockfd;
struct sockaddr_in servaddr;
mySocket(sockfd);
initSockAddr(servaddr,ip, port);
myConnect(sockfd,
(struct sockaddr*)&servaddr,
sizeof(servaddr));
handle_msg(sockfd);
exit(0);
}
void handle_msg(int sockfd) {
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
int maxfdpl, ret;
fd_set rset;
int normalTermi = 0;
FD_ZERO(&rset);
while(1) {
memset( sendbuf, '\0', BUFSIZE );
memset( recvbuf, '\0', BUFSIZE );
if (normalTermi == 0)
FD_SET( 0, &rset );
FD_SET( sockfd, &rset );
maxfdpl = sockfd + 1;
if(DEBUG)
printf("Debug: waiting in select\n");
if ( select( maxfdpl, &rset, NULL, NULL, NULL) < 0 ) {
fprintf(stderr,
"select failed.%s\n",
strerror(errno));
}
if(DEBUG)
printf("Debug: after select\n");
if (FD_ISSET( sockfd, &rset )) {
if (recv(sockfd, recvbuf, BUFSIZE, 0) == 0) {
if(DEBUG)
printf("Debug: ready to quit, normalTermi: %d\n" ,
normalTermi);
if (normalTermi == 1) {
printf("handle_msg: normal terminated.\n");
return;
}
else {
printf("handle_msg: server terminated.\n");
exit(0);
}
}
fprintf(stderr,
"recv back: %s\n",
recvbuf);
}
else if ( FD_ISSET( 0, &rset ) ) {
gets(sendbuf);
if (strlen(sendbuf) > 0) {
send(sockfd, sendbuf, strlen(sendbuf), 0);
if ( !strcmp(sendbuf, "exit") ) {
normalTermi = 1;
shutdown(sockfd, SHUT_WR);
FD_CLR(0, &rset);
continue;
}
}
}
}
close( sockfd );
return;
}
問題: 1 監聽標準輸入的描述符? 解決:標準輸入描述符:0 2 當客戶端傳送所有訊息,即可關閉連線,但是如果這時候呼叫close方法,會導致接收不到仍在傳送過來的資訊。 方案:需要一種關閉TCP連線其中一半的方法,也即是說,我們想給伺服器傳送一個FIN,告訴它我們已經完成了資料傳送,但是仍然保持套接字描述符開啟以便讀取。 完成這個功能的函式為shutdown。 shutdown函式可以不管描述符的引用計數,就激發TCP的正常連線終止序列。 關閉一半的圖示: 函式宣告: #include <sys/socket.h> int shutdown(int sockfd, int howto); howto:取值SHUT_RD(關閉這一端的讀,不再讀取連線上的資料) SHUT_WR(關閉這一端的寫,不再往連線上寫資料) SHUT_RDWR(關閉這一端的讀和寫) 3 套接字描述符的第一個可用描述符是多少? 答案:3。0 1 2分別為標準輸入,標準輸出,標準錯誤輸出。 4 伺服器程序終止後的動作? 這裡需要知道的一點是,當伺服器程序一終止,就會對客戶程序傳送一個FIN訊號,這時套接字連線可讀,read返回0 參考資料: 《Linux高效能伺服器程式設計》 《UNIX網路程式設計 卷1:套接字聯網API(第3版)》