国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

這篇具有很好參考價(jià)值的文章主要介紹了【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

悟已往之不諫,知來(lái)者之可追。抓不住的就放手,屬于你的都在路上……

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化



一、TCP網(wǎng)絡(luò)套接字編程

1.日志等級(jí)分類的日志輸出API

1.
為了讓我們的代碼更規(guī)范化,所以搞出了日志等級(jí)分類,常見的日志輸出等級(jí)有DEBUG NORMAL WARNING ERROR FATAL等,再配合上程序運(yùn)行的時(shí)間,輸出的內(nèi)容等,公司中就是使用日志分類的方式來(lái)記錄程序的輸出,方便程序員找bug。
實(shí)際上在系統(tǒng)目錄/var/log/messages文件中也記錄了Linux系統(tǒng)自己的日志輸出,可以看到我的Linux系統(tǒng)中之前在使用時(shí)產(chǎn)生了很多的error和warning,我們的代碼也可以搞出來(lái)這樣的輸出日志信息到文件的功能。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

2.
log.hpp中設(shè)置了5個(gè)日志等級(jí),ERROR是一種程序已經(jīng)出錯(cuò)了但并不影響代碼繼續(xù)跑的錯(cuò)誤,而FATAL是一種致命的錯(cuò)誤,一旦出錯(cuò)將會(huì)直接終止程序繼續(xù)運(yùn)行。
我們將DEBUG 和NORMAL等級(jí)的輸出內(nèi)容放到文件log.txt中,把WARNING ERROR FATAL等級(jí)的輸出內(nèi)容放到文件log.error中,以此來(lái)進(jìn)行一個(gè)簡(jiǎn)單的日志等級(jí)的文件分類。
logMessage使用了可變參數(shù)列表,使得外部調(diào)用logMessage進(jìn)行日志輸出時(shí)能夠更加靈活的使用該函數(shù),可變參數(shù)列表大家可以在網(wǎng)上搜一搜,其實(shí)就是va_list va_start va_arg va_end等宏的使用,對(duì)可變參數(shù)列表的參數(shù)進(jìn)行控制,但今天我們不使用最基本的玩法,也就是一個(gè)個(gè)的讀取參數(shù)列表中的參數(shù),而是直接用vsnprintf(int vsnprintf(char *str, size_t size, const char *format, va_list ap);)進(jìn)行可變參數(shù)列表中參數(shù)的讀取,并將讀取到的參數(shù)內(nèi)容格式化輸出到緩沖區(qū)str當(dāng)中,這樣就能夠完成參數(shù)列表中參數(shù)內(nèi)容的讀取。format是需要格式化的字符串,也就是使用可變參數(shù)列表時(shí)的" "中的內(nèi)容,這部分字符串會(huì)作為參數(shù)傳遞給vsnprintf中的format。
今天我們將日志輸出內(nèi)容分為logprefix和logcontent兩部分內(nèi)容,將日志的前綴格式化輸出到logprefix數(shù)組中,將日志的后綴內(nèi)容也就是含有可變參數(shù)的部分內(nèi)容,進(jìn)行可變參數(shù)讀取并將其格式化輸出到logcontent數(shù)組中。
完成上述工作之后,只需要fopen打開log.txt和log.error兩個(gè)文件,進(jìn)行追加式的寫入logprefix和logcontent內(nèi)容的集聯(lián)即可。這樣就完成了日志信息的文件輸出。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

2.單進(jìn)程版本的服務(wù)器客戶端通信

下面是tcp服務(wù)器客戶端的makefile文件,和udp沒什么區(qū)別。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
tcp服務(wù)器的調(diào)用邏輯和udp服務(wù)器一樣,都是在運(yùn)行時(shí)指明服務(wù)器進(jìn)程綁定的端口號(hào)即可,服務(wù)器綁定的ip地址為任意ip,其中有個(gè)守護(hù)進(jìn)程化daemonSelf()接口,這個(gè)現(xiàn)在用不到,最后講守護(hù)進(jìn)程時(shí)會(huì)談?wù)摗?/mark>
【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

1.
tcpserver類的成員變量只需要listenSockfd套接字和bind的端口號(hào)即可。
tcp服務(wù)器要做的第一件事和udp服務(wù)器相同,都是創(chuàng)建套接字,在調(diào)用socket時(shí),第二個(gè)參數(shù)不再是SOCK_DGRAM,而是變成了SOCK_STREAM即創(chuàng)建字節(jié)流式的套接字。
第二步也是給listenSockfd套接字文件描述符綁定ip和端口號(hào),綁定的邏輯也和udp服務(wù)器相同,只不過(guò)在tcp服務(wù)器這里的查錯(cuò)處理我們改成了日志等級(jí)輸出的文件方式而已。
第三步tcp服務(wù)器與udp就不同了,由于tcp協(xié)議是面向連接的,所以想要和tcp服務(wù)器通信就必須先建立連接,而TCP服務(wù)器需要設(shè)置自己的套接字listenSockfd為監(jiān)聽狀態(tài),即被動(dòng)的等待客戶端發(fā)送connect連接請(qǐng)求。到此為止就完成了tcp服務(wù)器的初始化準(zhǔn)備工作了。
TCP服務(wù)器死循環(huán)運(yùn)行開始后,服務(wù)器accept接收來(lái)自客戶端的連接請(qǐng)求,accept接收請(qǐng)求是阻塞式的,即接收來(lái)自哪里的客戶端的請(qǐng)求,需要一個(gè)peer結(jié)構(gòu)體作為輸出型參數(shù)傳遞給accept函數(shù),accept會(huì)返回一個(gè)專門用來(lái)網(wǎng)絡(luò)通信的套接字文件描述符sockfd,而之前socket創(chuàng)建出來(lái)的listenSockfd是專門用來(lái)監(jiān)聽客戶端的連接請(qǐng)求的。建立連接成功之后,就可以用accept返回的sockfd開始通信了,我們今天把通信的模塊serviceIO單拿出來(lái)做一個(gè)解耦,方便后續(xù)其他版本的通信代碼進(jìn)行測(cè)試,其他版本的代碼直接調(diào)用serviceIO即可。

初始化好的服務(wù)器會(huì)首先處于監(jiān)聽狀態(tài),靠的就是listenSockfd套接字文件描述符,
【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

在通信之后,要把a(bǔ)ccept返回的sockfd關(guān)閉掉,否則隨著建立連接的次數(shù)不斷上升,則可用的sockfd會(huì)越來(lái)越少,造成文件描述符資源泄露,我的云服務(wù)器默認(rèn)能打開的文件個(gè)數(shù)有10w多個(gè),但是只要服務(wù)器通信過(guò)后不關(guān)閉已經(jīng)使用完了的sockfd的話,一旦連接數(shù)變得很多,比如幾百個(gè)連接,那么服務(wù)器就會(huì)造成大量的文件描述符資源泄露,服務(wù)器就會(huì)變得越來(lái)越卡,所以通信完畢之后,關(guān)閉套接字文件描述符是一個(gè)好的習(xí)慣。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
2.
serviceIO的通信邏輯也很簡(jiǎn)單,就是系統(tǒng)接口的文件操作read的使用,從文件描述符sockfd中讀取客戶端的消息,然后再做一個(gè)簡(jiǎn)單的回顯消息到客戶端,寫回的時(shí)候調(diào)用system call write即可,如果read讀到0,則說(shuō)明寫端也就是客戶端不寫了,那么讀端會(huì)一直阻塞等待讀取sockfd中的內(nèi)容,為了不讓讀端一直阻塞,我們此時(shí)break跳出serviceIO的死循環(huán),結(jié)束掉這一次的通信即可。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

tcp客戶端調(diào)用這里的代碼也沒什么好說(shuō)的,和udp一模一樣。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
3.
客戶端初始化時(shí)也是一樣,需要?jiǎng)?chuàng)建網(wǎng)絡(luò)套接字,tcp的客戶端也是需要綁定的,但為了程序的魯棒性更好,就無(wú)需程序員顯示綁定,由OS來(lái)動(dòng)態(tài)分配客戶端綁定的ip和端口號(hào)。
客戶端開始死循環(huán)運(yùn)行時(shí),第一件事就是向服務(wù)器發(fā)起連接請(qǐng)求,這個(gè)連接的工作也不難做,因?yàn)榭蛻舳酥滥康膇p和目的port,所以直接填充server結(jié)構(gòu)體中的各個(gè)字段,然后直接發(fā)起連接請(qǐng)求即可。連接成功后就可以開始通信,同樣的客戶端也是使用read和write等接口來(lái)進(jìn)行數(shù)據(jù)包的發(fā)送和接收。如果客戶端讀到0,則說(shuō)明服務(wù)器已經(jīng)不寫了,那么如果客戶端繼續(xù)向服務(wù)器發(fā)消息,就相當(dāng)于寫端向已經(jīng)關(guān)閉的讀端繼續(xù)寫入,此時(shí)OS會(huì)終止掉客戶端進(jìn)程。
由于udp和tcp分別是無(wú)連接和面向連接的,所以兩者有些許不同,tcp的服務(wù)器如果掛掉,客戶端繼續(xù)寫,則客戶端進(jìn)程會(huì)被操作系統(tǒng)終止掉,而udp的服務(wù)器如果掛掉,客戶端是可以繼續(xù)寫的,只不過(guò)客戶端發(fā)送的數(shù)據(jù)包會(huì)被簡(jiǎn)單的丟棄掉罷了。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

4.
下面是實(shí)驗(yàn)現(xiàn)象,客戶端發(fā)送的消息是可以被服務(wù)器正確回顯的,一旦服務(wù)器終止掉之后,客戶端繼續(xù)向服務(wù)器寫入時(shí),客戶端進(jìn)程會(huì)立馬被操作系統(tǒng)殺死從而終止掉,這其實(shí)就是我們所說(shuō)的讀端關(guān)閉,寫端繼續(xù)寫則寫端進(jìn)程會(huì)被終止的現(xiàn)象。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

