+16

Có thể chạy hai backend chung port không

TL;DR: Chỉ cần bind vào 2 địa chỉ mạng khác nhau là được.

Giới thiệu

Nếu các bạn còn thắc mắc tại sao khi chạy backend thì mọi người hay nói "bind vào địa chỉ 0.0.0.0" thì frontend mới truy cập được, còn bind vào địa chỉ localhost hay 127.0.0.1 thì frontend sẽ không truy cập được thì đây là bài viết dành cho bạn. Các bạn cần có một chút kiến thức về hệ điều hành, địa chỉ ip là gì, port là gì, backend, frontend, ...

Bắt đầu

Như các bạn đã biết khi frontend gửi một request tới backend thì request đó sẽ đi từ frontend hay còn gọi là client (desktop, mobile, laptop, ...) từ trình duyệt xuống hệ điều hành, xong qua card mạng của máy đó, sau đó được chuyển tiếp thông qua một vài thiết bị mạng như switch, wifi, router ra public internet cuối cùng sẽ đến card mạng của máy tính backend và được chuyển lên hệ điều hành để xử lý sau đó.

Kể cả khi bạn gọi tới localhost thì vẫn phải qua một thiết bị chính là thiết bị bạn đang sử dụng.

Câu chuyện của việc bind 0.0.0.0, localhost, 127.0.0.1 khác nhau như nào - cùng tìm hiểu chi tiết phía dưới nhé.

Thử nghiệm

Mình sẽ viết một webserver đơn giản bằng nodejs, sau đó chạy chúng với các trường hợp bind khác nhau. main.js

const http = require("http");
const express = require("express");

const port = process.env.PORT || 8080;
const bind = process.env.BIND;

console.log(bind, port);
const app = express();
app.get('/', (req, res) => res.send('Hello, World!\n'));

const server = http.createServer(app);
server.listen(port, bind);

Chạy thử project, ở đây mình truyền bind thông qua biến môi trường BIND

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ BIND=localhost node main.js 
localhost 8080

Ok vậy là đã chạy thành công backend ở port 8080, các bạn có thể kiểm tra bằng lệnh netstat

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ netstat -lnt # flags: listen, tcp, number
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
...
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN     
...

Nếu các bạn ở trên Windows các bạn có thể vào Resource Monitor > Network > Listening Ports để xem thông tin liên quan tới port.

Trước khi thử nghiệm thử gọi request trong các setup khác nhau, mình sẽ show các bạn thông tin network của các máy trong mạng (đã lược bỏ các thông tin không cần thiết).

Máy đang chạy backend (laptop)

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)

tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
        inet 10.103.107.31  netmask 255.255.255.0  destination 10.103.107.31
        inet6 fe80::50b8:173b:e4af:d62b  prefixlen 64  scopeid 0x20<link>
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)

wlp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.14  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 2001:ee0:8202:d8a3:3522:e37:5cb0:9edc  prefixlen 64  scopeid 0x0<global>
        ether 38:ba:f8:95:07:3b  txqueuelen 1000  (Ethernet)

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ 

Ở đây mình có 3 card mạng chính:

  • wlp2s0: là card mạng wifi, đang bắt wifi và có ip là 192.168.1.14
  • tun0: là card mạng ảo, do openvpn quản lý, ip của máy hiện tại trong vpn là 10.103.107.31
  • lo: là card mạng ảo, viết tắt của loopback, card mạng đặc biệt này tham chiếu tới chính máy đó, tí nữa các bạn sẽ hiểu ý của mình.

Máy khác trong cùng mạng vpn (vm)

vagrant@tuana9a-dev ~ $ ifconfig tun0
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
        inet 10.103.107.8  netmask 255.255.255.0  destination 10.103.107.8
        ...
vagrant@tuana9a-dev ~ $

Trông nó sẽ như sau

image.png

Các bạn nếu vẫn chưa hiểu card mạng có thể xem thêm phần Giải thích thêm phía dưới.

Ok vậy mình cùng test thử các request tới các địa chỉ khác nhau và từ các máy khác nhau xem sao:

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ curl localhost:8080
Hello, World!
tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ 

Việc gửi request tới localhost trên chính máy tính đó: Thành công.

Giờ mình sẽ chuyển qua máy khác (vm).

vagrant@tuana9a-dev ~ $ curl 10.103.107.31:8080
curl: (7) Failed to connect to 10.103.107.31 port 8080: Connection refused
vagrant@tuana9a-dev ~ $

Từ một máy tính trong cùng vpn có ip là 10.103.107.8: Không thành công.

Giờ mình sẽ thử chạy lại backend với BIND=0.0.0.0 xem sao nhé

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ BIND=0.0.0.0 node main.js 
0.0.0.0 8080

Trên cùng máy

tuana9a@tuana9a-XPS-13-9370:~$ curl localhost:8080
Hello, World!
tuana9a@tuana9a-XPS-13-9370:~$ 

Từ một máy khác trong mạng vpn

vagrant@tuana9a-dev ~ $ curl 10.103.107.31:8080
Hello, World!

Lần này thì Thành công

Giải thích: địa chỉ 0.0.0.0 là một địa chỉ đặc biệt, địa chỉ này mang ý nghĩa tôi sẽ lắng nghe ở mọi địa chỉ - mọi card mạng trong máy tính hiện tại, tức bất kể request đến từ card mạng nào miễn là tới được máy tính hiện tại và có port là 8080 thì hệ điều hành sẽ forward nó tới tiến trình backend của bạn để xử lý.

