TCP併發伺服器,每個客戶乙個子程序

2021-08-05 21:29:49 字數 4840 閱讀 9322

今天我帶來的是伺服器模型中的第一種,也是最基本最常用的一種模型–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 ...