日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
「探索Linux網(wǎng)絡(luò)編程:socket多線程」(linuxsocket多線程)

探索 Linux 網(wǎng)絡(luò)編程:socket 多線程

創(chuàng)新互聯(lián)從2013年創(chuàng)立,先為公主嶺等服務(wù)建站,公主嶺等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為公主嶺企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

Linux 是基于開(kāi)源的操作系統(tǒng),它的網(wǎng)絡(luò)編程能力非常強(qiáng)大。為了更好地理解 Linux 網(wǎng)絡(luò)編程,我們需要探索 Socket 多線程技術(shù),這種技術(shù)可以提高網(wǎng)絡(luò)應(yīng)用的性能和并發(fā)連接的能力。

今天,本文將介紹 Socket 多線程的工作原理和實(shí)現(xiàn)方法,在實(shí)際項(xiàng)目中如何運(yùn)用 Socket 多線程技術(shù),以及 Socket 多線程的優(yōu)勢(shì)和限制。

什么是 Socket 多線程?

Socket 是一種網(wǎng)絡(luò)通信協(xié)議,可以用于實(shí)現(xiàn)客戶端和服務(wù)器之間的通信。我們可以使用 Linux 的 Socket 編程接口進(jìn)行網(wǎng)絡(luò)編程。多線程是一種并發(fā)編程方式,我們可以通過(guò)創(chuàng)建多個(gè)線程來(lái)并發(fā)執(zhí)行任務(wù),從而提高應(yīng)用程序的性能和效率。

因此,Socket 多線程是指在 Linux 環(huán)境下,通過(guò)創(chuàng)建多個(gè)線程來(lái)實(shí)現(xiàn) Socket 通信的并發(fā)連接能力,以提高網(wǎng)絡(luò)應(yīng)用的性能和效率。

Socket 多線程的工作原理

Socket 多線程的工作原理主要分為兩個(gè)階段:初始化和執(zhí)行。

初始化階段:

1.創(chuàng)建 Socket:在服務(wù)器端創(chuàng)建一個(gè) Socket,并監(jiān)聽(tīng)指定端口,等待客戶端連接。

2.創(chuàng)建線程池:在服務(wù)器端創(chuàng)建一個(gè)線程池,用于存放多個(gè)工作線程。

3.創(chuàng)建工作線程:在線程池中創(chuàng)建多個(gè)工作線程,等待任務(wù)分配。

4.等待客戶端連接:服務(wù)器進(jìn)入等待狀態(tài),等待客戶端連接。

執(zhí)行階段:

1.客戶端連接:當(dāng)一個(gè)客戶端連接到服務(wù)器端時(shí),服務(wù)器接受連接請(qǐng)求,并將客戶端 Socket 指派到一個(gè)工作線程。

2.工作線程處理任務(wù):工作線程從任務(wù)隊(duì)列中獲取任務(wù),并執(zhí)行任務(wù),處理客戶端請(qǐng)求。

3.任務(wù)完成:當(dāng)工作線程完成任務(wù)后,將任務(wù)返回給線程池,并等待下一個(gè)任務(wù)分配。

4.斷開(kāi)連接:當(dāng)客戶端下線時(shí),服務(wù)器端斷開(kāi)連接,釋放資源。

Socket 多線程的實(shí)現(xiàn)方法

在 Linux 環(huán)境下,我們可以使用 C/C++ 等編程語(yǔ)言實(shí)現(xiàn) Socket 多線程。下面是一個(gè)基于 C++ 實(shí)現(xiàn) Socket 多線程的示例:

“`c++

class Thread {

public:

Thread();

virtual ~Thread();

virtual void Run();

void Start();

void Stop();

void Join();

bool IsRunning() const;

pthread_t GetThreadId() const;

protected:

bool m_running;

pthread_t m_threadId;

static void* StartThread(void* arg);

};

class ThreadPool {

public:

ThreadPool(int size);

~ThreadPool();

void AddTask(Task* task);

private:

int m_size;

bool m_stop;

pthread_mutex_t m_mutex;

pthread_cond_t m_cond;

std::vector m_tasks;

std::vector m_threads;

void Initialize();

void Finalize();

void CreateThreads();

void DestroyThreads();

};

class Task {

public:

virtual void Run() = 0;

};

class SocketTask : public Task {

public:

SocketTask(int sockfd);

virtual ~SocketTask();

virtual void Run();

private:

int m_sockfd;

};

“`

上述代碼中,我們定義了三個(gè)類(lèi):Thread、Task 和 ThreadPool。Thread 類(lèi)是一個(gè)封裝了 POSIX 線程的類(lèi),它可以啟動(dòng)一個(gè)新線程,并執(zhí)行線程回調(diào)函數(shù);Task 類(lèi)是一個(gè)封裝了執(zhí)行任務(wù)的類(lèi),它必須繼承 Run() 函數(shù);ThreadPool 類(lèi)是一個(gè)封裝了一個(gè)線程池的類(lèi),它可以添加任務(wù)、初始化線程池、創(chuàng)建多個(gè)工作線程等。

在實(shí)際項(xiàng)目中如何運(yùn)用 Socket 多線程技術(shù)

在實(shí)際項(xiàng)目中,我們可以通過(guò)以下步驟來(lái)運(yùn)用 Socket 多線程技術(shù):

1.在服務(wù)器端創(chuàng)建 Socket,并監(jiān)聽(tīng)指定端口,等待客戶端連接。

2.當(dāng)客戶端連接服務(wù)器時(shí),將客戶端 Socket 分配給一個(gè)工作線程。

