【网络编程】UDP协议

发布于:2025-02-22 ⋅ 阅读:(15) ⋅ 点赞:(0)

在开始写代码之前,补充一下关于我们在连接中常用到的 Socket

Socket的位置

Socket

Socket 简介
	1982 - Berkeley Software Distributions 操作系统引入了socket作为本地进程之间通信的接口;
	1986 - Berkeley 扩展了socket 接口,使之支持UNIX 下的TCP/IP 通信;
	现在很多应用 (FTP, Telnet, etc) 都依赖这一接口。

Socket:
  * 是一个编程接口
  * 是一种特殊的文件描述符 (everything in Unix is a file)
  * 并不仅限于TCP/IP协议
  * 面向连接 (Transmission Control Protocol - TCP/IP)
  * 无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)

为什么需要Socket?
	普通的I/O操作过程:打开文件->读/写操作->关闭文件
TCP/IP协议被集成到操作系统的内核中,引入了新型的“I/O”操作:进行网络通信的两个进程在不同的机器上,如何连接?网络协议具有多样性,如何进行统一的操作 ——> 因此,此处需要一种通用的网络编程接口:Socket
	Socket独立于具体协议的网络编程接口 ——> 在OSI模型中,主要位于会话层和传输层之间,BSD Socket(伯克利套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。

Socket类型:
	> 流式套接字(SOCK_STREAM)
		> 提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
	> 数据报套接字(SOCK_DGRAM)
		> 提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
	> 原始套接字(SOCK_RAW)
		> 可以对较低层次协议如IP、ICMP直接访问。

套接字和端口
套接字和端口1

int socket (int domain, int type, int protocol);
domain 是地址族
	PF_INET  // internet 协议
	PF_UNIX // unix internal协议
	PF_NS      // Xerox NS协议
	PF_IMPLINK  // Interface Message协议
type  // 套接字类型
	SOCK_STREAM   // 流式套接字
	SOCK_DGRAM    // 数据报套接字
	SOCK_RAW         //  原始套接字
protocol 参数通常置为 0

请添加图片描述

UDP协议

UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

功能:
    提供不同主机上的进程通信

特点:
    1.发送数据之前不需要建立连接
    2.不对数据包的顺序进行检查
    3.没有错误检测和重传机制
    4.相对于TCP速度更快
    5.简单的请求、应答应用程序可使用UDP
    6.广播/组播式通信必须使用UDP

适用情况:
1、发送小尺寸数据(如对DNS服务器进行IP地址查询时)
2、在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
3、适合于广播/组播式通信中。
4、MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
5、流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输

    
1》框架
/**框架**/
     服务器(server)                                     客户端(client)
        
1.创建数据报套接字:socket();                        1.创建数据报套接字:socket();
 		 |                                                 |
2.绑定本地地址:bind();                              2.绑定本地地址:bind();//可忽略
   		 |                                                 |
3.开始和客户端通信:sendto()/recvfrom();             3.开始和服务器通信:sendto()/recvfrom();                 
    	 |                                                 |
4.关闭文件描述符:close();                           4.关闭文件描述符:close();

2》UDP编程相关的API函数

   1>sendto发送数据
   #include <sys/types.h>          /* See NOTES */
   #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);
   /**********************************************************************
    @brief:     UDP中发送数据
    
    @sockfd:   套接字文件描述符 

    @buf:      发送数据的容器
        
    @len:       buf的长度
    
    @flags:    一般为0
    
    @dest_addr:目的地的结构体指针
    
    @addrlen:  结构体的长度
    
    @retval:    成功:返回发送的字节数
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
   2>recvfrom接收数据
   #include <sys/types.h>          /* See NOTES */
   #include <sys/socket.h>
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen);
   /**********************************************************************
    @brief:     UDP中接收数据
    
    @sockfd:   套接字文件描述符 

    @buf:      接收数据的容器
        
    @len:       buf的长度
    
    @flags:    一般为0
    
    @dest_addr:数据源的结构体指针
    
    @addrlen:  结构体的长度
    
    @retval:    成功:返回接收的字节数
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/

UDP通信流程

📌 通信流程框架

服务器(server)客户端(client) 进行 UDP 通信的基本流程如下:

服务器(server) 客户端(client)
1、创建 UDP 套接字:socket() 1、创建 UDP 套接字:socket()
2、绑定本地地址:bind() 2、绑定本地地址(可忽略)
3、发送和接收数据:sendto() / recvfrom() 3、发送和接收数据:sendto() / recvfrom()
4、关闭套接字:close() 4、关闭套接字:close()

代码实现

✅ 服务器代码(server.c)

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#define PORT 8888

int main(void)
{
    struct sockaddr_in sin,cin;
    char buf[256];

    //创建数据报套接字
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd < 0)
    {
	perror("socket");
	exit(1);
    }

    //服务器的地址信息赋值
    bzero(&sin,sizeof(sin));
    sin.sin_family	    = AF_INET;
    sin.sin_port	    = htons(PORT);
    sin.sin_addr.s_addr	    = htonl(INADDR_ANY);//自动获取本机可用IP

    //绑定本地地址
    if(bind(sock_fd, (const struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
	perror("bind");
	exit(1);
    }

    //与客户端通信
    while(1)
    {
	bzero(buf,sizeof(buf));
	socklen_t len = sizeof(cin);
	if(recvfrom(sock_fd, buf, sizeof(buf), 0,(struct sockaddr *)&cin, &len) < 0)
	{
	    perror("recvfrom");
	    exit(1);
	}

	printf("客户端(%s):%s\n",inet_ntoa(cin.sin_addr),buf);

	if(!strncmp(buf,"quit",4))
	    break;
    }

    //关闭套接字文件描述符
    close(sock_fd);

    return 0;
}

✅ 客户端代码(client.c)

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#define PORT 8888

int main(void)
{
    struct sockaddr_in sin;
    char buf[256];

    //创建数据报套接字
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd < 0)
    {
	perror("socket");
	exit(1);
    }

    //服务器的地址信息声明
    bzero(&sin,sizeof(sin));
    sin.sin_family	    = AF_INET;
    sin.sin_port	    = htons(PORT);
    sin.sin_addr.s_addr     = inet_addr("192.168.30.223");	    

    //与客户端通信
    while(1)
    {
	bzero(buf,sizeof(buf));
	printf("请输入数据...:\n");
	fgets(buf,sizeof(buf),stdin);

	if(sendto(sock_fd, buf, strlen(buf), 0,(struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
	    perror("sendto");
	    exit(1);
	}

	if(!strncmp(buf,"quit",4))
	    break;
    }

    //关闭套接字文件描述符
    close(sock_fd);

    return 0;
}

✅ Makefile

#编译部分
CC = gcc

#SRC:当前目录中的.c文件名称
#wildcard:拓展通配符找到目录中所有.c文件,
#         返回成以空格隔开的形式,例如:1.c 2.c 3.c
SRC = ${wildcard *.c}

#OBJS:当前目录中的.c文件对应的同名可执行文件
#patsubst:替换通配符 %:匹配通配符 将.c文件替换成去除掉.c后缀的同名执行文件
OBJS = ${patsubst %.c,%,$(SRC)}

all:$(OBJS)

$(OBJS):%:%.c
	$(CC) -o $@ $^ -lpthread

#清除部分
.PHONY:clean
clean:
	$(RM) $(OBJS) .*.sw?

运行步骤:
1️⃣ 编译代码

make

该make编译后会显示:

gcc -o client client.c -lpthread
gcc -o server server.c -lpthread

2️⃣ 启动服务器

./server

3️⃣ 启动客户端

./client

4️⃣ 进行通信

  • 客户端输入消息,按回车发送
  • 服务器接收并打印消息
  • 服务器回复“Message received!”
  • 客户端打印服务器的响应
    请添加图片描述
UDP流程

服务器端

  1. socket(AF_INET, SOCK_DGRAM, 0):创建 UDP 套接字
  2. bind():绑定到本地 IP 和端口
  3. recvfrom():接收客户端消息
  4. sendto():向客户端发送响应
  5. close():关闭套接字

✅ 客户端

  1. socket(AF_INET, SOCK_DGRAM, 0):创建 UDP 套接字
  2. sendto():向服务器发送数据
  3. recvfrom():接收服务器响应
  4. close():关闭套接字

📌 UDP 的特点

✅ 无连接:无需 connect(),可发送数据到任意地址
✅ 速度快:适用于实时通信(如语音、视频流)
✅ 支持广播、多播:适用于物联网组播系统
✅ 不保证数据可靠性:数据可能丢失,需应用层处理
✅ 代码简单高效,适合 IoT、实时应用、游戏开发等

如果需要更可靠的 UDP 传输,可以在应用层实现:

🔹 ACK 确认机制(发送后要求接收方回复 ACK)
🔹 超时重传(未收到 ACK 时重新发送)
🔹 序列号(确保数据包顺序)

不过,如果需要可靠传输,还是考虑 TCP 代替 UDP。

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!