网络编程学习——4

一、UDP概述

无连接、不可靠、有失序、数据报格式

广播地址

​ 主机号全为255的为广播地址

ip地址分类

A类:前1段位网络号,后3段位主机号

​ 0.0.0.0——127.255.255.255

B类:前2段网络号,后2段主机号

​ 128.0.0.0——191.255.255.255

C类:前3段网络号,后1段主机号

​ 192.0.0.0——223.255.255.255

D类:组播地址

​ 224.0.0.0——239.255.255.255

二、UDP通信

1.单播

(1)流程

服务器

​ 1.创建套接字

​ 2.绑定套接字

​ 3.通信

while(1)
{
    4.接收数据
	recvfrom
}

客户端

​ 1.创建套接字

​ 2.绑定(可以省略)

​ 3.通信

while(1)
{
    4.发送消息
	sendto
}

(2)recvfrom函数

函数功能

​ 用于接收数据报(UDP 协议为主)的系统调用

函数原型

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

函数参数

参数 含义
sockfd 套接字描述符
buf 接收数据的缓冲区
len 接收字节数
flags 接收方式标志(通常为 0)
src_addr 输出参数,用于存储发送方的地址信息(struct sockaddr 类型指针)
addrlen 输入输出参数: - 输入:src_addr 缓冲区的大小(sizeof(*src_addr)) - 输出:实际存储的地址长度

返回值

  • 成功:返回接收到的字节数(≤ len)。
  • 失败:返回 -1,并设置 errno(如 EAGAIN 表示非阻塞模式下无数据,EBADF 表示无效套接字)。
  • 连接关闭:TCP 中返回 0(UDP 无连接,不会返回 0)。

(3)sendto函数

函数功能

​ 发送数据报的系统调用,主要用于 无连接协议(如 UDP)

函数原型

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
            const struct sockaddr *dest_addr, socklen_t addrlen);

函数参数

参数 含义
sockfd 套接字描述符(UDP 通常无需连接)
buf 待发送数据的缓冲区
len 待发送数据的长度(字节),不能超过缓冲区大小
flags 发送方式标志(通常为 0)
dest_addr 目标地址信息(struct sockaddr 类型指针,包含 IP 和端口)
addrlen dest_addr 结构体的大小(字节),通常为 sizeof(*dest_addr)

返回值

  • 成功:返回实际发送的字节数(≤ len)。
  • 失败:返回 -1,并设置 errno(如 EINVAL 表示参数无效,ENETUNREACH 表示网络不可达)。

(4)UDP双向通信 示例

CA.c源码

/*===============================================
*   文件名称:CA.c
*   创 建 者:青木莲华
*   创建日期:2025年08月19日
*   描    述:UDP双向通信 A端
================================================*/
#include "socket_head.h"
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    char buf[1024] = {0};
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    //2.初始化结构体 绑定套接字
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("192.168.6.174");

    socklen_t len = sizeof(addr);
    
    if(-1 == bind(sockfd,(struct sockaddr*)&addr,len))
    {
        perror("bind");
        return -1;
    }
    //开子进程用来接收数据
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    else if(0 == pid)           //子进程
    {
        while(1)
        {
            recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,&len);
            printf("%s[%hd] : %s",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),buf);
        }
    }
    else                        //父进程
    {
        addr.sin_port = htons(7777);
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);
            sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,len);
        }
    }
    return 0;
} 

CB.c源码

/*===============================================
*   文件名称:CB.c
*   创 建 者:青木莲华
*   创建日期:2025年08月19日
*   描    述:UDP双向通信B端
================================================*/
#include "socket_head.h"
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    char buf[1024] = {0};
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    //2.初始化结构体 绑定套接字
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = inet_addr("192.168.6.174");

    socklen_t len = sizeof(addr);
    
    if(-1 == bind(sockfd,(struct sockaddr*)&addr,len))
    {
        perror("bind");
        return -1;
    }
    //开子进程用来接收数据
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    else if(0 == pid)           //子进程
    {
        while(1)
        {
            recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,&len);
            printf("%s[%d] : %s",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),buf);
        }
    }
    else                        //父进程
    {
        addr.sin_port = htons(6666);
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);
            sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,len);
        }
    }
    return 0;

} 

