Chuyển phát nhanh có đảm bảo!

Trong mô hình OSI 7 lớp, TCP chiếm 1 vị trí quan trọng trong lớp 4 bên cạnh UDP, SCTP và DCCP. Và ngày nay giao thức chúng ta sử dụng nhiều nhất cũng chính là TCP. Với lập trình viên, chúng ta thường xuyên sử dụng TCP thông qua 2 giao thức HTTP và Websocket ở tầng 7.

Vấn đề gửi nhận

Vậy tại sao TCP lại quan trọng và được sử dụng nhiều đến vậy? Có lẽ nhiều bạn ở đây đã từng gửi và nhận thư rồi nhỉ? Để gửi và nhận thư thì chúng ta cần biết được địa chỉ của người nhận và ghi rõ ở bì thư thì đơn vị chuyển phát họ mới gửi đến đúng người nhận được, và ngược lại, để nhận được thư thì người gửi cũng phải biết địa chỉ cũng chúng ta và ghi rõ ở bì thư. Trọng mạng internet của chúng ta các gói tin bản chất là các tín hiệu gửi qua các thiết bị vật lý, việc bị mất tín hiệu do đường truyền không ổn định, do các thiết bị vật lý bị hỏng hoặc có vấn đề. Khi một tín hiệu bị mất đi nó sẽ khiến cho toàn bộ gói tin bị thay đổi. Hãy ví dụ nhé, chúng ta cần gửi gói tin "HelloWorld" qua mạng, gói tin này có thể bị chia nhỏ thành 10 gói tin khác nhau: 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'. Vậy điều gì xảy ra khi chúng ta bị mất 5 gói tin đầu? Tất nhiên rồi, chúng ta sẽ chỉ còn lại 5 gói tin và cái mà chúng ta nhận được chỉ còn la: World.

Mục tiêu ra đời

Để giải quyết các vấn đề kể trên, TCP đã ra đời với các mục tiêu:

  1. Đảm bảo gói tin phải đến được đích (thông qua cơ chế ACK), người nhận và người gửi phải biết được thông tin của nhau
  2. Đảm bảo người nhận có thể kiểm tra tính toàn vẹn của gói tin để biết được gói tin có bị lỗi không để yêu cầu gửi lại
  3. Đảm bảo thứ tự của các gói tin để thực hiện việc ghép nối gói tin lớn khi gửi nhận
  4. Đảm bảo rằng các gói tin trùng lặp sẽ bị loại bỏ
  5. Hạn chế tắc nghẽn đường truyền

Cái giá phải trả

Tuy nhiên để đảm bảo được các mục tiêu trên thì TCP cũng phải trả một chi phí không hề rẻ. Phần header của 1 gói tin TCP bao gồm:

  1. Source Port: 16 bits
  2. Destination Port: 16 bits
  3. Sequence Number: 32 bits
  4. Acknowledgment Number: 32 bits
  5. Data Offset: 4 bits
  6. Reserved: 6 bits
  7. Flags (9 bits): 9 bits
  8. Window: 16 bits
  9. Checksum: 16 bits
  10. Urgent Pointer: 16 bits
  11. Options (Variable 0–320 bits, in units of 32 bits)
  12. Padding: max 32-bit

Phức tạp nhưng phải thế!

Chúng ta có thể thấy phần header cực kì phức tạp và tốn nhiều bytes (tối thiểu 160 bits = 20 bytes), khi bạn chỉ có nhu cầu gửi 1 byte và bạn mất 20 bytes đi kèm, nó giống như việc bạn ăn 1 bán bún nhưng phải trả tiền 21 bát vậy. Để có thể hiểu nội dung ở phần dưới trước tiên chúng ta cần hiểu khái niệm Initial Sequence Number (ISN) đã nhé.

Initial Sequence Number

Mình đã cố nghĩ nhiều cách để minh hoạ cho dễ hiểu cái ISN này, tuy nhiên quay lại vẫn thấy wiki viết dễ hiểu hơn nhiều.

ISN là một con số 32 bit được 2 đầu gửi nhận sinh ra một cách ngẫu nhiên khi khởi tạo kết nối. Số thứ tự này được dùng để đánh dấu các khối dữ liệu gửi từ mỗi máy tính. Sau mỗi byte được truyền đi, số này lại được tăng lên. Nhờ vậy ta có thể sắp xếp lại chúng khi tới máy tính kia bất kể các gói tới nơi theo thứ tự thế nào.

Trên lý thuyết, mỗi byte gửi đi đều có một số thứ tự và khi nhận được thì máy tính nhận gửi lại tin báo nhận (ACK). Trong thực tế thì chỉ có byte dữ liệu đầu tiên được gán số thứ tự trong trường số thứ tự của gói tin và bên nhận sẽ gửi tin báo nhận bằng cách gửi số thứ tự của byte đang chờ.

Ví dụ: Máy tính A gửi 4 byte với số thứ tự ban đầu là 100 (theo lý thuyết thì 4 byte sẽ có thứ tự là 100, 101, 102, 103) thì bên nhận sẽ gửi tin báo nhận có nội dung là 104 vì đó là thứ tự của byte tiếp theo nó cần. Bằng cách gửi tin báo nhận là 104, bên nhận đã ngầm thông báo rằng nó đã nhận được các byte 100, 101, 102 và 103. Trong trường hợp 2 byte cuối bị lỗi thì bên nhận sẽ gửi tin báo nhận với nội dung là 102 vì 2 byte 100 và 101 đã được nhận thành công.

