Computer >> Hướng Dẫn Máy Tính >  >> Kết Nối Mạng >> Internet

Ngôn Ngữ Của Internet: Máy Tính Giao Tiếp Như Thế Nào

Câu Chuyện Về Giao Tiếp

Bạn từng tự hỏi Internet thực sự "nói" như thế nào? Làm sao một máy tính "trò chuyện" với máy tính khác qua Internet?

Khi con người giao tiếp, dùng từ ghép thành câu có vẻ có ý nghĩa. Câu chỉ có nghĩa vì chúng ta đồng ý ý nghĩa cho câu đó. Chúng ta định nghĩa protocol giao tiếp, nói cho cùng.

Thực ra, máy tính nói chuyện nhau qua Internet theo cách tương tự. Nhưng chúng ta đang vội. Con người dùng miệng giao tiếp, hãy tìm ra "miệng" của máy tính trước.

Socket Xuất Hiện

Socket là khái niệm cơ bản nhất trong khoa học máy tính. Có thể xây dựng toàn bộ mạng thiết bị interconnected dùng socket.

Giống mọi thứ trong CS, socket là khái niệm rất abstract. Vì vậy, thay vì định nghĩa socket là gì, dễ hơn định nghĩa socket làm gì.

Socket làm gì? Giúp hai máy tính giao tiếp. Làm sao? Có hai method: send() và recv() cho send và receive.

Ổn, nhưng send() và recv() thực sự send/nhận gì? Con người động miệng trao đổi từ. Socket dùng method trao đổi bits và bytes.

Minh họa method với ví dụ. Giả sử hai máy tính A và B. Máy A muốn nói gì đó với B. Vì vậy B lắng nghe A nói. Trông như sau:

Ngôn Ngữ Của Internet: Máy Tính Giao Tiếp Như Thế Nào
Socket Với Buffer

Đọc Buffer

Trông hơi lạ, phải không? Thứ nhất, cả hai máy trỏ vào thanh giữa, tiêu đề 'buffer'.

Buffer là gì? Buffer là memory stack. Nơi lưu data cho mỗi máy, allocated bởi kernel.

Tại sao cả hai trỏ cùng buffer? Thực ra không chính xác. Mỗi máy có buffer riêng allocated bởi kernel của nó và network transport data giữa hai buffer riêng. Nhưng không muốn đi sâu network detail, giả định cả hai truy cập cùng buffer đặt "đâu đó trong khoảng trống giữa".

Biết hình dung trực quan, abstract thành code.

# Máy A gửi data
computerA.send(data)
# Máy B nhận data
computerB.recv(1024)

Code snippet làm chính xác điều hình ảnh đại diện. Trừ một curiosity, không nói computerB.recv(data). Thay vào đó, chỉ định số ngẫu nhiên thay data.

Lý do đơn giản. Data qua network truyền thành bits. Vì vậy, khi recv ở computerB, chỉ định số bits sẵn sàng nhận tại một thời điểm.

Tại sao chọn 1024 bytes nhận một lần? Không lý do đặc biệt. Thường tốt nhất chỉ định số byte nhận là power of 2. Chọn 1024 = 2¹⁰.

Buffer xử lý ra sao? Máy A write/send data lưu trữ vào buffer. Máy B quyết định read/recv 1024 byte đầu tiên lưu trong buffer đó.

Tuyệt! Nhưng hai máy biết nói chuyện với nhau như thế nào? Ví dụ, máy A write vào buffer, làm sao biết máy B sẽ pick nó lên? Diễn đạt lại, làm sao đảm bảo connection giữa hai máy có buffer unique?

Port Và IP

Ngôn Ngữ Của Internet: Máy Tính Giao Tiếp Như Thế Nào
Port Và IP Của Máy Tính

Hình trên 보여 cùng hai máy làm việc cùng một chi tiết thêm. Có một bunch số liệt kê trước mỗi máy theo chiều dài thanh.

Xem thanh dài trước mỗi máy là router kết nối máy cụ thể đến internet. Những số trên thanh gọi là ports. Máy tính bạn có hàng nghìn port available ngay bây giờ. Mỗi port cho phép một socket connection. Chỉ show 6 port trong hình, nhưng bạn hiểu ý.

Port dưới 255 thường reserved cho system call và low-level connection. Khuyên mở connection trên port high 4-digit, như 8000. Không vẽ buffer trong hình, nhưng giả định mỗi port có buffer riêng.

Thanh cũng có số associated. Số này gọi IP address. IP address có bunch port associated. Nghĩ theo cách sau:

 127.0.0.1
  / | \
 /  |  \
/   |   \
8000 8001 8002

Tuyệt, setup connection trên port cụ thể giữa máy A và B.

# computerA.py
import socket
computerA = socket.socket()
# Kết nối localhost:8000
computerA.connect(('127.0.0.1', 8000))
string = 'abcd'
encoded_string = string.encode('utf-8')
computerA.send(encoded_string)

Code cho computerB.py:

# computerB.py
import socket
computerB = socket.socket()
# Lắng nghe localhost:8000
computerB.bind(('127.0.0.1', 8000))
computerB.listen(1)
client_socket, address = computerB.accept()
data = client_socket.recv(2048)
print(data.decode('utf-8'))

Trông như nhảy trước về code, nhưng tôi step through. Có hai máy A và B. Cần một gửi data, một nhận.