2.组播

组播可以跨网络

​ 用D类地址 224.0.0.0---239.255.255.2555

​ 发送方:

​ 允许发送组播,向组播地址发送信息(需要路由器等设备支持组播)

​ 接收方:

​ 加入组播

setsockopt函数设置组播

​ IP_MULTICAST_IF --- 是否允许组播

​ IP_MULTICCAST_TTL --- Server加入组播

​ IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP --- 接收端 加入/退出组播

示例

发送端

/*===============================================
*   文件名称:mcs.c
*   创 建 者:青木莲华
*   创建日期:2025年08月19日
*   描    述:组播发送端
================================================*/
#include "socket_head.h"

int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    char buf[1024] = {0};
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    //2.初始化结构体
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("225.2.2.2");

    //3.设置发送组播网络接口
    struct in_addr in;
    in.s_addr = inet_addr("0.0.0.0");
    if(-1 == setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&in,sizeof(in)))
    {
        perror("sockopt");
        return -1;
    }
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,sizeof(addr));
    }

    return 0;
} 

接收端

/*===============================================
*   文件名称:mcc.c
*   创 建 者:青木莲华
*   创建日期:2025年08月19日
*   描    述:组播接收端
================================================*/
#include "socket_head.h"

int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    char buf[1024] = {0};
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    //2.初始化结构体  绑定套接字
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");

    socklen_t len = sizeof(addr);

    if(-1 == bind(sockfd , (struct sockaddr*)&addr,len))
    {
        perror("bind");
        return -1;
    }
    //3.设置 ip_mreq 并 添加进组播
    struct ip_mreq req;
    req.imr_multiaddr.s_addr = inet_addr("225.2.2.2");  //组播地址
    req.imr_interface.s_addr = inet_addr("0.0.0.0");    //本地IP地址

    if(-1 == setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&req,sizeof(req)))
    {
        perror("sockopt");
        return -1;
    }

    while(1)
    {
        if(-1 == recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,&len))
        {
            perror("recvfrom");
            return -1;
        }
        printf("%s[%d] : %s",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),buf);
    }

    return 0;

} 

3.广播

发送方:

​ 发送地址为广播地址(主机地址全为255)

​ 允许发送广播消息

接收方:

​ 绑定广播地址 x.x.x.255 或 0.0.0.0

广播函数:setsockopt(设置套接字)

//设置允许广播,val为int类型 值为1
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&val,sizeof(val))

示例

server

/*===============================================
*   文件名称:bcc.c
*   创 建 者:青木莲华
*   创建日期:2025年08月19日
*   描    述:广播通信 Client 接收端
================================================*/
#include "socket_head.h"

int main(int argc, char *argv[])
{ 

    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    char buf[1024] = {0};
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    //2.初始化结构体  绑定套接字
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(addr);

    if(-1 == bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    //3.通信
    while(1)
    {
        if(-1 == recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,&len))
        {
            perror("recvfrom");
            return -1;
        }
        printf("%s[%d] : %s",
                inet_ntoa(addr.sin_addr),
                ntohs(addr.sin_port),buf);
    }


    return 0;
} 

client

/*===============================================
*   文件名称:bcc.c
*   创 建 者:青木莲华
*   创建日期:2025年08月19日
*   描    述:广播通信 Client 接收端
================================================*/
#include "socket_head.h"

int main(int argc, char *argv[])
{ 

    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    char buf[1024] = {0};
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    //2.初始化结构体  绑定套接字
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6666);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(addr);

    if(-1 == bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    //3.通信
    while(1)
    {
        if(-1 == recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,&len))
        {
            perror("recvfrom");
            return -1;
        }
        printf("%s[%d] : %s",
                inet_ntoa(addr.sin_addr),
                ntohs(addr.sin_port),buf);
    }


    return 0;
} 

4.setsockopt函数(设置套接字)

函数功能

​ 用于设置套接字选项的系统调用(设置允许广播、组播、加入组播、退出组播等)

函数原型

#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);

