目錄
前言
1.存在的問題
2.多進程版
3.多線程版
4.線程池版
總結
前言
? ? ? ? 在上一篇文章中使用TCP協議實現了一個簡單的服務器,可以用來服務端和客戶端通信,但是之前的服務器存在一個問題,就是當有多個客戶端連接服務器的時候,服務器只能和一個客戶端通信,其它的客戶端是無法通信的,這是為什么呢?有該如何解決呢?將在這篇文章中為大家介紹
1.存在的問題
如圖所示:
?為什么會存在這樣的問題呢?
如上圖所示,之前我們實現的服務器是單進程版的,所以當第一次和客戶端建立連接完成之后,就死循環處理讀取信息的邏輯了,所以就無法再和新的客戶端建立連接了 。找到問題之后,很明顯解決方式就是將建立連接和通信分開執行,此時我們就可以使用多進程和多線程來解決了,下面我們就具體來實現以下如何使用多進程和多線程。
2.多進程版
實現思路:與客戶端建立連接完成,fork創建子進程,讓父進程繼續建立連接,讓子進程實現后續的數據通信。
這樣實現存在的問題:當父進程創建完子進程之后,需要使用waitpid回收子進程的資源,否則子進程就會變為僵尸進程,導致資源泄漏,但是使用waitpid回收子進程資源,程序變為串行化執行了,就無法實現之前的需求,讓父進程負責建立連接,子進程負責數據通信了。
解決方式有兩種:
1.fork創建子進程,在子進程內部再fork創建子進程,然后讓父進程直接退出,此時之前的子進程作為父進程退出,新創建的子進程就變為孤兒進程被操作系統領養,并不會造成資源泄漏,并且讓該子進程負責通信
2.因為子進程退出之后操作系統會發送一個SIGCHLD信號,所以可以使用signal函數捕捉SIGCHLD信號,將默認處理動作設置為SIG_IGN,在這個默認動作里會回收子進程的資源,并不會造成資源泄漏,并且讓該子進程負責通信
思路1代碼:
pid_t id = fork();
if(id == 0)//child
{close(_sock);if(fork() > 0)exit(0);serviceIO(sock);close(sock);exit(0);
}
close(sock);
waitpid(id,nullptr,0);
思路2代碼:
signal(SIGCHLD,SIG_IGN);
if(id == 0)//child
{// 子進程會繼承父進程的文件描述符表,當子進程不需要時進行關閉,// 防止子進程文件描述符資源泄露close(_sock);serviceIO(sock);close(sock);exit(0);
}
運行截圖:
[myl@VM-8-12-centos tcp]$ ./tcpServer 8080
create socket success
bind socket success
listen socket success
accept a new link success
sock: 4
accept a new link success
sock: 4
recvice message: 你好,我是客戶端1
recvice message: 你好,我是客戶端2
此時就實現了一個客戶端可以被多個服務端連接并且實現通信。
3.多線程版
說明:相比于多進程,多線程的創建和銷毀對操作系統是更輕量的,消耗的資源也是更少的,所以實現數據通信可以采用多線程的方式,讓主線程負責建立,讓從線程負責數據通信
代碼實現:
class TcpServerData
{
public:TcpServerData(TcpServer* self,int sock):_self(self),_sock(sock) {}
public:TcpServer* _self;int _sock;
};
cout << "我是主線程" << endl;
pthread_t tid;
TcpServerData* tsd = new TcpServerData(this,sock);
pthread_create(&tid,nullptr,start_routine,tsd);//因為是類內成員函數,必須包含this指針,但是start_routine作為參數是沒有this指針的
//所以start_routine函數必須加上static,靜態成員方法是不能訪問類內成員的,所以參數傳遞this
//調用serviceIO,但是serviceIO函數需要傳遞參數sock,所以可以封裝一個結構體,在結構體中包含成員
//屬性sock和this
static void* start_routine(void* args) {
//設置與主線程分離,此時主線程不需要等待從線程退出了,而是繼續建立連接
cout << "我是從線程" << endl;
pthread_detach(pthread_self());
TcpServerData* t = static_cast<TcpServerData*>(args);
t->_self->serviceIO(t->_sock);
close(t->_sock);
delete t;
return nullptr;
運行截圖:
[myl@VM-8-12-centos tcp]$ ./tcpServer 8080
create socket success
bind socket success
listen socket success
accept a new link success
sock: 4
我是主線程
我是從線程
recvice message: 你好,我是客戶端1
accept a new link success
sock: 5
我是主線程
我是從線程
recvice message: 你好,我是客戶端2
4.線程池版
說明:線程池版的實現思路是基于多線程,雖然多線程創建和銷毀的消耗比多進程的低,但是為了更進一步提升效率,可以預先創建好一批線程,主線程負責建立連接獲取任務,然后將任務加入到隊列中,讓預先創建好的線程從隊列中獲取任務,然后處理獲取到的任務。
代碼實現:
void start()
{//線程池初始化:預先創建好一批線程:ThreadPool<Task>::getInstance()->run();for (;;){// 建立連接:struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_sock, (struct sockaddr *)&peer, &len); if (sock < 0){logMessage(ERROR, "accept error, next");continue;}logMessage(NORMAL, "accept a new link success");std::cout << "sock: " << sock << std::endl;//未來通信全部用sock,面向字節流的,后續全部都是文件操作:ThreadPool<Task>::getInstance()->push(Task(sock,serviceIO));}
}
運行截圖:
[myl@VM-8-12-centos tcp]$ ./tcpServer 8080
create socket success
bind socket success
listen socket success
thread-1 start ...
thread-2 start ...
thread-3 start ...
thread-4 start ...
thread-5 start ...
thread-6 start ...
thread-7 start ...
thread-8 start ...
thread-9 start ...
thread-10 start ...
accept a new link success
sock: 4
accept a new link success
sock: 5
recv message: 你好,我是客戶端1
recv message: 你好,我是客戶端2
注:關于線程池詳細的設計與實現可以觀看線程池這篇文章,里面有相信的代碼實現
總結
? ? ? ? 以上就是關于TCP服務器實現多進程版,多線程版,線程池版的詳細介紹,可以通過這篇文章發現之前在系統中學習的知識在網絡中全部結合起來了,今天的介紹就到這里了,感謝大家的閱讀,我們下次再見!