pos機服務(wù)器ip設(shè)置,linux服務(wù)器端編程之高性能服務(wù)器架構(gòu)設(shè)計總結(jié)

 新聞資訊  |   2023-03-25 08:31  |  投稿人:pos機之家

網(wǎng)上有很多關(guān)于pos機服務(wù)器ip設(shè)置,linux服務(wù)器端編程之高性能服務(wù)器架構(gòu)設(shè)計總結(jié)的知識,也有很多人為大家解答關(guān)于pos機服務(wù)器ip設(shè)置的問題,今天pos機之家(www.tjfsxbj.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

1、pos機服務(wù)器ip設(shè)置

pos機服務(wù)器ip設(shè)置

所謂高性能就是服務(wù)器能流暢地處理各個客戶端的連接并盡量低延遲地應(yīng)答客戶端的請求;所謂高并發(fā),指的是服務(wù)器可以同時支持多的客戶端連接,且這些客戶端在連接期間內(nèi)會不斷與服務(wù)器有數(shù)據(jù)來往。

這篇文章將從兩個方面來介紹,一個是服務(wù)器的框架,即單個服務(wù)器程序的代碼組織結(jié)構(gòu);另外一個是一組服務(wù)程序的如何組織與交互,即架構(gòu)。注意:本文以下內(nèi)容中的客戶端是相對概念,指的是連接到當(dāng)前討論的服務(wù)程序的終端,所以這里的客戶端既可能是我們傳統(tǒng)意義上的客戶端程序,也可能是連接該服務(wù)的其他服務(wù)器程序。

一、框架篇

按上面介紹的思路,我們先從單個服務(wù)程序的組織結(jié)構(gòu)開始介紹。

(一)、網(wǎng)絡(luò)通信

既然是服務(wù)器程序肯定會涉及到網(wǎng)絡(luò)通信部分,那么服務(wù)器程序的網(wǎng)絡(luò)通信模塊要解決哪些問題?

筆者認為至少要解決以下問題:

1. 如何檢測有新客戶端連接?

2. 如何接受客戶端連接?

3. 如何檢測客戶端是否有數(shù)據(jù)發(fā)來?

4.如何收取客戶端發(fā)來的數(shù)據(jù)?

5.如何檢測連接異常?發(fā)現(xiàn)連接異常之后,如何處理?

6.如何給客戶端發(fā)送數(shù)據(jù)?

7.如何在給客戶端發(fā)完數(shù)據(jù)后關(guān)閉連接?

稍微有點網(wǎng)絡(luò)基礎(chǔ)的人,都能回答上面說的其中幾個問題,比如接收客戶端連接用socket API的accept函數(shù),收取客戶端數(shù)據(jù)用recv函數(shù),給客戶端發(fā)送數(shù)據(jù)用send函數(shù),檢測客戶端是否有新連接和客戶端是否有新數(shù)據(jù)可以用IO multiplexing技術(shù)(IO復(fù)用)的select、poll、epoll等socket API。確實是這樣的,這些基礎(chǔ)的socket API構(gòu)成了服務(wù)器網(wǎng)絡(luò)通信的地基,不管網(wǎng)絡(luò)通信框架設(shè)計的如何巧妙,都是在這些基礎(chǔ)的socket API的基礎(chǔ)上構(gòu)建的。但是如何巧妙地組織這些基礎(chǔ)的socket API,才是問題的關(guān)鍵。我們說服務(wù)器很高效,支持高并發(fā),實際上只是一個技術(shù)實現(xiàn)手段,不管怎樣從軟件開發(fā)的角度來講無非就是一個程序而已,所以,只要程序能最大可能地滿足“盡量減少等待”就是高效。也就是說高效不是“忙的忙死,閑的閑死”,而是大家都可以閑著,但是如果有活要干,大家盡量一起干,而不是一部分忙著依次做事情123456789,另外一部分閑在那里無所事事。說的可能有點抽象,下面我們來舉一些例子具體來說明一下。

比如默認recv函數(shù)如果沒有數(shù)據(jù)的時候,線程就會阻塞在那里;

默認send函數(shù),如果tcp窗口不是足夠大,數(shù)據(jù)發(fā)不出去也會阻塞在那里;

connect函數(shù)默認連接另外一端的時候,也會阻塞在那里;

又或者是給對端發(fā)送一份數(shù)據(jù),需要等待對端回答,如果對方一直不應(yīng)答,當(dāng)前線程就阻塞在這里。

以上都不是高效服務(wù)器的開發(fā)思維方式,因為上面的例子都不滿足“盡量減少等待”的原則,為什么一定要等待呢?有沒用一種方法,這些過程不需要等待,最好是不僅不需要等待,而且這些事情完成之后能通知我。這樣在這些本來用于等待的cpu時間片內(nèi),我就可以做一些其他的事情。有,也就是我們下文要討論的IO Multiplexing技術(shù)(IO復(fù)用技術(shù))。

(二)、幾種IO復(fù)用機制的比較

目前windows系統(tǒng)支持select、WSAAsyncSelect、WSAEventSelect、完成端口(IOCP),linux系統(tǒng)支持select、poll、epoll。這里我們不具體介紹每個具體的函數(shù)的用法,我們來討論一點深層次的東西,以上列舉的API函數(shù)可以分為兩個層次:

層次一 select和poll

層次二 WSAAsyncSelect、WSAEventSelect、完成端口(IOCP)、epoll

為什么這么分呢?先來介紹第一層次,select和poll函數(shù)本質(zhì)上還是在一定時間內(nèi)主動去查詢socket句柄(可能是一個也可能是多個)上是否有事件,比如可讀事件,可寫事件或者出錯事件,也就是說我們還是需要每隔一段時間內(nèi)去主動去做這些檢測,如果在這段時間內(nèi)檢測出一些事件來,我們這段時間就算沒白花,但是倘若這段時間內(nèi)沒有事件呢?我們只能是做無用功了,說白了,還是在浪費時間,因為假如一個服務(wù)器有多個連接,在cpu時間片有限的情況下,我們花費了一定的時間檢測了一部分socket連接,卻發(fā)現(xiàn)它們什么事件都沒有,而在這段時間內(nèi)我們卻有一些事情需要處理,那我們?yōu)槭裁匆〞r間去做這個檢測呢?把這個時間用在做我們需要做的事情不好嗎?所以對于服務(wù)器程序來說,要想高效,我們應(yīng)該盡量避免花費時間主動去查詢一些socket是否有事件,而是等這些socket有事件的時候告訴我們?nèi)ヌ幚?。這也就是層次二的各個函數(shù)做的事情,它們實際相當(dāng)于變主動查詢是否有事件為當(dāng)有事件時,系統(tǒng)會告訴我們,此時我們再去處理,也就是“好鋼用在刀刃”上了。只不過層次二的函數(shù)通知我們的方式是各不相同,比如WSAAsyncSelect是利用windows消息隊列的事件機制來通知我們設(shè)定的窗口過程函數(shù),IOCP是利用GetQueuedCompletionStatus返回正確的狀態(tài),epoll是epoll_wait函數(shù)返回而已。

