網絡編程 - - TCP套接字通信及編程實現

概述

TCP(Transmission Control Protocol,傳輸控制協議)是一種面向連接的、可靠的傳輸層協議。在網絡編程中,TCP常用于實現客戶端和服務器之間的可靠數據傳輸。本文將基于C語言實現TCP服務端和客戶端建立通信的過程。

三次握手

在 TCP 連接建立之前,客戶端和服務器之間需要進行三次握手來同步雙方的序列號,并確認雙方都準備好進行數據傳輸

  • 第一次握手:客戶端向服務器發送一個 SYN(同步序列編號)報文段,表示請求建立連接。客戶端進入 SYN_SENT 狀態
  • 第二次握手:服務器收到 SYN 報文段后,回復一個 SYN-ACK(同步序列編號 + 確認)報文段,表示同意建立連接。服務器進入 SYN_RCVD 狀態
  • 第三次握手:客戶端收到 SYN-ACK 報文段后,回復一個 ACK(確認)報文段,表示確認收到服務器的響應。客戶端和服務器都進入 ESTABLISHED 狀態,連接正式建立

在這里插入圖片描述

圖片來源:https://img-blog.csdnimg.cn/39bb4f4da21a4513b9506ecdf6a40cf3.png

四次揮手

通信結束時,客戶端或服務器可以發起斷開連接的請求。斷開連接的過程稱為四次揮手,以確保雙方都能正確關閉連接并釋放資源

  • 第一次揮手:主動關閉方(通常是客戶端)發送一個 FIN(終止)報文段,表示不再發送數據。主動關閉方進入 FIN_WAIT_1 狀態
  • 第二次揮手:被動關閉方(通常是服務器)收到 FIN 報文段后,回復一個 ACK 報文段,表示確認收到 FIN。被動關閉方進入 CLOSE_WAIT 狀態,而主動關閉方進入 FIN_WAIT_2 狀態
  • 第三次揮手:被動關閉方在處理完所有未完成的數據后,發送一個 FIN 報文段,表示自己也不再發送數據。被動關閉方進入 LAST_ACK 狀態
  • 第四次揮手:主動關閉方收到 FIN 報文段后,回復一個 ACK 報文段,表示確認收到 FIN。主動關閉方進入 TIME_WAIT 狀態,等待一段時間(通常為2倍的最大報文段生命周期,即2MSL),以確保被動關閉方收到了最后的 ACK。之后,主動關閉方進入 CLOSED 狀態,連接完全關閉

2MSL:MSL 的默認值是 30 秒,基于經驗選擇的一個保守估計,用來確保大多數網絡環境下的數據包都能被接收或者超時。

大部分操作系統都允許用戶調整 MSL 的值,從而改變 TIME_WAIT 狀態的持續時間

在這里插入圖片描述

圖片來源:https://i-blog.csdnimg.cn/blog_migrate/843f121dd50cd8458daf1fa834bc1f36.png

TCP保證可靠傳輸方式

  • 序列號:每個TCP報文段都有一個序列號,表示該報文段中的第一個字節在整個數據流中的位置。接收方可以根據序列號重新排序接收到的報文段,確保數據按順序傳遞
  • 確認應答:接收方在收到報文段后,會發送一個確認應答,告訴發送方哪些數據已經成功接收。發送方根據確認應答判斷是否需要重傳丟失或損壞的報文段
  • 超時重傳:如果發送方在一定時間內沒有收到確認應答,它會認為報文段可能丟失或延遲,并重新發送該報文段。TCP使用動態調整的超時機制來優化重傳策略
  • 流量控制:TCP使用滑動窗口機制來控制發送方的發送速率,確保接收方不會被過多的數據淹沒。接收方會在確認應答中告知發送方當前可用的接收窗口大小,發送方根據這個信息調整自己的發送速率
  • 擁塞控制:TCP通過多種算法(如慢啟動、擁塞避免、快速重傳和快速恢復)來動態調整發送方的發送速率,避免網絡擁塞。這些算法旨在在網絡負載較高時減小發送速率,在網絡條件改善時逐漸增加發送速率