Nội dung trong header

Bây giờ hãy cùng xem nội dung của phần header có gì nhé:

  1. Source Port: 16 bits - Port của thiết bị gửi
  2. Destination Port: 16 bits - Port của thiết bị nhận
  3. Sequence Number: 32 bits - Được khởi tạo ban đầu bằng ISN, dùng để đảm dữ liệu được gửi nhận theo đúng thứ tự và đảm bảo khả năng gửi lại khi gói tin bị thất lạc
  4. Acknowledgment Number: 32 bits - Dùng để xác nhận rằng dữ liệu đã được nhận thành công
  5. Data Offset: 4 bits - Quy định kích thước của TCP header, header có độ dài tối thiểu 5 words và tối đa 15 words (2^4 - 1), mỗi word có độ dài là 4 vậy 1 header có độ dài tối thiểu 20 bytes và tối đa là 60 bytes
  6. Reserved: 6 bits: dùng cho tương lại, tạm thời bằng 0
  7. Flags (9 bits): 9 bits: Chứa các cờ
  8. Window size: 16 bits - Bạn hãy tưởng tượng như bạn đang chiếu phim vậy, mỗi khi gửi được 1 đoạn của gói tin cũng giống như chiếu được 1 đoạn phim, và window size chính là kích thước của đoạn tin hay chính là số ảnh trong 1 thước phim bạn chiếu qua trong 1 thời điểm
  9. Checksum: 16 bits - Dùng để kiểm tra xem có byte nào bị hỏng trong quá trình truyền tin không, hay nói cách khác là nội dung gói tin có dầy đủ hay không để yêu cầu gửi lại.
  10. Urgent Pointer: 16 bits - Nếu cờ URG bật thì giá trị trường này chính là số từ 16 bits mà số thứ tự gói tin (sequence number) cần dịch trái.
  11. Options (Variable 0–320 bits, in units of 32 bits) - Trường tùy chọn, có thể dùng trong tương lai.

Nó thực sự khó hiểu đúng không? Nhưng nó là thứ chúng ta cần phải hiểu nếu chúng ta muốn lập trình mạng và nó là kiến thức để chúng ta đưa ra khi phải lựa chọn một giao thức nào đó cho chương trình của mình.

Một số kiểu tấn công

  1. Chiếm quyền điều khiển: nếu biết được Sequence Number, host và port của máy gửi, kẻ tấn công có thể giả mạo gói tin và gửi đến máy đích, từ đó chiểm được quyền điều khiển
  2. Nghe lén: kẻ tấn công có thể nghe lén các thông tin gửi qua tcp
  3. Flooding: kẻ tấn công sẽ gửi liên tục gói tin ví dụ như sync đến server và làm cho nó luôn bận rộn

Cách giải quyết cho kiểu tấn công 1 và 2 là chúng ta có thể kết hợp giữa việc xác thực người dùng và sử dụng mã hoá SSL. Còn đối với kểu tấn công thứ 3, có lẽ chúng ta phải mua các loại phần cứng chuyên dụng, hoặc thuê các dịch vụ cloud đã hỗ trợ sẵn.

Khi nào dùng TCP

Với độ tin cậy cao nhưng phải trả cái giá không hề rẻ, TCP phù hợp với:

  1. Các ứng dụng web, các ứng dụng cho mobile như các website, chat, thương mại điện tử, gửi nhận mail, gửi nhận file ...
  2. Các trò chơi trực tuyết không yêu cầu gửi nhận quá cao như game dạng turnbase hay các game không yêu cầu đồng bộ vị trí.

Các ứng dụng không nên dùng TCP và phù hợp hơn với UDP có thể là:

  1. Các ứng dụng livestreaming yêu cầu gửi nhận dữ liệu liên tục và chấp nhận việc mất gói tin
  2. Các game online kiểu virtual world nhiều người chơi và phải đồng bộ vị trí để hiển thị liên tục.

Tổng kết lại

TCP là một giao thức quan trọng được sử dụng nhiều nhất hiện nay trên tất cả các lĩnh vực CNTT dù ít hay nhiều. Nó có những cơ chế hay ho để giúp chúng ta đảm bảo được khả năng gửi nhận gói tin theo đúng thứ tự và chống được tắc nghẽn, tuy nhiên để làm được điều đó thì nó cũng phải hy sinh tương đối nhiều dung lượng, chính vì thế nó sẽ không nhanh được bằng UDP.

TCP có rất nhiều thứ, để hiểu được hết thì tương đối phức tạp, riêng chuyện đọc rfc đã là rất đau đầu rồi, thế nên là một người lập trình ứng dụng, mình sẽ chỉ dừng lại ở đây thôi. Những anh em nào đang cần TCP socket server để làm game, làm app có thể tham khảo open source ezyfox-server nhé.

Tham khảo:

  1. TCP
  2. TCP sequence acknowledgement numbers
  3. TCP sequence acknowledgement
  4. Understanding tcp sequence acknowledgment numbers