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