TCP通信實現流程

涉及到的庫方法

  • 創建套接字(Socket)

    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
    
  • 綁定(Bind)套接字到指定的IP地址和端口

    #include <sys/socket.h>
    int bind(int socket, const struct sockaddr *address, socklen_t address_len);
    
  • 監聽(Listen)客戶端的連接請求

    #include <sys/socket.h>
    int listen(int socket, int backlog)
    
  • 接受(Accept)客戶端的連接

    #include <sys/socket.h>
    int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
    
  • 連接、發送和接收數據(Send/Recv)

    #include <sys/socket.h>
    int connect(int socket, const struct sockaddr *address, socklen_t address_len);
    ssize_t send(int socket, const void *buffer, size_t length, int flags);
    ssize_t recv(int socket, void *buffer, size_t length, int flags);
    
  • 關閉連接(Close)

    #include <unistd.h>
    int close(int fildes);
    

代碼實現

服務器代碼(Linux)

tcp_server.h

#ifndef TCP_SERVER_H
#define TCP_SERVER_H#include <pthread.h>
#include <netinet/in.h>
#include "cJson.h"
#include "common_base.h"// 定義常量
#define PORT 18888
#define BUFFER_SIZE 1024
#define TCP_IP "127.0.0.1"// 外部函數聲明
extern void getSerialNoStr(char *buf);/*** 啟動服務器主循環,等待客戶端連接*/
void start_serve_tcp(void *arg);/*** 解析接收到的消息
*/
void parse_message(char *data, size_t data_size);#endif // TCP_SERVER_H

tcp_server.c

