Linux-网络通信02-UDP 与 TCP Socket

一、UDP vs TCP 特征对比

特征 UDP TCP
连接方式 无连接 有连接(需三次握手)
可靠性 不可靠 可靠(应答、超时重传、拥塞控制)
传输延迟 较高
网络开销 大(需维护链路状态)
工作模式 半双工 全双工
套接字类型 SOCK_DGRAM SOCK_STREAM 流式套接字
读/写阻塞 有读阻塞,无写阻塞 有读阻塞,有写阻塞(64K)
数据顺序 不保证顺序到达 保证顺序(有序到达)
发送与接收次数 必须一一对应 不需要对应,数据连续
协议头大小 8 字节 20 字节

二、通用 Socket 函数

以下函数 UDP 和 TCP 都会用到。

2.1 创建套接字

cs 复制代码
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
参数 取值 说明
domain AF_INET / PF_INET 互联网程序(IPv4)
domain AF_UNIX / PF_UNIX 单机程序
type SOCK_STREAM 流式套接字 → TCP
type SOCK_DGRAM 用户数据报套接字 → UDP
type SOCK_RAW 原始套接字 → IP
protocol 0 自动适应应用层协议

返回值: 成功返回套接字 fd,失败返回 -1

2.2 绑定地址

cs 复制代码
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
  • 服务器端:将套接字与指定接口地址关联,用于从该接口接收数据
  • 客户端:可省略,由系统默认接口发送

地址结构体:

bash 复制代码
// 网络地址结构(传参时强转为 struct sockaddr*)
struct sockaddr_in 
{
    u_short        sin_family;  // 地址族,AF_INET
    u_short        sin_port;    // 端口号(网络字节序)
    struct in_addr sin_addr;    // IP 地址
    char           sin_zero[8]; // 填充位
};

三、UDP 编程

3.1 UDP 特性说明

  1. 发送次数和接收次数必须一一对应

  2. 发送和接收的大小需保持一致(若接收 buf 小于发送大小,超出部分丢失)

  3. 每次发送数据,链路都可能不同

  4. 有读阻塞(没有数据时会阻塞等待)

  5. 无写阻塞(发送太快时,接收方来不及处理会丢包)

  6. 半双工

3.2 UDP 编程步骤

cs 复制代码
服务器端:socket() → bind() → recvfrom() → sendto() → close()
客户端:  socket() → sendto() → recvfrom() → close()

3.3 UDP 收发函数

复制代码
// 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
参数 说明
sockfd 本地套接字 fd
buf 要发送的数据
len 数据长度
flags 0 表示阻塞发送
dest_addr 必填,目标主机地址结构体
addrlen 目标地址长度

返回值: 成功返回发送的字节数,失败返回 -1

bash 复制代码
// 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
参数 说明
sockfd 本地套接字 fd
buf 存储接收数据的内存
len 要接收的数据长度(一般为 buf 大小)
flags 0 表示阻塞接收
src_addr 可选,对方地址信息(NULL 表示不关心)
addrlen 对方地址结构体大小(src_addr 为 NULL 时也为 NULL)

返回值: 成功返回接收到的字节数,失败返回 -1

3.4 UDP 示例代码框架

cs 复制代码
typedef struct sockaddr * (SA);
int main(int argc, char **argv)
{
    // 1. internet , udp, 默认协议
    int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == udpfd)
    {
        perror("socket");
        return 1;
    }
    // 2. 给套接字 绑定ip ,端口号
    // man 7 ip 查询 ipv4 的地址结构体
    struct sockaddr_in ser, cli;  // 服务器的地址结构体, 客户端的地址结构体
    bzero(&ser, sizeof(ser));
    bzero(&cli, sizeof(cli));
    ser.sin_family = AF_INET;     // ipv4
    ser.sin_port = htons(50000);  // host to net short 小端转大端
    ser.sin_addr.s_addr = inet_addr("192.168.31.149");
    int ret = bind(udpfd, (SA)&ser, sizeof(ser));
    if (-1 == ret)
    {z
        perror("bind");
        return 1;
    }
    socklen_t len = sizeof(cli);
    while (1)
    {   
        char buf[1024]={0};
        recvfrom(udpfd,buf,sizeof(buf),0,(SA)&cli,&len);
        time_t tm;
        time(&tm);
        sprintf(buf,"%s %s",buf,ctime(&tm));
        sendto(udpfd,buf,strlen(buf),0,(SA)&cli,len);
    }

    return 0;
}
cs 复制代码
// ===== UDP 客户端 =====
typedef struct sockaddr *(SA);