函数参数

参数 含义
sockfd 套接字描述符
level 选项所属的协议层级(如通用套接字层、TCP 层、IP 层等)
optname 层级下的具体的选项名称(与 level 对应,如 SO_REUSEADDRTCP_NODELAY 等)
optval 指向选项值的指针(根据选项类型传递不同数据,如 intstruct timeval 等)
optlen optval 所指向数据的长度(字节数)

返回值

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno(如 EBADF 表示无效套接字,ENOPROTOOPT 表示不支持的选项)。

5.setsockopt 的常用level表

level 常量 含义说明 常用 option 选项 option 含义说明 数据类型(optval)
SOL_SOCKET 套接字级别(通用选项) SO_BROADCAST 允许发送广播消息(仅 UDP 有效) int(0 禁用,1 启用)
SO_REUSEADDR 允许重用本地地址和端口 int(0 禁用,1 启用)
SO_SNDTIMEO 设置发送操作超时时间 struct timeval
SO_RCVTIMEO 设置接收操作超时时间 struct timeval
SO_DEBUG 启用调试信息(仅内核使用) int(0 禁用,1 启用)
SO_DONTROUTE 禁用路由查找(仅发送到本地网络) int(0 禁用,1 启用)
IPPROTO_IP IPv4 协议级别 IP_MULTICAST_IF 指定发送IPV4组播的网络接口(多网卡情况下) struct in_addr
IP_ADD_MEMBERSHIP 加入指定的多播组(组播) struct ip_mreq
IP_DROP_MEMBERSHIP 退出指定的多播组(组播) struct ip_mreq
IP_MULTICAST_TTL 设置多播数据包的 TTL 值(0 表示仅限本地,1 表示本地网络) int(范围 0-255)
IP_MULTICAST_LOOP 控制多播数据包是否回环(本地发送者是否接收自己的多播数据) int(0 禁用,1 启用)
IP_TTL 设置 IPv4 数据包的 TTL(生存时间)值 int(默认 64,范围 1-255)
IPPROTO_IPV6 IPv6 协议级别 IPV6_TTL 设置 IPv6 数据包的 Hop Limit(类似 TTL) int(默认 64,范围 1-255)
IPV6_MULTICAST_HOPS 设置 IPv6 多播数据包的 Hop Limit int(范围 0-255)
IPV6_MULTICAST_LOOP 控制 IPv6 多播数据包是否回环 int(0 禁用,1 启用)
IPV6_JOIN_GROUP 加入 IPv6 多播组 struct ipv6_mreq
IPV6_LEAVE_GROUP 退出 IPv6 多播组 struct ipv6_mreq
IPV6_V6ONLY 限制套接字仅使用 IPv6(不接受 IPv4 映射地址) int(0 禁用,1 启用)
IPPROTO_UDP UDP 协议级别 UDP_CSUM 启用 UDP 校验和计算(部分系统默认启用) int(0 禁用,1 启用)

ip_mreq结构体

#include <netinet/in.h>
struct ip_mreq {
    struct in_addr imr_multiaddr;  // 多播组的 IP 地址(如 224.0.0.1)
    struct in_addr imr_interface;  // 本地网络接口的 IP 地址(或 INADDR_ANY 表示默认接口)
};

6.超时检测

1.发送/接收超时

​ SO_SNDTIMEO 发送超时

​ SO_RCVTIMEO 接收超时

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>


int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    s_addr.sin_port   = htons(6666); 
    //s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    s_addr.sin_addr.s_addr = inet_addr("0.0.0.0");

	//允许重用本地地址和端口
    int val = 1;
    if(-1 == setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, &val, sizeof(int)))
    {
        perror("opt");
        return -1;
    }

    struct timeval t_val;
    t_val.tv_sec = 5;
    //设置接收操作超时时间
    if(-1 == setsockopt(sockfd, SOL_SOCKET,SO_RCVTIMEO, &t_val, sizeof(t_val)))
    {
        perror("opt");
        return -1;
    }

    if(-1 == bind(sockfd, (struct sockaddr*)&s_addr, sizeof(s_addr)))
    {
        perror("bind");
        return -1;
    }



    if(-1 == listen(sockfd, 5))
    {
        perror("listen");
        return -1;
    }

    socklen_t len = sizeof(s_addr);

    int connfd = accept(sockfd, (struct sockaddr*)&s_addr, &len);
    if(-1 == connfd)
    {
        perror("accept");
        return -1;
    }

    printf("client ip=%s port=%d\n", inet_ntoa(s_addr.sin_addr), ntohs(s_addr.sin_port));
    while(1)
    {
        char buf[100];

        read(connfd, buf, sizeof(buf));

        puts(buf);

    }
}