可以看到當(dāng)服務(wù)器成功啟動(dòng)時(shí),日志消息確實(shí)被輸出到了文件log.txt當(dāng)中。
【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
當(dāng)客戶端和服務(wù)器連接成功之后,服務(wù)器依舊還有一個(gè)LISTEN監(jiān)聽狀態(tài),除此之外還有客戶端和服務(wù)器的ESTABLISHED連接建立成功的狀態(tài)。其實(shí)一條連接就已經(jīng)是全雙工的通信狀態(tài)了,而我們能看到兩條鏈接是因?yàn)榻裉熳鰷y(cè)試時(shí),客戶端和服務(wù)器在同一臺(tái)主機(jī)上,如果在不同的主機(jī)上,則各自主機(jī)都只能看到唯一的一條連接狀態(tài)。
【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
5.
而現(xiàn)在又出現(xiàn)了一個(gè)新的問(wèn)題,用戶1連接成功并開始通信時(shí),用戶2可以連接服務(wù)器,因?yàn)榉?wù)器一直處于監(jiān)聽狀態(tài),但用戶2發(fā)送的消息卻并不會(huì)被服務(wù)器回顯,而只有當(dāng)?shù)谝粋€(gè)用戶進(jìn)程被終止掉之后,用戶2進(jìn)程才會(huì)立馬回顯剛剛所發(fā)送的一堆消息,接下來(lái)用戶2才可以正常和服務(wù)器通信,這是為什么呢?其實(shí)主要是因?yàn)槲覀兊拇a邏輯是串行執(zhí)行的,一旦服務(wù)器啟動(dòng)時(shí),因?yàn)槭菃芜M(jìn)程,所以連接一個(gè)客戶端之后,服務(wù)器就會(huì)陷入serviceIO的死循環(huán),無(wú)法繼續(xù)循環(huán)執(zhí)行accept以接收來(lái)自客戶端的連接請(qǐng)求。而當(dāng)連接的客戶端終止掉之后,serviceIO會(huì)讀到0,此時(shí)才會(huì)break跳出死循環(huán),重新執(zhí)行accept建立新的連接。
所以如果想要讓服務(wù)器同時(shí)建立多個(gè)連接,可以通過(guò)多進(jìn)程或多線程以及線程池的方式來(lái)實(shí)現(xiàn)。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

3.多進(jìn)程版本和多線程版本

1.
多進(jìn)程的實(shí)現(xiàn)方案也很簡(jiǎn)單,讓父進(jìn)程去執(zhí)行和客戶端連接的代碼,也就是執(zhí)行accept的功能,讓fork出來(lái)的子進(jìn)程執(zhí)行和客戶端進(jìn)行通信的服務(wù)代碼,也就是執(zhí)行serviceIO,創(chuàng)建子進(jìn)程后,子進(jìn)程應(yīng)該將自己不使用的文件描述符關(guān)閉,防止子進(jìn)程對(duì)父進(jìn)程打開的文件進(jìn)行誤操作,尤其是像listenSockfd這樣的文件描述符,如果子進(jìn)程對(duì)listenSockfd進(jìn)行寫入什么的,很有可能會(huì)導(dǎo)致服務(wù)器崩潰,此外,關(guān)閉不用的文件描述符也可以給子進(jìn)程騰出來(lái)一部分文件描述符表的下標(biāo)位置,防止文件描述符泄露。
創(chuàng)建出來(lái)的子進(jìn)程是需要等待的,在下面代碼中使用非阻塞式等待是一個(gè)非常不好用的做法,這會(huì)讓服務(wù)器的工作主線偏離,因?yàn)槿绻褂梅亲枞降却?,則勢(shì)必得通過(guò)輪詢的方式來(lái)檢測(cè)子進(jìn)程的狀態(tài),那服務(wù)器就需要一直詢問(wèn)子進(jìn)程是否退出,但我服務(wù)器的核心工作主線是接收客戶端的請(qǐng)求并建立連接進(jìn)行網(wǎng)絡(luò)通信的啊,一旦非阻塞等待,服務(wù)器的性能就一定會(huì)下降,因?yàn)樾枰恢弊霾槐匾墓ぷ鳎罕热缭儐?wèn)子進(jìn)程狀態(tài),況且waitpid還是系統(tǒng)調(diào)用,每次循環(huán)還要陷入內(nèi)核,所以非阻塞式等待是一個(gè)非常不好的方案,不要用他。
第一種解決方案就是讓子進(jìn)程fork出孫子進(jìn)程,子進(jìn)程立馬退出終止,讓孫子進(jìn)程去提供serviceIO服務(wù),孫子進(jìn)程退出時(shí)會(huì)被1號(hào)進(jìn)程init進(jìn)程接管,回收孫子進(jìn)程(孤兒進(jìn)程)的資源。父進(jìn)程此時(shí)就可以阻塞式等待子進(jìn)程退出,這個(gè)阻塞其實(shí)可以忽略不計(jì),因?yàn)橐坏﹦?chuàng)建出子進(jìn)程,子進(jìn)程就會(huì)立馬退出,父進(jìn)程也會(huì)立馬回收掉子進(jìn)程的資源,從而父進(jìn)程可以繼續(xù)向后accept其他客戶端的連接請(qǐng)求,而讓孫子進(jìn)程提供serviceIO服務(wù),當(dāng)孫子進(jìn)程退出后,1號(hào)進(jìn)程會(huì)回收他的資源。
第二種解決方案就比較簡(jiǎn)單輕松,可以直接捕捉SIGCHLD信號(hào)顯示設(shè)置為SIG_IGN,這樣的話,父進(jìn)程就不需要等待子進(jìn)程,當(dāng)子進(jìn)程退出時(shí),linux系統(tǒng)會(huì)自動(dòng)幫我們回收子進(jìn)程資源,父進(jìn)程就省心了,不用管子進(jìn)程退不退出的事了,把這件事丟給linux系統(tǒng)來(lái)干,我父進(jìn)程專心accept其他的客戶端連接請(qǐng)求就OK。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

下面是多進(jìn)程版本代碼的實(shí)驗(yàn)現(xiàn)象,服務(wù)器這回就可以同時(shí)給多個(gè)客戶端提供通信服務(wù)了,因?yàn)榉?wù)器中搞出了多進(jìn)程,也就是多個(gè)執(zhí)行流,主執(zhí)行流負(fù)責(zé)接收來(lái)自多個(gè)客戶端的連接請(qǐng)求,其他的多個(gè)子進(jìn)程負(fù)責(zé)給多個(gè)客戶端提供通信服務(wù)。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

2.
多進(jìn)程并不是一個(gè)好的實(shí)現(xiàn)方案,因?yàn)閯?chuàng)建一個(gè)進(jìn)程的代價(jià)遠(yuǎn)比線程大得多,頻繁的創(chuàng)建和回收進(jìn)程會(huì)給系統(tǒng)帶來(lái)很大的壓力,所以多進(jìn)程是一個(gè)比較重量化方案,而反觀多線程是一種輕量化的方案,所以使用多線程能讓服務(wù)器的性能消耗更小一些。
實(shí)現(xiàn)的方式也比較簡(jiǎn)單,我們知道threadRoutine要傳給pthread_create的話,必須為靜態(tài)方法,如果是靜態(tài)方法就無(wú)法調(diào)用serviceIO,所以我們搞一個(gè)結(jié)構(gòu)體td包含TcpServer類的指針this,以及accept返回的用于通信的套接字文件描述符sockfd,將td地址傳遞給threadRoutine函數(shù),線程函數(shù)內(nèi)部進(jìn)行回調(diào)serviceIO,serviceIO如果調(diào)用結(jié)束不要忘記將sockfd關(guān)閉,避免文件描述符資源泄露。在線程這里只有阻塞式等待join和不等待兩種情況,沒有非阻塞式等待,所以主線程創(chuàng)建線程之后如果不想阻塞式j(luò)oin從線程的退出,則可以創(chuàng)建線程之后立馬將從線程設(shè)置為detach狀態(tài)即線程分離,線程函數(shù)執(zhí)行完畢之后退出時(shí),由操作系統(tǒng)負(fù)責(zé)回收從線程資源,主線程也就撒手不管了。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

3.
下面是實(shí)驗(yàn)現(xiàn)象,在多線程方案下,我們的tcp服務(wù)器依舊可以實(shí)現(xiàn)同時(shí)連接多個(gè)客戶端,線程不僅在內(nèi)存上較為輕量化,實(shí)際在代碼上寫起來(lái)也比較輕量化,不費(fèi)頭發(fā)。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

4.線程池版本

1.
多進(jìn)程和多線程從實(shí)現(xiàn)服務(wù)器的功能角度來(lái)說(shuō)其實(shí)都挺不錯(cuò)的,但他們都有一個(gè)共同的問(wèn)題,當(dāng)連接請(qǐng)求到來(lái)的時(shí)候他們才會(huì)去創(chuàng)建對(duì)應(yīng)的線程或進(jìn)程,所以就會(huì)存在一個(gè)頻繁創(chuàng)建和銷毀的問(wèn)題,那么能不能提前創(chuàng)建好一批線程,等到請(qǐng)求到來(lái)的時(shí)候不用去創(chuàng)建線程,而是直接讓線程去和客戶端進(jìn)行通信,這樣服務(wù)器的效率是不是會(huì)高一些呢?當(dāng)然是的。
一般來(lái)說(shuō),線程池適用于快速響應(yīng)客戶端的請(qǐng)求,執(zhí)行短暫不繁瑣的任務(wù)處理,在執(zhí)行過(guò)后可以將線程還給線程池,那么線程池內(nèi)的線程就都可以重復(fù)利用起來(lái),而我們現(xiàn)在寫的serviceIO代碼中執(zhí)行的是一個(gè)死循環(huán),實(shí)際中是肯定不會(huì)這樣做的,因?yàn)橐坏┚€程去執(zhí)行serviceIO后就回不來(lái)了,線程無(wú)法返回給線程池,那么有可能線程池中的所有線程都被serviceIO占著,此時(shí)若有其他客戶端發(fā)起連接請(qǐng)求,服務(wù)器此時(shí)就無(wú)法接收連接了,我們的代碼主要是demo代碼,并不是真實(shí)的實(shí)際中應(yīng)用的代碼。