int	main(int argc, char **argv)
{
    //1创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        perror("socket");
        return 1;
    }
    struct sockaddr_in ser,cli;
    // 清空结构体
    bzero(&ser, sizeof(ser));
    bzero(&cli, sizeof(cli));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(50000);
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    //2主动连接服务器
    int ret = connect(sockfd,(SA)&ser,sizeof(ser));
    if (-1 == ret)
    {
        perror("connect");
        return 1;

    }
    while (1)
    {
        char buf[512] = "hello,woshihuihui";

    //3发送数据 返回值>0,实际发送的字节数 ==0 网络状态不好,没有发出数据 -1错误
    send(sockfd, buf, strlen(buf), 0);

    bzero(buf, sizeof(buf));
    //4接收数据 >0 实际接收的字节数,==0表示断开,-1表示错误
    int rd_ret = recv(sockfd, buf, sizeof(buf), 0);
    if (rd_ret<=0)
    {
        break;
    }
    printf("from ser:%s\n",buf);
    sleep(1);
    }
    //5断开连接
    close(sockfd);
    return 0;
}

四、TCP 编程

4.1 TCP 特性说明

复制代码
1. 有连接:通信前需建立链路(三次握手),通信中保持,断开时四次挥手
2. 可靠传输:应答机制 + 超时重传机制 + 拥塞控制
3. 全双工:同一时刻可以同时收发
4. 流式套接字:数据连续,发送/接收次数不需要一一对应
5. 有读阻塞,有写阻塞(缓冲区 64K)
6. 保证数据顺序到达

4.2 TCP 编程步骤

复制代码
服务器端:socket() → bind() → listen() → accept() → recv()/send() → close()
客户端:  socket() → connect() → send()/recv() → close()

4.3 TCP 专用函数

listen() - 监听
复制代码
int listen(int sockfd, int backlog);
参数 说明
sockfd 套接字 fd
backlog 允许连接的最大客户端数量

返回值: 成功 0,失败 -1

accept() - 接受连接
复制代码
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数 说明
sockfd 监听套接字 fd(listfd)
addr NULL 表示不获取客户端信息;否则传入地址变量地址
addrlen addr 为 NULL 时也为 NULL;否则 len = sizeof(struct sockaddr)

返回值: 成功返回新的通信套接字 fd(connfd),失败返回 -1

⚠️ 注意:listfd 负责监听,connfd 负责实际通信,两者分开!

connect() - 发起连接(客户端专用)
复制代码
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 说明
sockfd 本地 socket() 创建的套接字 fd
addr 远程目标主机地址信息
addrlen 参数2的长度

返回值: 成功 0,失败 -1

recv() / send() - 收发数据
复制代码
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *msg, size_t len, int flags);
参数 说明
sockfd 服务器用 connfd(accept返回值),客户端用 sockfd(socket返回值)
buf/msg 数据存储/发送的内存
len 数据长度
flags 0 表示阻塞

4.4 TCP 示例代码框架

cs 复制代码
// ===== TCP 服务器端 =====
int main() {
    // 1. 创建监听套接字
    int listfd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 绑定地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    bind(listfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    // 3. 监听
    listen(listfd, 5);

    // 4. 接受连接
    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);
    int connfd = accept(listfd, (struct sockaddr*)&client_addr, &len);

    // 5. 收发数据(循环)
    char buf[1024] = {0};
    while (1) {
        int n = recv(connfd, buf, sizeof(buf), 0);
        if (n <= 0) break;  // 客户端断开
        printf("recv: %s\n", buf);
        send(connfd, buf, n, 0);
    }

    // 6. 关闭
    close(connfd);
    close(listfd);
    return 0;
}
cs 复制代码
// ===== TCP 客户端 =====
int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr);

    // 发起连接(三次握手)
    connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    char buf[] = "hello";
    send(sockfd, buf, strlen(buf), 0);

    char recv_buf[1024] = {0};
    recv(sockfd, recv_buf, sizeof(recv_buf), 0);
    printf("recv: %s\n", recv_buf);

    close(sockfd);
    return 0;
}

