socket通信

基于TCP协议和基于UDP协议的通信流程图_udp工作流程图-CSDN博客

目录

[1 函数解析](#1 函数解析)

[1.1 TCP 套接字函数作用](#1.1 TCP 套接字函数作用)

一、服务器端函数流程

二、客户端函数流程

三、关键机制说明

四、实际应用建议

[1.2 UDP 套接字作用](#1.2 UDP 套接字作用)

一、服务器端

二、客户端

[三、与 TCP 的关键区别(基于图示)](#三、与 TCP 的关键区别(基于图示))

[2 代码示例](#2 代码示例)

[2.1 TCP](#2.1 TCP)

[2.1.1 TCP Server](#2.1.1 TCP Server)

[2.1.2 TCP Client](#2.1.2 TCP Client)

[2.2 UDP](#2.2 UDP)

[2.2.1 UDP Server](#2.2.1 UDP Server)

[2.2.2 UDP Client](#2.2.2 UDP Client)

基于TCP协议和基于UDP协议的通信流程图_udp工作流程图-CSDN博客

1 函数解析

1.1 TCP 套接字函数作用

一、服务器端函数流程

  1. socket():创建一个套接字(Socket),它是网络通信的端点,相当于一个"文件描述符",用于后续绑定、监听和收发数据。
  2. bind():将创建的套接字与本地 IP 地址和端口号绑定,使服务器能在指定地址上接收连接请求。
  3. listen():将套接字设置为监听状态,开始等待客户端的连接请求。此时服务器进入"被动打开"状态,内核会维护一个连接队列。
  4. accept():从监听队列中取出一个已完成的连接,创建一个新的套接字用于与该客户端通信。原监听套接字继续等待其他连接。
  5. recv() / send() :通过 accept() 返回的新套接字进行数据的接收和发送。TCP 是面向字节流的,因此 send() 和 recv() 不保证一次发送对应一次接收,需应用层自行处理消息边界。
  6. close():关闭套接字,释放资源,终止连接。TCP 会执行四次挥手过程以确保数据完整传输。

二、客户端函数流程

  1. socket():同样创建一个套接字,作为客户端通信的端点。
  2. connect():主动向服务器的 IP 和端口发起连接请求,触发 TCP 三次握手。成功后,客户端与服务器建立可靠连接。
  3. send() / recv():连接建立后,通过该套接字发送和接收数据。
  4. close():通信结束后关闭套接字,释放资源。

三、关键机制说明

  • 三次握手 :由 connect() 和 accept() 触发,确保双方都准备好通信。
  • 四次挥手 :由 close() 触发,确保数据完整传输后断开连接。
  • 阻塞与非阻塞 :默认情况下,accept()、connect()、recv() 等函数是阻塞的,即调用后会等待直到操作完成。可通过设置非阻塞模式或使用 I/O 多路复用(如 select、epoll)优化性能。
  • 套接字类型 :图中使用的是流式套接字(SOCK_STREAM),对应 TCP 协议;若使用数据报套接字(SOCK_DGRAM),则对应 UDP 协议,无需 listen() 和 accept()。

四、实际应用建议

  • 服务器端:通常采用多线程或 I/O 多路复用处理多个客户端连接,避免单线程阻塞。
  • 客户端 :连接前需确保服务器已启动并监听,否则 connect() 会失败。
  • 错误处理 :所有函数调用后都应检查返回值,处理可能的错误(如 ECONNREFUSED、ETIMEDOUT 等)。

1.2 UDP 套接字作用

与之前的 TCP 流程图相比,最显著的区别在于 没有 listen()、accept() 和 connect() ,且数据收发使用的是 sendto() 和 recvfrom()。

  1. 服务器先启动并绑定端口等待。
  2. 客户端直接向服务器发送数据(sendto)。
  3. 服务器收到数据(recvfrom),得知客户端地址。
  4. 服务器利用获取到的地址向客户端回传数据(sendto)。

以下是图中各函数的详细含义及 UDP 通信的特点解析:

一、服务器端

  1. socket() :创建一个 UDP 类型的套接字。在代码中通常体现为第二个参数使用 SOCK_DGRAM(数据报套接字),而不是 TCP 的 SOCK_STREAM。
  2. bind():将套接字绑定到特定的 IP 地址和端口。这是必须的,因为服务器需要一个固定的"地址"来接收数据,否则客户端不知道往哪里发。
  3. recvfrom() :接收数据 。
    • 这是一个阻塞函数,程序会停在这里等待数据到来。
    • 关键点 :它不仅接收数据内容,还会获取发送方的地址信息(IP 和端口)。这样服务器才知道是谁发的,以便稍后回复。
  4. sendto() :发送数据 。
    • 因为 UDP 是无连接的,服务器没有像 TCP 那样建立一个专属的"连接通道"。
    • 所以每次发送数据时,必须明确指定目标地址 (即刚才通过 recvfrom 获取到的客户端地址)。
  5. close():关闭套接字,释放资源。

二、客户端

  1. socket():创建一个 UDP 套接字。
  2. sendto() :发送数据 。
    • 客户端不需要先建立连接(不需要 connect),直接调用此函数向指定的服务器 IP 和端口发送数据包。
  3. recvfrom() :接收数据 。
    • 等待并接收服务器的回复。虽然图中画在 sendto 之后,但在实际编程中,客户端也可以先发制人,或者只发不收。
  4. close():关闭套接字。

三、与 TCP 的关键区别(基于图示)

特性 TCP (上一张图) UDP (这张图) 含义
连接建立 需要 connect, listen, accept 无 UDP 就像寄信/发电报,不需要先打电话确认对方在不在,直接发即可。
收发函数 send / recv sendto / recvfrom TCP 建立了"专线",知道发给谁;UDP 每次发包都要在包裹上写明"收件人地址"(to),收包时也要看"寄件人地址"(from)。
可靠性 高(有重传、排序) 低 UDP 不保证数据一定到达,也不保证顺序,可能会丢包。
速度 较慢(握手、确认开销大) 快 头部开销小,无需握手,实时性好(如视频直播、在线游戏)。

2 代码示例

2.1 TCP

2.1.1 TCP Server

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    const char *hello = "Hello from Server";

    // 1. socket(): 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置地址结构
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
    address.sin_port = htons(PORT);

    // 2. bind(): 绑定 IP 和端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 3. listen(): 开始监听,最大连接队列长度为 3
    if (listen(server_fd, 3) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server waiting for connections on port %d...\n", PORT);

    // 4. accept(): 阻塞等待客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept failed");
        exit(EXIT_FAILURE);
    }

    // 5. recv(): 接收数据
    int valread = read(new_socket, buffer, BUFFER_SIZE);
    printf("Client message: %s\n", buffer);

    // 6. send(): 发送数据
    send(new_socket, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 7. close(): 关闭连接
    close(new_socket);
    close(server_fd);
    return 0;
}

2.1.2 TCP Client

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    const char *hello = "Hello from Client";

    // 1. socket(): 创建套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将 IPv4 地址从文本转换为二进制形式
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 2. connect(): 发起连接(触发三次握手)
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    // 3. send(): 发送数据
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 4. recv(): 接收数据
    read(sock, buffer, BUFFER_SIZE);
    printf("Server message: %s\n", buffer);

    // 5. close(): 关闭连接
    close(sock);
    return 0;
}

2.2 UDP

2.2.1 UDP Server

复制代码
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    // 1. socket(): 创建 UDP 套接字 (SOCK_DGRAM)
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        return EXIT_FAILURE;
    }

    // 配置服务器地址结构
    struct sockaddr_in serverAddr, clientAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    memset(&clientAddr, 0, sizeof(clientAddr));

    serverAddr.sin_family = AF_INET; // IPv4
    serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
    serverAddr.sin_port = htons(PORT); // 端口号

    // 2. bind(): 绑定 IP 和端口
    if (bind(sockfd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return EXIT_FAILURE;
    }

    std::cout << "Server waiting for data on port " << PORT << "..." << std::endl;

    char buffer[BUFFER_SIZE];
    socklen_t len = sizeof(clientAddr); // 用于存储客户端地址长度

    while (true) {
        // 3. recvfrom(): 接收数据并获取客户端地址
        // 注意:这里会阻塞,直到收到数据
        int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, 
                         (struct sockaddr *)&clientAddr, &len);
        
        if (n > 0) {
            buffer[n] = '\0'; // 确保字符串结束
            std::cout << "Received: " << buffer << std::endl;

            // 4. sendto(): 向刚才获取到的 clientAddr 发送回复
            const char *reply = "Hello from Server!";
            sendto(sockfd, reply, strlen(reply), 0, 
                   (const struct sockaddr *)&clientAddr, len);
        }
    }

    // 5. close(): 关闭套接字
    close(sockfd);
    return 0;
}

2.2.2 UDP Client

复制代码
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    // 1. socket(): 创建 UDP 套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        return EXIT_FAILURE;
    }

    // 配置服务器地址结构
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    
    // 将 IP 地址转换为网络字节序 (假设服务器在本地)
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

    char buffer[BUFFER_SIZE];
    const char *hello = "Hello from Client";
    socklen_t len = sizeof(serverAddr);

    // 2. sendto(): 直接向服务器发送数据
    sendto(sockfd, hello, strlen(hello), 0, 
           (const struct sockaddr *)&serverAddr, len);
    
    std::cout << "Message sent to server." << std::endl;

    // 3. recvfrom(): 等待服务器回复
    // 如果不需要接收回复,可以省略此步
    int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, 
                     (struct sockaddr *)&serverAddr, &len);
    
    if (n > 0) {
        buffer[n] = '\0';
        std::cout << "Server Reply: " << buffer << std::endl;
    }

    // 4. close(): 关闭套接字
    close(sockfd);
    return 0;
}
相关推荐
创界工坊工作室2 小时前
46-基于AT89C51脉冲式电表数据采集器设计
stm32·单片机·嵌入式硬件·51单片机·proteus
恒锐丰科技林技术员2 小时前
EG27324 双通道驱动芯片:小封装下的高性能功率驱动方案
经验分享·嵌入式硬件·硬件工程
Brilliantwxx2 小时前
【STM32】 独立看门狗 IWDG 深度详解:从硬件结构到工程实战
数据库·stm32·单片机·嵌入式硬件·mongodb
AMASENSYS艾马斯3 小时前
变频器过载能力参数解读与工程应用
驱动开发·嵌入式硬件·其他·硬件工程·精益工程
cu1433 小时前
细谈IT6563的具体功能与其应用
c语言·c++·单片机·嵌入式硬件
坐看风云变幻5 小时前
《嵌入式工程师的软硬件修仙之路》第 5 期|STM32/GPIO 寄存器操作:从地址映射到点亮一盏灯的全过程
stm32·单片机·嵌入式硬件
POMAGTOR5 小时前
【硬件笔记|磁吸连接器安装规范、失效分析与选型验证方案】
网络·嵌入式硬件
路上有只喵5 小时前
认识元器件 ·贴片电阻· 规格书解读
嵌入式硬件·硬件架构
dqsh065 小时前
STM32开发用调试器ST-Link V2.1的制作 保姆级教程
stm32·单片机·嵌入式硬件·调试工具·st-link