3.工作線程從任務(wù)隊(duì)列中獲取任務(wù),并執(zhí)行任務(wù),處理客戶端請(qǐng)求。

4.當(dāng)客戶端下線時(shí),服務(wù)器端斷開(kāi)連接,釋放資源。

通過(guò) Socket 多線程技術(shù),我們可以提高服務(wù)器端的并發(fā)處理能力,加快網(wǎng)絡(luò)應(yīng)用的響應(yīng)速度,同時(shí)提高系統(tǒng)的可靠性和穩(wěn)定性。

Socket 多線程的優(yōu)勢(shì)和限制

Socket 多線程技術(shù)的優(yōu)勢(shì)和限制如下:

優(yōu)勢(shì):

1.提高應(yīng)用程序的性能和效率:多線程可以并發(fā)執(zhí)行任務(wù),提高應(yīng)用程序的性能和效率,減少響應(yīng)時(shí)間。

2.提高并發(fā)連接能力:通過(guò)多線程技術(shù),服務(wù)器可以處理多個(gè)連接,提高應(yīng)用程序的并發(fā)連接能力。

3.實(shí)現(xiàn)靈活:線程池可以動(dòng)態(tài)管理線程,保證了應(yīng)用程序的靈活性。

限制:

1.線程安全問(wèn)題:由于多個(gè)線程同時(shí)操作共享數(shù)據(jù),可能會(huì)產(chǎn)生線程安全問(wèn)題。

2.資源占用問(wèn)題:多線程需要占用更多的系統(tǒng)資源,包括 CPU、內(nèi)存資源等,可能會(huì)導(dǎo)致系統(tǒng)資源緊張。

3.實(shí)現(xiàn)復(fù)雜:線程池需要對(duì)多個(gè)線程進(jìn)行管理,實(shí)現(xiàn)起來(lái)較為復(fù)雜。

成都網(wǎng)站建設(shè)公司-創(chuàng)新互聯(lián)為您提供網(wǎng)站建設(shè)、網(wǎng)站制作、網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù)!

socket編程在windows和linux下的區(qū)別

