1. 程式人生 > >TCP 客戶端和伺服器端

TCP 客戶端和伺服器端

轉自:http://blog.csdn.net/itcastcpp/article/details/39047265

前面幾篇中實現的client每次執行只能從命令列讀取一個字串發給伺服器,再從伺服器收回來,現在我們把它改成互動式的,不斷從終端接受使用者輸入並和server互動。

  1. /* client.c */
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <netinet/in.h>
  6. #include "wrap.h"
  7. #define MAXLINE 80
  8. #define SERV_PORT 8000
  9. int main(int argc, char *argv[])  
  10. {  
  11.          structsockaddr_in servaddr;  
  12.          charbuf[MAXLINE];  
  13.          intsockfd, n;  
  14.          sockfd= Socket(AF_INET, SOCK_STREAM, 0);  
  15.          bzero(&servaddr,sizeof(servaddr));  
  16.          servaddr.sin_family= AF_INET;  
  17.          inet_pton(AF_INET,"127.0.0.1"
    , &servaddr.sin_addr);  
  18.          servaddr.sin_port= htons(SERV_PORT);  
  19.          Connect(sockfd,(struct sockaddr *)&servaddr, sizeof(servaddr));  
  20.          while(fgets(buf, MAXLINE, stdin) != NULL) {  
  21.                    Write(sockfd,buf, strlen(buf));  
  22.                    n= Read(sockfd, buf, MAXLINE);  
  23.                    if(n == 0)  
  24.                             printf("theother side has been closed.\n");  
  25.                    else
  26.                             Write(STDOUT_FILENO,buf, n);  
  27.          }  
  28.          Close(sockfd);  
  29.          return0;  
  30. }  

編譯並執行server和client,看看是否達到了你預想的結果。

這時server仍在執行,但是client的執行結果並不正確。原因是什麼呢?仔細檢視server.c可以發現,server對每個請求只處理一次,應答後就關閉連線,client不能繼續使用這個連線傳送資料。但是client下次迴圈時又呼叫write發資料給server,write呼叫只負責把資料交給TCP傳送緩衝區就可以成功返回了,所以不會出錯,而server收到資料後應答一個RST段,client收到RST段後無法立刻通知應用層,只把這個狀態儲存在TCP協議層。client下次迴圈又呼叫write發資料給server,由於TCP協議層已經處於RST狀態了,因此不會將資料發出,而是發一個SIGPIPE訊號給應用層,SIGPIPE訊號的預設處理動作是終止程式,所以看到上面的現象。

為了避免client異常退出,上面的程式碼應該在判斷對方關閉了連線後break出迴圈,而不是繼續write。另外,有時候程式碼中需要連續多次呼叫write,可能還來不及呼叫read得知對方已關閉了連線就被SIGPIPE訊號終止掉了,這就需要在初始化時呼叫sigaction處理SIGPIPE訊號,如果SIGPIPE訊號沒有導致程序異常退出,write返回-1並且errno為EPIPE。

另外,我們需要修改server,使它可以多次處理同一客戶端的請求。

  1. /* server.c */
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <netinet/in.h>
  5. #include "wrap.h"
  6. #define MAXLINE 80
  7. #define SERV_PORT 8000
  8. int main(void)  
  9. {  
  10.          structsockaddr_in servaddr, cliaddr;  
  11.          socklen_tcliaddr_len;  
  12.          intlistenfd, connfd;  
  13.          charbuf[MAXLINE];  
  14.          charstr[INET_ADDRSTRLEN];  
  15.          inti, n;  
  16.          listenfd= Socket(AF_INET, SOCK_STREAM, 0);  
  17.          bzero(&servaddr,sizeof(servaddr));  
  18.          servaddr.sin_family= AF_INET;  
  19.          servaddr.sin_addr.s_addr= htonl(INADDR_ANY);  
  20.          servaddr.sin_port= htons(SERV_PORT);  
  21.          Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));  
  22.          Listen(listenfd,20);  
  23.          printf("Acceptingconnections ...\n");  
  24.          while(1) {  
  25.                    cliaddr_len= sizeof(cliaddr);  
  26.                    connfd= Accept(listenfd,  
  27.                                      (structsockaddr *)&cliaddr, &cliaddr_len);  
  28.                    while(1) {  
  29.                             n= Read(connfd, buf, MAXLINE);  
  30.                             if(n == 0) {  
  31.                                      printf("theother side has been closed.\n");  
  32.                                      break;  
  33.                             }  
  34.                             printf("receivedfrom %s at PORT %d\n",  
  35.                                    inet_ntop(AF_INET,&cliaddr.sin_addr, str, sizeof(str)),  
  36.                                    ntohs(cliaddr.sin_port));  
  37.                             for(i = 0; i < n; i++)  
  38.                                      buf[i]= toupper(buf[i]);  
  39.                             Write(connfd,buf, n);  
  40.                    }  
  41.                    Close(connfd);  
  42.          }  
  43. }  