比如connect函數(shù)連接另外一端,如果連接socket是異步的,那么connect雖然不能立刻連接完成,但是也是會立刻返回,無需等待,等連接完成之后,WSAAsyncSelect會返回FD_CONNECT事件告訴我們連接成功,epoll會產(chǎn)生EPOLLOUT事件,我們也能知道連接完成。甚至socket有數(shù)據(jù)可讀時,WSAAsyncSelect產(chǎn)生FD_READ事件,epoll產(chǎn)生EPOLLIN事件,等等。所以有了上面的討論,我們就可以得到網(wǎng)絡(luò)通信檢測可讀可寫或者出錯事件的正確姿勢。這是我這里提出的第二個原則:盡量減少做無用功的時間。這個在服務(wù)程序資源夠用的情況下可能體現(xiàn)不出來什么優(yōu)勢,但是如果有大量的任務(wù)要處理,個人覺得這個可能帶來無用

(三)、檢測網(wǎng)絡(luò)事件的正確姿勢

根據(jù)上面的介紹,第一,為了避免無意義的等待時間,第二,不采用主動查詢各個socket的事件,而是采用等待操作系統(tǒng)通知我們有事件的狀態(tài)的策略。我們的socket都要設(shè)置成異步的。在此基礎(chǔ)上我們回到欄目(一)中提到的七個問題:

1. 如何檢測有新客戶端連接?

2. 如何接受客戶端連接?

默認accept函數(shù)會阻塞在那里,如果epoll檢測到偵聽socket上有EPOLLIN事件,或者WSAAsyncSelect檢測到有FD_ACCEPT事件,那么就表明此時有新連接到來,這個時候調(diào)用accept函數(shù),就不會阻塞了。當(dāng)然產(chǎn)生的新socket你應(yīng)該也設(shè)置成非阻塞的。這樣我們就能在新socket上收發(fā)數(shù)據(jù)了。

3. 如何檢測客戶端是否有數(shù)據(jù)發(fā)來?

4.如何收取客戶端發(fā)來的數(shù)據(jù)?

同理,我們也應(yīng)該在socket上有可讀事件的時候才去收取數(shù)據(jù),這樣我們調(diào)用recv或者read函數(shù)時不用等待,至于一次性收多少數(shù)據(jù)好呢?我們可以根據(jù)自己的需求來決定,甚至你可以在一個循環(huán)里面反復(fù)recv或者read,對于非阻塞模式的socket,如果沒有數(shù)據(jù)了,recv或者read也會立刻返回,錯誤碼EWOULDBLOCK會表明當(dāng)前已經(jīng)沒有數(shù)據(jù)了。示例:

bool CIUSocket::Recv() {     int nRet = 0;     while(true)     {         char buff[512];         nRet = ::recv(m_hSocket, buff, 512, 0);     if(nRet == SOCKET_ERROR)                //一旦出現(xiàn)錯誤就立刻關(guān)閉Socket     {         if (::WSAGetLastError() == WSAEWOULDBLOCK)           break;         else             return false;     }     else if(nRet < 1)         return false;         m_strRecvBuf.append(buff, nRet);         ::Sleep(1);     }     return true; } 

5.如何檢測連接異常?發(fā)現(xiàn)連接異常之后,如何處理?

同樣當(dāng)我們收到異常事件后例如EPOLLERR或關(guān)閉事件FD_CLOSE,我們就知道了有異常產(chǎn)生,我們對異常的處理一般就是關(guān)閉對應(yīng)的socket。另外,如果send/recv或者read/write函數(shù)對一個socket進行操作時,如果返回0,那說明對端已經(jīng)關(guān)閉了socket,此時這路連接也沒必要存在了,我們也可以關(guān)閉對應(yīng)的socket。

6.如何給客戶端發(fā)送數(shù)據(jù)?

給客戶端發(fā)送數(shù)據(jù),比收數(shù)據(jù)要稍微麻煩一點,也是需要講點技巧的。首先我們不能像檢測數(shù)據(jù)可讀一樣檢測數(shù)據(jù)可寫,因為如果檢測可寫的話,一般情況下只要對端正常收取數(shù)據(jù),我們的socket就都是可寫的,如果我們設(shè)置監(jiān)聽可寫事件,會導(dǎo)致頻繁地觸發(fā)可寫事件,但是我們此時并不一定有數(shù)據(jù)需要發(fā)送。所以正確的做法是:如果有數(shù)據(jù)要發(fā)送,則先嘗試著去發(fā)送,如果發(fā)送不了或者只發(fā)送出去部分,剩下的我們需要將其緩存起來,然后設(shè)置檢測該socket上可寫事件,下次可寫事件產(chǎn)生時,再繼續(xù)發(fā)送,如果還是不能完全發(fā)出去,則繼續(xù)設(shè)置偵聽可寫事件,如此往復(fù),一直到所有數(shù)據(jù)都發(fā)出去為止。一旦所有數(shù)據(jù)都發(fā)出去以后,我們要移除偵聽可寫事件,避免無用的可寫事件通知。不知道你注意到?jīng)]有,如果某次只發(fā)出去部分數(shù)據(jù),剩下的數(shù)據(jù)應(yīng)該暫且存起來,這個時候我們就需要一個緩沖區(qū)來存放這部分數(shù)據(jù),這個緩沖區(qū)我們稱為“發(fā)送緩沖區(qū)”。發(fā)送緩沖區(qū)不僅存放本次沒有發(fā)完的數(shù)據(jù),還用來存放在發(fā)送過程中,上層又傳來的新的需要發(fā)送的數(shù)據(jù)。為了保證順序,新的數(shù)據(jù)應(yīng)該追加在當(dāng)前剩下的數(shù)據(jù)的后面,發(fā)送的時候從發(fā)送緩沖區(qū)的頭部開始發(fā)送。也就是說先來的先發(fā)送,后來的后發(fā)送。