下面大概分幾個(gè)方面進(jìn)行羅列:

  Linux要包含

  

  #include

  #include

  #include

  #include

  等頭文件,而windows下則是包含

  

  #include

  。

  Linux中socket為整形,Windows中為一個(gè)SOCKET。

  Linux中關(guān)閉socket為close,Windows中為closesocket。

  Linux中有變量socklen_t,Windows中直接為int。

  因?yàn)閘inux中的socket與普通的fd一樣,所以可以在TCP的socket中,發(fā)送與接收數(shù)據(jù)時(shí),直接使用read和write。而windows只能使用recv和send。

  設(shè)置socet選項(xiàng),比如設(shè)置socket為非阻塞的。Linux下為

  

  flag = fcntl (fd, F_GETFL);

  fcntl (fd, F_SETFL, flag | O_NONBLOCK);

  ,Windows下為

  

  flag = 1;

  ioctlsocket (fd, FIONBIO, (unsigned long *) &flag);

  。

  當(dāng)非阻塞socket的TCP連接正在進(jìn)行時(shí),Linux的錯(cuò)誤號(hào)為EINPROGRESS,Windows的錯(cuò)誤號(hào)為WSAEWOULDBLOCK。

  file

  Linux下面,文件換行是春攜仔”\n”,而windows下面是”\r\n”。

  Linux下面,目錄分隔符是”/”,而windows下面是”\”。

  Linux與Windows下面,均可以使用stat調(diào)用來(lái)查詢文件信息。但是,Linux只支持2G大小,而Windows只支持4G大小。為了支持更大的文件查詢,可以在隱猜Linux環(huán)境下加

  _FILE_OFFSET_BITS=64定義,在Windows下面使用_stat64調(diào)用,入?yún)閟truct __stat64。

  Linux中可根據(jù)stat的st_mode判斷文件類(lèi)型,有S_ISREG、S_ISDIR等宏。Windows中沒(méi)有,需要自己定義相應(yīng)的宏,如

  

  #define S_ISREG(m) (((m) &) == ())

  #define S_ISDIR(m) (((m) &) == ())

  Linux中刪除文件是unlink,Windows中為DeleteFile。

  time

  Linux中,time_t結(jié)構(gòu)是長(zhǎng)整形。而windows中,time_t結(jié)構(gòu)是64位的整形。如果要在windows始time_t為32位無(wú)符號(hào)整形,可以加宏定義,_USE_32BIT_TIME_T。

  Linux中,sleep的單位為秒。Windows中,Sleep的單位為毫秒。即,Linux下sleep (1),在Windows環(huán)境下則需要Sleep (1000)。

  Windows中的timecmp宏,不支持大于等于或者小于等于。

  Windows中沒(méi)有struct timeval結(jié)構(gòu)的加減宏可以使用,需要手動(dòng)定義:

  

  #define MICROSECONDS (1000 * 1000)

  

  #define timeradd(t1, t2, t3) do { \

  (t3)->tv_sec = (t1)->tv_sec + (t2)->tv_sec; \

  (t3)->tv_usec = (t1)->tv_usec + (t2)->tv_usec % MICROSECONDS;\

  if ((t1)->tv_usec + (t2)->tv_usec > MICROSECONDS) (t3)->tv_sec ++;\

 扒汪 } while (0)

  

  #define timersub(t1, t2, t3) do { \

  (t3)->tv_sec = (t1)->tv_sec – (t2)->tv_sec; \

  (t3)->tv_usec = (t1)->tv_usec – (t2)->tv_usec; \

  if ((t1)->tv_usec – (t2)->tv_usec tv_usec –, (t3)->tv_usec += MICROSECONDS; \

  } while (0)

  調(diào)用進(jìn)程

  Linux下可以直接使用system來(lái)調(diào)用外部程序。Windows更好使用WinExec,因?yàn)閃inExec可以支持是打開(kāi)還是隱藏程序窗口。用WinExec的第二個(gè)入?yún)⒅该?,?/p>

  SW_SHOW/SW_HIDE。

  雜項(xiàng)

  Linux為srandom和random函數(shù),Windows為srand和rand函數(shù)。

  Linux為snprintf,Windows為_(kāi)snprintf。

  同理,Linux中的strcasecmp,Windows為_(kāi)stricmp。

  錯(cuò)誤處理

  Linux下面,通常使用全局變量errno來(lái)表示函數(shù)執(zhí)行的錯(cuò)誤號(hào)。Windows下要使用GetLastError ()調(diào)用來(lái)取得。

  Linux環(huán)境下僅有的

  這些函數(shù)或者宏,Windows中完全沒(méi)有,需要用戶手動(dòng)實(shí)現(xiàn)。

  atoll

  

  long long

  atoll (const char *p)

  {

  int minus = 0;

  long long value = 0;

  if (*p == ‘-‘)

  {

  minus ++;

  p ++;

  }

  while (*p >= ‘0’ && *p tv_sec = (long) (t /);

  tv->tv_usec = (long) (t %);

  }

  

  if (tz)

  {

  if (!tzflag)

  {

  _tzset ();

  tzflag++;

  }

  tz->tz_minuteswest = _timezone / 60;

  tz->tz_dsttime = _daylight;

  }

  

  return 0;

  }

  編譯相關(guān)

  當(dāng)前函數(shù),Linux用__FUNCTION__表示,Windows用__func__表示。

  Socket 編程 windows到Linux代碼移植遇到的問(wèn)題

  1)頭文件

  windows下winsock.h/winsock2.h

  linux下sys/socket.h

  錯(cuò)誤處理:errno.h

  2)初始化

  windows下需要用WSAStartup

  linux下不需要

  3)關(guān)閉socket

  windows下closesocket(…)

  linux下close(…)

  4)類(lèi)型

  windows下SOCKET

  linux下int

  如我用到的一些宏:

  #ifdef WIN32

  typedef int socklen_t;

  typedef int ssize_t;

  #endif

  #ifdef __LINUX__

  typedef int SOCKET;

  typedef unsigned char BYTE;

  typedef unsigned long DWORD;

  #define FALSE 0

  #define SOCKET_ERROR (-1)

  #endif

  5)獲取錯(cuò)誤碼

  windows下getlasterror()/WSAGetLastError()

  linux下errno變量

  6)設(shè)置非阻塞

  windows下ioctlsocket()

  linux下fcntl()

  7)send函數(shù)最后一個(gè)參數(shù)

  windows下一般設(shè)置為0

  linux下更好設(shè)置為MSG_NOSIGNAL,如果不設(shè)置,在發(fā)送出錯(cuò)后有可 能會(huì)導(dǎo)致程序退出。

  8)毫秒級(jí)時(shí)間獲取

  windows下GetTickCount()

  linux下gettimeofday()

  3、多線程

  多線程: (win)process.h –〉(linux)pthread.h

  _beginthread –> pthread_create

  _endthread –> pthread_exit

  windows與linux平臺(tái)使用的socket均繼承自Berkeley socket(rfc3493),他們都支持select I/O模型,均支持使用getaddrinfo與getnameinfo實(shí)現(xiàn)協(xié)議無(wú)關(guān)編程。但存在細(xì)微差別,

  主要有:

  頭文件及類(lèi)庫(kù)。windows使用winsock2.h(需要在windows.h前包含),并要鏈接庫(kù)ws2_32.lib;linux使用netinet/in.h, netdb.h等。

  windows下在使用socket之前與之后要分別使用WSAStartup與WSAClean。

  關(guān)閉socket,windows使用closesocket,linux使用close。

  send*與recv*函數(shù)參數(shù)之socket長(zhǎng)度的類(lèi)型,windows為int,linux為socklen_t,可預(yù)編譯指令中處理這一差異,當(dāng)平臺(tái)為windows時(shí)#define socklen_t unsigned int。

  select函數(shù)之一個(gè)參數(shù),windows忽略該參數(shù),linux下該參數(shù)表示中socket的上限值,一般設(shè)為sockfd(需select的socket) + 1。

  windows下socket函數(shù)返回值類(lèi)型為SOCKET(unsigned int),其中發(fā)生錯(cuò)誤時(shí)返回INVALID_SOCKET(0),linux下socket函數(shù)返回值類(lèi)型int, 發(fā)生錯(cuò)誤時(shí)返回-1。

  另外,如果綁定本機(jī)回環(huán)地址,windows下sendto函數(shù)可以通過(guò),linux下sendto回報(bào)錯(cuò):errno=22, Invalid arguement。一般情況下均綁定通配地址。

轉(zhuǎn)載jlins

下面大概分幾個(gè)方面進(jìn)行羅列:

Linux要包含

#include

#include

#include

#include

等頭文件,而windows下則是包含

#include

。

Linux中socket為整形,Windows中為一個(gè)SOCKET。

Linux中關(guān)閉socket為close,Windows中為closesocket。

Linux中有變量socklen_t,Windows中直接為int。

因?yàn)閘inux中的socket與普通的fd一樣,所以可以在TCP的socket中,發(fā)送與接收數(shù)據(jù)時(shí),直接使用read和write。而windows只能使用recv和send。

