?? tcp 客戶-服務器例子.txt
字號:
TCP 客戶-服務器例子
--------------------------------------------------------------------------------
第八軍團 時間:2004-1-17 21:34:58
概述
回射服務器:echo server
7th 端口; /etc/inetd.conf
1.客戶從標準輸入讀一行文本,寫到服務器上
2.服務器從網絡輸入讀此行,并回射給客戶
3.客戶讀此回射行并寫到標準輸出。
--------------------------------------------------------------------------------
幾個讀寫有關的函數
fputs 和 fgets 標準庫函數
//簡單的輸入和輸出程序:
// mystr_client.c
#include "unp.h"
void main(void)
{
char sendline[MAXLINE],recevline[MAXLINE];
fgets(sendline, MAXLINE, stdin); //標準輸入
memcpy(recevline, sendline, strlen(sendline));
fputs(recevline, stdout); //標準輸出
}
//writen 和 readline字節流讀寫函數
ssize_t Writen(int filedes, const void *buff, size_t nbytes);
ssize_t Readline(int filedes, void *buff, size_t maxlen);
--------------------------------------------------------------------------------
TCP回射服務器程序
//main源程序:
#include "unp.h"
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
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);//9877
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//綁定端口
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);//等待客戶完成連接accept
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
//服務器程序str_echo 函數
//讀如一行 Readline
//回射此行 Writen
#include "unp.h"
#include "sum.h"
void
str_echo(int sockfd)
{
ssize_t n;
struct args args;
struct result result;
for ( ; ; ) {
if ( (n = Readn(sockfd, &args, sizeof(args))) == 0)
return; /* connection closed by other end */
result.sum = args.arg1 + args.arg2;
Writen(sockfd, &result, sizeof(result));
}
}
--------------------------------------------------------------------------------
TCP回射客戶端程序
//main創建套接口,裝填IP地址結構socket bzero與服務器連接connect
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
//tcp回射客戶程序str_cli函數
#include "unp.h"
void
str_echo(int sockfd)
{
long arg1, arg2;
ssize_t n;
char line[MAXLINE];
for ( ; ; ) {
if ( (n = Readline(sockfd, line, MAXLINE)) == 0)
//從服務器讀回射行
return; /* connection closed by other end */
if (sscanf(line, "%ld%ld", &arg1, &arg2) == 2)
snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);
else
snprintf(line, sizeof(line), "input error\n");
n = strlen(line);
Writen(sockfd, line, n);//寫到服務器
}
}
--------------------------------------------------------------------------------
正常啟動
服務器后臺啟動 ./tcpserv01 &
檢查監聽套接口狀態 netstat –a
過濾:netstat –a |grep 9877
啟動客戶端程序 ./tcpcli01 127.0.0.1
檢查TCP的連接 netstat –a
過濾:netstat –a |grep 9877
檢查進程的狀態和關系 ps –l
tty控制臺 ; pts/0偽終端
pid進程編號 ppid 父進程編號
父進程的ppid 是shell (bash)
狀態S
I 空閑 Idle S 停止 stop Z 僵尸 zombie
狀態 WCHAN
鍵入 EOF (Control-D)結束
TCP連接終止序列
客戶端鍵入EOF字符
fgets返回一個空指針
str_cli返回main
main通過調用exit終止
客戶套接口由內核關閉
客戶TCP發送一個FIN給服務器
服務器TCP則以ACK響應
此時,服務器套接口處于CLOSE—WAIT狀態,客戶套接口處于FIN—WAIT2狀態
服務器TCP接收FIN時,服務器子進程阻塞于readline調用.readline返回0
函數str_echo返回服務器子進程的main
服務器子進程通過調用exit來終止
服務器子進程中打開的所有描述字隨之關閉。
由于進程來關閉已連接套接口引發TCP連接終止序列的最后兩個分節:
一個從服務器到客戶的FIN和一個從客戶到服務器的ACK。至此,連接完全終止,
客戶套接口進入TIME—WAIT狀態。
進程終止處理的另一部分是在服務器子進程終止時給父進程發一個信號SIGCHLD。
但我們在代碼中未捕獲,于是出現了:僵尸進程 zombie process,用
ps -la 查看
--------------------------------------------------------------------------------
Posix 信號處理
信號signal:某事件發生時對進程的通知(軟中斷)
信號是異步的不可提前知道信號發生的時間
信號的流向:進程->進程(本身) 內核->進程
SIGCHLD: 內核在某進程終止時發給父進程的信號
每個信號都有處理方法disposition /action
處理函數sigaction的選擇
信號處理程序(signal handler)來捕獲(catching)信號.
void handler(int signo);SIGKILL , SIGSTOP不能被捕獲
設置信號的處理辦法為SIG—IGN來忽略信號;SIGKILL , SIGSTOP不能被捕獲
設置信號的處理辦法為SIG_DFL來設置缺省處理辦法, 在接收到信號時終止進程
特定信號還在當前工作目錄產生一個進程的核心映像。
個別信號的缺省處理辦法是忽略,SIGCHLD就是缺省處理辦法是忽略的信號。
signal 函數
通用的信號處理函數:sigaction,signal
為了方便起見,建立新的信號處理函數signal
Sigfunc *signal(int signo,Sigfunc *func);
/lib/ signal.c /desktop/signal.txt
一旦安裝了信號處理程序,它便一直安裝著。
當一個信號處理程序正在執行時,所遞交的信號是阻塞的除了被捕獲的信號外,
沒有額外信號阻塞。如果一個信號在阻塞時生成了一次或多次,在信號解阻塞后
一般只遞送一次。缺省時Unix信號是不排隊的有選擇性地阻塞或不阻塞一組信號
是可能的可以在某段臨界區代碼執行時,不許捕獲某些信號, 以此來保護這段代碼。
--------------------------------------------------------------------------------
處理SIGCHLD 信號
設置僵尸(zombie)狀態的目的:維護子進程的信息,以便父進程在稍后的某個時候取回。
僵尸(zombie))狀態的信息:子進程的ID 終止狀態
子進程的資源利用信息(CPU時間、內存等等)。
如果一個進程終止,且該進程有子進程處于僵尸狀態,則所有僵尸子進程的父進程ID均
置為1(init進程)。
init進程將作為這些子進程的繼父并負責清除它們(init進程將wait它們,從而去除僵尸
進程)。僵尸進程輸出的COMMAND列為<defunt>(ps命令輸出)。
僵尸進程的危害:占用內核空間,最終導致無法工作。
處理僵尸進程
處理SIGCHLD的Signal 函數在listen之后, accept之后:
void Signal (SIGCHID, sig_chld)
{
pid+t pid;
int stat;
pid= wait(&stat);
prtinf(“child %d terminated\n”, pid);
return;
}
處理步驟
鍵入EOF字符來終止客戶,客戶TCP發一個FIN給服務器,服務器以ACK響應。
FIN的接收導致服務器TCP遞送一個EOF給子進程阻塞中的readline,從而子進程終止。
當信號SIGCHLD遞交時,父進程阻塞于accept調用。函數sig_chld(信號處理程序)執
行,它調用函數wait取到子進程的PID和終止狀態,再調用printf.然后返回。
由于該信號是在父進程阻塞于慢系統調用(accept)時由父進程鋪獲的,所以內核將
使accept返回一個EINTE錯誤(被中斷的系統調用).而父進程不處理此錯誤,所以中
止。//有些系統。
--------------------------------------------------------------------------------
wait 和waitpid 函數
pid_t wait (int *statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);
二者均返回:進程ID為0成功,或-1出錯
如果沒有終止的子進程讓進程來調用wait,但有一個或多個正在執行的子進程,則阻塞
直到第一個現有子進程終止。
waitpid對等待哪個進程及是否阻塞給了我們更多的控制:參數pid讓指定想等待的進程ID,
值-1表示等待第一個終止的子進程。參數option指定附加選項。最常用的選項是wNOHANG,
它通知內核在沒有己終止子進程時不要阻塞。
調用wait并不足以防止出現僵尸進程:
5個信導都在信號處理程序執行之前產生,信號處理程序又只執行一次,因為unix 信號
一般是不排對的。更嚴重的是,此問題是不確定的,信號處理程序可能執行三次或四次。
在我們剛剛運行的例子中,客戶與 服務器在同一主機上,信號處理程序執行一次,留下
四個僵尸進程。但若我們在不同的主機 上運行客戶和服務器,信號處理程序一般執行兩
次:一次作為第一個產生的信號的結果,由 于另外4個信號在信號處理程序執行時發生,
所以處理程序一般情況下去再被調用一次,這 就留下了三個僵尸進程。但有時,可能依
賴于FIN到達服務器主機的時機,信號處理程序執 行三次或四次。
正確的解決辦法是調用watpid而不是wait.
在循環內調用waitpid取得了所有已終止子進程的狀態。
指定選項WNOHANG,它告訴waitpid在有末終止的子進程運行 時不要阻塞。
不能在循環中調用wait,因為沒有辦法防止wait在有未終止的子進程運行時阻塞。
--------------------------------------------------------------------------------
關于數據格式的問題
客戶和服務器之間傳遞文本
在本地轉換和處理文本,不論客戶和服務器主機的字節序如何,客戶和服務器都能工
作得很好。
在客戶與服務器之間傳遞二進制結構潛在的問題:
不同的實現以不同的格式存儲二進制數,最常用的方法便是大端格式與小端格式
不同的實現在存儲相同的C數據類型時可能不同。32bits/64bits?
不同的實現給結構打包的方式也是不同的,取決于所用數據類型的垃數及機器的對
齊限制,跨套接口來傳送二進制結構是很不明智的。
解決此數據格式問題常用方法
把所有的數值數據作為文本串來傳遞要以兩個主機有相同的字符集為基礎。
顯式定義所支持數據類型的二進制格式位效,大端或小端,在客戶與服務器之間以此
格式傳遞所有數據。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -