乙個簡單的回射伺服器

2021-07-25 17:39:04 字數 3749 閱讀 3460

本文編寫了乙個初步的簡單的回射伺服器例子。基本內容書上都能找到,認真看書就行了,所以盡量講一些自己認為應該注意的地方。

功能:

客戶端從標準輸入讀入一行文字,寫給伺服器;伺服器讀入這行文字,並回射給客戶端;客戶端讀入這行回射文字,顯示在標準輸出上。

思路:

編寫的伺服器是乙個併發伺服器,所以當連線到來時,會fork乙個子程序來對客戶請求進行處理。其他部分就按簡單的客戶-伺服器通訊的步驟來寫就行,客戶端:socket()--->connect()--->處理函式;伺服器:socket()--->bind()--->listen()--->accept()--->處理函式

程式如下:

伺服器:

#include #include #include #include #include //出錯函式

#define err_exit(m)\

//最大連線數

#define listenq 1024

//伺服器端口號

#define serv_port 9877

//接收和傳送的緩衝區大小

#define bufsize 4096

//處理客戶端請求函式

void str_echo(int confd);

int main(int argc, char **argv)

close(confd);//父程序不需要連線套接字

}}void str_echo(int confd)

客戶端:

#include #include #include #include #include #define err_exit(m)\

#define serv_port 9877

#define bufsize 4096

//客戶端具體操作函式

void str_cli(file *fp, int sockfd);

int main(int argc, char **argv)

int sockfd;

struct sockaddr_in servaddr;

int status;

bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = af_inet;

servaddr.sin_port = htons(serv_port);

inet_pton(af_inet, argv[1], &servaddr.sin_addr);//將點分十進位制ip位址轉化為網路位元組序的二進位制位址

sockfd = socket(af_inet, sock_stream, 0);

if (sockfd == -1)

err_exit("socket");

status = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//連線伺服器

if (sockfd == -1)

err_exit("connect");

str_cli(stdin, sockfd);

exit(0);

}void str_cli(file *fp, int sockfd)

}

結果:伺服器:

客戶端:

分析:

程式分析:

1.伺服器是乙個併發伺服器,而在子程序被建立出來以前,父程序已經有監聽套接字和已連線套接字兩個檔案描述符了,所以fork子程序後,子程序是共享這兩個套接字的,而子程序不需要監聽套接字,父程序不需要已連線套接字,所以需要各自關閉相應的套接字。

2.主機位元組序和網路位元組序不同,所以傳送資料到網路上時,要將主機位元組序轉換成網路位元組序,而從網路上接收資料時,要將網路位元組序轉換為主機位元組序。要轉換的為協議位址結構內的ip位址和埠號,因為ip位址為32位,埠號為16位,所以htonl()和ntohl()用於ip位址,htons()和ntohs()用於埠號。但為什麼inet_pton()在將點分十進位制的ip位址轉換為二進位制位址後,傳送到網上去之前沒有使用htonl()將這二進位制位址轉換為網路位元組序呢?因為inet_pton()轉換後的二進位制位址是已經轉換為網路位元組序的了,所以不需要htonl()這一步了。

3.accept()如果要接收客戶端的協議位址,則使用accept()之前,應先得到位址結構的大小,再作為第三個引數傳進去,而不是直接就傳進去。這一點容易忘記。

4.標頭檔案:socket程式設計少不了標頭檔案,是因為使用了bzero()函式,是因為使用了常量exit_failure;

深入分析:

要明白的是,當啟動伺服器程式和客戶端程式後,實際上是有3個程序在執行:客戶端程序、伺服器父程序、伺服器子程序。

1先在後台執行伺服器程式,看看此時的網路連線狀況,接著又執行了客戶端程序:

我們看到伺服器程式server,程序id為1607。此時伺服器程序為listen狀態,阻塞與accept()函式。

2接著,當執行客戶端程序後,再看此時的網路連線情況:

此時,發現多了客戶端程序和伺服器子程序這兩個網路狀態了。「s」表示程序處於阻塞狀態。此時客戶端程序id為1609,establish,阻塞與fgets()呼叫,等待標準輸入;伺服器子程序id為1610,establish,阻塞與read()呼叫,等待從已連線套接字讀資料;伺服器父程序id為1607,阻塞與accept()呼叫,等待下乙個連線的完成。伺服器使用的埠號為9877,客戶端使用的臨時埠號為40657,因為是在同一臺主機執行客戶端程式和伺服器程式,所以ip位址相同。

3.現在我們關閉客戶端,然後看看網路連線情況

我們發現自程序此時為「z」,就是表示處於僵死狀態,此時子程序變成了殭屍程序。為什麼會這樣呢?下面分析一下:

客戶端退出,則fgets返回空指標,從str_cli()函式退出,執行exit(0),客戶端程序終止。客戶端退出,則伺服器子程序的read()呼叫返回0,子程序退出str_echo()函式,執行exit(0),子程序終止。子程序終止這一訊息,會給父程序傳送乙個sigchld訊號,但父程序未對此訊號進行處理,而此訊號的預設行為是忽略此訊號。那麼就知道了,由於父程序未清理子程序的終止狀態,所以子程序變成了殭屍程序。

這是乙個併發伺服器,但父程序未對子程序的退出狀態進行清理,導致產生了殭屍程序。若伺服器的連線數達到了上萬條,那麼則會有大量的殭屍程序,最終會耗完系統資源,所以必須不能使子程序成為殭屍程序,解決辦法就是讓父程序清理子程序的退出狀態,這就是通過訊號處理的辦法來解決。具體解決方法以及由此會帶來的其他問題,另寫一篇文章分析。

參考:《unix網路程式設計》《unix環境高階程式設計》

TCP 回射伺服器

tcp reflect server client tcp回射伺服器。學習了 unp 的第五章前面的知識,自己把 敲出來了,加深了理解吧。簡單地說就是,client傳送給server一條訊息 一行文字 server再將同樣地訊息傳送回client。就像這樣 用到的函式和api包括 1 socket ...

Linux C 回射伺服器

回射伺服器就是服務端將客戶端的資料傳送回去。我實現的回射伺服器返回增加了時間。服務端 可以很容易看懂 cpp view plain copy include include include include include include include include include define ...

TCP 回射伺服器

本例為多程序的 tcp 回射程式 服務端 include intmain int argc,char ar close connfd 父程序斷開連線 void str echo int sockfd 套接字 sockaddr in cliaddr,servaddr 網際套接字位址結構,包含協議族 i...