在前面的幾篇有說到,不同的 process 間可以使用 IPC 通信來進行溝通,但如果是不同電腦呢 ? 要如何溝通呢 ? 我們這時就可以使用 socket 來進行溝通。
在開始說明 socket 前,我們需要先準備一些基本知識,那就是常聽到的 tcp/ip
。
TCP/IP 通訊模型
tcp/ip
它是一種網路協定,它定義了點對點如何的傳輸,如何將資料封裝、定址、傳輸、路由以及在目的地如何接受,全部都加以標準化
,它基本上可以分為四層應用層
、傳輸層
、網路互連層
與網路介面層
,它常被視為簡化的七層 OSI 模型。
在了解 socket 前,我們需要了解應用層
與傳輸層
的基本概念。
應用層
這個層級主要是定義 :
應用程式的溝通協定,也可以理解為不同應用程式如何協同工作。
在這個層級的協定,大部份都會使用到兩個傳輸協定tcp
與udp
,至於何時使用 tcp 或 udp 取決於,該協定是否保證資料完整的傳送到另一端,這邊我們只要記得tcp可靠
而udp不可靠
這兩件事情就夠了。
我們常用的 http 就是屬於這一層協定,smtp 也屬於這層,我們簡單的來說明一下 http 的概念。
HTTP (超文字傳輸協定)
它是一種應用層的傳輸協定,它主要定義了下面的事情 :
它是一個用戶端與伺服器端請求和應答的標準
通常 http 用戶端的發出一個請求,它會建立一個到伺服器端的 TCP 連線 。
傳輸層
這個層級主要是定義 :
定義點到點如何傳輸
其中tcp、udp
就是這一層,我們簡單的來說明一下 tcp 的工作,就會知道這個層級主要是做啥事情。
TCP (傳輸控制協定)
它是根據傳輸層
的定義,所完成的協定,這個協定宗旨在於 :
提供一個可靠(不會掉資料)的資料流傳送服務
那它用什麼方法來處理可靠的問題呢 ? 答案就是tcp三次對話
。
我們簡單的用下面例子來說明,假設 A 和 B 兩台電腦要傳輸資料了,這時就開始要準備建立 tcp 連線,但在連線之前需要有三次對話 :
A : 我想發資料給你(B),好嗎 ?
B : 喔好啊,我會發送一個同意連接
與要求同步(ack1)
的給你喔 。
A : 好我收到了,然後我回發了一個同意要求同步(ack2)
的給你喔。
經過以上三次對話,A 才能正式的傳送資料給 B。
只要是對可靠性要求的傳輸,都必須使用 tcp 協定。
那 Socket 是啥 ?
在簡單的理解完上面的網路概念後,我們就可以來理解,什麼是 socket 。
socket 是在應用層
與傳輸層
之間的一個抽象層,它是一組接口,隱藏了底層的複雜操作,同時你也可以把他想成一個雙向的 endpoint ,可以給人連線或連線它人,而且由於它有綁定一個特定的 port 所以這也代表傳輸層它們那邊可以用它來定位應用程式。
你可以吧 socket 想成一個兩個節點的節點的
溝通機制
的概念,然後在由其它語言來實作這個概念。
而他的運作流程圖如下 :
Node 的 Socket 實作
在上面大概理解了 socket 機制後,我們簡單的使用 nodejs 來建立兩個 socket 節點,一個為 server ,另一個則為 client。
Server端
我們 server socket 主要是用來接受 socket client 的資料。
var net = require('net');
var HOST = '127.0.0.1';
var PORT = '61111';
net.createServer(function(sock){
console.log('Server open !');
sock.on('data',function(data){
console.log('I receved data from client :' + data);
});
sock.on('close',function(){
console.log('close');
});
}).listen(PORT, HOST);
Client端
而我們的 client 端則為每一秒傳輸資料到 server 端去。
var net = require('net');
var HOST = '127.0.0.1';
var PORT = '61111';
var client = net.Socket();
client.connect(PORT, HOST, function(){
console.log('client connected');
setInterval(function(){
client.write('I am Mark');
},1000)
});
client.on('close', function(){
console.log('close client');
});
Socket VS Websocket
不一樣的東西,前面有提到,socket 基本上是屬於傳輸層與應用層的抽象層,而 websocket 它基本是屬於應用層的協定了。
ws://example.com/wsapi
wss://secure.example.com/
上面先說結論,我們先簡單的來看一下什麼是 websocket 。
輪詢
在 web 開發時,我們有時後會遇到這樣的需求,就是當 server 端資料有變動時,client 端畫面會變動,很古老的做法是輪詢
,也就是說定時的去 server 端問問看有沒有空的資料,但很明顯的,這浪費很多資源,有可能 10 次呼叫,只有一次才真的有新的資料。
Comet
再來出現的是comet
它是一種推播技術,也就是 server 那可以更新資訊時傳送到 client 端,它有一種實現方式長時間輪詢 long-polling
,它是 client 和 server 進行溝通後,它的連線會先留一段時間,等某段時間沒資料時,再發送請求到 server 端,
但他還是有缺點,那就是當 server 沒有資料時,那個連線還會繼續連接,會造成 server 資源浪費的。
Websocket
最後是websocket
,它是 html5 規範發布的新協議,等同於應用層(ex. http),它的基本概念為 server 端與 client 端的建立是持久連接
,使用這項協議後, server 端可以主動傳送資料給 client 端,而且它 tcp 握手只要一次,不像 http1.0 每次使用都需要 1 次的 tcp 握手,但 http1.1 時,則可以在一次的連接處理多個請求,但還是比不上 websocket 的一次。
這種東東很適合用來處理聊天室
或報價系統
這類型的應用。
NodeJS 中的 Socket.io
socket.io
是一個 nodejs 的套件,它做了什麼事情呢 ? 它將上面提的溝通方式全部的整合在一起,讓我們前端與後端可以處理推播功能
,它有支援以下的傳輸方式 :
- xhr-polling
- xhr-multipart
- htmlfile
- websocket
- flashsocket
- jsonp-polling
上面對種類中有polling
字樣就是我們上面所說的溝通方式,我們直接拿官網的程式碼來看看使用的方法,下面為 server 端的程式碼。
var io = require('socket.io').listen(8080);
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
而下面為前端。
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8080');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
這樣就可以完成推播功能。但這邊有個地方有注意一下,我們都沒有設定要用什麼傳輸方式,那它要用那種傳輸方式呢 ? polling ? websocket ?
socket.io 自動會根據瀏覽器支援到什麼程度來決定使用什麼。
很方便吧 ~ 事實上 socket.io 就等同於 net 開發者都很熟悉的signaR
。
Network Socket VS Unix Socket
在網路上尋找 socket 文章時,常常會看到 socket 有分為network socket
與unix socket
,那這兩個有什麼不同呢 ?
上面一章節有說到 socket 本身是建立在 tcp/ip 網路層級的應用層與傳輸層中間,所以說它本身一開始是定位於網路溝通使用,也就是我們這邊說的network socket
,但後來發現在 socket 的概念上,建立一個 IPC 通信,可以使我們電腦內的溝通更有效率,這時的產生出來的東東,就是我們所謂的unix socket
。
unix socket
不像network socket
一樣,它不需要經過網路協定,tcp 連線、握手等步驟,而只是將應用層的資料,從一個 process 傳輸到另一個 process上,它於其它 ipc 機制相比來說,算是目前最常使用的 ipc 機制 (會有另一篇來比較 ipc 機制)。
在 nodejs 中,我們也可以建立 unix socket 程式碼如下,這裡的程式碼基本上與 network socket 相似,不同點在於我們 listen 不是個網址,而是個檔案,而你可以將這個檔案想成為socket 節點
。
const net = require('net');
const server = net.createServer((c) => {
// 'connection' listener
console.log('client connected');
c.on('end', () => {
console.log('client disconnected');
});
c.write('hello\r\n');
c.pipe(c);
});
server.on('error', (err) => {
throw err;
});
server.listen("/tmp/echo.sock", () => {
console.log('server bound');
});
然後我們就可以使用以下指令,建立一個連線到/tmp/echo.sock
的 client 端 socket 。
nc -U /tmp/echo.sock
然後 client 端這邊應該會收到從 server 端的 socket 傳送回來的訊息 :
hello
然後 server 端應該也會輸出以下訊息 :
client connected