設(shè)置socet選項(xiàng),比如設(shè)置socket為非阻塞的。Linux下為

flag = fcntl (fd, F_GETFL);

fcntl (fd, F_SETFL, flag | O_NONBLOCK);

,Windows下為

flag = 1;

ioctlsocket (fd, FIONBIO, (unsigned long *) &flag);

。

當(dāng)非阻塞socket的TCP連接正在進(jìn)行時(shí),Linux的錯(cuò)誤號(hào)豎笑為EINPROGRESS,Windows的錯(cuò)誤號(hào)為WSAEWOULDBLOCK。

file

Linux下面,文件換行是”\n”,而windows下面是”\r\n”。

Linux下面,目錄分隔符是”/”,而windows下面是”\”。

Linux與Windows下面,均可以使用stat調(diào)用來(lái)查詢文件信息。但是,Linux只支持2G大小,而Windows只支持4G大小。為了支持更大的文件查詢,可以在Linux環(huán)境下加

_FILE_OFFSET_BITS=64定義,在Windows下面使用_stat64調(diào)用,入?yún)閟truct __stat64。

Linux中可根據(jù)stat的st_mode判斷文件類(lèi)型,有S_ISREG、S_ISDIR等宏。罩圓Windows中沒(méi)有,需要自己定義相應(yīng)的宏,如

#define S_ISREG(m) (((m) &) == ())

#define S_ISDIR(m) (((m) &) == ())

Linux中刪除文件是unlink,Windows中為DeleteFile。

time

Linux中,time_t結(jié)構(gòu)是長(zhǎng)整形。而windows中,time_t結(jié)構(gòu)是64位的整形。如果要在windows始time_t為32位無(wú)符號(hào)整形,可以加宏定義,_USE_32BIT_TIME_T。

Linux中,sleep的單位為秒。Windows中,Sleep的單位為毫秒。即,Linux下sleep (1),在Windows環(huán)境下則需要Sleep (1000)。

Windows中的timecmp宏,不支持大于等于或者余悶含小于等于。

Windows中沒(méi)有struct timeval結(jié)構(gòu)的加減宏可以使用,需要手動(dòng)定義:

#define MICROSECONDS (1000 * 1000)

#define timeradd(t1, t2, t3) do { \

(t3)->tv_sec = (t1)->tv_sec + (t2)->tv_sec; \

(t3)->tv_usec = (t1)->tv_usec + (t2)->tv_usec % MICROSECONDS;\

if ((t1)->tv_usec + (t2)->tv_usec > MICROSECONDS) (t3)->tv_sec ++;\

} while (0)

#define timersub(t1, t2, t3) do { \

(t3)->tv_sec = (t1)->tv_sec – (t2)->tv_sec; \

(t3)->tv_usec = (t1)->tv_usec – (t2)->tv_usec; \

if ((t1)->tv_usec – (t2)->tv_usec tv_usec –, (t3)->tv_usec += MICROSECONDS; \

} while (0)

調(diào)用進(jìn)程

Linux下可以直接使用system來(lái)調(diào)用外部程序。Windows更好使用WinExec,因?yàn)閃inExec可以支持是打開(kāi)還是隱藏程序窗口。用WinExec的第二個(gè)入?yún)⒅该鳎?/p>

SW_SHOW/SW_HIDE。

雜項(xiàng)

Linux為srandom和random函數(shù),Windows為srand和rand函數(shù)。

Linux為snprintf,Windows為_(kāi)snprintf。

同理,Linux中的strcasecmp,Windows為_(kāi)stricmp。

錯(cuò)誤處理

Linux下面,通常使用全局變量errno來(lái)表示函數(shù)執(zhí)行的錯(cuò)誤號(hào)。Windows下要使用GetLastError ()調(diào)用來(lái)取得。

Linux環(huán)境下僅有的

這些函數(shù)或者宏,Windows中完全沒(méi)有,需要用戶手動(dòng)實(shí)現(xiàn)。

atoll

long long

atoll (const char *p)

{

int minus = 0;

long long value = 0;

if (*p == ‘-‘)

{

minus ++;

p ++;

}

while (*p >= ‘0’ && *p tv_sec = (long) (t /);

tv->tv_usec = (long) (t %);

}

if (tz)

{

if (,tzflag)

{

_tzset ();

tzflag++;

}

tz->tz_minuteswest = _timezone / 60;

tz->tz_dsttime = _daylight;

}

return 0;

}

編譯相關(guān)

當(dāng)前函數(shù),Linux用__FUNCTION__表示,Windows用__func__表示。

Socket 編程 windows到Linux代碼移植遇到的問(wèn)題

1)頭文件

windows下winsock.h/winsock2.h

linux下sys/socket.h

錯(cuò)誤處理:errno.h

2)初始化

windows下需要用WSAStartup

linux下不需要

3)關(guān)閉socket

windows下closesocket(…)

linux下close(…)

4)類(lèi)型

windows下SOCKET

linux下int

如我用到的一些宏:

#ifdef WIN32

typedef int socklen_t;

typedef int ssize_t;

#endif

#ifdef __LINUX__

typedef int SOCKET;

typedef unsigned char BYTE;

typedef unsigned long DWORD;

#define FALSE 0

#define SOCKET_ERROR (-1)

#endif

5)獲取錯(cuò)誤碼

windows下getlasterror()/WSAGetLastError()

linux下errno變量

6)設(shè)置非阻塞

windows下ioctlsocket()

linux下fcntl()

7)send函數(shù)最后一個(gè)參數(shù)

windows下一般設(shè)置為0

