Trong bài trước, chúng ta đã làm quen với việc đóng mở kết nối của giao thức TCP, tiếp tục bài này mình sẽ hướng dẫn các bạn truyền nhận dữ liệu trong giao thức TCP
Nhận dữ liệu từ client gửi lên
Khi client gửi dữ liệu lên cho server, nó sẽ set cờ PSH|ACK, chúng ta sẽ kiểm tra cờ này trong hàm TCP_read nhé
1 2 3 4 |
else if(TCP_Struct_Frame->TCP_Flags == (TCP_PSH|TCP_ACK)) { } |
Tính độ dài của dữ liệu gửi đến
Căn cứ vào trường IP->Totolength để tính độ dài, IP->Totolength là độ dài của gói IP + TCP. Do đó mình sẽ trừ đi 20 ( độ dài mặc định của gói IP) và căn cứ vào trường TCP->dataOfset để tính độ dài phần header của gói TCP
Trường TCP->dataOfset có độ dài 4 bit và là 4bit cao nên mình >>4. Mỗi giá trị trong dataOfset tương ứng 4byte nên ta nhân 4 lên. ( TCP->dataOfset >> 4)* 4 = TCP->dataOfset >> 2
Tóm lại: Độ dài của trường TCP data = Totolength – 20 – ( dataOfset >> 2 )
Sau khi tính được độ dài thì mình sẽ in từng byte data ra màn hình debug bằng lệnh UART_putChar
1 2 3 4 5 6 |
//tinh do dai cua goi data va in ra man hinh datalength= swap16(TCP_Struct_Frame->TotoLength) -20 - (TCP_Struct_Frame->data_offset >> 2); // ( >> 4)*4 = >> 2 sprintf(debug_string,"%u:",datalength); UART_putString(debug_string); for(i=0;i<datalength;i++) UART_putChar(TCP_Struct_Frame->data[i]); |
Phản hồi lại ACK cho client
Khi nhận được gói tin, chúng ta bắt buộc phải phản hồi lại cho client bằng cờ ACK, mình sẽ tận dụng luộn gói dữ liệu client vừa gửi đến và cắt bỏ đi trường TCP->Data và remake lại 1 vài thông tin, cụ thể như sau:
- Totolength phải giảm đi 1 lượng bằng dữ liệu của TCP vì mình cắt bỏ đi mà
- Acknowledgement sẽ không phải là + thêm 1 nữa mà phải cộng thêm độ dài của trường TCP->data Như vậy bên client nhận được gói tin ACK sẽ biết chắc chắn là sever đã nhận được đúng từng này data rồi. Còn nếu sai client tự động gửi lại. Bởi cơ chế phản hồi này nên giao thức TCP rất đáng tin cậy
Toàn bộ code xử lí cờ PSH|ACK, các bạn khai báo thêm các biến cục bộ i,datalength trên đầu hàm nhé !
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 |
else if(TCP_Struct_Frame->TCP_Flags == (TCP_PSH|TCP_ACK)) { //tinh do dai cua goi data va in ra man hinh datalength= swap16(TCP_Struct_Frame->TotoLength) -20 - (TCP_Struct_Frame->data_offset >> 2); // ( >> 4)*4 = >> 2 sprintf(debug_string,"%u:",datalength); UART_putString(debug_string); for(i=0;i<datalength;i++) UART_putChar(TCP_Struct_Frame->data[i + ((TCP_Struct_Frame->data_offset >> 2) - 20) ]); UART_putString("\r\n"); //make reply len-=datalength; //resize len 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; TCP_Struct_Frame->TotoLength = swap16(40); //defaul totolength TCP_Struct_Frame->data_offset = 0x50; //defaul data_offset 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) + datalength); 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 } |
Tiếp tục test gửi thử bằng chức năng TCP Client của phần mềm Hercules
WireShark cũng đã bắt được đầy đủ các gói tin
Viết bản tin phản hồi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void TCP_send(TCP_struct *TCP_Struct_Frame,uint16_t len,uint8_t *data) { uint16_t data_length; strcpy(TCP_Struct_Frame->data,data); data_length=strlen(data); len+=data_length; TCP_Struct_Frame->TotoLength = swap16(swap16(TCP_Struct_Frame->TotoLength) + data_length); //make totolength TCP_Struct_Frame->CheckSum=0; TCP_Struct_Frame->TCP_Checksums=0; TCP_Struct_Frame->TCP_Flags = TCP_PSH|TCP_ACK; 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); } |
Ngay khi nhận được tin nhắn và gửi tra ACK, mình sẽ gọi hàm repply lại
1 |
TCP_send(TCP_Struct_Frame,len,"Xin chao TCP Client\r\n"); //(fin|ack) |
Phần mềm Hercules đã nhận được gói tin phản hồi, tuy nhiên, ở hàm trên, mình mới phản hồi như kiểu UDP chứ chưa kiểm tra lại xem đã gửi thành công chưa, vì như đã nói lúc nãy, TCP có cơ chế gửi và kiểm tra rất chặt chẽ, gửi 1 lúc chưa thấy cờ ACK phản hồi sẽ phải gửi lại. Tuy nhiên vấn đề này không dễ , chúng ta sẽ hoàn thiện nó trong các bài tiếp.
Dowload
Tải code cho bài này tại đây
chờ bài 14 của bác :(((
link tải DIE rồi em ơi