2.
之前的文章實(shí)現(xiàn)過(guò)條件變量和互斥鎖構(gòu)成的單例模式的線程池,我們直接把當(dāng)時(shí)的線程池作為組件拿來(lái)用,線程池自帶任務(wù)隊(duì)列,線程池的構(gòu)造函數(shù)和run方法,分別對(duì)應(yīng)了Thread.hpp中的無(wú)參構(gòu)造函數(shù)Thread(),線程函數(shù)和給線程函數(shù)傳的參數(shù)這兩個(gè)為參的Thread(func_t func, void *args=nullptr)參數(shù),實(shí)現(xiàn)無(wú)參構(gòu)造的主要目的是想構(gòu)建出來(lái)線程名,把所有帶有線程名的線程push_back到線程池的任務(wù)隊(duì)列中,等待線程池run起來(lái)的時(shí)候,再把線程執(zhí)行的方法也就是serviceIO傳給線程,調(diào)用pthread_create讓線程執(zhí)行serviceIO,實(shí)現(xiàn)和客戶端的網(wǎng)絡(luò)通信。
所以線程池的任務(wù)隊(duì)列中存放的任務(wù)實(shí)際就是serviceIO,當(dāng)客戶端發(fā)起連接時(shí),我們就把Task(sockfd, serviceIO)任務(wù)對(duì)象push_back到線程池的任務(wù)隊(duì)列_task_queue里面,然后線程池中在條件變量下等待的各個(gè)線程中的某個(gè)線程會(huì)被signal,線程此時(shí)就會(huì)執(zhí)行serviceIO和客戶端進(jìn)行網(wǎng)絡(luò)通信。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

3.
下面是線程池代碼的實(shí)驗(yàn)現(xiàn)象,一旦線程池服務(wù)器啟動(dòng),可以看到tcpserver進(jìn)程里面直接多出來(lái)10個(gè)線程,線程池中的線程都可以提供通信服務(wù)。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

5.守護(hù)進(jìn)程化的線程池服務(wù)器

1.
上面的線程池服務(wù)器已經(jīng)很完美了,但美中不足的是只要我的xshell或者vscode關(guān)閉了,該線程池服務(wù)器就會(huì)被終止掉,我們還需要重新啟動(dòng)服務(wù)器,我們希望的是只要服務(wù)器啟動(dòng)之后,就不再受用戶登錄和注銷的影響,這樣的服務(wù)器進(jìn)程我們把他叫做守護(hù)進(jìn)程。
當(dāng)xshell打開時(shí),linux會(huì)為我們創(chuàng)建一個(gè)會(huì)話,在一個(gè)會(huì)話當(dāng)中有且只能有一個(gè)前臺(tái)任務(wù),可以有0個(gè)或多個(gè)后臺(tái)任務(wù),linux創(chuàng)建的會(huì)話中,剛開始都是以bash作為前臺(tái)任務(wù),bash就是命令行解釋器,用于客戶輸入指令和linux kernel進(jìn)行交互,當(dāng)我們的程序運(yùn)行起來(lái)時(shí),bash進(jìn)程會(huì)自動(dòng)被切換為后臺(tái)進(jìn)程,所以你可以簡(jiǎn)單的試一下,當(dāng)在命令行中啟動(dòng)進(jìn)程后,執(zhí)行pwd,ls,touch等bash指令一定是無(wú)效的,因?yàn)榇藭r(shí)bash被切到后臺(tái)運(yùn)行了,等到進(jìn)程終止退出后,linux會(huì)重新將bash從后臺(tái)切換為前臺(tái)進(jìn)程,此時(shí)用戶就又可以通過(guò)bash指令重新和Linux kernel交互了。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

2.
命令行中運(yùn)行進(jìn)程時(shí)加上&,此進(jìn)程將會(huì)被切換為后臺(tái)一直運(yùn)行,此時(shí)客戶端可以直接連接服務(wù)器進(jìn)行網(wǎng)絡(luò)通信,通過(guò)jobs可以查看當(dāng)前會(huì)話中后臺(tái)的進(jìn)程組,fg可以用于把進(jìn)程組提到前臺(tái)運(yùn)行,ctrl+z可以暫停前臺(tái)進(jìn)程組的運(yùn)行,此時(shí)該進(jìn)程組會(huì)自動(dòng)被切換為后臺(tái)并暫停運(yùn)行,如果想要恢復(fù)后臺(tái)進(jìn)程的運(yùn)行則可以使用bg+進(jìn)程組編號(hào)恢復(fù)后臺(tái)作業(yè)的運(yùn)行,可以看到我們搞出來(lái)的6個(gè)sleep進(jìn)程的會(huì)話id都是27119,而27119正是bash進(jìn)程的pid,所以你在命令行中啟動(dòng)的進(jìn)程都是在bash這個(gè)會(huì)話里面的,bash不僅僅是一個(gè)命令行解釋器他也是一個(gè)shell腳本語(yǔ)言。
當(dāng)你關(guān)閉終端,也就是關(guān)閉該會(huì)話時(shí),該會(huì)話中的所有進(jìn)程組中的所有進(jìn)程都會(huì)被終止,所以只要關(guān)閉會(huì)話這些進(jìn)程就又全都沒有了,包括我們的tcp服務(wù)器進(jìn)程,所以想要讓進(jìn)程不受用戶登錄注銷的影響唯一的辦法就是讓TCP服務(wù)器進(jìn)程自成一個(gè)會(huì)話,那么bash會(huì)話關(guān)閉時(shí)是不會(huì)影響到我們的TCP服務(wù)器進(jìn)程的。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

3.
下面是線程池服務(wù)器守護(hù)進(jìn)程話的組件daemon.hpp,首先做的第一件事就是讓服務(wù)器進(jìn)程忽略掉一些信號(hào),以增強(qiáng)服務(wù)器的魯棒性,比如忽略掉SIGPIPE信號(hào),這樣客戶端關(guān)閉時(shí),服務(wù)器不會(huì)受影響。
第二件事就是守護(hù)進(jìn)程化我們的TCP服務(wù)器,可以使用daemon和setsid來(lái)實(shí)現(xiàn)服務(wù)器的守護(hù)進(jìn)程化,我們使用setsid即可,但調(diào)用setsid的前提是調(diào)用的進(jìn)程不是進(jìn)程組組長(zhǎng),這個(gè)其實(shí)也好解決我們fork子進(jìn)程即可,讓子進(jìn)程調(diào)用setsid來(lái)進(jìn)行服務(wù)器的守護(hù)進(jìn)程化,所以調(diào)用setsid之前相當(dāng)于貍貓換太子,原先的父進(jìn)程直接退出,由子進(jìn)程接管父進(jìn)程剩余的所有代碼的執(zhí)行。
第三件事就是將原先父進(jìn)程打開的012文件描述符都重定向到文件黑洞/dev/null中,讓守護(hù)進(jìn)程不依賴終端,使其能夠獨(dú)立運(yùn)行。如果不重定向012,那么當(dāng)終端退出時(shí),守護(hù)進(jìn)程的012三個(gè)文件描述符會(huì)變成無(wú)效的,則會(huì)導(dǎo)致守護(hù)進(jìn)程異常退出。
重定向到文件黑洞之后,守護(hù)進(jìn)程服務(wù)器可以將日志消息輸出到文件中,方便后續(xù)從文件中來(lái)讀取服務(wù)器的日志。
因?yàn)槭刈o(hù)進(jìn)程往往運(yùn)行很長(zhǎng)時(shí)間,如果直接將進(jìn)程的消息輸出到終端,會(huì)積累很多日志,這可能會(huì)淹沒有效信息,降低日志的有用性,所以我們要dup2重定向012文件描述符到文件黑洞,以便于后期從文件中讀取服務(wù)器日志。如果/deb/null無(wú)法打開,那就無(wú)法實(shí)現(xiàn)重定向,我們也就只能被迫選擇關(guān)閉012文件描述符。
第四件事可做可不做,我們可以選擇更改守護(hù)進(jìn)程服務(wù)器的工作目錄,通過(guò)chdir來(lái)實(shí)現(xiàn),可以選擇改也可以選擇不改。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
4.
下面是守護(hù)進(jìn)程服務(wù)器的實(shí)驗(yàn)現(xiàn)象,進(jìn)程一旦啟動(dòng)就會(huì)守護(hù)進(jìn)程化,客戶端可以直接連接服務(wù)器進(jìn)行網(wǎng)絡(luò)通信。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

下面的實(shí)驗(yàn)現(xiàn)象中我說(shuō)錯(cuò)了一點(diǎn),bash會(huì)話中還是能看到守護(hù)進(jìn)程的,只不過(guò)不是在bash會(huì)話中看到的,而是在bash中執(zhí)行ps axj指令查看系統(tǒng)中所有的進(jìn)程來(lái)看到的,所以可以算是說(shuō)對(duì)了一半。
【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

5.
實(shí)際上除使用setsid進(jìn)行進(jìn)程的守護(hù)化外,還可以使用daemon接口,但這樣的接口實(shí)際沒有setsid好用,兩者的作用是相同的沒有什么差別。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

6.三次握手和四次揮手的感性理解

1.
上面我們所寫的TCP服務(wù)器實(shí)際上是存在很大問(wèn)題的,比如read的時(shí)候,你怎么確定你讀到的數(shù)據(jù)一定是完整的呢?有沒有可能對(duì)方發(fā)來(lái)一塊數(shù)據(jù),你讀了一半呢?或者是發(fā)來(lái)多塊數(shù)據(jù),你只讀了一個(gè)半呢?又或者是你直接把所有數(shù)據(jù)一下子讀取上來(lái)了呢?這些情況對(duì)于面向字節(jié)流的TCP協(xié)議來(lái)說(shuō),都是有可能發(fā)生的!和我們以前學(xué)的管道一樣,寫端有可能寫了一大批數(shù)據(jù),讀端有多少讀多少,一下子把所有數(shù)據(jù)都讀上來(lái)了,一般取決于讀端的緩沖區(qū)有多大。
對(duì)于面向字節(jié)流這樣不確定的讀取該怎么解決呢?實(shí)際要通過(guò)定制協(xié)議來(lái)解決!
定制協(xié)議這個(gè)話題我們先拋出來(lái),第二部分會(huì)進(jìn)行講解。