2.IO多路复用

#include "head.h"
int main()
{

    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    //2.绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port    = htons(6666);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
    {
        perror("bind");
        return -1;
    }
    //3.监听
    if(-1 == listen(sockfd, 5))
    {
        perror("listen");
        return -1;
    }
    //4.创建位图 存储关心的  用来拷贝的
    fd_set readfds, tempfds;
    //5.清空位图
    FD_ZERO(&readfds);
    //6.把socket加入位图 关心的
    FD_SET(sockfd, &readfds);
    int nfds = sockfd+1;

    struct timeval val = {10};
    while(1)
    {
        //7.select 把拷贝的位图拷贝到内核, 内核轮询判断是否就绪, 有就绪 就拷贝回用户空间
        tempfds = readfds;
        int ret;
        //设置超时
        val. tv_sec = 10;
        if(-1 == (ret= select(nfds, &tempfds, NULL, NULL, &val)))
        {
            perror("select");
            return -1;
        }
        if(ret == 0)
        {
            printf("timeout\n");
            continue;
        }
        //8.遍历位图,处理就绪的文件描述符
        int i;
        for(i = 0; i < nfds; i++)
        {
            if(FD_ISSET(i, &tempfds))
            {
                if(i == sockfd)
                {
                    //如果是sockfd就应该做连接
                    int connfd = accept(i, NULL, NULL);
                    if(-1 == connfd)
                    {
                        perror("accept");
                        return -1;
                    }
                    //有新的连接,就需要把通信套节子加入位图
                    FD_SET(connfd, &readfds);
                    //比较connfd+1和nfds大小,如果大于就需要更新nf
                    connfd+1 > nfds ? (nfds = connfd+1) : (nfds = nfds);
                }
                else
                {
                    //如果不是sockfd就应该做通信
                    char buf[100];
                    if(0 == recv(i, buf, sizeof(buf), 0))
                    {
                        //更新位图,把对应文件描述符在位图中清空
                        FD_CLR(i, &readfds);
                        //关闭对应文件描述符
                        close(i);

                    }
                    puts(buf);
                }				
            }
        }		
    }
    close(sockfd);

}

3.alarm signal

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>

//超时函数
void sighanderl(int sig)
{
    if(sig == SIGALRM)
        printf("timeout\n");
}

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    s_addr.sin_port   = htons(6666); 
    s_addr.sin_addr.s_addr = inet_addr("0.0.0.0");

    if(-1 == bind(sockfd, (struct sockaddr*)&s_addr, sizeof(s_addr)))
    {
        perror("bind");
        return -1;
    }

    if(-1 == listen(sockfd, 5))
    {
        perror("listen");
        return -1;
    }

    socklen_t len = sizeof(s_addr);

    signal(SIGALRM, sighanderl);

    while(1)
    {
        //定时器
        alarm(5);
        int connfd = accept(sockfd, (struct sockaddr*)&s_addr, &len);
        if(-1 == connfd)
        {
            perror("accept");
            return -1;
        }
        printf("client ip=%s port=%d\n", inet_ntoa(s_addr.sin_addr), ntohs(s_addr.sin_port));
        char buf[100];

        puts(buf);

    }
}

三、本地套接字/Unix域套接字

1.在地址族选择 AF_UNIX

2.结构体

struct sockaddr_un
{
    sa_family_t sun_family;	//AF_UNIX
    char sun_path[108];	//路径
}

3.不能存在多个(套接字文件)

4.sendmsg/recvmsg

5.传递文件描述符(例如 传递证书)