7.如何在給客戶端發(fā)完數(shù)據(jù)后關(guān)閉連接?

這個問題比較難處理,因為這里的“發(fā)送完”不一定是真正的發(fā)送完,我們調(diào)用send或者write函數(shù)即使成功,也只是向操作系統(tǒng)的協(xié)議棧里面成功寫入數(shù)據(jù),至于能否被發(fā)出去、何時被發(fā)出去很難判斷,發(fā)出去對方是否收到就更難判斷了。所以,我們目前只能簡單地認為send或者write返回我們發(fā)出數(shù)據(jù)的字節(jié)數(shù)大小,我們就認為“發(fā)完數(shù)據(jù)”了。然后調(diào)用close等socket API關(guān)閉連接。關(guān)閉連接的話題,我們再單獨開一個小的標(biāo)題來專門討論一下。

(四)被動關(guān)閉連接和主動關(guān)閉連接

在實際的應(yīng)用中,被動關(guān)閉連接是由于我們檢測到了連接的異常事件,比如EPOLLERR,或者對端關(guān)閉連接,send或recv返回0,這個時候這路連接已經(jīng)沒有存在必要的意義了,我們被迫關(guān)閉連接。

而主動關(guān)閉連接,是我們主動調(diào)用close/closesocket來關(guān)閉連接。比如客戶端給我們發(fā)送非法的數(shù)據(jù),比如一些網(wǎng)絡(luò)攻擊的嘗試性數(shù)據(jù)包。這個時候出于安全考慮,我們關(guān)閉socket連接。

(五)發(fā)送緩沖區(qū)和接收緩沖區(qū)

上面已經(jīng)介紹了發(fā)送緩沖區(qū)了,并說明了其存在的意義。接收緩沖區(qū)也是一樣的道理,當(dāng)收到數(shù)據(jù)以后,我們可以直接進行解包,但是這樣并不好,理由一:除非一些約定俗稱的協(xié)議格式,比如http協(xié)議,大多數(shù)服務(wù)器的業(yè)務(wù)的協(xié)議都是不同的,也就是說一個數(shù)據(jù)包里面的數(shù)據(jù)格式的解讀應(yīng)該是業(yè)務(wù)層的事情,和網(wǎng)絡(luò)通信層應(yīng)該解耦,為了網(wǎng)絡(luò)層更加通用,我們無法知道上層協(xié)議長成什么樣子,因為不同的協(xié)議格式是不一樣的,它們與具體的業(yè)務(wù)有關(guān)。理由二:即使知道協(xié)議格式,我們在網(wǎng)絡(luò)層進行解包處理對應(yīng)的業(yè)務(wù),如果這個業(yè)務(wù)處理比較耗時,比如讀取磁盤文件,或者連接數(shù)據(jù)庫進行賬號密碼驗證,那么我們的網(wǎng)絡(luò)線程會需要大量時間來處理這些任務(wù),這樣其它網(wǎng)絡(luò)事件可能沒法及時處理。鑒于以上二點,我們確實需要一個接收緩沖區(qū),將收取到的數(shù)據(jù)放到該緩沖區(qū)里面去,并由專門的業(yè)務(wù)線程或者業(yè)務(wù)邏輯去從接收緩沖區(qū)中取出數(shù)據(jù),并解包處理業(yè)務(wù)。