2.
實(shí)際上連接的過(guò)程并沒有我們所想象那么簡(jiǎn)單,只要客戶端調(diào)用connect,服務(wù)器先調(diào)用listen,后調(diào)用accept就完成連接過(guò)程了,根本不是這么簡(jiǎn)單的事情!
而connect僅僅只是發(fā)起了連接請(qǐng)求,發(fā)起連接的請(qǐng)求和真正建立連接這是兩碼事,你看到一個(gè)喜歡的女生,你想要發(fā)起追求人家的請(qǐng)求,那和你們倆真正成為男女朋友是一回事嗎?當(dāng)然不是一回事!想要真正成為男女朋友,中間是要有復(fù)雜的連接過(guò)程的。我們的客戶端連接過(guò)程同樣也是如此。真正連接的過(guò)程實(shí)際就是雙方操作系統(tǒng)三次握手的過(guò)程,這個(gè)過(guò)程是由雙方的操作系統(tǒng)自動(dòng)完成的。
我們知道上層發(fā)起連接請(qǐng)求和收獲連接結(jié)果是通過(guò)connect和accept系統(tǒng)調(diào)用來(lái)完成的,而真實(shí)的連接過(guò)程和這兩個(gè)系統(tǒng)調(diào)用沒什么關(guān)系,連接過(guò)程是由雙方的操作系統(tǒng)執(zhí)行各自的內(nèi)核代碼自動(dòng)完成連接過(guò)程的。
所以accept并不參與三次握手的任何細(xì)節(jié),他僅僅只負(fù)責(zé)拿走連接結(jié)果的勝利果實(shí)。換句話說(shuō),就算上層不調(diào)用accept,三次握手的過(guò)程也能夠建立好,因?yàn)閼?yīng)用是應(yīng)用,底層是底層,三次握手就是底層,和你應(yīng)用沒半毛錢關(guān)系,這是我雙方的操作系統(tǒng)自主完成的工作。
另外我們所說(shuō)的TCP協(xié)議保證可靠性和應(yīng)用有關(guān)系嗎?照樣沒半毛錢關(guān)系!因?yàn)閼?yīng)用是應(yīng)用,底層是底層,TCP協(xié)議是傳輸層的,傳輸層在操作系統(tǒng)內(nèi)部實(shí)現(xiàn)。
相同的,四次揮手的過(guò)程也是由雙方操作系統(tǒng)完成的,而close(sockfd)的作用僅僅只是觸發(fā)了四次揮手而已。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
3.
我們知道肯定不可能只有一個(gè)客戶端連接服務(wù)器,如果是多個(gè)客戶端連接服務(wù)器的話,服務(wù)器要不要對(duì)這么多的連接請(qǐng)求做管理呢?反過(guò)來(lái)一個(gè)客戶端如果連接了多個(gè)服務(wù)器的話,那么多個(gè)服務(wù)器返回的連接結(jié)果,客戶端要不要做管理呢?當(dāng)然是要的!
所以客戶端和服務(wù)器都要對(duì)大量的連接請(qǐng)求做管理,那該怎么做管理呢?先描述,再組織!雙方的操作系統(tǒng)內(nèi)部一定維護(hù)了連接請(qǐng)求所對(duì)應(yīng)的內(nèi)核結(jié)構(gòu)對(duì)象,描述特定的某個(gè)連接的屬性信息,然后再用數(shù)據(jù)結(jié)構(gòu)將這些對(duì)象連接起來(lái)進(jìn)行管理,至此我們就完成了從表層泛泛而談的連接到內(nèi)核這一層的理解過(guò)程。
所以維護(hù)TCP的連接有成本嗎?一定是有的,因?yàn)殡p方的操作系統(tǒng)要在各自底層建立描述連接的結(jié)構(gòu)對(duì)象,然后用數(shù)據(jù)結(jié)構(gòu)將這些結(jié)構(gòu)對(duì)象管理起來(lái),這些都是要花時(shí)間和內(nèi)存空間的,所以維護(hù)連接一定是有成本的。
而四次揮手與三次握手有所不同,三次握手是某一方先發(fā)起連接請(qǐng)求然后進(jìn)行連接,斷開連接是雙方的事情,client對(duì)server說(shuō)我要和你斷開連接,server說(shuō)好呀,我同意,然后server又對(duì)client說(shuō),我也要和你斷開連接,client說(shuō)OK,我也同意,至此才完成了斷開連接的過(guò)程。所以斷開連接是雙方的事情,少了任何一方都只能算作通知,只有雙方共同協(xié)商才能完成斷開連接的過(guò)程。

4.
三次握手:client調(diào)用connect,向服務(wù)器發(fā)起連接請(qǐng)求,connect會(huì)發(fā)出SYN段并阻塞等待服務(wù)器應(yīng)答(第一次),服務(wù)器收到客戶端的SYN段后,會(huì)給客戶端應(yīng)答一個(gè)SYN-ACK段表示"同意建立連接"(第二次),客戶端收到SYN-ACK段后會(huì)從connect系統(tǒng)調(diào)用返回,同時(shí)應(yīng)答一個(gè)ACK段(第三次),此時(shí)連接建立成功。
四次揮手:客戶端如果沒有請(qǐng)求之后,就會(huì)調(diào)用close關(guān)閉連接,此時(shí)客戶端會(huì)向服務(wù)器發(fā)送FIN段(第一次),服務(wù)器收到FIN段后,會(huì)回應(yīng)一個(gè)ACK段(第二次),同時(shí)服務(wù)器的read會(huì)讀到0,當(dāng)read返回后服務(wù)器就會(huì)知道客戶端關(guān)閉了連接,他此時(shí)也會(huì)調(diào)用close關(guān)閉連接,同時(shí)向客戶端發(fā)送FIN段(第三次), 客戶端收到FIN段后,會(huì)給服務(wù)器返回一個(gè)ACK段(第四次)。
(socketAPI的connect被調(diào)用時(shí)會(huì)發(fā)出SYN段,read返回時(shí)表明服務(wù)器收到FIN段)

二、序列化/反序列化的協(xié)議定制

1.定制協(xié)議

1.
在定制協(xié)議的時(shí)候,一定是離不開序列化和反序列化的,這兩個(gè)名詞聽起來(lái)高大上,實(shí)際啥也不是。在網(wǎng)絡(luò)發(fā)送數(shù)據(jù)時(shí),比如我要發(fā)頭像URL,時(shí)間,昵稱,消息等字段,如果我一個(gè)一個(gè)發(fā)送的話,效率很非常的低,并且接收的一方也會(huì)很痛苦,這么多數(shù)據(jù)接收方該如何分辨哪個(gè)是頭像,哪個(gè)是時(shí)間,哪個(gè)是昵稱,哪個(gè)是消息呢?
所以為了提升網(wǎng)絡(luò)發(fā)送的效率,往往要進(jìn)行零散字段的序列化,將他們打包為一個(gè)報(bào)文(也可以稱為一個(gè)字符串),一并發(fā)送到網(wǎng)絡(luò)中,當(dāng)服務(wù)器收到這個(gè)序列化后的報(bào)文時(shí),如果想要拿到報(bào)文里面的各個(gè)字段進(jìn)行客戶端請(qǐng)求的響應(yīng),則首先需要進(jìn)行反序列化,將這一個(gè)報(bào)文拆開,拿到對(duì)應(yīng)的各個(gè)零散字段。
實(shí)際上序列化和反序列化的工作對(duì)應(yīng)的就是將零散字段打包進(jìn)行發(fā)送,和將報(bào)文打散為可讀取的零散字段。

2.
而我們所說(shuō)的定制協(xié)議服務(wù)于哪個(gè)部分呢?實(shí)際就是用于網(wǎng)絡(luò)發(fā)送或網(wǎng)絡(luò)讀取時(shí)的數(shù)據(jù)黏包問(wèn)題,因?yàn)槊嫦蜃止?jié)流的TCP會(huì)存在這樣的問(wèn)題,而UDP并不會(huì),他發(fā)送的數(shù)據(jù)是一個(gè)數(shù)據(jù)報(bào),服務(wù)器再接收時(shí),天然接收到的就是一個(gè)數(shù)據(jù)報(bào),不會(huì)存在什么黏包問(wèn)題等等。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

2.網(wǎng)絡(luò)發(fā)送的本質(zhì)和讀取黏包的處理(應(yīng)用層和傳輸層之間數(shù)據(jù)的拷貝)

