今天我帶來的是伺服器模型中的第一種,也是最基本最常用的一種模型–tcp併發伺服器,每個客戶乙個子程序。
先簡單介紹一下:tcp併發伺服器,每個客戶乙個子程序,也就是說併發伺服器呼叫fork派生乙個子程序來處理每個子程序,使得伺服器能夠同時為多個客戶服務,每個程序乙個客戶。客戶數目的唯一限制是作業系統對以其名義執行伺服器的使用者id能夠同時擁有多少子程序的限制。
具體到我們的需求,我們的客戶端傳送某個指令,服務端接收。如果符合服務端的要求,就將當時的時間發回給客戶端。需求很簡單,我們的著重點在伺服器的模型。
okay,來看**:
這是服務端的主**(serv.c):
#include "pub.h"
#define listenq 1024
void sig_child(int signo);
//serv
int main(int argc, char **argv)
if((listenfd = socket(af_inet, sock_stream, 0)) < 0)
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = af_inet;
servaddr.sin_port = htons(atoi(argv[1]));
servaddr.sin_addr.s_addr = htonl(inaddr_any);
//設定可重用
setsockopt(listenfd, sol_socket, so_reuseaddr, &on, sizeof(on));
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
if(listen(listenfd, listenq) < 0)
//訊號處理函式
//這裡主要是處理僵死程序
signal(sigchld, sig_child); //每乙個子程序終止時就會產生sigchld訊號,預設是忽略
for( ; ; )
}ptr = inet_ntoa(cliaddr.sin_addr);不用取位址
fprintf(stdout,"%s has connected\n", ptr);
if((childpid = fork()) == 0)//child
close(connfd);//父程序關閉connfd
}close(listenfd);
exit(0);
}void sig_child(int signo)
return;
}
看這小段連線的**:
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
}ptr = inet_ntoa(cliaddr.sin_addr);不用取位址
fprintf(stdout,"%s has connected\n", ptr);
注意我們這裡的大迴圈,每次迴圈的開始都是accept,也就是從已連線的佇列中返回其中乙個,如果返回成功,就列印一下ip,顯示是哪個ip來連線。
這裡採用的是inet_ntoa()函式,這是個專門為ipv4準備的函式。你也可以使用inet_pton()函式,這是ipv4和ipv6通用的函式。
再看下面的一小段:
if((childpid = fork()) == 0)//child
這裡就是真正的fork出乙個子程序。
先close(listenfd),注意這裡close並不會真正的關閉listenfd,這只是減少了listenfd的乙個引用次數,父程序還有乙個引用。
然後是do_child(connfd),這裡是我們自定義的來處理連線的函式,注意已經將connfd傳遞給這個函式。稍後我們在討論一下這個函式。
如果能從do_child()函式返回,就再列印一下,某乙個ip 已經離開。
最後就是close(connfd),再exit(0)退出,這裡的close(connfd),可以省略,子程序退出,就會關掉自身所開啟的描述符。
注意到我們這裡還有一訊號處理函式,sig_child,如下所示:
void sig_child(int signo)
return;
}
我們在之前已經註冊了這個訊號處理函式,
//訊號處理函式
signal(sigchld, sig_child); //每乙個子程序終止時就會產生sigchld訊號,預設是忽略
這裡我們的處理函式,就是列印一下離開的子程序的pid,就如同注釋所示,採用這種形式:
while((pid = waitpid(-1, &stat, wnohang)) > 0) 可以防止同時有幾個子程序killed,加while,知道處理完所有killed child
到了這裡,伺服器主要的模型已經展示出來了,現在我們來看主要的處理客戶端連線的do_child()函式。
來看**:
#include "pub.h"
void do_child(int sockfd)
else
}else
else}}
}
這裡採用的處理很簡單,就如同我注釋中講的,比較前幾個字串,是不是gettime或者gettime,是則返回時間,否則返回 gettime command error
看這裡:
mytime = time(null);
snprintf(buff, sizeof(buff), "%s", ctime(&mytime));
writen(sockfd, buff, strlen(buff));//這裡最好用writen(自定義)
通過time()和ctime()用字串的形式列印出當前時間。注意這裡採用的是writen()函式,自定義函式。
其實就是write相當數目的buff,同時防止訊號打斷
來看writen函式的**:
#include "pub.h"
int writen(int sockfd,const
char *buff, int n)
nleft -= ncount;
ptr += ncount;
}return n - nleft;
}
接下來,我們寫了乙個測試用的客戶端(client.c):
來看**:
#include "pub.h"
//client
int main(int argc, char **argv)
if((sockfd = socket(af_inet, sock_stream, 0)) < 0)
memset(&servaddr, 0,sizeof(servaddr));
servaddr.sin_family = af_inet;
servaddr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &servaddr.sin_addr);
if((connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)
strcli(sockfd);
exit(0);
}
這裡就是簡單的客戶端**,通過引數傳進來要連線的ip與port,傳送請求的函式是str_cli()。
來看str_cli函式:
#include "pub.h"
void strcli(int sockfd)
}
這裡其實就是讀取標準輸入,傳送給服務端,並read阻塞,等服務端傳送回後,將服務端傳送回的資料列印到標準輸出上。
另外,還有乙個註冊訊號處理函式signal函式,之前講過,但沒有給出**。
我這裡直接將unix網路程式設計裡的signal拿了過來,可以直接使用。
也來看一下,signal.c:
/* include signal */
#include "pub.h"
sigfunc *
signal(int signo, sigfunc *func)
else
if (sigaction(signo, &act, &oact) < 0)
return(sig_err);
return(oact.sa_handler);
}/* end signal */
sigfunc *
signal(int signo, sigfunc *func) /* for our signal() function */
return(sigfunc);
}
至此,我們的第一種伺服器模型就已經完成了。全部的**都已經曬了出來,並經過測試。你可以在你自己的linux上試試,看行不行。
另外,我們這裡就是簡單的客戶端與服務端查詢時間的處理請求。你也可以進行別的處理請求。比如可以雙方通訊,採用多執行緒,或者試試傳送檔案,都可以。just up to you.而且,你只需要修改do_child()和str_cli()函式,其他的可以不修改,除非你有別的需求。
最後,還得講一下這種模型的問題。主要就是為每乙個客戶fork乙個子程序比較消耗cpu時間,幾百或幾千的的客戶是沒有問題的,但是現在的每天的tcp連線都是上百萬的,這裡就會有問題。當然,如果系統負載較輕,這種模型是不錯好的選擇。
TCP併發伺服器模型(一)
本篇敘述的tcp併發伺服器模型如下圖所示 伺服器建立並繫結套接字後fork出幾個子程序,子程序中分別進行accept 該函式為阻塞函式 recv 處理資料然後再次acept,這樣迴圈下去。所有客戶端發來的資訊都是直接由子程序處理。例程 如下,在處理客戶端請求之前,伺服器先fork了3個子程序,然後將...
Erlang 建立乙個簡單的TCP伺服器
一 建立 tcpserver.erl module tcpserver export start 0 start 建立乙個埠2345用於監聽來自客戶端的請求,是資訊表頭用4位元組表示長度,需要與客戶端一致 gen tcp listen 2345,binary,開始接受乙個請求 gen tcp acc...
Erlang之乙個簡單的TCP伺服器
簡單tcp伺服器 module my socket server export start 0,loop 1 start gen tcp listen 2345,binary,自定義打包規則,生成乙個監聽2345埠的socket gen tcp accept listen 生存乙個新的socket ...