linux下更好設(shè)置為MSG_NOSIGNAL,如果不設(shè)置,在發(fā)送出錯(cuò)后有可 能會(huì)導(dǎo)致程序退出。

8)毫秒級(jí)時(shí)間獲取

windows下GetTickCount()

linux下gettimeofday()

3、多線程

多線程: (win)process.h –〉(linux)pthread.h

_beginthread –> pthread_create

_endthread –> pthread_exit

windows與linux平臺(tái)使用的socket均繼承自Berkeley socket(rfc3493),他們都支持select I/O模型,均支持使用getaddrinfo與getnameinfo實(shí)現(xiàn)協(xié)議無(wú)關(guān)編程。但存在細(xì)微差別,

主要有:

頭文件及類(lèi)庫(kù)。windows使用winsock2.h(需要在windows.h前包含),并要鏈接庫(kù)ws2_32.lib;linux使用netinet/in.h, netdb.h等。

windows下在使用socket之前與之后要分別使用WSAStartup與WSAClean。

關(guān)閉socket,windows使用closesocket,linux使用close。

send*與recv*函數(shù)參數(shù)之socket長(zhǎng)度的類(lèi)型,windows為int,linux為socklen_t,可預(yù)編譯指令中處理這一差異,當(dāng)平臺(tái)為windows時(shí)#define socklen_t unsigned int。

select函數(shù)之一個(gè)參數(shù),windows忽略該參數(shù),linux下該參數(shù)表示中socket的上限值,一般設(shè)為sockfd(需select的socket) + 1。

windows下socket函數(shù)返回值類(lèi)型為SOCKET(unsigned int),其中發(fā)生錯(cuò)誤時(shí)返回INVALID_SOCKET(0),linux下socket函數(shù)返回值類(lèi)型int, 發(fā)生錯(cuò)誤時(shí)返回-1。

另外,如果綁定本機(jī)回環(huán)地址,windows下sendto函數(shù)可以通過(guò),linux下sendto回報(bào)錯(cuò):errno=22, Invalid arguement。一般情況下均綁定通配地址。

如何看懂《Linux多線程服務(wù)端編程

一:進(jìn)程和線程

每個(gè)進(jìn)程有自己獨(dú)立的地址空間。“在同一個(gè)進(jìn)程”還是“不在同一個(gè)進(jìn)程”是系統(tǒng)功能劃分的重要決策點(diǎn)。《Erlang程序設(shè)計(jì)》把進(jìn)程比喻為人:

每個(gè)人有自己的記憶(內(nèi)存),人與人通過(guò)談話(消息傳遞)來(lái)交流,談話既可以是面談姿并野(同一臺(tái)服務(wù)器),也可以在里談(不同的服務(wù)器,有網(wǎng)絡(luò)通信)。面談和談的區(qū)別在于,面談可以立即知道對(duì)方是否死了(crash,SIGCHLD),而談只能通過(guò)周期性的心跳來(lái)判斷對(duì)方是否還活著。

有了這些比喻,設(shè)計(jì)分布式系統(tǒng)時(shí)可以采取“角色扮演”,團(tuán)隊(duì)里的幾個(gè)人各自扮演一個(gè)進(jìn)程,人的角色由進(jìn)程的代碼決定(管登錄的、管消息分發(fā)的、管買(mǎi)賣(mài)的等等)。每個(gè)人有自己的記憶,但不知道別人的記憶,要想知道別人的看法,只能通過(guò)交談(暫不考慮共享內(nèi)存這種IPC)。然后就可以思考:跡喊

·容錯(cuò):萬(wàn)一有人突然死了

·擴(kuò)容:新人中途加進(jìn)來(lái)

·負(fù)載均衡:把甲的活兒挪給乙做

·退休:甲要修復(fù)bug,先別派新任務(wù),等他做完手上的事情就把他重啟

等等各種場(chǎng)景,十分便利。

線程的特點(diǎn)是共享地址空間,從而可以高效地共享數(shù)據(jù)。一臺(tái)機(jī)器上的多個(gè)進(jìn)程能高效地共享代碼段(操作系統(tǒng)可以映射為同樣的物理內(nèi)存),但不能共享數(shù)據(jù)。如果多個(gè)進(jìn)程大量共享內(nèi)存,等于是把多進(jìn)程程序當(dāng)成多線程來(lái)寫(xiě),掩耳盜鈴。

“多線程”的價(jià)值,我認(rèn)為是為了更好地發(fā)揮多核處理器(multi-cores)的效能。在單核時(shí)代,多線程沒(méi)有多大價(jià)值(個(gè)人想法:如果要完成的任務(wù)是CPU密集型的,那多線程沒(méi)有優(yōu)勢(shì),甚至因?yàn)榫€程切換的開(kāi)銷(xiāo),多線程反而更慢;如果要完成的任務(wù)既有CPU計(jì)算,又有磁盤(pán)或網(wǎng)絡(luò)IO,則使用多線程的好處是,當(dāng)某個(gè)線程因?yàn)镮O而阻塞時(shí),OS可以調(diào)度其他線程執(zhí)行,雖然效率確實(shí)要比任務(wù)的順序執(zhí)行效率要高,然而,這種類(lèi)型的任務(wù),可以通過(guò)單線程的”non-blocking IO+IO multiplexing”的模型(事件驅(qū)動(dòng))來(lái)提高效率,采用多線程的方式,帶來(lái)的可能僅僅是編程上的簡(jiǎn)單而已)。Alan Cox說(shuō)過(guò):”A computer is a state machine.Threads are for people who can’t program state machines.”(計(jì)算機(jī)是一臺(tái)狀態(tài)機(jī)。線程是給那些不能編寫(xiě)狀態(tài)機(jī)程序的人準(zhǔn)備的)如果只有一塊CPU、一個(gè)執(zhí)行單元,那么確實(shí)如Alan Cox所說(shuō),按狀態(tài)機(jī)的思路去寫(xiě)程序是最蔽謹(jǐn)高效的。

