Linux系統程式設計(34)—— socket程式設計之TCP伺服器與客戶端的互動

尹成發表於2014-09-04


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

 

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
 
#define MAXLINE 80
#define SERV_PORT 8000
 
int main(int argc, char *argv[])
{
         structsockaddr_in servaddr;
         charbuf[MAXLINE];
         intsockfd, n;
   
         sockfd= Socket(AF_INET, SOCK_STREAM, 0);
 
         bzero(&servaddr,sizeof(servaddr));
         servaddr.sin_family= AF_INET;
         inet_pton(AF_INET,"127.0.0.1", &servaddr.sin_addr);
         servaddr.sin_port= htons(SERV_PORT);
   
         Connect(sockfd,(struct sockaddr *)&servaddr, sizeof(servaddr));
 
         while(fgets(buf, MAXLINE, stdin) != NULL) {
                   Write(sockfd,buf, strlen(buf));
                   n= Read(sockfd, buf, MAXLINE);
                   if(n == 0)
                            printf("theother side has been closed.\n");
                   else
                            Write(STDOUT_FILENO,buf, n);
         }
 
         Close(sockfd);
         return0;
}


編譯並執行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,使它可以多次處理同一客戶端的請求。

 

/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
 
#define MAXLINE 80
#define SERV_PORT 8000
 
int main(void)
{
         structsockaddr_in servaddr, cliaddr;
         socklen_tcliaddr_len;
         intlistenfd, connfd;
         charbuf[MAXLINE];
         charstr[INET_ADDRSTRLEN];
         inti, n;
 
         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);
 
         printf("Acceptingconnections ...\n");
         while(1) {
                   cliaddr_len= sizeof(cliaddr);
                   connfd= Accept(listenfd,
                                     (structsockaddr *)&cliaddr, &cliaddr_len);
                   while(1) {
                            n= Read(connfd, buf, MAXLINE);
                            if(n == 0) {
                                     printf("theother side has been closed.\n");
                                     break;
                            }
                            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 < n; i++)
                                     buf[i]= toupper(buf[i]);
                            Write(connfd,buf, n);
                   }
                   Close(connfd);
         }
}

 

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

 

 

 

 

 

 

 

 

 

 

 

相關文章