說了這么多,那發(fā)送緩沖區(qū)和接收緩沖區(qū)該設(shè)計成多大的容量?這是一個老生常談的問題了,因為我們經(jīng)常遇到這樣的問題:預(yù)分配的內(nèi)存太小不夠用,太大的話可能會造成浪費。怎么辦呢?答案就是像string、vector一樣,設(shè)計出一個可以動態(tài)增長的緩沖區(qū),按需分配,不夠還可以擴展。

需要特別注意的是,這里說的發(fā)送緩沖區(qū)和接收緩沖區(qū)是每一個socket連接都存在一個。這是我們最常見的設(shè)計方案。

(六)協(xié)議的設(shè)計

除了一些通用的協(xié)議,如http、ftp協(xié)議以外,大多數(shù)服務(wù)器協(xié)議都是根據(jù)業(yè)務(wù)制定的。協(xié)議設(shè)計好了,數(shù)據(jù)包的格式就根據(jù)協(xié)議來設(shè)置。我們知道tcp/ip協(xié)議是流式數(shù)據(jù),所以流式數(shù)據(jù)就是像流水一樣,數(shù)據(jù)包與數(shù)據(jù)包之間沒有明顯的界限。比如A端給B端連續(xù)發(fā)了三個數(shù)據(jù)包,每個數(shù)據(jù)包都是50個字節(jié),B端可能先收到10個字節(jié),再收到140個字節(jié);或者先收到20個字節(jié),再收到20個字節(jié),再收到110個字節(jié);也可能一次性收到150個字節(jié)。這150個字節(jié)可以以任何字節(jié)數(shù)目組合和次數(shù)被B收到。所以我們討論協(xié)議的設(shè)計第一個問題就是如何界定包的界線,也就是接收端如何知道每個包數(shù)據(jù)的大小。目前常用有如下三種方法:

固定大小,這種方法就是假定每一個包的大小都是固定字節(jié)數(shù)目,比如上文中討論的每個包大小都是50個字節(jié),接收端每收氣50個字節(jié)就當(dāng)成一個包;

指定包結(jié)束符,比如以一個\\(換行符和回車符)結(jié)束,這樣對端只要收到這樣的結(jié)束符,就可以認為收到了一個包,接下來的數(shù)據(jù)是下一個包的內(nèi)容;

指定包的大小,這種方法結(jié)合了上述兩種方法,一般包頭是固定大小,包頭中有一個字段指定包體或者整個大的大小,對端收到數(shù)據(jù)以后先解析包頭中的字段得到包體或者整個包的大小,然后根據(jù)這個大小去界定數(shù)據(jù)的界線。

協(xié)議要討論的第二個問題是,設(shè)計協(xié)議的時候要盡量方便解包,也就是說協(xié)議的格式字段應(yīng)該盡量清晰明了。

協(xié)議要討論的第三個問題是,根據(jù)協(xié)議組裝的數(shù)據(jù)包應(yīng)該盡量小,這樣有如下好處:第一、對于一些移動端設(shè)備來說,其數(shù)據(jù)處理能力和帶寬能力有限,小的數(shù)據(jù)不僅能加快處理速度,同時節(jié)省大量流量費用;第二、如果單個數(shù)據(jù)包足夠小的話,對頻繁進行網(wǎng)絡(luò)通信的服務(wù)器端來說,可以大大減小其帶寬壓力,其所在的系統(tǒng)也能使用更少的內(nèi)存。試想:假如一個股票服務(wù)器,如果一只股票的數(shù)據(jù)包是100個字節(jié)或者1000個字節(jié),那100只股票和10000只股票區(qū)別呢?

協(xié)議要討論的第二個問題是,對于數(shù)值類型,我們應(yīng)該顯式地指定數(shù)值的長度,比如long型,如果在32位機器上是32位的4個字節(jié),但是如果在64位機器上,就變成了64位8個字節(jié)了。這樣同樣是一個long型,發(fā)送方和接收方可能會用不同的長度去解碼。所以建議最好,在涉及到跨平臺使用的協(xié)議最好顯式地指定協(xié)議中整型字段的長度,比如int32,int64等等。下面是一個協(xié)議的接口的例子:

class BinaryReadStream {     private:         const char* const ptr;         const size_t      len;         const char*      cur;         BinaryReadStream(const BinaryReadStream&);         BinaryReadStream& operator=(const BinaryReadStream&);     public:         BinaryReadStream(const char* ptr, size_t len);         virtual const char* GetData() const;         virtual size_t GetSize() const;         bool IsEmpty() const;         bool ReadString(string* str, size_t maxlen, size_t& outlen);         bool ReadCString(char* str, size_t strlen, size_t& len);         bool ReadCCString(const char** str, size_t maxlen, size_t& outlen);         bool ReadInt32(int32_t& i);         bool ReadInt64(int64_t& i);         bool ReadShort(short& i);         bool ReadChar(char& c);         size_t ReadAll(char* szBuffer, size_t iLen) const;         bool IsEnd() const;         const char* GetCurrent() const{ return cur; }     public:         bool ReadLength(size_t & len);         bool ReadLengthWithoutOffset(size_t &headlen, size_t & outlen);     };     class BinaryWriteStream     {     public:         BinaryWriteStream(string* data);         virtual const char* GetData() const;         virtual size_t GetSize() const;         bool WriteCString(const char* str, size_t len);         bool WriteString(const string& str);         bool WriteDouble(double value, bool isNULL = false);         bool WriteInt64(int64_t value, bool isNULL = false);         bool WriteInt32(int32_t i, bool isNULL = false);         bool WriteShort(short i, bool isNULL = false);         bool WriteChar(char c, bool isNULL = false);         size_t GetCurrentPos() const{ return m_data->length(); }         void Flush();         void Clear();     private:         string* m_data;   }; 

其中BinaryWriteStream是編碼協(xié)議的類,BinaryReadStream是解碼協(xié)議的類。可以按下面這種方式來編碼和解碼。

編碼:

std::string outbuf; BinaryWriteStream writeStream(&outbuf); writeStream.WriteInt32(msg_type_register); writeStream.WriteInt32(m_seq); writeStream.WriteString(retData); writeStream.Flush(); 

解碼:

BinaryReadStream readStream(strMsg.c_str(), strMsg.length()); int32_t cmd; if (!readStream.ReadInt32(cmd)) {     return false; } //int seq; if (!readStream.ReadInt32(m_seq)) {     return false; } std::string data; size_t datalength; if (!readStream.ReadString(&data, 0, datalength)) {     return false; } 

(七)、服務(wù)器程序結(jié)構(gòu)的組織

由于內(nèi)容過多,后續(xù)會單獨組織一篇文章詳細介紹

二、架構(gòu)篇

一個項目的服務(wù)器端往往由很多服務(wù)組成,就算單個服務(wù)在性能上做到極致,支持的并發(fā)數(shù)量也是有限的,舉個簡單的例子,假如一個聊天服務(wù)器,每個用戶的信息是1k,那對于一個8G的內(nèi)存的機器,在不考慮其它的情況下8*1024*1024*1024 / 100 = 1024,實際有838萬,但實際這只是非常理想的情況。所以我們有時候需要需要某個服務(wù)部署多套,就單個服務(wù)的實現(xiàn)來講還是《框架篇》中介紹的。我們舉個例子:

這是蘑菇街TeamTalk的服務(wù)器架構(gòu)。MsgServer是聊天服務(wù),可以部署多套,每個聊天服務(wù)器啟動時都會告訴loginSever和routeSever自己的ip地址和端口號,當(dāng)有用戶上下或者下線的時候,MsgServer也會告訴loginSever和routeSever自己上面最新的用戶數(shù)量和用戶id列表?,F(xiàn)在一個用戶需要登錄,先連接loginServer,loginServer根據(jù)記錄的各個MsgServer上的用戶情況,返回一個最小負載的MsgServer的ip地址和端口號給客戶端,客戶端再利用這個ip地址和端口號去登錄MsgServer。當(dāng)聊天時,位于A MsgServer上的用戶給另外一個用戶發(fā)送消息,如果該用戶不在同一個MsgServer上,MsgServer將消息轉(zhuǎn)發(fā)給RouteServer,RouteServer根據(jù)自己記錄的用戶id信息找到目標(biāo)用戶所在的MsgServer并轉(zhuǎn)發(fā)給對應(yīng)的MsgServer。

