劉生建 李俊琴
摘要:現(xiàn)代生活離不開互聯(lián)網(wǎng),計(jì)算機(jī)的網(wǎng)絡(luò)通信技術(shù)最先發(fā)源于UNIX系統(tǒng),而Windows平臺(tái)雖然起步稍晚,但是目前對(duì)互聯(lián)網(wǎng)技術(shù)的支持也有長足的進(jìn)步?,F(xiàn)在很多的網(wǎng)絡(luò)游戲客戶端都是基于Windows平臺(tái)的。使用的底層通信技術(shù)經(jīng)過多年的發(fā)展,也出現(xiàn)了各種技術(shù)解決方案,該文研究概括總結(jié)了在Windows平臺(tái)上計(jì)算機(jī)網(wǎng)絡(luò)通信技術(shù)的主要技術(shù)方法。
關(guān)鍵詞:實(shí)時(shí)應(yīng)用;套接字;Node.js
中圖分類號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2014)27-6298-03
Abstract: Modern life cannot do without the Internet, network communication technology and computer originated in the first UNIX system and Windows platform, although it started late, but the support of Internet technology has made great progress. Now a lot of network game client is based on Windows platform. Using the underlying communication technology after years of development, also appeared all kinds of technical solutions, this paper summed up the Windows platform in the computer network communication technology of the main technical method.
Key words: Real - Time Application ,Socket, Node.js
現(xiàn)代生活離不開互聯(lián)網(wǎng),計(jì)算機(jī)的網(wǎng)絡(luò)通信技術(shù)最先發(fā)源于UNIX系統(tǒng),而Windows平臺(tái)雖然起步稍晚,但是目前對(duì)互聯(lián)網(wǎng)技術(shù)的支持也有長足的進(jìn)步?,F(xiàn)在很多的網(wǎng)絡(luò)游戲客戶端都是基于Windows平臺(tái),而TCP/IP協(xié)議在計(jì)算機(jī)的網(wǎng)絡(luò)通信技術(shù)發(fā)揮著巨大的作用。在金融系統(tǒng)、社交應(yīng)用、網(wǎng)絡(luò)游戲等軟件中使用較多的是TCP,它能保證數(shù)據(jù)包的有序傳送,在通信鏈路建立后,所有的數(shù)據(jù)包都從該通道鏈路進(jìn)行傳送。
應(yīng)用層的網(wǎng)絡(luò)通信一般都通過Berkeley Socket編程接口實(shí)現(xiàn),在Windows平臺(tái)上對(duì)應(yīng)為Winsock技術(shù)。大部分Winsock API在WS2_32.dll中實(shí)現(xiàn),在WINSOCK2.H文件中申明[1]。目前應(yīng)用廣泛的應(yīng)用如:QQ、微信、微博,還有一些流行的網(wǎng)頁游戲、手機(jī)游戲,也直接或者間接的依賴Socket來傳送數(shù)據(jù)。Socket簡稱套接字,用于實(shí)現(xiàn)網(wǎng)絡(luò)上客戶和服務(wù)器之間的連接,套接字是在比較低的層次上通信的,不同的操作系統(tǒng)對(duì)Socket有不同的支持方式。使用Socket進(jìn)行網(wǎng)絡(luò)通訊屏蔽了復(fù)雜的網(wǎng)絡(luò)底層協(xié)議差異性。目前所有主流的操作系統(tǒng)對(duì)原生的Socket都有全面的支持。
1 傳統(tǒng)Socket通信過程
在TCP/IP網(wǎng)絡(luò)應(yīng)用中,通信的兩個(gè)進(jìn)程間相互主要采用C/S(客戶端/服務(wù)器)通信模式,即客戶向服務(wù)器發(fā)出服務(wù)請(qǐng)求,服務(wù)器接收到請(qǐng)求后,提供相應(yīng)的服務(wù)。使用此模型的通常情況是:網(wǎng)絡(luò)的中各節(jié)點(diǎn)設(shè)備的軟硬件資源、運(yùn)算能力不均等,需要共享,擁有眾多資源的服務(wù)主機(jī)提供服務(wù),資源較少的客戶請(qǐng)求服務(wù);網(wǎng)間進(jìn)程通信完全是異步的,相互通信的進(jìn)程間通常不共享內(nèi)存緩沖區(qū),服務(wù)端和客戶端的執(zhí)行過程如圖1所示:
服務(wù)器方需要首先啟動(dòng),并根據(jù)請(qǐng)求提供相應(yīng)服務(wù)。主要步驟:
1) 打開一通信通道并告知本地主機(jī),它愿意在互聯(lián)網(wǎng)地址的特定端口(如WWW為80,F(xiàn)TP為21等)接收客戶請(qǐng)求。
2) 等待客戶請(qǐng)求到達(dá)該端口。
3) 接收到客戶服務(wù)請(qǐng)求,處理該請(qǐng)求并發(fā)送應(yīng)答信號(hào)。接收到并發(fā)服務(wù)請(qǐng)求,啟動(dòng)一個(gè)新進(jìn)程來處理這個(gè)客戶請(qǐng)求,并交由該新進(jìn)程來處理此客戶后續(xù)請(qǐng)求。服務(wù)完成后,關(guān)閉此新進(jìn)程與客戶的通信鏈路并結(jié)束。
4) 返回第2步繼續(xù)等待新的客戶請(qǐng)求。
客戶方的主要執(zhí)行步驟:
1) 打開一通信通道,連接到服務(wù)器所在主機(jī)的特定端口。
2) 向服務(wù)器發(fā)服務(wù)請(qǐng)求,等待并接收應(yīng)答。
3) 接收服務(wù)器方返回的處理結(jié)果。
4) 再次發(fā)出服務(wù)請(qǐng)求直到結(jié)束。
5) 請(qǐng)求結(jié)束后關(guān)閉通信通道并終止。
2 Node.js中的網(wǎng)絡(luò)編程
傳統(tǒng)的網(wǎng)站服務(wù)器采用為每個(gè)連接分配一個(gè)線程的做法來提供網(wǎng)絡(luò)服務(wù)。雖然可以使用線程池來減少線程新建的時(shí)間,但是在處理并發(fā)請(qǐng)求上一直是一個(gè)棘手的問題。普遍的做法是通過增加服務(wù)器內(nèi)存和CPU數(shù)量硬件手段加以解決。
Node.js在設(shè)計(jì)之初就采用了全新的思想,采用一個(gè)單一的主服務(wù)進(jìn)程來處理所有的連接請(qǐng)求,但是所有的API調(diào)用都是非阻塞的,要求程序員同樣不能在處理函數(shù)進(jìn)行復(fù)雜計(jì)算。對(duì)文件或者數(shù)據(jù)庫這種比較耗時(shí)的操作,在讀寫完成后通過回調(diào)函數(shù)把結(jié)果數(shù)據(jù)通知到請(qǐng)求者。Node.js運(yùn)行平臺(tái)基于谷歌Chrome瀏覽器的JavaScript運(yùn)行環(huán)境,可以在所有主流操作系統(tǒng)上順暢運(yùn)行。它是一個(gè)容易快速構(gòu)建,可擴(kuò)展好的網(wǎng)絡(luò)應(yīng)用程序平臺(tái)。Node.js使用一個(gè)事件驅(qū)動(dòng)的、非阻塞I/O運(yùn)行控制模型,使得它輕巧、高效,十分適合運(yùn)行數(shù)據(jù)密集型分布式實(shí)時(shí)應(yīng)用程序的運(yùn)行[1]。
使用Node.js來開發(fā)網(wǎng)絡(luò)應(yīng)用的主要步驟:
1) 從官方網(wǎng)站www.nodejs.org下載對(duì)應(yīng)自己操作系統(tǒng)的Node.js安裝包;
2) 安裝Node.js;
3) 使用JavaScript語言編寫后臺(tái)應(yīng)用程序;
4) 使用node命令運(yùn)行編寫的應(yīng)用,注意監(jiān)聽端口不能被其他程序占用;
5) 利用控制臺(tái)等工具調(diào)試程序,確保程序運(yùn)行符合預(yù)期結(jié)果;
在Node.js中有三種socket:TCP、UDP、Unix域套接字。使用TCP需要引用net模塊,該模塊是Node.js中網(wǎng)絡(luò)編程的封裝。利用JavaScript的閉包特性,可以省去不少的參數(shù)傳遞,網(wǎng)絡(luò)應(yīng)用的編寫顯得簡單明了。如果要使用http協(xié)議,則可以直接使用http模塊;如果要做一個(gè)大數(shù)據(jù)、計(jì)算不太密集型的社交型或者企業(yè)門戶網(wǎng)站,還可以使用express模塊,使經(jīng)典的MVC模式提升開發(fā)質(zhì)量并縮短開發(fā)時(shí)間。
3 使用Socket.IO簡化網(wǎng)絡(luò)開發(fā)
很多的社交應(yīng)用和網(wǎng)絡(luò)游戲是基于網(wǎng)頁或者移動(dòng)設(shè)備的本地應(yīng)用程序的。在客戶端安裝一個(gè)Node.js也許有些大材小用。這時(shí)我們可以選擇使用Socket.IO客戶端來解決這個(gè)問題。
目前主流瀏覽器都能支持WebSocket,這樣就可以直接使用標(biāo)準(zhǔn)的Scoket編程步驟加上事件回調(diào)處理方式來進(jìn)行客戶端與服務(wù)器的通訊。Socket.IO的誕生則統(tǒng)一了網(wǎng)絡(luò)分布應(yīng)用的前后端通訊方式,即便再老式的瀏覽器,比如IE8,也能運(yùn)行基于“Socket”的網(wǎng)絡(luò)交互。
Socket.IO的第一個(gè)版本在Node.JS出現(xiàn)的不久就開發(fā)出來。目前1.0版本也已經(jīng)發(fā)布,還提供了對(duì)二進(jìn)制數(shù)據(jù)的傳輸支持,方便了圖片、聲音的文件的傳送,降低了網(wǎng)絡(luò)應(yīng)用的編寫復(fù)雜度。Socket.IO其實(shí)也是Web上的事件發(fā)生器(EventEmitter)。Socket.IO的1.0版本代碼已經(jīng)不再處理傳輸與瀏覽器兼容的事情了。那些工作已經(jīng)并入到新模塊Engine.IO里面了,Engine.IO是一套類WebSocket風(fēng)格的API實(shí)現(xiàn)。Socket.IO的服務(wù)端只有一千兩百多行代碼;客戶端代碼只有代碼不到一千行。
在分布式應(yīng)用中,客戶端可以使用Socket.IO連接后后端服務(wù)器來獲取資料。使用Socket.IO時(shí),不用關(guān)心包、幀、TCP等底層概念,而只需要關(guān)注什么事件被發(fā)送和接收。在Node.js上使用Socket.IO開發(fā)一個(gè)簡單聊天應(yīng)用[3]只需要很少的幾行代碼。Socket.IO還提供了namespace和room等概念,方便消息頻道及私有組內(nèi)部的通訊。
4 Node.js應(yīng)用的負(fù)載均衡設(shè)計(jì)
大多數(shù)輕量級(jí) Web 服務(wù)器,比如 nginx 和 lighttpd,都能夠針對(duì)多臺(tái) HTTP 服務(wù)器進(jìn)行負(fù)載平衡,但如果您想要在非 HTTP 服務(wù)器之間實(shí)現(xiàn)平衡,nginx 可能無法滿足要求[4]。而使用Node.js平臺(tái)后,由于由于Node.js是單線程非阻塞方式運(yùn)行的,沒有多進(jìn)程競爭也沒有死鎖,一臺(tái)標(biāo)準(zhǔn)配置的服務(wù)器也可以同時(shí)為上萬個(gè)客戶端同時(shí)提供服務(wù)。而現(xiàn)在的標(biāo)準(zhǔn)服務(wù)器一般都配置有多核CPU或者多個(gè)CPU。在這種情況下,可以讓一些CPU核去執(zhí)行計(jì)算型任務(wù),避免Node.js天生不適合復(fù)雜運(yùn)算的缺點(diǎn)。
從Node.js的0.8版本開始內(nèi)置了cluster的特性。對(duì)于小型的網(wǎng)站應(yīng)用,可以單獨(dú)使用Nodejs作為開發(fā)方案。在使用cluster時(shí),最重要的兩個(gè)概念是master和worker。其中是master總控主進(jìn)程,作為服務(wù)管理者,worker是具體服務(wù)進(jìn)程??梢愿鶕?jù)CPU的數(shù)量,啟動(dòng)相應(yīng)數(shù)量的worker。值得注意的是由于具體服務(wù)不是固定某個(gè)具體的worker上,所以同一客戶端的兩次http請(qǐng)求應(yīng)該沒有任何聯(lián)系,即沒有共享狀態(tài)才能有效的利用負(fù)載均衡,對(duì)于確實(shí)需要共享狀態(tài)的會(huì)話,可以把共享狀態(tài)存放在數(shù)據(jù)庫中。如果使用Socket長連接,則不存在會(huì)話狀態(tài)的問題,但是會(huì)大大降低并發(fā)處理能力。
4.1 簡單服務(wù)代理的編寫
前面是單機(jī)上的集群處理辦法,如果是多服務(wù)器,我們可以選擇一臺(tái)服務(wù)器來兼做服務(wù)代理。為了說明分布式處理思想,這里假設(shè)所有的后端服務(wù)器一直都是可用的,程序內(nèi)也不進(jìn)行任何錯(cuò)誤處理。它接收一個(gè)來自客戶端的套接字連接,隨機(jī)挑選一個(gè)實(shí)際目標(biāo)服務(wù)器進(jìn)行連接,然后將來自客戶端的所有數(shù)據(jù)轉(zhuǎn)發(fā)給該服務(wù)器,并將來自該服務(wù)器的所有數(shù)據(jù)都發(fā)回到客戶端。
假設(shè)每個(gè)服務(wù)請(qǐng)求的處理時(shí)間為100毫秒,服務(wù)配有4核CPU,代理程序和3個(gè)實(shí)際服務(wù)進(jìn)程分別占用1個(gè)CPU核充分利用多核來進(jìn)行并行計(jì)算。集群運(yùn)行后能同時(shí)處理3個(gè)請(qǐng)求,如果只有不到4個(gè)請(qǐng)求同時(shí)到達(dá),每個(gè)請(qǐng)求都會(huì)在接近100毫秒時(shí)間內(nèi)得到處理結(jié)果。如果同時(shí)收到30個(gè)服務(wù)請(qǐng)求,那么是最后發(fā)出請(qǐng)求的客戶端要大約1秒后(100毫秒*30/3)才能得到處理結(jié)果。為了提升效應(yīng)速度,可以多增加幾臺(tái)計(jì)算服務(wù)器。
上面的方案設(shè)計(jì)簡單,容易編寫,實(shí)際編程時(shí)可以通過一個(gè)配置文件確定工作服務(wù)器的地址和端口。這個(gè)方案的缺點(diǎn)也比較明顯,后端服務(wù)器的配置信息暴露給了客戶端,在云計(jì)算環(huán)境下同步更新困難,該方案沒有監(jiān)控所有后端服務(wù)進(jìn)程的狀態(tài)并自動(dòng)更新可用服務(wù)器的信息,極有可能出現(xiàn)將客戶端請(qǐng)求連接到失效后端服務(wù)器的情況。
4.2 借助Redis作為作業(yè)消息隊(duì)列
Redis是一個(gè)開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。只要選擇一臺(tái)普通配置的服務(wù)器,配置能夠適當(dāng)?shù)膬?nèi)存??蛻舳撕头?wù)工作進(jìn)程都直接連接到該服務(wù)器,這臺(tái)服務(wù)器其實(shí)充當(dāng)了一個(gè)網(wǎng)絡(luò)上的消息隊(duì)列。實(shí)現(xiàn)的思想如圖2所示:
具體實(shí)現(xiàn)分為以下幾個(gè)主要步驟:
1) 所有服務(wù)進(jìn)程向Redis訂閱自己可處理請(qǐng)求服務(wù)名稱的通知消息;
2) 客戶進(jìn)程發(fā)送請(qǐng)求到Redis內(nèi)存消息隊(duì)列并產(chǎn)生作業(yè)編號(hào),并通過Redis發(fā)布廣播通知。
3) 多個(gè)服務(wù)進(jìn)程收到通之后,按照先到先得的處理原則從從消息隊(duì)列中提取一個(gè)任務(wù)并開始處理;
4) 處理完畢后將結(jié)果放到一個(gè)單獨(dú)結(jié)果隊(duì)列中,該隊(duì)列實(shí)際上是一個(gè)以作業(yè)號(hào)作為key的哈希表。
這個(gè)方案的優(yōu)點(diǎn)是不需要配置服務(wù)進(jìn)行的地址和端口信息,可以按照請(qǐng)求量在線動(dòng)態(tài)增加服務(wù)進(jìn)程的數(shù)量。但是缺點(diǎn)是要多安裝Redis服務(wù),而Redis很可能成為通訊的瓶頸,也容易造成單點(diǎn)故障。當(dāng)然可以采用雙機(jī)熱備或者Redis提供的集群方法來避免單點(diǎn)故障。
5 結(jié)論
Windows系統(tǒng)是最常用的辦公和游戲的平臺(tái),現(xiàn)在的很多應(yīng)用都需要互聯(lián)網(wǎng)實(shí)時(shí)通信技術(shù),該文總結(jié)了在Windows平臺(tái)上開發(fā)這種應(yīng)用常用的技術(shù),通過本文的總結(jié)可以看出WinSocket通信是最基本的技術(shù),現(xiàn)代基于Node.js的各種實(shí)時(shí)通信技術(shù)其實(shí)是對(duì)原有技術(shù)封裝利用,但是利用這些現(xiàn)代技術(shù)將使我們更快地開發(fā)出高質(zhì)量的實(shí)時(shí)應(yīng)用。
參考文獻(xiàn):
[1] [美]Anthony Jones,Jim Ohlund.Windows網(wǎng)絡(luò)編程[M]. 楊合慶,譯.2版.北京:清華大學(xué)出版社,2002,33.
[2] Node.js官方網(wǎng)站.http://www.nodejs.org/.
[3] Socket.IO官方網(wǎng)站.http://socket.io/get-started/chat/.
[4] Noah Gift ,Jeremy Jones.使用Node.js作為完整的云環(huán)境開發(fā)堆棧. http://www.ibm.com/developerworks/cn/cloud/library/cl-nodejscloud/