TCP 客戶端和伺服器端
轉自:http://blog.csdn.net/itcastcpp/article/details/39047265
前面幾篇中實現的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_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);
- }
- }
經過上面的修改後,客戶端和伺服器可以進行多次互動了。
前面幾篇中實現的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);
- }
- }
經過上面的修改後,客戶端和伺服器可以進行多次互動了。