Chọn tùy ý A gửi, B nhận. Dòng computerA.connect((‘127.0.0.1’, 8000) kết nối computerA đến port 8000 IP 127.0.0.1.

Lưu ý: 127.0.0.1 thường nghĩa localhost, tham chiếu máy của bạn

Đối với computerB, bind port 8000 IP 127.0.0.1. Bạn thắc mắc tại sao cùng IP cho hai máy khác nhau.

Vì tôi cheat. Dùng một máy demo socket (kết nối từ và đến cùng máy cho đơn giản). Thường hai máy khác nhau có IP khác nhau.

Biết chỉ bits gửi như phần data packet, nên encode string trước khi gửi. Tương tự decode string ở máy B. Nếu chạy local hai file, chắc chắn chạy computerB.py trước. Chạy computerA.py trước sẽ lỗi connection refused.

Phục Vụ Client

Ngôn Ngữ Của Internet: Máy Tính Giao Tiếp Như Thế Nào
Truyền Data Giữa Client Và Server

Chắc nhiều bạn nhận ra mình mô tả client-server model đơn giản. Thấy từ hình trên, chỉ thay máy A thành client, máy B thành server.

Có luồng giao tiếp liên tục giữa client và server. Trong code trước, mô tả one-shot data transfer. Thay vào đó, muốn constant stream data từ client đến server. Tuy nhiên, cũng muốn biết khi data transfer complete, để biết dừng lắng nghe.

Dùng analogy xem thêm. Tưởng tượng cuộc hội thoại sau giữa hai người.

Ngôn Ngữ Của Internet: Máy Tính Giao Tiếp Như Thế Nào

Hai người giới thiệu bản thân. Tuy nhiên, không nói cùng lúc. Giả sử Raj nói trước. John chờ Raj xong mới giới thiệu mình. Dựa trên heuristic học được nhưng có thể mô tả protocol.

Client và server cần protocol tương tự. Không thì làm sao biết lượt gửi packet data?

Làm đơn giản minh họa. Giả sử gửi data là array string. Array như sau:

arr = ['random', 'strings', 'that', 'need', 'to', 'be', 'transferred', 'across', 'the', 'network', 'using', 'sockets']

Data trên viết từ client đến server. Tạo constraint khác. Server cần accept data exactly equivalent data occupied by string gửi lúc đó.

Ví dụ, client gửi string 'random', giả sử mỗi char 1 byte, string chiếm 6 byte. 6 byte = 6*8 = 48 bits. Vì vậy, để 'random' transfer qua socket client->server, server cần biết truy cập 48 bits cho packet data cụ thể đó.

Cơ hội tốt break down problem. Có vài thứ cần giải quyết trước.

Làm Sao Biết Số Byte String Chiếm Trong Python?

Bắt đầu tìm length string. Dễ, gọi len(). Nhưng vẫn cần số byte occupied by string, không chỉ length.

Convert string sang binary trước, tìm length binary representation. Đó là số byte used.

len('random'.encode('utf-8')) cho kết quả muốn

Làm Sao Gửi Số Byte Mỗi String Cho Server?

Dễ, convert số byte (integer) thành binary representation số đó, gửi cho server. Giờ server expect nhận length string trước rồi nhận string.

Server Biết Khi Nào Client Gửi Xong Tất Cả String?

Nhớ analogy hội thoại, cần cách biết data transfer complete. Máy tính không có heuristic tự tin cậy. Cho random rule. Nói khi gửi string 'end', nghĩa là server nhận hết string và có thể close connection. Tức không dùng string 'end' phần khác array trừ cuối.

Protocol thiết kế đến đây:

Ngôn Ngữ Của Internet: Máy Tính Giao Tiếp Như Thế Nào
Protocol Đơn Giản Của Chúng Tôi

Length string 2 byte, tiếp theo string thực tế variable length. Phụ thuộc string length gửi packet trước, luân phiên gửi string length và string. EOT = End Of Transmission, gửi string 'end' nghĩa không còn data gửi.

Lưu ý: Trước khi tiếp tục, chỉ ra điều gì. Đây protocol rất đơn giản và ngu ngốc. Muốn xem well-designed protocol, nhìn HTTP protocol.

Code ra. Comment trong code dưới self-explanatory.

Great, client chạy. Cần server.

Giải thích vài dòng code cụ thể trong gists. Đầu tiên, từ clientSocket.py.

len_in_bytes = (len_of_string).to_bytes(2, byteorder='little')

Dòng trên convert số thành bytes. Tham số đầu to_bytes function là số byte allocated cho kết quả convert len_of_string sang binary.

Tham số hai quyết định Little Endian hay Big Endian. Đọc thêm ở đây. Hiện tại chỉ biết luôn dùng little cho tham số đó.

Dòng tiếp theo:

client_socket.send(string.encode('utf-8'))

Convert string sang binary format dùng encoding 'utf-8'.

Tiếp, trong serverSocket.py:

data = client_socket.recv(2)
str_length = int.from_bytes(data, byteorder='little')

Dòng đầu nhận 2 byte data từ client. Nhớ khi convert string length sang binary trong clientSocket.py, quyết định lưu kết quả 2 byte. Vì vậy đọc 2 byte cho data đó.

Dòng tiếp convert binary format sang integer. byteorder là "little", match byteorder dùng ở client.

Chạy hai socket, server sẽ print string client gửi across. Thiết lập communication!

Kết Luận

Đã cover khá nhiều. Cụ thể: socket là gì, dùng như thế nào, thiết kế protocol đơn giản ngu ngốc. Muốn học thêm socket, khuyến khích đọc Beej's Guide To Network Programming. E-book đó có nhiều thứ hay.

Có thể áp dụng đọc bài này cho problem phức tạp hơn như stream ảnh từ RaspberryPi camera đến máy tính. Enjoy!

Muốn follow Twitter hoặc GitHub. Cũng check blog ở đây. Luôn available nếu muốn reach out!

Originally published at https://redixhumayun.github.io/networking/2019/02/14/how-the-internet-speaks.html on February 14, 2019.