Những khái niệm cơ bản trong lập trình mạng (lập trình socket)


Khi nói về mạng và lập trình mạng ngày nay, hầu hết chúng ta sẽ chỉ quan tâm đến TCP/IP, đây có thể coi là giao thức thống trị bởi nó là nền tảng cho Internet, tuy nhiên bên cạnh TCP/IP cũng còn nhiều kiến thức khác cũng như những thứ liên quan đến giao thức này.

Trong bài viết này tôi sẽ giới thiệu một số khái niệm cơ bản, cũng như ôn lại một vài kiến thức cũ giúp bạn có một nền tảng cơ bản về mạng và lập trình mạng. Các kiến thức sẽ được giới thiệu một cách ngắn gọn để bạn có một cái nhìn tổng quan về cách thức mọi thứ hoạt động. Các kiến thức sâu hơn sẽ được bổ sung thêm trong các bài viết riêng.

(ảnh minh họa từ: https://www.cs.dartmouth.edu/~campbell/cs50/socketprogramming.html)

Các toán tử bit

Khi ta đọc ghi dữ liệu, ta luôn phải làm trên đơn vị byte. Tuy nhiên với những trường giá trị nhỏ, người ta có thể kết hợp lại chung, khi đó một byte có thể chứa nhiều đơn vị nhỏ hơn và ta phải dùng các toán tử bit để đọc hoặc ghi các giá trị đơn lẻ đó.

AND: a AND b chỉ bằng 1 khi cả a và b đều bằng 1.

Ta thường dùng AND để “mask” một số bit nào đó, tức đưa tất cả các bít không liên quan về 0.

Ví dụ 1:

00110001b (a) AND 00001111b (b) = 00000001b

Trong ví dụ trên a = 00110001b, và nó được dùng để chứa 2 giá trị trên mỗi 4 bit. Vậy khi ta cần lấy giá trị của 4 bit thấp (bên phải) ta sẽ làm phép AND với 00001111b, ngược lại nếu muốn lấy 4bit cao ta dùng 11110000b (ta có thể kết hợp thêm với phép dịch bit – xem phần sau).

(Trong bài này ta quy ước nếu một số được viết ở dạng binary sẽ có thêm ký tự b phía sau, tương tự ta sử dụng ký tự h cho hex)

OR: a OR b bằng 1 khi một trong hai bằng 1

Ta thường dùng OR để “dựng bit”, tức đặt giá trị 1 cho một vài bit riêng lẻ nào đó.

Ví dụ 2:

Ta có a = 00100110b, nếu ta muốn đặt bit thứ 4 từ bên trái thành 1, ta sẽ viết:

a = a OR 00010000b, kết quả a = 00110110b

Vậy nếu muốn tắt riêng bit đó thì sao? Ta sẽ dùng phép AND:

Nếu a = 00110110b, và a = a AND 11101111b, kết quả sẽ là a = 00100110b

XOR: a XOR b bằng 1 khi a != b

Trên từng bit, nó cũng tương tự như: c = (a != b) ? 1 : 0

XOR có một đặc tính là: a == (a XOR b) XOR b

Tức là nếu ta XOR a với một giá trị b nào đó, sau đó lại tiếp tục lấy kết quả XOR với b một lần nữa, ta sẽ có lại giá trị a ban đầu. (Ah tôi vừa phát mình ra một thuật toán mã hóa dữ liệu :D)

Người ta dùng XOR trong nhiều trường hợp, ví dụ trong các thuật toán kiểm tra và sửa lỗi dữ liệu, ngoài ra bạn sẽ gặp XOR trong nhiều nơi khác nữa. Ví dụ khi đọc code Assembly, nếu muốn gán giá trị 0 cho eax, bạn sẽ thấy người ta dùng XOR eax, eax thay vì viết MOV eax, 0. Vì sao? Vì khi một số XOR với chính nó sẽ luôn cho ra giá trị 0, mã lệnh XOR eax, eax có kích thước nhỏ hơn MOV eax, 0 (vì eax có kích thước 4 byte nên giá trị 0 cũng là một số 4 byte), kích thước câu lệnh nhỏ hơn sẽ giúp tối ưu hơn.

NOT: cái này quá đơn giản rồi tự hiểu đi nhé!

Các phép dịch bít

Các phép dịch bit cho phép ta chuyển dịch các bit sang trái hoặc sang phải.

Trong ví dụ 1 phía trên, khi muốn lấy 4 bit cao (bên trái) của a (00110001b), ta dùng: a = a AND 11110000b

Khi đó kết quả là 00110000b, tuy nhiên giá trị này khi chuyển đổi sang thập phân sẽ là 60, bởi nó dính cả 4 bit 0 bên phải, nếu muốn lấy giá trị đúng, bạn sẽ phải dich chúng sang phải 4 bit: a = (a AND 11110000b) >> 4, kết quả bây giờ sẽ là 00000011b (3).

Khi học về networking bạn sẽ phải làm việc ở cấp độ bit, bởi rất nhiều các cấu trúc dữ liệu được tổ chức ở cấp độ này, chúng giúp tiết kiệm được rất nhiều không gian. Nếu có một tỷ gói tin, mỗi gói bạn tiết kiệm được 1 byte, điều đó đồng nghĩa với việc bạn tiết kiệm được một tỷ byte.

Khi nói về các bit, bạn cũng nghe về khái niệm cờ (flag), nó có nghĩa là 1 bit nào đó được dùng để đánh dấu một giá trị là bật hay tắt. Thường thì các bit này sẽ được đặt một cái tên nào đó, như trong phần Flags nói về các cờ trong cấu trúc header của gói tin IPv4 (https://en.wikipedia.org/wiki/Internet_Protocol_version_4#Flags), bạn sẽ thấy có 2 bit được đặt tên là DF và MF, khi bit DF = 1 ta gọi là cờ DF bật, ngược lại ta gọi là cờ DF tắt.

Địa chỉ MAC

Mỗi một thiết bị mạng sẽ được gán một số định danh, số này về nguyên tắc là duy nhất trên thế giới. Khi bạn kết nối vào một mạng wifi, hoặc cắm cáp vào switch, thiết bị đó (wifi hotspot, switch) và máy tính của bạn sẽ phải trao đổi thông tin và cả địa chỉ MAC của nhau và sẽ dùng chúng để gửi dữ liệu sau này.

TCP/IP:

Là một gói giao thức (protocol suite) cho phép bạn truyền/nhận dữ liệu. Sở dĩ gọi là một gói giao thức vì nó có nhiều giao thức chạy trên nhiều tầng khác nhau, ngay cả TCP và IP cũng là tên của 2 giao thức trong gói này với những nhiệm vụ khác nhau. Trong đó:

IP là một routed protocol cho phép bạn gửi các gói tin qua nhiều node để đến một đích nào đó. Một giao thức không phải routed protocol chỉ cho phép truyền nhận dữ liệu trong cùng một mạng, tức 2 máy phải “thấy” được nhau và có khả năng nói chuyện trực tiếp với nhau thông qua địa chỉ MAC.

Trong khi IP cho phép truyền các gói dữ liệu từ nguồn đến đích, TCP cho phép truyền chúng theo các luồng tin cậy. Bởi các gói tin IP có thể đi theo nhiều đường, gói sau đôi khi lại đến trước, một số gói có thể thất lạc… TCP giúp chúng ta kiểm tra lỗi, sắp xếp lại các gói tin sao cho khi đọc dữ liệu ta sẽ thấy nó y như lúc được gửi.

Ngoài IP, còn có IPX cũng là một routed protocol khác và cũng đã từng được dùng khá phổ biến trước đây (bạn nào chơi StarCraft 1 ngày xưa chắc sẽ nhớ cái này khi chơi mạng).

Địa chỉ IP:

Là một con số 32 bit (IPv4) hoặc 128 bit (IPv6) giúp định danh một máy tính trên mạng IP. Ta gọi nó là một địa chỉ bởi nó giúp ta tìm đến được một máy tính nào đó trên mạng IP một cách chính xác.

Bạn có thể tìm địa chỉ IP của mình bằng cách dùng lệnh ipconfig (Windows) hoặc ifconfig (Linux).

Ở đây bạn sẽ thấy có thêm hai thông số quan trọng nữa là Default Gateway và Subnet mask, chúng ta sẽ tìm hiểu vai trò của chúng trong phần sau.

Có nhiều địa chỉ IP hoặc nhóm địa chỉ đặc biệt, một địa chỉ mà máy tính nào cũng có là 127.0.0.1, nó còn được gọi là địa chỉ loopback, khi đó nếu bạn gửi một gói tin đến địa chỉ này, nó sẽ quay trở lại chính máy của bạn (mà không cần chuyển đến card mạng để gửi ra ngoài).

Routing:

Làm thế nào để một gói tin từ máy tính của bạn lại chạy đến đúng một máy tính khác của Google tận bên Mỹ?

Nó làm được vậy là nhờ các bộ định tuyến (router), các router này kết nối với nhau, biết thông tin về nhau nhờ người ta thiết lập hoặc thông qua các giao thức riêng của chúng (routing protocols). Khi bạn gửi dữ liệu đến một địa chỉ IP, máy tính của bạn sẽ xác định xem địa chỉ IP đó là thuộc cùng mạng hay khác mạng. Để xác định chúng sẽ lấy (IP của bạn AND subnet mask), nếu nó bằng với (IP đích AND subnet mask) có nghĩa là bạn đang cố gắng gửi đến một máy tính trong cùng mạng, ngược lại nó sẽ gửi gói tin đó đến router (có địa chỉ trong phần Default Gateway), rồi router sẽ tìm cách chuyển nó đến máy đích (có thể sẽ phải qua nhiều node khác nữa).

Ở đây bạn sẽ thấy Subnet mask luôn có dạng 111111100000…., tức một dãy bit 1 rồi đến một dãy 0.

Như đã nói ở trên, bạn chỉ có thể gửi dữ liệu trực tiếp đến một máy khác kết nối cùng mạng thông qua địa chỉ MAC (bao gồm cả trường hợp bạn gửi đến Default Gateway), do đó nó còn một giao thức nữa là ARP giúp tìm ra địa chỉ MAC của một máy thông qua IP. Bạn có thể tham khảo thêm lệnh arp (Windows/Linux) để biết thêm về giao thức này.

Hãy thử gõ lệnh tracert http://www.google.com và xem để đến máy chủ Google, các gói tin từ máy bạn phải đi qua những node nào.

Port:

Giờ tôi đã biết làm sao gói tin đi từ một máy tính A sang máy tính B, nhưng làm sao nó chỉ ra được chính xác ứng dụng nào đã gửi/nhận gói tin đó?

Mỗi một ứng dụng khi muốn gửi hoặc nhận dữ liệu thông qua TCP/IP sẽ phải đăng ký 1 con số 16 bit với hệ thống, ta gọi con số này là port (cổng), hiểu nôm na đây là một con số logic giúp phân biệt giữa các luồng dữ liệu với nhau, giống như một process thì có ID, một ô nhớ thì có địa chỉ vậy. Mỗi cổng là duy nhất trên một máy tính, do vậy khi đã mở một cổng, ứng dụng khác sẽ không thể mở được nữa.

Các cổng từ 0-1023 đều được gán cho các giao thức phổ biến, như 80 cho HTTP, 443 cho HTTPS, 21 cho FTP, 25 cho SMTP…

Một sự kết hợp giữa địa chỉ IP và port sẽ cho phép việc truyền dữ liệu ở cấp ứng dụng đến ứng dụng.

Socket:

Là một khái niệm dùng trong lập trình mạng TCP/IP, một socket đại diện cho một cặp IP:port và các luồng dữ liệu của nó.

Có 2 loại socket: server socket được dùng để mở một cổng và chờ kết nối đến (quá trình chờ này gọi là listen, và khi chấp nhận một kết nối đến ta gọi là accept), và client socket được dùng để kết nối đến một server socket nào đó (đã mở).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s