上面是分布式部署的一個例子。我們再來看另外一個例子,這個例子是單個服務(wù)的策略,實際服務(wù)器在處理網(wǎng)絡(luò)數(shù)據(jù)的時候,如果同時有多個socket上有數(shù)據(jù)要處理,可能會出現(xiàn)一直服務(wù)前幾個socket,直到前幾個socket處理完畢后再處理后面幾個socket的數(shù)據(jù)。這就相當(dāng)于,你去飯店吃飯,大家都點了菜,但是有些桌子上一直在上菜,而有些桌子上一直沒有菜。這樣肯定不好,我們來看下如何避免這種現(xiàn)象:

int CFtdEngine::HandlePackage(CFTDCPackage *pFTDCPackage, CFTDCSession *pSession) {     //NET_IO_LOG0("CFtdEngine::HandlePackage\");     FTDC_PACKAGE_DEBUG(pFTDCPackage);     if (pFTDCPackage->GetTID() != FTD_TID_ReqUserLogin)     {         if (!IsSessionLogin(pSession->GetSessionID()))         {             SendErrorRsp(pFTDCPackage, pSession, 1, "客戶未登錄");             return 0;         }     }     CalcFlux(pSession, pFTDCPackage->Length());  //統(tǒng)計流量     REPORT_EVENT(LOG_DEBUG, "Front/Fgateway", "登錄請求%0x", pFTDCPackage->GetTID());     int nRet = 0;     switch(pFTDCPackage->GetTID())     {     case FTD_TID_ReqUserLogin:         ///huwp:20070608:檢查過高版本的API將被禁止登錄         if (pFTDCPackage->GetVersion()>FTD_VERSION)         {             SendErrorRsp(pFTDCPackage, pSession, 1, "Too High FTD Version");             return 0;         }         nRet = OnReqUserLogin(pFTDCPackage, (CFTDCSession *)pSession);         FTDRequestIndex.incValue();         break;     case FTD_TID_ReqCheckUserLogin:         nRet = OnReqCheckUserLogin(pFTDCPackage, (CFTDCSession *)pSession);         FTDRequestIndex.incValue();         break;     case FTD_TID_ReqSubscribeTopic:         nRet = OnReqSubscribeTopic(pFTDCPackage, (CFTDCSession *)pSession);         FTDRequestIndex.incValue();         break;       }     return 0; } 

當(dāng)有某個socket上有數(shù)據(jù)可讀時,接著接收該socket上的數(shù)據(jù),對接收到的數(shù)據(jù)進行解包,然后調(diào)用CalcFlux(pSession, pFTDCPackage->Length())進行流量統(tǒng)計:

void CFrontEngine::CalcFlux(CSession *pSession, const int nFlux) {     TFrontSessionInfo *pSessionInfo = m_mapSessionInfo.Find(pSession->GetSessionID());     if (pSessionInfo != NULL)     {         //流量控制改為計數(shù)         pSessionInfo->nCommFlux ++;         ///若流量超過規(guī)定,則掛起該會話的讀操作         if (pSessionInfo->nCommFlux >= pSessionInfo->nMaxCommFlux)         {             pSession->SuspendRead(true);         }     } } 

該函數(shù)會先讓某個連接會話(Session)處理的包數(shù)量遞增,接著判斷是否超過最大包數(shù)量,則設(shè)置讀掛起標(biāo)志:

void CSession::SuspendRead(bool bSuspend) {     m_bSuspendRead = bSuspend; } 

這樣下次將會從檢測的socket列表中排除該socket:

void CEpollReactor::RegisterIO(CEventHandler *pEventHandler) {     int nReadID, nWriteID;     pEventHandler->GetIds(&nReadID, &nWriteID);     if (nWriteID != 0 && nReadID ==0)     {         nReadID = nWriteID;     }     if (nReadID != 0)     {         m_mapEventHandlerId[pEventHandler] = nReadID;         struct epoll_event ev;         ev.data.ptr = pEventHandler;         if(epoll_ctl(m_fdEpoll, EPOLL_CTL_ADD, nReadID, &ev) != 0)         {             perror("epoll_ctl EPOLL_CTL_ADD");         }     } } void CSession::GetIds(int *pReadId, int *pWriteId) {     m_pChannelProtocol->GetIds(pReadId,pWriteId);     if (m_bSuspendRead)     {         *pReadId = 0;     } } 

也就是說不再檢測該socket上是否有數(shù)據(jù)可讀。然后在定時器里1秒后重置該標(biāo)志,這樣這個socket上有數(shù)據(jù)的話又可以重新檢測到了:

const int SESSION_CHECK_TIMER_ID    = 9; const int SESSION_CHECK_INTERVAL    = 1000; SetTimer(SESSION_CHECK_TIMER_ID, SESSION_CHECK_INTERVAL); void CFrontEngine::OnTimer(int nIDEvent) {     if (nIDEvent == SESSION_CHECK_TIMER_ID)     {         CSessionMap::iterator itor = m_mapSession.Begin();         while (!itor.IsEnd())         {             TFrontSessionInfo *pFind = m_mapSessionInfo.Find((*itor)->GetSessionID());             if (pFind != NULL)             {                 CheckSession(*itor, pFind);             }             itor++;         }     } } void CFrontEngine::CheckSession(CSession *pSession, TFrontSessionInfo *pSessionInfo) {     ///重新開始計算流量     pSessionInfo->nCommFlux -= pSessionInfo->nMaxCommFlux;     if (pSessionInfo->nCommFlux < 0)     {         pSessionInfo->nCommFlux = 0;     }     ///若流量超過規(guī)定,則掛起該會話的讀操作     pSession->SuspendRead(pSessionInfo->nCommFlux >= pSessionInfo->nMaxCommFlux);}

這就相當(dāng)與飯店里面先給某一桌客人上一些菜,讓他們先吃著,等上了一些菜之后不會再給這桌繼續(xù)上菜了,而是給其它空桌上菜,大家都吃上后,繼續(xù)回來給原先的桌子繼續(xù)上菜。實際上我們的飯店都是這么做的。上面的例子是單服務(wù)流量控制的實現(xiàn)的一個非常好的思路,它保證了每個客戶端都能均衡地得到服務(wù),而不是一些客戶端等很久才有響應(yīng)。

另外加快服務(wù)器處理速度的策略可能就是緩存了,緩存實際上是以空間換取時間的策略。對于一些反復(fù)使用的,但是不經(jīng)常改變的信息,如果從原始地點加載這些信息就比較耗時的數(shù)據(jù)(比如從磁盤中、從數(shù)據(jù)庫中),我們就可以使用緩存。所以時下像redis、leveldb、fastdb等各種內(nèi)存數(shù)據(jù)庫大行其道。我在flamingo中用戶的基本信息都是緩存在聊天服務(wù)程序中的,而文件服務(wù)啟動時會去加載指定目錄里面的所有程序名稱,這些文件的名稱都是md5,為該文件內(nèi)容的md5。這樣當(dāng)客戶端上傳了新文件請求時,如果其傳上來的文件md5已經(jīng)位于緩存中,則表明該文件在服務(wù)器上已經(jīng)存在,這個時候服務(wù)器就不必再接收該文件了,而是告訴客戶端文件已經(jīng)上傳成功了。

說了這么多,一般來說,一個服務(wù)器的架構(gòu),往往更多取決于其具體的業(yè)務(wù),我們要在結(jié)合當(dāng)前的情況來實際去組織鋪排,沒有一套系統(tǒng)是萬能的。多思考,多實踐,多總結(jié),相信很快你也能擁有很不錯的架構(gòu)能力。

需要C/C++ Linux服務(wù)器架構(gòu)師學(xué)習(xí)資料私信“資料”(資料包括C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等),免費分享

以上就是關(guān)于pos機服務(wù)器ip設(shè)置,linux服務(wù)器端編程之高性能服務(wù)器架構(gòu)設(shè)計總結(jié)的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機服務(wù)器ip設(shè)置的知識,希望能夠幫助到大家!

轉(zhuǎn)發(fā)請帶上網(wǎng)址:http://www.tjfsxbj.com/news/11883.html

你可能會喜歡:

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 babsan@163.com 舉報,一經(jīng)查實,本站將立刻刪除。