Việc bind vào địa chỉ 0.0.0.0 khá tiện lợi (kiểu nhạc nào cũng nhảy) khi các bạn không cần am hiểu quá về network. Vậy liệu chúng ta có thể giới hạn lại việc backend của chúng ta chỉ lắng nghe từ một dải ip được hay không, đây chính là ý nghĩa thực sự của bind.

Ví dụ: Bạn có 2 hệ thống website ở trên cùng một máy tính ở cùng port 8080 và bạn không muốn phải đổi port và đi nhớ port nào cho website nào. Nếu máy tính đó có nhiều card mạng, ví dụ như máy tính hiện tại của mình có 2 card mạng:

  • wifi với ip: 192.168.1.14
  • openvpn với ip: 10.103.107.31

Thì chúng ta có thể chạy 2 backend và bind vào 2 địa chỉ ip tương ứng. Mình sẽ sửa app của mình đi một chút và chạy theo kiểu trên

const http = require("http");
const express = require("express");

const port = process.env.PORT || 8080;
const bind = process.env.BIND;
const id = process.env.ID; // Thêm cái này để phân biệt backend

console.log(bind, port);
const app = express();
app.get('/', (req, res) => res.send(`Hello, World! ${id}\n`)); // Xong rồi return id về client để còn biết backend nào

const server = http.createServer(app);
server.listen(port, bind);

Chạy lại lệnh trên ở shell (terminal) hiện tại trên máy laptop

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ BIND=192.168.1.14 ID=1 node main.js
192.168.1.14 8080

Mở thêm một shell (terminal) khác

tuana9a@tuana9a-XPS-13-9370:~/Projects/test$ BIND=10.103.107.31 ID=2 node main.js
10.103.107.31 8080

Test trên cùng máy (laptop)

curl localhost:8080
tuana9a@tuana9a-XPS-13-9370:~$ curl localhost:8080
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Connection refused
tuana9a@tuana9a-XPS-13-9370:~$ 
tuana9a@tuana9a-XPS-13-9370:~$ curl 192.168.1.14:8080
Hello, World! 1
tuana9a@tuana9a-XPS-13-9370:~$ 
tuana9a@tuana9a-XPS-13-9370:~$ curl 10.103.107.31:8080
Hello, World! 2
tuana9a@tuana9a-XPS-13-9370:~$ 

Test trên máy khác (vm)

6:17:50 vagrant@tuana9a-dev ~ curl 10.103.107.31:8080
Hello, World! 2
6:50:40 vagrant@tuana9a-dev ~ 

Vậy là máy ở giải mạng 10.103.107.x sẽ được backend 2 "phục vụ". Ngon vậy là có thể chạy 2 backend cùng port trên cùng một máy rồi.

Giải thích thêm

Giải thích nhanh card mạng: đây là thứ giúp hệ điều hành (OS) có thể truy cập internet và gửi các gói tin trong mạng mọi truy cập từ máy bạn ra internet và từ internet về máy bạn đều phải quả card mạng, card mạng ảo đơn giản là cơ chế ảo hóa của hệ điều hành, thường các card mạng ảo sẽ được quản lý bởi phần mềm hệ thống (có thể cài thêm) và các phần mềm này sẽ điều khiển logic của các gói tin nếu gói tín đó tới được card mạng ảo.

VD: Với card mạng vật lý: khi bạn vào facebook ở trên máy laptop của bạn, gói tin đi từ trình duyệt tới card wifi máy laptop của bạn, card wifi sẽ gửi gói tin tới router-wifi sau đó gói tin của bạn được router-wifi gửi qua network của việt nam, cứ như vậy máy này qua máy kia tới máy chủ của facebook, tới card mạng máy chủ facebook, máy chủ sau đó forward tới server backend của facebook, sau đó server facebook xử lý và trả về kết quả, gói tin lại đi ngược lại, đi qua internet, đi tới router-wifi của bạn, sau đó router-wifi sẽ gửi gói tin này tới máy tính của bạn, card mạng trên máy tính của bạn (card wifi) sẽ nhận gói tin này và sau đó đưa cho OS sử lý và OS chuyển tiếp gói tin tới trình duyệt (Chrome chẳng hạn) và hiển thị trên giao diện cho bạn.

Với card mạng ảo, mình sẽ lấy openvpn làm ví dụ: mô hình của openvpn hay các vpn truyền thống sẽ có một vpn-server tập chung, sau đó các máy client sẽ cài một phần mềm vpn tương ứng (openvpn) để kết nối vào server tập chung này. Khi các bạn kết nối openvpn thì các bạn sẽ được cấp 1 ip "ảo" ứng với đó là một card mạng ảo (VD: 10.103.107.31 và "tun0" ở phía trên).

Nhiệm vụ của card mạng ảo này như sau: nếu máy tính của mình gửi request tới một địa chỉ nằm trong dải 10.103.107.0/24 thì theo routing table OS sẽ sử dụng card mạng "tun0" này để gửi gói tin, card mạng ảo này thực chất được quản lý bởi phần mềm vpn client tương ứng, phần mềm này sau đó sử dụng card mạng vật lý để chuyển tiếp gói tin tới vpn server bằng kết nối đã thiết lập trước đó (UDP hoặc TCP), sau đó vpn server sẽ gửi tiếp gói tin đó tới máy trong mạng.

Như vậy về mặt hành vi chúng ta đang truy cập một máy tính khác trong dải mạng private (10.x.x.x) mà không cần phải ngồi chung văn phòng - Từ đó sinh ra VPN: Virtual Private Network.

Hết.

Cảm ơn ae đã đọc 😃


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.