Bạn nào chưa tìm hiểu về lí thuyết của giao thức TCP thì xem lại bài trước nhé !
Hãy chắc chắn vững vàng lí thuyết ở bài trước thì bài này mới hiểu vì mình không nhắc lại mà đi vào code luôn 🙂
Khởi tạo thư viện tcp
Tiếp tục tạo 2 file tcp.h và tcp.c và copy nội dung khởi tạo và add file thư viện vào project
Nội dung khởi tạo cho file tcp.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#ifndef TCP_H_ #define TCP_H_ //-------------------------------------------------- //Include cac thu vien can thiet #include <mega328p.h> #include <delay.h> #include <string.h> #include <stdlib.h> #include <stdint.h> #include <stdio.h> #include <spi.h> #include "uart.h" #include "net.h" //-------------------------------------------------- void TCP_read(uint8_t *TCP_Frame,uint16_t len); //-------------------------------------------------- #endif /* TCP_H_ */ |
Nội dung khởi tạo cho file tcp.c
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include "tcp.h" extern const uint8_t macaddr[6]; extern const uint8_t ip[4]; extern uint8_t debug_string[60]; void TCP_read(uint8_t *TCP_Frame,uint16_t len) { } //-------------------------------------------------- //end file |
Các bạn add thư viện tcp.h vào thư viện net.h và tiến hành kiểm tra gói IP có protocol = 0x06 ở hàm NET_ipRead để ném gói cho thư viện tcp xử lí
Vậy là xong quá trình khởi tạo mà chắc các bạn đã quá quen thuộc. Từ giờ là công việc của thư viện tcp
Thiết lập kết nối cho giao thức TCP
Chúng ta sẽ bắt xem đã nhận được gói tcp nào chưa nhé. Trong file hàm TCP_read mình thêm dòng debug
1 |
UART_putString("Da nhan 1 goi TCP\r\n"); |
Chạy mô phỏng, mở WireShark, chọn chế độ lọc IP để tránh show các gói không cần thiết, và mở phần mềm Hercules, tab TCP Client, điền IP của ENC28j60 là 192.168.1.197 và ấn Connect
Chúng ta đã bắt đc các gói TCP gửi đến khi ấn Connect, đồng thời WireShark cũng bắt được và Show cụ thể các gói tin ra
Click vào gói tin đầu tiên và mình đã thấy cơ Syn được SET, như vậy gói tin mở đầu của việc bắt tay đã được gửi tới cho ENC28J60. Ngay bây giờ chúng ta phải viết hàm để phản hồi lại gói tin này bằng các việc sau:
- Set cờ SYN và cơ ACK
- Trường acknowledgment number = sequence number +1
- sequence number = ngẫu nhiên
Mình sẽ tạo 1 Struct giống như đã tạo ở bài UDP và bỏ vào file tcp.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
//------------------------------------------------- typedef struct { uint8_t MAC_dich[6]; //--------------| uint8_t MAC_nguon[6]; // | => It is Ethernet Frame II uint16_t Ethernet_type; //--------------| uint8_t Header_length; //--------------| => IP uint8_t Services; // | uint16_t TotoLength; // | uint16_t Identification; // | uint16_t Flag; // | uint8_t TimeToLive; // | uint8_t Protocol; // | uint16_t CheckSum; // | uint8_t SourceIP[4]; // | uint8_t DestIP[4]; //--------------| uint16_t Source_Port;//---------------| //TCP uint16_t Dest_Port; // | uint32_t Sequence_Number;// | uint32_t Acknowledgement;// | uint8_t data_offset;// | uint8_t TCP_Flags;// | uint16_t Window;// | uint16_t TCP_Checksums;// | uint16_t Urgent_Pointer;// | uint8_t data[];//--------------------| }TCP_struct; //dinh nghia cac macro cho truong Flags #define TCP_CWR 0x80 #define TCP_ECE 0x40 #define TCP_URG 0x20 #define TCP_ACK 0x10 #define TCP_PSH 0x08 #define TCP_RST 0x04 #define TCP_SYN 0x02 #define TCP_FIN 0x01 //-------------------------------------------------- |
Bắt đầu viết mã cho hàm TCP_read, mình sẽ debug từng tí một, trước tiên là kiểm cờ SYN
1 2 3 4 5 6 7 8 9 10 |
void TCP_read(uint8_t *TCP_Frame,uint16_t len) { TCP_struct *TCP_Struct_Frame = (TCP_struct *)TCP_Frame; //kiem tra dia chi ip xem co phai no gui cho minh khong if( memcmp(TCP_Struct_Frame->DestIP,ip,4) )return; // dung memcmp de so sanh, neu khac thi thoat if(TCP_Struct_Frame->TCP_Flags == TCP_SYN) { UART_putString("Da nhan 1 goi goi tin ket noi\r\n"); } } |
Tiếp tục dùng Hercules gửi thử 1 tin Connect
OK rồi, giờ phải phản hồi lại, để phản hồi thì phải tính kèm checksum, nên mình sẽ tạo hàm tính checksum cho giao thức TCP hen
Checksum của TCP gồm toàn bộ gói TCP cộng thêm 8 byte IP, 2byte TCP length và 1 byte TCP Protocol ( tương tự gói checksum của UDP)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
uint16_t TCP_checksum(TCP_struct *TCP_Struct_Frame) { uint32_t checksum; uint8_t *ptr; uint16_t length; length = swap16(TCP_Struct_Frame->TotoLength) - 20 + 8 ; //tinh length bat dau tu checksum ptr = (uint8_t *)&TCP_Struct_Frame->SourceIP; //dia chi bat dau tinh checksum checksum=6 + length - 8; while(length>1) //cong het cac byte16 lai { checksum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1)); ptr+=2; length-=2; }; if(length) checksum+=((uint32_t)*ptr)<<8; //neu con le 1 byte while (checksum>>16) checksum=(uint16_t)checksum+(checksum>>16); //nghich dao bit checksum=~checksum; //hoan vi byte thap byte cao return swap16(checksum); } |
Quay trở lại với hàm TCP_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void TCP_read(uint8_t *TCP_Frame,uint16_t len) { uint16_t port; TCP_struct *TCP_Struct_Frame = (TCP_struct *)TCP_Frame; //kiem tra dia chi ip xem co phai no gui cho minh khong if( memcmp(TCP_Struct_Frame->DestIP,ip,4) )return; // dung memcmp de so sanh, neu khac thi thoat if(TCP_Struct_Frame->TCP_Flags == TCP_SYN) { //reply voi SYN|ACK //make reply memcpy(TCP_Struct_Frame->MAC_dich,TCP_Struct_Frame->MAC_nguon,6); memcpy(TCP_Struct_Frame->MAC_nguon,macaddr,6); TCP_Struct_Frame->CheckSum=0; memcpy(TCP_Struct_Frame->DestIP,TCP_Struct_Frame->SourceIP,4); //hoan vi source, dest memcpy(TCP_Struct_Frame->SourceIP,ip,4); //ip cua minh port = TCP_Struct_Frame->Source_Port; TCP_Struct_Frame->Source_Port = TCP_Struct_Frame->Dest_Port; TCP_Struct_Frame->Dest_Port = port; TCP_Struct_Frame->Acknowledgement = swap32(swap32(TCP_Struct_Frame->Sequence_Number) + 1); TCP_Struct_Frame->Sequence_Number = swap32(2071998); TCP_Struct_Frame->TCP_Flags = TCP_SYN | TCP_ACK; TCP_Struct_Frame->TCP_Checksums=0; TCP_Struct_Frame->Urgent_Pointer=0; TCP_Struct_Frame->CheckSum = NET_ipchecksum((uint8_t *)&TCP_Struct_Frame->Header_length); //tinh checksum cho goi IO TCP_Struct_Frame->TCP_Checksums = TCP_checksum(TCP_Struct_Frame); NET_SendFrame((uint8_t *)TCP_Struct_Frame,len); //gui goi tin tcp reply } if(TCP_Struct_Frame->TCP_Flags == TCP_ACK) { UART_putString("Da ket noi 1 client\r\n"); } } |
Biên dịch và chạy lại mô phỏng. Bây giờ khi ấn Connect trên Herculess chúng ta đã kết nối thành công tới server 🙂
WireShark cũng đã bắt lại toàn bộ quá trình !
Ngắt kết nối giao thức TCP
Hãy thử ấn Disconnect trên Hercules, bạn sẽ thấy ngay 1 gói tin với cờ FIN và ACK (Wikipedia nó không hề lừa mình 🙂 )
Tiếp tục bắt 2 cờ này và phản hồi lại nào ( cứ theo lí thuyết mà phang thôi 🙂 )
Tronng hàm TCP_read
1 2 3 4 5 |
else if(TCP_Struct_Frame->TCP_Flags == (TCP_FIN|TCP_ACK)) { UART_putString("Nhan 1 ban tin yeu cau ngat ket noi\r\n"); } |
Vẫn với phương châm test từng tí một. ! Tiếp tục test tiếp bằng debug
Rồi ! Giờ xóa dòng debug đi và gửi phản hồi lại
Code make header gần như copy y chang, chỉ sửa lại sequence number và acknowledgment number và tính lại checksum
Mình khai báo thêm uint32_t dat_ack ở đầu hàm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
else if(TCP_Struct_Frame->TCP_Flags == (TCP_FIN|TCP_ACK)) { //reply voi ACK //make reply memcpy(TCP_Struct_Frame->MAC_dich,TCP_Struct_Frame->MAC_nguon,6); memcpy(TCP_Struct_Frame->MAC_nguon,macaddr,6); TCP_Struct_Frame->CheckSum=0; memcpy(TCP_Struct_Frame->DestIP,TCP_Struct_Frame->SourceIP,4); //hoan vi source, dest memcpy(TCP_Struct_Frame->SourceIP,ip,4); //ip cua minh port = TCP_Struct_Frame->Source_Port; TCP_Struct_Frame->Source_Port = TCP_Struct_Frame->Dest_Port; TCP_Struct_Frame->Dest_Port = port; dat_ack = TCP_Struct_Frame->Acknowledgement; TCP_Struct_Frame->Acknowledgement = swap32(swap32(TCP_Struct_Frame->Sequence_Number) + 1); TCP_Struct_Frame->Sequence_Number = dat_ack; TCP_Struct_Frame->TCP_Flags = TCP_ACK; TCP_Struct_Frame->TCP_Checksums=0; TCP_Struct_Frame->Urgent_Pointer=0; TCP_Struct_Frame->CheckSum = NET_ipchecksum((uint8_t *)&TCP_Struct_Frame->Header_length); //tinh checksum cho goi IO TCP_Struct_Frame->TCP_Checksums = TCP_checksum(TCP_Struct_Frame); NET_SendFrame((uint8_t *)TCP_Struct_Frame,len); //gui goi tin tcp reply } |
Giờ ngắt kết nối thử nhé
WireShark đã bắt được gói tin phản hồi ngắt kết nối của ENC28J60 (ACK)
Hiện tại mới chỉ Client ngắt kết nối thôi, server tiếp tục gửi cở FIN|ACK để ngắt kết nối pha thứ 2
1 2 3 4 5 |
TCP_Struct_Frame->TCP_Flags = TCP_FIN|TCP_ACK; TCP_Struct_Frame->TCP_Checksums=0; TCP_Struct_Frame->TCP_Checksums = TCP_checksum(TCP_Struct_Frame); NET_SendFrame((uint8_t *)TCP_Struct_Frame,len); //gui goi tin tcp reply |
Server phản hồi FIN|ACK và client trả lời lại ACK, hoàn tất đóng kết nối
Tóm tắt quy trình
Các bạn có thể tải file cho bài này tại đây