#include "tcp_server.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>/*** 啟動TCP服務器,等待客戶端連接并處理接收到的數據。** @param arg 傳遞給線程的參數,通常為NULL。*/
void start_serve_tcp(void *arg) 
{int server_fd, client_fd;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);char buffer[BUFFER_SIZE] = {0};// 創建 Socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){printf("Socket 創建失敗");return;}// 配置服務器地址memset_s(&server_addr, sizeof(server_addr), 0, sizeof(server_addr));server_addr.sin_family = AF_INET;// 配置端口號和IPserver_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = inet_addr(TCP_IP);// 綁定 Socketif (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {printf("綁定失敗\n");close(server_fd);return;}// 開始監聽if (listen(server_fd, 5) == -1){printf("監聽失敗\n");close(server_fd);return;}printf("服務器已啟動,監聽地址:%s:%d\n", inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));// 等待客戶端連接while (1){printf("等待客戶端連接...\n");if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len)) == -1) {printf("接受客戶端連接失敗");continue;}printf("客戶端已連接:%s:%d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 接收數據int received_size;printf("receive before client_fd: %d\n", client_fd);while ((received_size = recv(client_fd, buffer, BUFFER_SIZE, 0)) > 0) {printf("收到數據,大小: %d 字節\n", received_size);parse_message(buffer, received_size);printf("jsonString: %s\n", jsonString);sleep(1);if(jsonString){              printf("receive after client_fd: %d\n", client_fd);// 發送 JSON 數據到客戶端ssize_t sent_size = send(client_fd, jsonString, strlen(jsonString), 0);if (sent_size == -1){printf("發送數據失敗\n");} else {printf("成功發送 %zd 字節到客戶端\n", sent_size);}free(jsonString);jsonString = NULL;}else {printf("生成 JSON 數據失敗\n");}}if (received_size == 0){printf("客戶端已斷開連接\n");} else if (received_size == -1){printf("接收數據失敗");}close(client_fd);}close(server_fd);
}void parse_message(char *data, size_t data_size) 
{cJSON *rootMsg = NULL;  cJSON *serialNo = NULL; // 序列號cJSON *netCmd = NULL;   // 操作行為MANUAL_TRIG_PARAM manualTrParam;    // 手動觸發抓拍接口傳參結構體// 確保消息頭部完整性(消息類型:4字節,數據長度:4字節)if (data_size < 8){printf("數據長度不足,無法解析\n");return;}// 解析消息類型和數據長度int message_type = ntohl(*(int *)data);int data_length = ntohl(*(int *)(data + 4));printf("消息類型: %d\n", message_type);printf("數據長度: %d\n", data_length);// 檢查數據長度是否匹配if (data_size < 8 + data_length) {printf("數據長度與實際內容不匹配\n");return;}// 解析消息內容char *message_content = (char *)malloc(data_length + 1);if (!message_content) {printf("內存分配失敗\n");return;}memcpy(message_content, data + 8, data_length);message_content[data_length] = '\0'; // 確保字符串以 \0 結尾printf("消息內容: %s\n", message_content);rootMsg = cJSON_Parse(message_content);if (NULL == rootMsg){printf("rootMsg is not json tpye\n");free(message_content);return;}printf("解析成功\n");serialNo = cJSON_GetObjectItem(rootMsg, "serialNo");netCmd = cJSON_GetObjectItem(rootMsg, "netCmd");if (serialNo && netCmd){printf("serialNo: %s\n", serialNo->valuestring);printf("netCmd: %s\n", netCmd->valuestring);} else{printf("缺少必要的 JSON 字段\n");cJSON_Delete(rootMsg);free(message_content);return;}char serial[64] = {0};getSerialNoStr(serial);printf("getSerialNoStr: %s\n", serial);printf("serialNo: %s\n", serialNo->valuestring);if(strncmp(serial, serialNo->valuestring, 9) == 0){// TODO:業務處理}else{printf("serialNo is not equal\n");}cJSON_Delete(rootMsg);free(message_content);
}
客戶端代碼(Windows)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")void send_message(const char *ip, int port, int message_type, const char *content) {WSADATA wsa;SOCKET sock;struct sockaddr_in server_addr;char buffer[1024];int recv_size;char recv_buffer[1024];// 初始化 Winsockif (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {printf("Winsock 初始化失敗,錯誤代碼: %d\n", WSAGetLastError());exit(EXIT_FAILURE);}// 創建 Socketif ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {printf("Socket 創建失敗,錯誤代碼: %d\n", WSAGetLastError());WSACleanup();exit(EXIT_FAILURE);}// 配置服務器地址server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);server_addr.sin_addr.s_addr = inet_addr(ip);// 連接服務器if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {printf("連接服務器失敗,錯誤代碼: %d\n", WSAGetLastError());closesocket(sock);WSACleanup();exit(EXIT_FAILURE);}// 準備消息int data_length = strlen(content);int net_message_type = htonl(message_type);int net_data_length = htonl(data_length);memcpy(buffer, &net_message_type, 4);memcpy(buffer + 4, &net_data_length, 4);memcpy(buffer + 8, content, data_length);// 發送消息send(sock, buffer, 8 + data_length, 0);// 接收服務器返回的數據if ((recv_size = recv(sock, recv_buffer, sizeof(recv_buffer) - 1, 0)) == SOCKET_ERROR) {printf("接收數據失敗,錯誤代碼: %d\n", WSAGetLastError());} else {recv_buffer[recv_size] = '\0'; // 確保以 null 結尾printf("服務器返回: %s\n", recv_buffer);}closesocket(sock);WSACleanup();
}int main() {send_message("127.0.0.1", 18888, 1, "{id: 'FS123456', command: 'NET_CAP'}");return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/66212.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66212.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66212.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2023-2024 學年 廣東省職業院校技能大賽(高職組)“信息安全管理與評估”賽題一

2023-2024 學年 廣東省職業院校技能大賽(高職組“信息安全管理與評估”賽題一&#xff09; 模塊一:網絡平臺搭建與設備安全防護第一階段任務書任務 1&#xff1a;網絡平臺搭建任務 2&#xff1a;網絡安全設備配置與防護DCRS:DCFW:DCWS:DCBC:WAF: 模塊二&#xff1a;網絡安全事件…

thinkphp6 + redis實現大數據導出excel超時或內存溢出問題解決方案

redis下載安裝&#xff08;window版本&#xff09; 參考地址&#xff1a;https://blog.csdn.net/Ci1693840306/article/details/144214215 php安裝redis擴展 參考鏈接&#xff1a;https://blog.csdn.net/jianchenn/article/details/106144313 解決思路&#xff1a;&#xff0…

PT8M2302 觸控 A/D 型 8-Bit MCU

1. 產品概述 PT8M2302 是一款可多次編程&#xff08; MTP &#xff09; A/D 型 8 位 MCU &#xff0c;其包括 2K*16bit MTP ROM 、 256*8bit SRAM、 ADC 、 PWM 、 Touch 等功能&#xff0c;具有高性能精簡指令集、低工作電壓、低功耗特性且完全集 成觸控按鍵功能。為…

如何使用策略模式并讓spring管理

1、策略模式公共接口類 BankFileStrategy public interface BankFileStrategy {String getBankFile(String bankType) throws Exception; } 2、策略模式業務實現類 Slf4j Component public class ConcreteStrategy implements BankFileStrategy {Overridepublic String ge…

前端開發:盒子模型、塊元素

1.border邊框 *{box-sizing:border-box; } //使所有邊框不再撐大盒子模型 粗細 : border-width 樣式 : border-style, 默認沒邊框 . solid 實線邊框 dashed 虛線邊框 dotted 點線邊框 顏色 : border-color div { width : 200px ; height : 200px ; border : …

Nvidia Blackwell架構深度剖析:深入了解RTX 50系列GPU的升級

在CES 2025上&#xff0c;英偉達推出了基于Blackwell架構的GeForce RTX 50系列顯卡&#xff0c;包括RTX 5090、RTX 5080、RTX 5070 Ti和RTX 5070。一段時間以來&#xff0c;我們已經知曉了該架構的各種細節&#xff0c;其中許多此前還只是傳聞。不過&#xff0c;英偉達近日在20…

計算機網絡 (45)動態主機配置協議DHCP

前言 計算機網絡中的動態主機配置協議&#xff08;DHCP&#xff0c;Dynamic Host Configuration Protocol&#xff09;是一種網絡管理協議&#xff0c;主要用于自動分配IP地址和其他網絡配置參數給連接到網絡的設備。 一、基本概念 定義&#xff1a;DHCP是一種網絡協議&#xf…

“扣子”開發之四:與千帆AppBuilder比較

上一個專題——“扣子”開發——未能落地&#xff0c;開始抱著極大的熱情進入&#xff0c;但迅速被稚嫩的架構模型折磨打擊&#xff0c;硬著頭皮堅持了兩周&#xff0c;終究還是感覺不實用不趁手放棄了。今天詢問了下豆包&#xff0c;看看還有哪些比較好的AI開發平臺&#xff0…

RV1126+FFMPEG推流項目(7)AI音頻模塊編碼流程

一、AI 模塊和外設麥克風的關系 AI 模塊是 RV1126 芯片的一個重要組成部分。它的主要功能是將外部接入的麥克風采集到的模擬信號通過內置的驅動程序轉換為數字信號。這意味著麥克風作為外設&#xff0c;提供音頻輸入信號&#xff0c;AI 模塊通過其硬件和軟件的結合&#xff0c…

遺傳算法 (Genetic Algorithm) 算法詳解及案例分析

遺傳算法 (Genetic Algorithm) 算法詳解及案例分析 目錄 遺傳算法 (Genetic Algorithm) 算法詳解及案例分析1. 引言2. 遺傳算法的基本概念2.1 遺傳算法的定義2.2 遺傳算法的核心思想2.3 遺傳算法的應用領域3. 遺傳算法的主要步驟3.1 初始化種群3.2 選擇3.3 交叉3.4 變異3.5 更新…

Rust 強制類型轉換和動態指針類型的轉換

在 Rust 中的強制類型轉換&#xff08;Coercion&#xff09;語義&#xff0c;與 Java 或 C 中的子類到父類的轉換有某些相似之處&#xff0c;但兩者的實現機制和使用場景有很大的區別。 我們將從 Java/C 的子類到父類轉換 和 Rust 的強制類型轉換 的角度進行比較&#xff0c;幫…

第十二章:算法與程序設計

文章目錄&#xff1a; 一&#xff1a;基本概念 1.算法與程序 1.1 算法 1.2 程序 2.編譯預處理 3.面向對象技術 4.程序設計方法 5.SOP標志作業流程 6.工具 6.1 自然語言 6.2 流程圖 6.3 N/S圖 6.4 偽代碼 6.5 計算機語言 二&#xff1a;程序設計 基礎 1.常數 …

【后端面試總結】tls中.crt和.key的關系

tls中.crt和.key的關系 引言 在現代網絡通信中&#xff0c;特別是基于SSL/TLS協議的加密通信中&#xff0c;.crt和.key文件扮演著至關重要的角色。這兩個文件分別代表了數字證書和私鑰&#xff0c;是確保通信雙方身份認證和數據傳輸安全性的基石。本文旨在深入探討TLS中.crt和…

【k8s面試題2025】2、練氣初期

在練氣初期&#xff0c;靈氣還比較稀薄&#xff0c;只能勉強在體內運轉幾個周天。 文章目錄 簡述k8s靜態pod為 Kubernetes 集群移除新節點&#xff1a;為 K8s 集群添加新節點Kubernetes 中 Pod 的調度流程 簡述k8s靜態pod 定義 靜態Pod是一種特殊類型的Pod&#xff0c;它是由ku…

初學stm32 --- CAN

目錄 CAN介紹 CAN總線拓撲圖 CAN總線特點 CAN應用場景 CAN物理層 CAN收發器芯片介紹 CAN協議層 數據幀介紹 CAN位時序介紹 數據同步過程 硬件同步 再同步 CAN總線仲裁 STM32 CAN控制器介紹 CAN控制器模式 CAN控制器模式 CAN控制器框圖 發送處理 接收處理 接收過…

運輸層安全協議SSL

安全套接字層 SSL (Secure Socket Layer) SSL 作用在端系統應用層的 HTTP 和運輸層之間&#xff0c;在 TCP 之上建立起一個安全通道&#xff0c;為通過 TCP 傳輸的應用層數據提供安全保障。 應用層使用 SSL 最多的就是 HTTP&#xff0c;但 SSL 并非僅用于 HTTP&#xff0c;而是…

ZooKeeper 常見問題與核心機制解析

Zookeeper集群本身不直接支持動態添加機器。在Zookeeper中&#xff0c;集群的配置是在啟動時靜態定義的&#xff0c;并且集群中的每個成員都需要知道其他所有成員。當你想要增加一個新的Zookeeper服務器到現有的集群中時&#xff0c;你需要更新所有現有服務器的配置文件&#x…

【Sql遞歸查詢】Mysql、Oracle、SQL Server、PostgreSQL 實現遞歸查詢的區別與案例(詳解)

文章目錄 Mysql 5.7 遞歸查詢Mysql 8 實現遞歸查詢Oracle遞歸示例SQL Server 遞歸查詢示例PostgreSQL 遞歸查詢示例 更多相關內容可查看 Mysql 5.7 遞歸查詢 MySQL 5.7 本身不直接支持標準 SQL 中的遞歸查詢語法&#xff08;如 WITH RECURSIVE 這種常見的遞歸查詢方式&#xf…

【Rust自學】13.2. 閉包 Pt.2:閉包的類型推斷和標注

13.2.0. 寫在正文之前 Rust語言在設計過程中收到了很多語言的啟發&#xff0c;而函數式編程對Rust產生了非常顯著的影響。函數式編程通常包括通過將函數作為值傳遞給參數、從其他函數返回它們、將它們分配給變量以供以后執行等等。 在本章中&#xff0c;我們會討論 Rust 的一…

【JavaScript】比較運算符的運用、定義函數、if(){}...esle{} 語句

比較運算符 !><> < 自定義函數&#xff1a; function 函數名&#xff08;&#xff09;{ } 判斷語句&#xff1a; if(判斷){ }else if(判斷){ 。。。。。。 }else{ } 代碼示例&#xff1a; <!DOCTYPE html> <html> <head><meta charset&quo…