五、三次握手与四次挥手

5.1 三次握手(建立连接)

cs 复制代码
Client                    Server
  |  ---- SYN, seq=1000 ---->  |   第1次:客户端请求连接
  |  <-- SYN+ACK, seq=8000 --- |   第2次:服务器确认并请求
  |  ---- ACK 8001 ----------> |   第3次:客户端确认
  |       [连接建立]           |

5.2 四次挥手(断开连接)

cs 复制代码
Client                    Server
  |  ---- FIN, ACK ---------> |   第1次:客户端请求断开
  |  <---- ACK --------------- |   第2次:服务器确认
  |  <---- FIN, ACK ---------- |   第3次:服务器也请求断开
  |  ---- ACK ----------------> |   第4次:客户端确认
  |       [连接关闭]           |

六、通信套接字与监听套接字

套接字类型 说明
listfd(监听套接字) 绑定 IP + Port,专门监听客户端连接请求
connfd(通信套接字) 每当有客户端连接成功,服务端生成一个新的 connfd 用于实际通信
复制代码
服务器:
  listfd(3) → 192.168.31.177:50000(监听)
  connfd(4) → 与客户端 a 通信
  connfd(5) → 与客户端 b 通信

七、TCP 黏包问题

7.1 什么是黏包?

发送方多次发送 数据,接收方因为流式传输特性,无法正确区分每次发送的数据边界,导致无法正常解析

7.2 解决方案

方案 说明
设置结束标志 在数据末尾添加特殊标记,如 \r\n
发送固定大小 使用 struct 固定报文大小,收发都按 sizeof(struct) 操作
自定义协议(变长) 报文头包含数据长度字段,先读头再按长度读数据
cs 复制代码
// 方案2:固定大小结构体示例
typedef struct {
    char buf[1024];  // 正文数据
    int  buf_len;    // 实际数据长度
    int  type;       // 类型:1=chat, 2=file
} MSG;

// 发送
MSG msg = {0};
strcpy(msg.buf, "hello");
msg.buf_len = strlen(msg.buf);
msg.type = 1;
send(connfd, &msg, sizeof(MSG), 0);

// 接收
MSG recv_msg;
recv(connfd, &recv_msg, sizeof(MSG), 0);

八、API 速查表

函数 TCP/UDP 作用
socket() 通用 创建套接字
bind() 通用 绑定地址(服务器必用)
listen() TCP 服务器 开始监听连接
accept() TCP 服务器 接受客户端连接
connect() TCP 客户端 发起连接
send() TCP 发送数据
recv() TCP 接收数据
sendto() UDP 发送数据(含目标地址)
recvfrom() UDP 接收数据(可获取来源地址)
close() 通用 关闭套接字

上一篇:网络通信(一)------ 基础概念与网络模型 下一篇:网络通信(三)------ 协议头结构、HTTP 与 Wireshark 抓包

相关推荐
攻城狮在此3 分钟前
IPSG配置(IP与MAC地址绑定,动态绑定)
网络·安全
浦信仿真大讲堂8 分钟前
CST FAQ 006:Linux系统CST安装指导
linux·运维·服务器·仿真软件·达索软件
zhaoshuzhaoshu14 分钟前
蓝牙 ACL 与 SCO 链路联系与详细区别对比
网络·物联网·蓝牙·无线
AI+程序员在路上26 分钟前
Linux C 条件变量阻塞线程用法:等待时CPU占用率为0
linux·运维·c语言
CHENKONG_CK38 分钟前
晨控 RFID:重塑车载检测全流程智能化管控
网络·自动化·rfid
HABuo43 分钟前
【linux线程(三)】生产者消费者模型(条件变量阻塞队列版本、信号量环形队列版本)详细剖析
linux·运维·服务器·c语言·c++·ubuntu·centos
Milu_Jingyu1 小时前
Windows与Ubuntu文件共享详细指南
linux·windows·ubuntu
运维行者_1 小时前
使用 Applications Manager 实现 AWS 云监控:保障业务应用高效运行
大数据·运维·服务器·网络·数据库·云计算·aws
安科士andxe1 小时前
深度解析|安科士100G QSFP28 30km光模块核心技术,破解中长距传输痛点
运维·服务器·网络
Java面试题总结1 小时前
Linux根分区爆满(占用81%)排查与解决实战
linux·运维·服务器