1.
實(shí)際上sockfd會(huì)指向一個(gè)操作系統(tǒng)給分配好的socket file control block,而這個(gè)socket文件控制塊內(nèi)部會(huì)維護(hù)網(wǎng)絡(luò)發(fā)送和網(wǎng)絡(luò)接收的緩沖區(qū),我們調(diào)用的所有網(wǎng)絡(luò)發(fā)送函數(shù),write send sendto等實(shí)際就是將數(shù)據(jù)從應(yīng)用層緩沖區(qū)拷貝到TCP協(xié)議層,也就是操作系統(tǒng)內(nèi)部的發(fā)送緩沖區(qū),而網(wǎng)絡(luò)接收函數(shù),read recv recvfrom等實(shí)際就是將數(shù)據(jù)從TCP協(xié)議層的接收緩沖區(qū)拷貝到用戶層的緩沖區(qū)中,而實(shí)際雙方主機(jī)的TCP協(xié)議層之間的數(shù)據(jù)發(fā)送是完全由TCP自主決定的,什么時(shí)候發(fā)?發(fā)多少?發(fā)送時(shí)出錯(cuò)了怎么辦?這些全部都是由TCP協(xié)議自己決定的,這是操作系統(tǒng)內(nèi)部的事情,和我們用戶層沒有任何瓜葛,這也就是為什么TCP叫做傳輸控制協(xié)議的原因,因?yàn)閭鬏數(shù)倪^(guò)程是由他自己所控制決定的。
c->s和s->c之間發(fā)送使用的是不同對(duì)兒的發(fā)送和接收緩沖區(qū),所以c給s發(fā)是不影響s給c發(fā)送的,這也就能說(shuō)明TCP是全雙工的,一個(gè)在發(fā)送時(shí),不影響另一個(gè)也再發(fā)送,所以網(wǎng)絡(luò)發(fā)送的本質(zhì)就是數(shù)據(jù)拷貝。
服務(wù)器在調(diào)用網(wǎng)絡(luò)接收函數(shù)進(jìn)行TCP協(xié)議層接收緩沖區(qū)的數(shù)據(jù)拷貝到應(yīng)用層時(shí),有一個(gè)問(wèn)題,如果客戶端發(fā)送的報(bào)文很多怎么辦?接收緩沖區(qū)會(huì)堆積很多的報(bào)文,而這些報(bào)文都會(huì)黏到一起,服務(wù)器該怎么保證read的時(shí)候讀取到的是一個(gè)完整的報(bào)文呢?為了解決這個(gè)問(wèn)題,就需要我們?cè)趹?yīng)用層定制協(xié)議明確報(bào)文和報(bào)文的邊界。
常見的解決方式有定長(zhǎng),特殊符號(hào),自描述方式等,而我們今天所寫的代碼會(huì)將后兩個(gè)方式合起來(lái)一塊使用,我們會(huì)進(jìn)行報(bào)文與報(bào)文之間添加特殊符號(hào)進(jìn)行分隔,同時(shí)還會(huì)在報(bào)文前增加報(bào)頭來(lái)表示報(bào)文的長(zhǎng)度,以及在報(bào)頭和報(bào)文的正文間增加特殊符號(hào)進(jìn)行分隔,那么在讀取的時(shí)候就可以以報(bào)頭和報(bào)文之間的特殊符號(hào)作為依據(jù)先將報(bào)頭讀取上來(lái),然后報(bào)頭里存儲(chǔ)的不是正文長(zhǎng)度嗎?那我們就再向后讀取正文長(zhǎng)度個(gè)字節(jié),這樣就完成了一個(gè)完整報(bào)文的讀取。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
2.
說(shuō)應(yīng)用層緩沖區(qū)怕大家感覺到抽象,其實(shí)所謂的應(yīng)用層緩沖區(qū)就是我們自己定義的buffer,可以看到下面的6個(gè)網(wǎng)絡(luò)發(fā)送接收接口都有對(duì)應(yīng)的buf形參,我們?cè)谑褂玫臅r(shí)候肯定要傳參數(shù)進(jìn)去,而傳的參數(shù)就是我們?cè)趹?yīng)用層所定義出來(lái)的緩沖區(qū)。
這里多說(shuō)一句,下面的六個(gè)接口在進(jìn)行網(wǎng)絡(luò)發(fā)送和網(wǎng)絡(luò)讀取數(shù)據(jù)的時(shí)候,都會(huì)做網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序之間的轉(zhuǎn)換,recvfrom和sendto是程序員自己顯示做轉(zhuǎn)換,其余的四個(gè)接口是操作系統(tǒng)自動(dòng)做轉(zhuǎn)換,這是鐵鐵的事實(shí)!
網(wǎng)上有人會(huì)說(shuō)其余的四個(gè)接口不會(huì)做轉(zhuǎn)換,這是錯(cuò)誤的!他們一定會(huì)做轉(zhuǎn)換的,因?yàn)椴晦D(zhuǎn)換網(wǎng)絡(luò)通信時(shí)一定會(huì)出現(xiàn)問(wèn)題的,但事實(shí)上他們并不會(huì)出現(xiàn)問(wèn)題,所以調(diào)用下面這六個(gè)接口時(shí),一定都會(huì)做網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序之間的轉(zhuǎn)換。(gpt也是從網(wǎng)絡(luò)中爬出來(lái)的數(shù)據(jù),他說(shuō)的不一定是對(duì)的,不要完全相信gpt!)

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

3.自定義協(xié)議和序列化方案的代碼