經過上面的修改後,客戶端和伺服器可以進行多次互動了。

前面幾篇中實現的client每次執行只能從命令列讀取一個字串發給伺服器,再從伺服器收回來,現在我們把它改成互動式的,不斷從終端接受使用者輸入並和server互動。

  1. /* client.c */
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <netinet/in.h>
  6. #include "wrap.h"
  7. #define MAXLINE 80
  8. #define SERV_PORT 8000
  9. int main(int argc, char *argv[])  
  10. {  
  11.          structsockaddr_in servaddr;  
  12.          charbuf[MAXLINE];  
  13.          intsockfd, n;  
  14.          sockfd= Socket(AF_INET, SOCK_STREAM, 0);  
  15.          bzero(&servaddr,sizeof(servaddr));  
  16.          servaddr.sin_family= AF_INET;  
  17.          inet_pton(AF_INET,"127.0.0.1", &servaddr.sin_addr);  
  18.          servaddr.sin_port= htons(SERV_PORT);  
  19.          Connect(sockfd,(struct sockaddr *)&servaddr, sizeof(servaddr));  
  20.          while(fgets(buf, MAXLINE, stdin) != NULL) {  
  21.                    Write(sockfd,buf, strlen(buf));  
  22.                    n= Read(sockfd, buf, MAXLINE);  
  23.                    if(n == 0)  
  24.                             printf("theother side has been closed.\n");  
  25.                    else
  26.                             Write(STDOUT_FILENO,buf, n);  
  27.          }  
  28.          Close(sockfd);  
  29.          return0;  
  30. }  

編譯並執行server和client,看看是否達到了你預想的結果。

這時server仍在執行,但是client的執行結果並不正確。原因是什麼呢?仔細檢視server.c可以發現,server對每個請求只處理一次,應答後就關閉連線,client不能繼續使用這個連線傳送資料。但是client下次迴圈時又呼叫write發資料給server,write呼叫只負責把資料交給TCP傳送緩衝區就可以成功返回了,所以不會出錯,而server收到資料後應答一個RST段,client收到RST段後無法立刻通知應用層,只把這個狀態儲存在TCP協議層。client下次迴圈又呼叫write發資料給server,由於TCP協議層已經處於RST狀態了,因此不會將資料發出,而是發一個SIGPIPE訊號給應用層,SIGPIPE訊號的預設處理動作是終止程式,所以看到上面的現象。

為了避免client異常退出,上面的程式碼應該在判斷對方關閉了連線後break出迴圈,而不是繼續write。另外,有時候程式碼中需要連續多次呼叫write,可能還來不及呼叫read得知對方已關閉了連線就被SIGPIPE訊號終止掉了,這就需要在初始化時呼叫sigaction處理SIGPIPE訊號,如果SIGPIPE訊號沒有導致程序異常退出,write返回-1並且errno為EPIPE。

另外,我們需要修改server,使它可以多次處理同一客戶端的請求。

  1. /* server.c */
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <netinet/in.h>
  5. #include "wrap.h"
  6. #define MAXLINE 80
  7. #define SERV_PORT 8000
  8. int main(void)  
  9. {  
  10.          structsockaddr_in servaddr, cliaddr;  
  11.          socklen_tcliaddr_len;  
  12.          intlistenfd, connfd;  
  13.          charbuf[MAXLINE];  
  14.          charstr[INET_ADDRSTRLEN];  
  15.          inti, n;  
  16.          listenfd= Socket(AF_INET, SOCK_STREAM, 0);  
  17.          bzero(&servaddr,sizeof(servaddr));  
  18.          servaddr.sin_family= AF_INET;  
  19.          servaddr.sin_addr.s_addr= htonl(INADDR_ANY);  
  20.          servaddr.sin_port= htons(SERV_PORT);  
  21.          Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));  
  22.          Listen(listenfd,20);  
  23.          printf("Acceptingconnections ...\n");  
  24.          while(1) {  
  25.                    cliaddr_len= sizeof(cliaddr);  
  26.                    connfd= Accept(listenfd,  
  27.                                      (structsockaddr *)&cliaddr, &cliaddr_len);  
  28.                    while(1) {  
  29.                             n= Read(connfd, buf, MAXLINE);  
  30.                             if(n == 0) {  
  31.                                      printf("theother side has been closed.\n");  
  32.                                      break;  
  33.                             }  
  34.                             printf("receivedfrom %s at PORT %d\n",  
  35.                                    inet_ntop(AF_INET,&cliaddr.sin_addr, str, sizeof(str)),  
  36.                                    ntohs(cliaddr.sin_port));  
  37.                             for(i = 0; i < n; i++)  
  38.                                      buf[i]= toupper(buf[i]);  
  39.                             Write(connfd,buf, n);  
  40.                    }  
  41.                    Close(connfd);  
  42.          }  
  43. }  

經過上面的修改後,客戶端和伺服器可以進行多次互動了。