二:?jiǎn)尉€程服務(wù)器的常用編程模型

據(jù)我了解,在高性能的網(wǎng)絡(luò)程序中,使用得最為廣泛的恐怕要數(shù)”non-blocking IO + IO multiplexing”這種模型,即Reactor模式。

在”non-blocking IO + IO multiplexing”這種模型中,程序的基本結(jié)構(gòu)是一個(gè)事件循環(huán)(event loop),以事件驅(qū)動(dòng)(event-driven)和事件回調(diào)的方式實(shí)現(xiàn)業(yè)務(wù)邏輯:

view plain copy

//代碼僅為示意,沒(méi)有完整考慮各種情況

while(!done)

{

int timeout_ms = max(1000, getNextTimedCallback());

int retval = poll(fds, nfds, timeout_ms);

if (retval0){

處理IO事件,回調(diào)用戶的IO event handler

}

}

}

這里select(2)/poll(2)有伸縮性方面的不足(描述符過(guò)多時(shí),效率較低),Linux下可替換為epoll(4),其他操作系統(tǒng)也有對(duì)應(yīng)的高性能替代品。

Reactor模型的優(yōu)點(diǎn)很明顯,編程不難,效率也不錯(cuò)。不僅可以用于讀寫(xiě)socket,連接的建立(connect(2)/accept(2)),甚至DNS解析都可以用非阻塞方式進(jìn)行,以提高并發(fā)度和吞吐量(throughput),對(duì)于IO密集的應(yīng)用是個(gè)不錯(cuò)的選擇。lighttpd就是這樣,它內(nèi)部的fdevent結(jié)構(gòu)十分精妙,值得學(xué)習(xí)。

基于事件驅(qū)動(dòng)的編程模型也有其本質(zhì)的缺點(diǎn),它要求事件回調(diào)函數(shù)必須是非阻塞的。對(duì)于涉及網(wǎng)絡(luò)IO的請(qǐng)求響應(yīng)式協(xié)議,它容易割裂業(yè)務(wù)邏輯,使其散布于多個(gè)回調(diào)函數(shù)之中,相對(duì)不容易理解和維護(hù)。

三:多線程服務(wù)器的常用編程模型

大概有這么幾種:

a:每個(gè)請(qǐng)求創(chuàng)建一個(gè)線程,使用阻塞式IO操作。在Java 1.4引人NIO之前,這是Java網(wǎng)絡(luò)編程的推薦做法??上炜s性不佳(請(qǐng)求太多時(shí),操作系統(tǒng)創(chuàng)建不了這許多線程)。

b:使用線程池,同樣使用阻塞式IO操作。與第1種相比,這是提高性能的措施。

c:使用non-blocking IO + IO multiplexing。即Java NIO的方式。

d:Leader/Follower等高級(jí)模式。

在默認(rèn)情況下,我會(huì)使用第3種,即non-blocking IO + one loop per thread模式來(lái)編寫(xiě)多線程C++網(wǎng)絡(luò)服務(wù)程序。

1:one loop per thread

此種模型下,程序里的每個(gè)IO線程有一個(gè)event loop,用于處理讀寫(xiě)和定時(shí)事件(無(wú)論周期性的還是單次的)。代碼框架跟“單線程服務(wù)器的常用編程模型”一節(jié)中的一樣。

libev的作者說(shuō):

One loop per thread is usually a good model. Doing this is almost never wrong, some times a better-performance model exists, but it is always a good start.

這種方式的好處是:

a:線程數(shù)目基本固定,可以在程序啟動(dòng)的時(shí)候設(shè)置,不會(huì)頻繁創(chuàng)建與銷(xiāo)毀。

b:可以很方便地在線程間調(diào)配負(fù)載。

c:IO事件發(fā)生的線程是固定的,同一個(gè)TCP連接不必考慮事件并發(fā)。

Event loop代表了線程的主循環(huán),需要讓哪個(gè)線程干活,就把timer或IO channel(如TCP連接)注冊(cè)到哪個(gè)線程的loop里即可:對(duì)實(shí)時(shí)性有要求的connection可以單獨(dú)用一個(gè)線程;數(shù)據(jù)量大的connection可以獨(dú)占一個(gè)線程,并把數(shù)據(jù)處理任務(wù)分?jǐn)偟搅韼讉€(gè)計(jì)算線程中(用線程池);其他次要的輔助性connections可以共享一個(gè)線程。

比如,在dbproxy中,一個(gè)線程用于專(zhuān)門(mén)處理客戶端發(fā)來(lái)的管理命令;一個(gè)線程用于處理客戶端發(fā)來(lái)的MySQL命令,而與后端數(shù)據(jù)庫(kù)通信執(zhí)行該命令時(shí),是將該任務(wù)分配給所有事件線程處理的。

對(duì)于non-trivial(有一定規(guī)模)的服務(wù)端程序,一般會(huì)采用non-blocking IO + IO multiplexing,每個(gè)connection/acceptor都會(huì)注冊(cè)到某個(gè)event loop上,程序里有多個(gè)event loop,每個(gè)線程至多有一個(gè)event loop。

多線程程序?qū)vent loop提出了更高的要求,那就是“線程安全”。要允許一個(gè)線程往別的線程的loop里塞東西,這個(gè)loop必須得是線程安全的。