1.
TCP服務(wù)器的日志函數(shù)我們做了裁剪,不搞那么麻煩了,直接將日志內(nèi)容輸出到顯示器上,方便我們進(jìn)行觀察。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
2.
接下來(lái)要做的工作就是將我們?cè)鹊腡CP服務(wù)器改造一下,將其改造為一個(gè)網(wǎng)絡(luò)版本的計(jì)算器,它能夠說(shuō)明很多上面我們上面談到過(guò)的問(wèn)題,例如零散字段的序列化和報(bào)文的構(gòu)建,報(bào)頭及有效載荷分離和有效載荷的反序列化,以及對(duì)于黏包的網(wǎng)絡(luò)讀取等問(wèn)題。
對(duì)于服務(wù)器的代碼,我們不應(yīng)該簡(jiǎn)單的進(jìn)行read讀取報(bào)文,這是一個(gè)繁雜的處理過(guò)程,所以我們要進(jìn)行軟件的分層,將服務(wù)器和客戶端的會(huì)話分為一層,將報(bào)文的讀取和處理再單獨(dú)分為一層,也就是將handlerEnter接口單獨(dú)拿出來(lái),不要和服務(wù)器的通信耦合在一起,我們的服務(wù)器很純粹,他只負(fù)責(zé)accept接收來(lái)自多個(gè)客戶端的連接請(qǐng)求,至于連接后報(bào)文的讀取和報(bào)文的處理工作交給子進(jìn)程來(lái)做,子進(jìn)程執(zhí)行的代碼就是handlerEnter接口。
在handlerEnter接口中,我們?cè)撊绾未_定服務(wù)器讀到了一個(gè)完整的請(qǐng)求報(bào)文req_text呢?這個(gè)工作就交給recvPackage來(lái)做,將網(wǎng)絡(luò)接收的報(bào)文放到我們定義的string req_text里面,然后我們要對(duì)這個(gè)報(bào)文進(jìn)行有效載荷和報(bào)頭協(xié)議的分離,也就是deLength接口,將req_text的內(nèi)容進(jìn)行分離,然后放到我們定義的req_str里面,到此我們僅僅只拿到了有效載荷,還沒有進(jìn)行真正的反序列化,所以我們可以定義出Request req對(duì)象,將有效載荷req_str進(jìn)行反序列化deserialize,將反序列化的結(jié)果填充到req對(duì)象中,到此為止我們才算是真正拿到了客戶端想要發(fā)送給我們的數(shù)據(jù),然后服務(wù)器要構(gòu)建響應(yīng)對(duì)象Response resp,將客戶端發(fā)送的數(shù)據(jù)進(jìn)行計(jì)算處理,而這個(gè)計(jì)算處理就是我們軟件分出來(lái)的第三層也就是應(yīng)用層,等服務(wù)器真正將數(shù)據(jù)拿到手之后,才開始進(jìn)行數(shù)據(jù)的實(shí)際的業(yè)務(wù)場(chǎng)景處理,而這個(gè)邏輯處理我們通過(guò)回調(diào)func的方式來(lái)完成,這個(gè)func會(huì)在服務(wù)器啟動(dòng)start()的時(shí)候,由外部將具體的業(yè)務(wù)邏輯處理方法傳進(jìn)來(lái),而handlerEnter內(nèi)部只需要回調(diào)外部的業(yè)務(wù)邏輯處理方法即可,至此我們實(shí)際就將軟件分為了三層,業(yè)務(wù)邏輯處理之后,會(huì)將對(duì)應(yīng)的處理結(jié)果填充到resp對(duì)象里面,然后我們需要將這個(gè)響應(yīng)結(jié)果發(fā)送回客戶端,發(fā)送前我們需要將resp進(jìn)行序列化,調(diào)用resp的serialize將序列化的結(jié)果放到string resp_str中,還差最后一步就是通過(guò)調(diào)用enLength接口進(jìn)行string resp_str對(duì)象的添加報(bào)頭處理,這樣才算徹底構(gòu)建完成了完整的響應(yīng)報(bào)文,然后再調(diào)用send將send_string這個(gè)完整的響應(yīng)報(bào)文發(fā)送回客戶端。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
3.
上面的服務(wù)器處理報(bào)文的框架說(shuō)完了,接下來(lái)就是實(shí)際的protocol.hpp協(xié)議組件的實(shí)現(xiàn)了,包括了我們所說(shuō)的請(qǐng)求和響應(yīng)的類,請(qǐng)求和響應(yīng)對(duì)象的序列化和反序列化,以及如何從網(wǎng)絡(luò)中讀取到完整的請(qǐng)求recvPackage,以及網(wǎng)絡(luò)發(fā)送報(bào)文前的報(bào)頭添加enLength(),網(wǎng)絡(luò)接收?qǐng)?bào)文后的報(bào)頭和有效載荷分離deLength(),等接口的具體實(shí)現(xiàn)。
我們先來(lái)談Request和Response,他們中的序列化和反序列化我們現(xiàn)在只看MYSELF條件編譯的部分,#else的部分是使用json的序列化和反序列化,后面我們會(huì)講。
Request主要包含x y op三個(gè)字段,序列化的工作其實(shí)也很簡(jiǎn)單,我們自己定制協(xié)議,要求請(qǐng)求的序列化結(jié)果必須為"x op y"這樣以空格作為分隔符的字符串形式,所以serialize就直接做字符串拼接,包含定義的SEP宏,表示字符串中的空格,然后將拼接的結(jié)果賦值給輸出型參數(shù)out即可。
反序列化的工作先將操作符左右的空格找到,將其迭代器位置記錄下來(lái),也就是left和right,接下來(lái)要做的是對(duì)于left和right的位置進(jìn)行查錯(cuò)處理,我們定好協(xié)議了,所以你這里不能違反協(xié)議,如果left和right找不到或者是位置重疊,那么就說(shuō)明請(qǐng)求在序列化的時(shí)候違反了我們的協(xié)議,那我們就返回false,表示反序列化失敗,外層的handlerEnter函數(shù)也會(huì)直接return,此次的客戶端和服務(wù)器通信過(guò)程就此結(jié)束。如果查找成功,我們接下來(lái)要做的工作其實(shí)就是截取子串,對(duì)輸入型參數(shù)in字符串進(jìn)行子串的截取,在截取這里也有一些細(xì)節(jié),substr的第一個(gè)參數(shù)是開始截取的位置,第二個(gè)參數(shù)是截取的子串的長(zhǎng)度,那我們就可以調(diào)用substr進(jìn)行子串的截取,截取之后還是要做判斷,不能違反協(xié)議規(guī)定,op的左右兩個(gè)操作數(shù)都不能為空,判斷成功后,我們利用stoi進(jìn)行整數(shù)的轉(zhuǎn)換,然后將對(duì)應(yīng)的x y op都分別賦值給Request的三個(gè)成員變量中,這就完成了反序列化的工作,將有效載荷進(jìn)行反序列化后的各個(gè)內(nèi)容分別填充到Request的各個(gè)字段中。
Response主要包含exitcode和result三個(gè)字段,我們定好協(xié)議,規(guī)定exitcode為0,1,2,3分別對(duì)應(yīng)OK,DIV_ZERO,MOD_ZERO,OP_ERROR,等四個(gè)含義,如果exitcode為0則說(shuō)明計(jì)算成功,對(duì)應(yīng)的result為計(jì)算的結(jié)果,如果exitcode為其他的三個(gè)數(shù)字,則計(jì)算結(jié)果result已經(jīng)不重要了,因?yàn)榇藭r(shí)計(jì)算已經(jīng)失敗出錯(cuò)了。Response的序列化就是將他的成員變量搞成"exitcode result",也是以宏SEP作為分隔符,前面是我們的協(xié)議規(guī)定,將序列化后的結(jié)果賦值到out輸出型參數(shù)即可。
反序列化的工作先進(jìn)行SEP的查找,查找后對(duì)mid迭代器位置進(jìn)行查錯(cuò),看是否違反了協(xié)議規(guī)定,如果沒違反,那就還是對(duì)輸入型參數(shù)in進(jìn)行子串的截取,截取到exitcode和result,將截取的結(jié)果也就是查錯(cuò),看是否違反了協(xié)議規(guī)定,如果沒有違反那就調(diào)用stoi將轉(zhuǎn)換后的結(jié)果填充到Response的兩個(gè)成員變量中,至此完成有效載荷的反序列化工作。
接下來(lái)需要談?wù)摰木褪顷P(guān)于報(bào)頭的添加和分離了,這也屬于協(xié)議的定制,我們規(guī)定一個(gè)完整的請(qǐng)求報(bào)文必須是"x op y" -> “content_len”\r\n"x op y"\r\n這個(gè)樣子的,一個(gè)完整的響應(yīng)報(bào)文必須是"exitcode result" -> “content_len”\r\n"exitcode result"\r\n這個(gè)樣子的,根據(jù)這樣的標(biāo)準(zhǔn)來(lái)進(jìn)行報(bào)文的構(gòu)建和其與有效載荷的分離。對(duì)于enLength,我們返回以text作為正文,正文長(zhǎng)度字符串化的結(jié)果作為報(bào)頭,也就是加上to_string(text.size()),以及用LINE_SEP作為分隔符,對(duì)于Request和Response我們定的完整報(bào)文標(biāo)準(zhǔn)是一樣的,都是以正文長(zhǎng)度作為報(bào)頭,報(bào)頭和有效載荷之間用LINE_SEP分隔,有效載荷尾部也增加LINE_SEP作為報(bào)文和報(bào)文之間的分隔。對(duì)于deLength,其實(shí)就是完整報(bào)文package進(jìn)行報(bào)頭和有效載荷的分離,然后把有效載荷放到text輸出型參數(shù)里面,過(guò)程也并不復(fù)雜,先進(jìn)行LINE_SEP的find,找到之后進(jìn)行content_len報(bào)頭字符串的截取,然后將這個(gè)字符串轉(zhuǎn)成int整數(shù)text_len,而text_len不就是有效載荷的長(zhǎng)度嗎?根據(jù)這個(gè)長(zhǎng)度我們就可以截取出有效載荷,不包含有效載荷后面的\r\n,然后將這個(gè)有效載荷賦值給輸出型參數(shù)text即完成報(bào)文的有效載荷分離工作。
接下來(lái)最重要的部分就是recvPackage了,你怎么保證你從服務(wù)器傳輸層的接收緩沖區(qū)讀到的是一個(gè)完整的請(qǐng)求報(bào)文呢?反過(guò)來(lái)你又怎么保證你從客戶端傳輸層的接收緩沖區(qū)讀到的是一個(gè)完整的響應(yīng)報(bào)文呢?這樣面向字節(jié)流的網(wǎng)絡(luò)讀取的問(wèn)題就是通過(guò)recvPackage接口來(lái)解決的?。?!
從網(wǎng)絡(luò)中讀取的邏輯是一個(gè)while死循環(huán),我們先定義一個(gè)char buffer,把recv從sockfd中讀到的報(bào)文暫時(shí)存儲(chǔ)到buffer里面,如果讀到的字節(jié)數(shù)大于0,我們將讀取到的內(nèi)容進(jìn)行字符串化處理,因?yàn)榘l(fā)送的時(shí)候我們發(fā)送的是C++字符串string,C++字符串不會(huì)以\0作為字符串的末尾標(biāo)識(shí),而讀取這里我們用的是C語(yǔ)言的字符串,我們將讀到的內(nèi)容進(jìn)行C語(yǔ)言式的字符串化處理,所以進(jìn)行buffer[n] = 0這樣的操作,然后我們?cè)賹語(yǔ)言字符串化后的buffer尾插到string inbuffer里面。我們之前定過(guò)協(xié)議,所以我們調(diào)用inbuffer的find()接口查找LINE_SEP,如果找不到,那就說(shuō)明你現(xiàn)在連一個(gè)完整的報(bào)文都沒有,那就重新循環(huán)continue繼續(xù)讀取,重新調(diào)用recv進(jìn)行接收緩沖區(qū)中內(nèi)容的讀取,如果找到了LINE_SEP那就說(shuō)明最起碼現(xiàn)在肯定是有content_len這個(gè)報(bào)頭了,然后我們將報(bào)頭截取出來(lái)存到text_len_string里面,再調(diào)用stoi將其轉(zhuǎn)換為整數(shù)text_len,有了text_len和text_len_string報(bào)頭字符串的size()接口我們就能計(jì)算出一個(gè)完整報(bào)文的大小total_len了,接下來(lái)繼續(xù)進(jìn)行判斷,拿inbuffer字符串的size和total_len進(jìn)行比較,如果小于那就還是說(shuō)明inbuffer中連一個(gè)完整的報(bào)文都沒有,但此時(shí)是有content_len這個(gè)報(bào)頭的,所以我們?cè)谶@里打印一個(gè)提示語(yǔ)句:“你輸入的消息, 沒有嚴(yán)格遵守我們的協(xié)議, 正在等待后續(xù)的內(nèi)容, continue”,因?yàn)橛锌赡芸蛻舳税l(fā)出的請(qǐng)求違反了協(xié)議,長(zhǎng)度沒有達(dá)到標(biāo)準(zhǔn),有可能少發(fā)了一部分?jǐn)?shù)據(jù),那么此時(shí)就繼續(xù)continue重新到網(wǎng)絡(luò)里面進(jìn)行recv,如果inbuffer的size大于等于total_len,那就說(shuō)明至少inbuffer里面存在一個(gè)完整的報(bào)文,那此時(shí)就可以將報(bào)文內(nèi)容直接賦值到輸出型參數(shù)text里面,然后在調(diào)用inbuffer的erase接口將這一個(gè)完整的報(bào)文去除掉,進(jìn)行下一個(gè)完整報(bào)文的讀取,此時(shí)就可以break出死循環(huán),返回true了。
至此為止就完成了一個(gè)完整報(bào)文的讀取。
多提一句,由于recvPackage未來(lái)可能是多線程調(diào)用的函數(shù),所以在函數(shù)內(nèi)部定義靜態(tài)的inbuffer變量會(huì)出現(xiàn)線程安全的問(wèn)題,所以我們給recvPackage接口多增加一個(gè)輸出型參數(shù)inbuffer,由外部調(diào)用recvPackage的地方進(jìn)行inbuffer的定義,并傳入到recvPackage里面來(lái)。

除了上面recvPackage一次讀取一個(gè)報(bào)文外,我們還可以設(shè)置出recvPackageAll這樣的接口來(lái)進(jìn)行多個(gè)報(bào)文的讀取,用vector來(lái)保存每一個(gè)完整的報(bào)文,但我們今天不用這樣的讀取報(bào)文的方式,僅僅只是作為demo來(lái)談?wù)撘幌露选?br>【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
4.
下面就是服務(wù)器代碼的調(diào)用邏輯,我們之前說(shuō)軟件分成了三層,其中的第三層應(yīng)用層就是在這里體現(xiàn)出來(lái)的,也就是對(duì)于讀取到真正要處理的內(nèi)容,也就是Request的有效載荷反序列化后的結(jié)果進(jìn)行業(yè)務(wù)邏輯處理,并將業(yè)務(wù)邏輯處理后的結(jié)果放到響應(yīng)對(duì)象resp這個(gè)輸出型參數(shù)中。
代碼實(shí)現(xiàn)也并不復(fù)雜,因?yàn)镽equest已經(jīng)反序列化了,所以他的成員變量已經(jīng)是客戶端發(fā)給服務(wù)器要處理的數(shù)據(jù)了,我們可以直接進(jìn)行字段的提取并進(jìn)行計(jì)算,計(jì)算的邏輯就是switch,如果計(jì)算成功那么exitcode就是0,result為對(duì)應(yīng)的計(jì)算結(jié)果,如果計(jì)算失敗,那么exitcode為非0,具體的值對(duì)應(yīng)我們定的標(biāo)準(zhǔn)中的錯(cuò)誤類型,初始化時(shí),我們將exitcode和result都初始化為宏OK,也就是0.
代碼主要邏輯執(zhí)行完后,將業(yè)務(wù)邏輯從處理結(jié)果填充到Response resp對(duì)象中后,直接返回true即可,因?yàn)橛?jì)算的成功與否已經(jīng)通過(guò)resp的exitcode字段體現(xiàn)了,無(wú)須通過(guò)返回值來(lái)體現(xiàn)計(jì)算的正誤。
而這個(gè)第三層的接口cal,在服務(wù)器啟動(dòng)的時(shí)候傳到start中,start中進(jìn)行會(huì)話層和表示層的工作后,會(huì)回調(diào)這個(gè)cal方法進(jìn)行網(wǎng)絡(luò)通信所拿到的客戶端數(shù)據(jù)的處理。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

