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
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