在dbproxy中,線程向其他線程分發(fā)任務(wù),是通過(guò)管道和隊(duì)列實(shí)現(xiàn)的。比如主線程accept到連接后,將表示該連接的結(jié)構(gòu)放入隊(duì)列,并向管道中寫(xiě)入一個(gè)字節(jié)。計(jì)算線程在自己的event loop中注冊(cè)管道的讀事件,一旦有數(shù)據(jù)可讀,就嘗試從隊(duì)列中取任務(wù)。

2:線程池

不過(guò),對(duì)于沒(méi)有IO而光有計(jì)算任務(wù)的線程,使用event loop有點(diǎn)浪費(fèi)??梢允褂靡环N補(bǔ)充方案,即用blocking queue實(shí)現(xiàn)的任務(wù)隊(duì)列:

view plain copy

typedef boost::functionFunctor;

BlockingQueue taskQueue; //線程安全的全局阻塞隊(duì)列

//計(jì)算線程

void workerThread()

{

while (running) //running變量是個(gè)全局標(biāo)志

{

Functor task = taskQueue.take(); //this blocks

task(); //在產(chǎn)品代碼中需要考慮異常處理

}

}

// 創(chuàng)建容量(并發(fā)數(shù))為N的線程池

int N = num_of_computing_threads;

for (int i = 0; i task = boost::bind(&Foo::calc,&foo);

taskQueue.post(task);

除了任務(wù)隊(duì)列,還可以用BlockingQueue實(shí)現(xiàn)數(shù)據(jù)的生產(chǎn)者消費(fèi)者隊(duì)列,即T是數(shù)據(jù)類(lèi)型而非函數(shù)對(duì)象,queue的消費(fèi)者從中拿到數(shù)據(jù)進(jìn)行處理。其實(shí)本質(zhì)上是一樣的。

3:總結(jié)

總結(jié)而言,我推薦的C++多線程服務(wù)端編程模式為:one (event) loop per thread + thread pool:

event loop用作IO multiplexing,配合non-blockingIO和定時(shí)器;

thread pool用來(lái)做計(jì)算,具體可以是任務(wù)隊(duì)列或生產(chǎn)者消費(fèi)者隊(duì)列。

以這種方式寫(xiě)服務(wù)器程序,需要一個(gè)優(yōu)質(zhì)的基于Reactor模式的網(wǎng)絡(luò)庫(kù)來(lái)支撐,muduo正是這樣的網(wǎng)絡(luò)庫(kù)。比如dbproxy使用的是libevent。

程序里具體用幾個(gè)loop、線程池的大小等參數(shù)需要根據(jù)應(yīng)用來(lái)設(shè)定,基本的原則是“阻抗匹配”(解釋見(jiàn)下),使得CPU和IO都能高效地運(yùn)作。所謂阻抗匹配原則:

如果池中線程在執(zhí)行任務(wù)時(shí),密集計(jì)算所占的時(shí)間比重為 P (0 就能立刻列出用到某服務(wù)的客戶端地址(Foreign Address列),然后在客戶端的機(jī)器上用netstat或lsof命令找出是哪個(gè)進(jìn)程發(fā)起的連接。TCP短連接和UDP則不具備這一特性。二是通過(guò)接收和發(fā)送隊(duì)列的長(zhǎng)度也較容易定位網(wǎng)絡(luò)或程序故障。在正常運(yùn)行的時(shí)候,netstat打印的Recv-Q和Send-Q都應(yīng)該接近0,或者在0附近擺動(dòng)。如果Recv-Q保持不變或持續(xù)增加,則通常意味著服務(wù)進(jìn)程的處理速度變慢,可能發(fā)生了死鎖或阻塞。如果Send-Q保持不變或持續(xù)增加,有可能是對(duì)方服務(wù)器太忙、來(lái)不及處理,也有可能是網(wǎng)絡(luò)中間某個(gè)路由器或交換機(jī)故障造成丟包,甚至對(duì)方服務(wù)器掉線,這些因素都可能表現(xiàn)為數(shù)據(jù)發(fā)送不出去。通過(guò)持續(xù)監(jiān)控Recv-Q和Send-Q就能及早預(yù)警性能或可用性故障。以下是服務(wù)端線程阻塞造成Recv-Q和客戶端Send-Q激增的例子:

view plain copy

$netstat -tn

Proto Recv-Q Send-Q Local Address Foreign

tcp.0.0.10:.0.0.10:#服務(wù)端連接

tcp.0.0.10:.0.0.10:#客戶端連接

tcp.0.0.10:.0.0.4:

五:多線程服務(wù)器的適用場(chǎng)合

如果要在一臺(tái)多核機(jī)器上提供一種服務(wù)或執(zhí)行一個(gè)任務(wù),可用的模式有:

a:運(yùn)行一個(gè)單線程的進(jìn)程;

b:運(yùn)行一個(gè)多線程的進(jìn)程;

c:運(yùn)行多個(gè)單線程的進(jìn)程;

d:運(yùn)行多個(gè)多線程的進(jìn)程;

考慮這樣的場(chǎng)景:如果使用速率為50MB/s的數(shù)據(jù)壓縮庫(kù),進(jìn)程創(chuàng)建銷(xiāo)毀的開(kāi)銷(xiāo)是800微秒,線程創(chuàng)建銷(xiāo)毀的開(kāi)銷(xiāo)是50微秒。如何執(zhí)行壓縮任務(wù)?

如果要偶爾壓縮1GB的文本文件,預(yù)計(jì)運(yùn)行時(shí)間是20s,那么起一個(gè)進(jìn)程去做是合理的,因?yàn)檫M(jìn)程啟動(dòng)和銷(xiāo)毀的開(kāi)銷(xiāo)遠(yuǎn)遠(yuǎn)小于實(shí)際任務(wù)的耗時(shí)。

如果要經(jīng)常壓縮500kB的文本數(shù)據(jù),預(yù)計(jì)運(yùn)行時(shí)間是10ms,那么每次都起進(jìn)程 似乎有點(diǎn)浪費(fèi)了,可以每次單獨(dú)起一個(gè)線程去做。

如果要頻繁壓縮10kB的文本數(shù)據(jù),預(yù)計(jì)運(yùn)行時(shí)間是200微秒,那么每次起線程似 乎也很浪費(fèi),不如直接在當(dāng)前線程搞定。也可以用一個(gè)線程池,每次把壓縮任務(wù)交給線程池,避免阻塞當(dāng)前線程(特別要避免阻塞IO線程)。

由此可見(jiàn),多線程并不是萬(wàn)靈丹(silver bullet)。

1:必須使用單線程的場(chǎng)合

據(jù)我所知,有兩種場(chǎng)合必須使用單線程:

a:程序可能會(huì)fork(2);

實(shí)際編程中,應(yīng)該保證只有單線程程序能進(jìn)行fork(2)。多線程程序不是不能調(diào)用fork(2),而是這么做會(huì)遇到很多麻煩:

fork一般不能在多線程程序中調(diào)用,因?yàn)長(zhǎng)inux的fork只克隆當(dāng)前線程的thread of control,不可隆其他線程。fork之后,除了當(dāng)前線程之外,其他線程都消失了。