5.
下面是客戶端代碼的實(shí)現(xiàn),客戶端的創(chuàng)建套接字socket,發(fā)起連接請(qǐng)求connect等代碼我們都沒有變,但需要改變的是客戶端發(fā)送報(bào)文的邏輯,我們現(xiàn)在發(fā)送的不再是之前那樣的一段聊天消息了,我們現(xiàn)在發(fā)送的是一個(gè)請(qǐng)求報(bào)文,接收的是一個(gè)響應(yīng)報(bào)文。
首先需要做的就是從鍵盤中讀取需要計(jì)算的數(shù)據(jù),我們定好標(biāo)準(zhǔn),輸入的形式必須是"1+1"這樣的形式,中間不能有空格,否則就違反了標(biāo)準(zhǔn),將輸入后的內(nèi)容暫存到line里面,然后我們對(duì)line作Parse解析,將line中的內(nèi)容解析到Request res這個(gè)請(qǐng)求對(duì)象中,ParseLine是一個(gè)簡(jiǎn)易版本的狀態(tài)機(jī),根據(jù)status的狀態(tài)進(jìn)行分批處理,如果status為0表示op的左操作數(shù),如果為1表示op操作符,如果為2表示op的右操作數(shù),我們定義出line字符串的下標(biāo)i,以及左操作數(shù)string left和右操作數(shù)string right,以及操作符op,將line截取后的結(jié)果分別存儲(chǔ)到left right op中,然后把left和right轉(zhuǎn)換為int類型,構(gòu)建出一個(gè)Request對(duì)象,將這個(gè)對(duì)象拷貝返回即可。至此為止就定義出了一個(gè)Request請(qǐng)求對(duì)象。
定義出這個(gè)請(qǐng)求對(duì)象還不行,我們需要將這個(gè)對(duì)象序列化為一個(gè)字符串,然后給這個(gè)字符串添加報(bào)頭以及分隔符LINE_SEP等,也就是調(diào)用enLength接口,這樣才能發(fā)送完整的請(qǐng)求報(bào)文到網(wǎng)絡(luò)中,等待服務(wù)器接收?qǐng)?bào)文并做處理。
所以我們調(diào)用req的序列化接口serialize,將序列化后的內(nèi)容放到string content中,然后我們?cè)俳ocontent添加報(bào)頭做完整報(bào)文的處理工作,直接調(diào)用enLength即可,enLength返回的string結(jié)果就是一個(gè)完整的請(qǐng)求報(bào)文,我們直接調(diào)用sendto將這個(gè)完整的請(qǐng)求報(bào)文發(fā)送到TCP傳輸層的發(fā)送緩沖區(qū),之后由TCP協(xié)議自主決定什么時(shí)候發(fā)送報(bào)文到服務(wù)器。
客戶端需要進(jìn)行響應(yīng)的讀取,和服務(wù)器面臨的問(wèn)題相同,客戶端如何確定自己讀到的是一個(gè)完整的響應(yīng)報(bào)文呢?所以客戶端也需要調(diào)用recvPackage來(lái)從自己傳輸層的接收緩沖區(qū)中讀取出一個(gè)完整的響應(yīng)報(bào)文,如果沒有讀到那就直接continue。讀到報(bào)文的第一件事就是將報(bào)頭和有效載荷進(jìn)行分離,將分離后的有效載荷存儲(chǔ)到text中,之后我們定義出一個(gè)響應(yīng)對(duì)象resp,調(diào)用resp的反序列化接口deserialize,將text這個(gè)有效載荷反序列化后的結(jié)果放到resp對(duì)象里面,然后我們打印出resp對(duì)象中的exitcode和result即可。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

下面是客戶端的調(diào)用邏輯,調(diào)用邏輯并沒有什么變化和之前的TCP服務(wù)器一模一樣,大家看一眼就可以。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

6.
下面是代碼的全部功能測(cè)試結(jié)果,除0模0非法op等操作都對(duì)應(yīng)了123這三個(gè)退出碼,并且inbuffer處理前是有一個(gè)完整的報(bào)文的,處理后inbuffer就空了,報(bào)文處理前是有報(bào)頭表面有效載荷的長(zhǎng)度的,去報(bào)文之后就只剩有效載荷了,服務(wù)器計(jì)算完成后會(huì)有序列化后的結(jié)果也就是一個(gè)字符串,添加報(bào)頭后就變成了一個(gè)完整的響應(yīng)報(bào)文。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

7.
如果我們故意少發(fā)送報(bào)文,也就是只發(fā)送報(bào)文的一部分長(zhǎng)度,那么服務(wù)器就會(huì)打印出提示消息,我們現(xiàn)在已經(jīng)違反了協(xié)議的規(guī)定,所以client就會(huì)阻塞住了,一直保持cin等待我們輸入的狀態(tài),但我們是多進(jìn)程版本的服務(wù)器,一個(gè)連接掛了不影響其他連接,其他的客戶端以及可以連接我們的網(wǎng)絡(luò)版計(jì)算器功能的服務(wù)器,比如左邊的客戶端掛了不會(huì)影響中間的客戶端,除非中間的客戶端也違反了協(xié)議規(guī)定。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

4.自定義協(xié)議和json序列化方案的代碼

1.
到目前為止,我們的代碼都是采用自己定制協(xié)議,自己手寫序列化和反序列化的方案,但實(shí)際上序列化和反序列化的工作已經(jīng)有人替我們做好了,常見現(xiàn)成的方案一般有json,protobuf,XML這三種,企業(yè)內(nèi)部自己一般會(huì)使用protobuf,對(duì)外使用json,所以對(duì)于序列化和反序列化是有現(xiàn)成的解決方案的,絕對(duì)不會(huì)自己去寫!
但協(xié)議還是可以自己定的,所以序列化時(shí)我們都會(huì)直接使用現(xiàn)成的方案,例如json和protobuf等。

2.
下面是json庫(kù)的下載和頭文件以及庫(kù)文件的路徑,在包含json頭文件時(shí),由于json在include路徑下還有二級(jí)三級(jí)目錄,所以包含json頭文件時(shí)我們要將目錄jsoncpp/json也包含上,包含的形式就是#include <jsoncpp/json/json.h> 。
庫(kù)文件默認(rèn)下載的是動(dòng)態(tài)庫(kù),沒有靜態(tài)庫(kù),庫(kù)文件直接安裝到了lib64目錄下,我們直接可以通過(guò)-ljsoncpp進(jìn)行使用

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

3.
使用條件編譯和jsoncpp庫(kù)時(shí),makefile比較容易寫錯(cuò),注意兩個(gè)文件都要帶上-ljsoncpp,否則編譯會(huì)報(bào)錯(cuò)找不到庫(kù)文件,如果想要使用自己的序列化方案可以在兩個(gè)文件的依賴方法后都帶上-DMYSELF。
也可以自己定義一個(gè)變量-LD存儲(chǔ)#-DMYSELF,想要切換回我們自己的序列化方案時(shí),只要去掉#就可以了。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化
4.
json的序列化和反序列化方案用起來(lái)就比較簡(jiǎn)單了,Json::Reader中的parse方法就是進(jìn)行反序列化,Json::FastWriter中的write方法就是進(jìn)行序列化,Json::Value則是定義一個(gè)萬(wàn)能對(duì)象,實(shí)際是用key value的鍵值對(duì)來(lái)存儲(chǔ)的,這個(gè)萬(wàn)能對(duì)象可以存儲(chǔ)很多的鍵值對(duì),例如在請(qǐng)求的序列化中,我們定義了三個(gè)鍵值對(duì),分別是<first,_x> <second,_x> <oper,_x>,序列化時(shí)直接調(diào)用write方法即可,傳入萬(wàn)能對(duì)象,write方法會(huì)返回一個(gè)string對(duì)象,該string對(duì)象就是序列化后的結(jié)果。
反序列化時(shí),需要將輸入型參數(shù)in的反序列化結(jié)果解析到root萬(wàn)能對(duì)象中,然后我們可以直接通過(guò)root的key拿到對(duì)應(yīng)的value值,把提取出來(lái)的value值分別賦值給_x _y _op成員變量,這就完成了反序列化的工作。
對(duì)于Response同樣也是如此,我們通過(guò)json就可以很輕松的完成序列化和反序列化的工作。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

5.
下面是json方案的服務(wù)器和客戶端輸出結(jié)果,可以看到請(qǐng)求和響應(yīng)序列化后的結(jié)果就是json的格式,即以逗號(hào)作為分隔符的鍵值對(duì)的序列化形式。
在序列化和反序列化這里,json還是非常香的,使用起來(lái)可讀性非常好而且用起來(lái)還很簡(jiǎn)單,使用成本不高。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

6.
只要我們需要,我們可以定制出無(wú)數(shù)多個(gè)協(xié)議,服務(wù)器想要知道用的是哪個(gè)協(xié)議,這樣的信息我們依舊可以藏在報(bào)文里面,比如第一部分是有效載荷的長(zhǎng)度,第二部分是協(xié)議編號(hào),第三部分是有效載荷,不同的協(xié)議針對(duì)于不同的使用場(chǎng)景。
比如http,https,SSH等應(yīng)用層協(xié)議,他們的地位和我們自己定制的協(xié)議有什么不同呢?只不過(guò)我們今天定制的協(xié)議是為了解決數(shù)據(jù)的計(jì)算所定制的,他們的協(xié)議是針對(duì)于其他的場(chǎng)景所定制的,兩者從本質(zhì)上來(lái)講,并沒有任何差別。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化

