Socket 的哩哩扣扣
network
Lastmod: 2019-12-15

在前面的幾篇有說到,不同的 process 間可以使用 IPC 通信來進行溝通,但如果是不同電腦呢 ? 要如何溝通呢 ? 我們這時就可以使用 socket 來進行溝通。

在開始說明 socket 前,我們需要先準備一些基本知識,那就是常聽到的 tcp/ip

TCP/IP 通訊模型

tcp/ip 它是一種網路協定,它定義了點對點如何的傳輸,如何將資料封裝、定址、傳輸、路由以及在目的地如何接受,全部都加以標準化,它基本上可以分為四層應用層傳輸層網路互連層網路介面層,它常被視為簡化的七層 OSI 模型。

圖片來源:鳥哥

在了解 socket 前,我們需要了解應用層傳輸層的基本概念。

應用層

這個層級主要是定義 :

應用程式的溝通協定,也可以理解為不同應用程式如何協同工作。

在這個層級的協定,大部份都會使用到兩個傳輸協定tcpudp,至於何時使用 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 socketunix 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

參考資料

comments powered by Disqus