這就造成一種危險(xiǎn)的局面。其他線程可能正好處于臨界區(qū)之內(nèi),持有了某個(gè)鎖,而它突然死亡,再也沒(méi)有機(jī)會(huì)去解鎖了。此時(shí)如果子進(jìn)程試圖再對(duì)同一個(gè)mutex加鎖,就會(huì)立即死鎖。因此,fork之后,子進(jìn)程就相當(dāng)于處于signal handler之中(因?yàn)椴恢勒{(diào)用fork時(shí),父進(jìn)程中的線程此時(shí)正在調(diào)用什么函數(shù),這和信號(hào)發(fā)生時(shí)的場(chǎng)景一樣),你不能調(diào)用線程安全的函數(shù)(除非它是可重入的),而只能調(diào)用異步信號(hào)安全的函數(shù)。比如,fork之后,子進(jìn)程不能調(diào)用:

malloc,因?yàn)閙alloc在訪問(wèn)全局狀態(tài)時(shí)幾乎肯定會(huì)加鎖;

任何可能分配或釋放內(nèi)存的函數(shù),比如snprintf;

任何Pthreads函數(shù);

printf系列函數(shù),因?yàn)槠渌€程可能恰好持有stdout/stderr的鎖;

除了man 7 signal中明確列出的信號(hào)安全函數(shù)之外的任何函數(shù)。

因此,多線程中調(diào)用fork,唯一安全的做法是fork之后,立即調(diào)用exec執(zhí)行另一個(gè)程序,徹底隔斷子進(jìn)程與父進(jìn)程的聯(lián)系。

在多線程環(huán)境中調(diào)用fork,產(chǎn)生子進(jìn)程后。子進(jìn)程內(nèi)部只存在一個(gè)線程,也就是父進(jìn)程中調(diào)用fork的線程的副本。

使用fork創(chuàng)建子進(jìn)程時(shí),子進(jìn)程通過(guò)繼承整個(gè)地址空間的副本,也從父進(jìn)程那里繼承了所有互斥量、讀寫(xiě)鎖和條件變量的狀態(tài)。如果父進(jìn)程中的某個(gè)線程占有鎖,則子進(jìn)程同樣占有這些鎖。問(wèn)題是子進(jìn)程并不包含占有鎖的線程的副本,所以子進(jìn)程沒(méi)有辦法知道它占有了哪些鎖,并且需要釋放哪些鎖。

盡管Pthread提供了pthread_atfork函數(shù)試圖繞過(guò)這樣的問(wèn)題,但是這回使得代碼變得混亂。因此《Programming With Posix Threads》一書(shū)的作者說(shuō):”Avoid using fork in threaded code except where the child process will immediately exec a new program.”。

b:限制程序的CPU占用率;

這個(gè)很容易理解,比如在一個(gè)8核的服務(wù)器上,一個(gè)單線程程序即便發(fā)生busy-wait,占滿1個(gè)core,其CPU使用率也只有12.5%,在這種最壞的情況下,系統(tǒng)還是有87.5%的計(jì)算資源可供其他服務(wù)進(jìn)程使用。

linux socket多線程的介紹就聊到這里吧,感謝你花時(shí)間閱讀本站內(nèi)容,更多關(guān)于linux socket多線程,「探索 Linux 網(wǎng)絡(luò)編程:socket 多線程」,socket編程在windows和linux下的區(qū)別,如何看懂《Linux多線程服務(wù)端編程的信息別忘了在本站進(jìn)行查找喔。

創(chuàng)新互聯(lián)(cdcxhl.com)提供穩(wěn)定的云服務(wù)器,香港云服務(wù)器,BGP云服務(wù)器,雙線云服務(wù)器,高防云服務(wù)器,成都云服務(wù)器,服務(wù)器托管。精選鉅惠,歡迎咨詢:028-86922220。


新聞名稱:「探索Linux網(wǎng)絡(luò)編程:socket多線程」(linuxsocket多線程)
網(wǎng)站鏈接:http://www.dlmjj.cn/article/cccgdds.html