5.軟件分層和OSI上三層模型的聯(lián)系

1.
我們今天所寫的網(wǎng)絡(luò)版計(jì)算器的server代碼,完美契合了OSI的上三層模型,分別是會(huì)話,表示和應(yīng)用。
server通過(guò)listen accept等接口來(lái)申請(qǐng)和拿到連接就是會(huì)話層。
handlerEnter進(jìn)行網(wǎng)絡(luò)中請(qǐng)求報(bào)文的讀取和處理,以及構(gòu)建響應(yīng)報(bào)文發(fā)送回客戶端等就是表示層,在表示層這里我們使用了json或自定義的序列化方案,以及自定義的針對(duì)于數(shù)據(jù)簡(jiǎn)單計(jì)算場(chǎng)景的協(xié)議。
而handlerEnter中回調(diào)的cal方法就是應(yīng)用層,即對(duì)報(bào)文解包反序列化后的數(shù)據(jù)進(jìn)行業(yè)務(wù)邏輯處理。

【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-472722.html

到了這里,關(guān)于【Linux】TCP網(wǎng)絡(luò)套接字編程+協(xié)議定制+序列化和反序列化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Linux網(wǎng)絡(luò)編程——tcp套接字

    Linux網(wǎng)絡(luò)編程——tcp套接字

    本章Gitee倉(cāng)庫(kù):tcp套接字 客戶端: 客戶端: 關(guān)于構(gòu)造和初始化,可以直接在構(gòu)造的時(shí)候,將服務(wù)器初始化,那為什么還要寫到 init 初始化函數(shù)里面呢? 構(gòu)造盡量簡(jiǎn)單一點(diǎn),不要做一些“有風(fēng)險(xiǎn)”的操作。 tcp 是面向連接的,通信之前要建立連接,服務(wù)器處于等待連接到來(lái)的

    2024年02月20日
    瀏覽(96)
  • TCP/IP網(wǎng)絡(luò)編程(二) 套接字協(xié)議及其數(shù)據(jù)傳輸特性

    關(guān)于協(xié)議 如果相隔比較遠(yuǎn)的兩人進(jìn)行通話,必須先決定通話方式,如果一方選擇電話,另一方也必須選擇電話,否則接受不到消息。 總之,協(xié)議就是為了完成數(shù)據(jù)交換而定好的約定。 創(chuàng)建套接字 協(xié)議族 通過(guò)socket函數(shù)的第一個(gè)參數(shù)傳遞套接字中使用的協(xié)議分類信息,此協(xié)議

    2024年02月10日
    瀏覽(92)
  • 【Linux網(wǎng)絡(luò)編程】網(wǎng)絡(luò)編程套接字(TCP服務(wù)器)

    【Linux網(wǎng)絡(luò)編程】網(wǎng)絡(luò)編程套接字(TCP服務(wù)器)

    作者:愛寫代碼的剛子 時(shí)間:2024.4.4 前言:本篇博客主要介紹TCP及其服務(wù)器編碼 只介紹基于IPv4的socket網(wǎng)絡(luò)編程,sockaddr_in中的成員struct in_addr sin_addr表示32位 的IP地址 但是我們通常用點(diǎn)分十進(jìn)制的字符串表示IP地址,以下函數(shù)可以在字符串表示和in_addr表示之間轉(zhuǎn)換 字符串轉(zhuǎn)in

    2024年04月14日
    瀏覽(108)
  • Linux下基于TCP協(xié)議的Socket套接字編程(客戶端&服務(wù)端)入門詳解

    Linux下基于TCP協(xié)議的Socket套接字編程(客戶端&服務(wù)端)入門詳解

    寫在前面: 本篇博客探討實(shí)踐環(huán)境如下: 1.操作系統(tǒng): Linux 2.版本(可以通過(guò)命令 cat /etc/os-release 查看版本信息):PRETTY_NAME=“CentOS Linux 7 (Core)” 編程語(yǔ)言:C 常常說(shuō)socket 、套接字 那么socket 到底指的是什么? socket 本質(zhì)上是一個(gè)抽象的概念,它是一組用于 網(wǎng)絡(luò)通信的 API , 提供

    2024年02月01日
    瀏覽(19)
  • 【探索Linux】P.29(網(wǎng)絡(luò)編程套接字 —— 簡(jiǎn)單的TCP網(wǎng)絡(luò)程序模擬實(shí)現(xiàn))

    【探索Linux】P.29(網(wǎng)絡(luò)編程套接字 —— 簡(jiǎn)單的TCP網(wǎng)絡(luò)程序模擬實(shí)現(xiàn))

    在前一篇文章中,我們?cè)敿?xì)介紹了UDP協(xié)議和TCP協(xié)議的特點(diǎn)以及它們之間的異同點(diǎn)。 本文將延續(xù)上文內(nèi)容,重點(diǎn)討論簡(jiǎn)單的TCP網(wǎng)絡(luò)程序模擬實(shí)現(xiàn) 。通過(guò)本文的學(xué)習(xí),讀者將能夠深入了解TCP協(xié)議的實(shí)際應(yīng)用,并掌握如何編寫簡(jiǎn)單的TCP網(wǎng)絡(luò)程序。讓我們一起深入探討TCP網(wǎng)絡(luò)程序的

    2024年04月14日
    瀏覽(98)
  • [Linux] 網(wǎng)絡(luò)編程 - 初見TCP套接字編程: 實(shí)現(xiàn)簡(jiǎn)單的單進(jìn)程、多進(jìn)程、多線程、線程池tcp服務(wù)器

    [Linux] 網(wǎng)絡(luò)編程 - 初見TCP套接字編程: 實(shí)現(xiàn)簡(jiǎn)單的單進(jìn)程、多進(jìn)程、多線程、線程池tcp服務(wù)器

    網(wǎng)絡(luò)的上一篇文章, 我們介紹了網(wǎng)絡(luò)變成的一些重要的概念, 以及 UDP套接字的編程演示. 還實(shí)現(xiàn)了一個(gè)簡(jiǎn)單更簡(jiǎn)陋的UDP公共聊天室. [Linux] 網(wǎng)絡(luò)編程 - 初見UDP套接字編程: 網(wǎng)絡(luò)編程部分相關(guān)概念、TCP、UDP協(xié)議基本特點(diǎn)、網(wǎng)絡(luò)字節(jié)序、socket接口使用、簡(jiǎn)單的UDP網(wǎng)絡(luò)及聊天室實(shí)現(xiàn)…

    2024年02月16日
    瀏覽(32)
  • 【JaveEE】網(wǎng)絡(luò)編程之TCP套接字、UDP套接字

    【JaveEE】網(wǎng)絡(luò)編程之TCP套接字、UDP套接字

    目錄 1.網(wǎng)絡(luò)編程的基本概念 1.1為什么需要網(wǎng)絡(luò)編程? 1.2服務(wù)端與用戶端 1.3網(wǎng)絡(luò)編程五元組? 1.4套接字的概念 2.UDP套接字編程 2.1UDP套接字的特點(diǎn) ?2.2UDP套接字API 2.2.1DatagramSocket類 2.2.2DatagramPacket類? 2.2.3基于UDP的回顯程序 2.2.4基于UDP的單詞查詢? 3.TCP套接字編程 3.1TCP套接字的特

    2023年04月13日
    瀏覽(915)
  • 【JavaEE】網(wǎng)絡(luò)編程之TCP套接字、UDP套接字

    【JavaEE】網(wǎng)絡(luò)編程之TCP套接字、UDP套接字

    目錄 1.網(wǎng)絡(luò)編程的基本概念 1.1為什么需要網(wǎng)絡(luò)編程? 1.2服務(wù)端與用戶端 1.3網(wǎng)絡(luò)編程五元組? 1.4套接字的概念 2.UDP套接字編程 2.1UDP套接字的特點(diǎn) ?2.2UDP套接字API 2.2.1DatagramSocket類 2.2.2DatagramPacket類? 2.2.3基于UDP的回顯程序 2.2.4基于UDP的單詞查詢? 3.TCP套接字編程 3.1TCP套接字的特

    2023年04月20日
    瀏覽(120)
  • 網(wǎng)絡(luò)編程套接字應(yīng)用分享【Linux &C/C++ 】【UDP應(yīng)用 | TCP應(yīng)用 | TCP&線程池小項(xiàng)目】

    網(wǎng)絡(luò)編程套接字應(yīng)用分享【Linux &C/C++ 】【UDP應(yīng)用 | TCP應(yīng)用 | TCP&線程池小項(xiàng)目】

    目錄 前提知識(shí) 1. 理解源ip,目的ip和Macip 2. 端口號(hào) 3. 初識(shí)TCP,UDP協(xié)議 4.?網(wǎng)絡(luò)字節(jié)序 5. socket 編程 sockaddr類型? 一,基于udp協(xié)議編程? 1. socket——?jiǎng)?chuàng)建套接字 2. bind——將套接字強(qiáng)綁定? 3. recvfrom——接受數(shù)據(jù) 4. sendto——發(fā)出信息 ?遇到的問(wèn)題 (1. 云服務(wù)器中以及無(wú)法分配I

    2024年04月08日
    瀏覽(27)
  • 網(wǎng)絡(luò)編程套接字( TCP )

    網(wǎng)絡(luò)編程套接字( TCP )

    目錄 1、實(shí)現(xiàn)一個(gè)TCP網(wǎng)絡(luò)程序(單進(jìn)程版) ????????1.1、服務(wù)端serverTcp.cc文件 ?????????????????服務(wù)端創(chuàng)建套接字 ?????????????????服務(wù)端綁定 ?????????????????服務(wù)端監(jiān)聽 ?????????????????服務(wù)端獲取連接 ?????????????????服務(wù)

    2024年01月17日
    瀏覽(1816)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包