网络编程:UDP网路编程
参考:盛延敏:网络编程实战
一、UDP和TCP的不同
- UDP 是一种“数据报”协议,而 TCP 是一种面向连接的“数据流”协议。
- TCP 是一个面向连接的协议,TCP 在 IP 报文的基础上,增加了诸如重传、确认、有序传输、拥塞控制等能力,通信的双方是在一个确定的上下文中工作的。而 UDP 则不同,UDP 没有这样一个确定的上下文,它是一个不可靠的通信协议,没有重传和确认,没有有序控制,也没有拥塞控制。可以简单地理解为,在 IP 报文的基础上,UDP 增加的能力有限。
UDP 不保证报文的有效传递,不保证报文的有序,也就是说使用 UDP 的时候,我们需要做好丢包、重传、报文组装等工作。
TCP 的发送和接收每次都是在一个上下文中,类似这样的过程:
A 连接上: 接收→发送→接收→发送→…
B 连接上: 接收→发送→接收→发送→…
而 UDP 的每次接收和发送都是一个独立的上下文,类似这样:
接收 A→发送 A→接收 B→发送 B →接收 C→发送 C→ …
二、UDP网络编程

服务器端创建 UDP 套接字之后,绑定到本地端口,调用 recvfrom 函数等待客户端的报文发送;客户端创建套接字之后,调用 sendto 函数往目标地址和端口发送 UDP 报文,然后客户端和服务器端进入互相应答过程。
recvfrom 和 sendto 是 UDP 用来接收和发送报文的两个主要函数:
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
- sockfd 是本地创建的套接字描述符
- buff 指向本地的缓存
- nbytes 表示最大接收数据字节。
- flags 是和 I/O 相关的参数,这里还用不到,设置为 0。
- 后面两个参数 from 和 addrlen,实际上是返回对端发送方的地址和端口等信息,和 TCP 非常不一样,TCP 是通过 accept 函数拿到的描述字信息来决定对端的信息。另外 UDP 报文每次接收都会获取对端的信息,也就是说报文和报文之间是没有上下文的。
- 返回值告诉实际接受的字节数。
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
- sockfd 是本地创建的套接字描述符
- buff 指向发送的缓存
- nbytes 表示发送字节数
- 第四个参数 flags 依旧设置为 0。
- 后面两个参数 to 和 addrlen,表示发送的对端地址和端口等信息。
- 函数的返回值告诉我们实际发送的字节数。
三、编程实践
udp服务端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define SERV_PORT 43211
#define MAXLINE 4096
static int count;
static void recvfrom_int(int signo)
{
printf("\nrecevied %d datagrams\n",count);
exit(0);
}
int main(int argc,char*argv[])
{
int socket_fd;
struct sockaddr_in server_addr;
socket_fd = socket(AF_INET,SOCK_DGRAM,0);
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERV_PORT);
bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));
socklen_t client_len;
char message[MAXLINE];
count = 0;
signal(SIGINT,recvfrom_int);
struct sockaddr_in client_addr;
client_len = sizeof(client_addr);
for(;;)
{
int n = recvfrom(socket_fd,message,MAXLINE,0,(struct sockaddr *) &client_addr, &client_len);
message[n] = 0;
printf("received %d bytes: %s\n",n,message);
char send_line[MAXLINE];
sprintf(send_line,"Hi,%s\n",message);
sendto(socket_fd,send_line,strlen(send_line),0, (struct sockaddr *) &client_addr, client_len);
count++;
}
}
udp客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define SERV_PORT 43211
#define MAXLINE 4096
int main(int argc,char* argv[])
{
if(argc != 2)
{
perror("Usage:udpclient <IPaddress>");
return -1;
}
int socket_fd;
socket_fd = socket(AF_INET,SOCK_DGRAM,0);
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,argv[1],&server_addr.sin_addr);
socklen_t server_len = sizeof(server_addr);
struct sockaddr *reply_addr;
reply_addr = malloc(server_len);
char send_line[MAXLINE],recv_line[MAXLINE+1];
socklen_t len;
int n;
while(fgets(send_line,MAXLINE,stdin) != NULL)
{
int i = strlen(send_line);
if(send_line[i-1] == '\n')
{
send_line[i-1] = 0;
}
printf("now sending %s \n",send_line);
size_t rt = sendto(socket_fd,send_line,strlen(send_line),0,(struct sockaddr *)&server_addr,server_len);
if(rt < 0)
{
perror("send faild");
}
printf("send bytes %zu \n",rt);
len = 0;
n = recvfrom(socket_fd,recv_line,MAXLINE,0,reply_addr,&len);
if(n < 0)
{
perror("recvfrom failed");
}
recv_line[n] = 0;
fputs(recv_line,stdout);
fputs("\n",stdout);
}
exit(0);
}
效果:
1)若只运行客户端,程序一直会阻塞在recvfrom上

2)先开服务端,再开客户端


3) 开服务端,开两个客户端
服务端:

客户端1:

客户端2:

两个客户端发送的报文,依次都被服务端收到,并且客户端也可以收到服务端处理之后的报文。如果我们此时把服务器端进程杀死,就可以看到信号函数在进程退出之前,打印出服务器端接收到的报文个数。
再重启服务器端进程,并使用客户端 1 和客户端 2 继续发送新的报文,这也体现了UDP 报文的“无连接”的特点,可以在 UDP 服务器重启之后,继续进行报文的发送,这就是 UDP 报文“无上下文”的最好说明。(但在服务端进程被杀死后,此时就是1)状态了,若此时在任意客户端发送数据,程序便会一直阻塞在recvfrom上)
注:
详解inet_pton()和inet_ntop()函数
网络编程:UDP网路编程的更多相关文章
- java UDP网路编程
大家都知道java中的socket网络编程,而其采用的协议分别有tcp和udp协议两种. 通常的理解tcp协议类似于打电话,udp类似于发短信.前者是线程安全的,但是效率比较低.后者则刚好相反. 今天 ...
- C++网络套接字编程TCP和UDP实例
原文地址:C++网络套接字编程TCP和UDP实例作者:xiaojiangjiang 1. 创建一个简单的SOCKET编程流程如下 面向有连接的套接字编程 服务器: 1) 创建套接字(so ...
- c/c++ 网络编程 UDP 设定MTU
网络编程 UDP 设定MTU MTU(Maximun Transmisson Unit):一次送信的最大size. 在程序里动态改变MTU.注意:程序运行需要root权限. 程序运行的方法: sudo ...
- c/c++ 网络编程 UDP up/down 网卡
网络编程 UDP up/down 网卡 在程序里动态改变网卡的状态.注意:程序运行需要root权限. 程序运行的方法: sudo ./a.out 1,关闭网卡 #include <stdio.h ...
- c/c++ 网络编程 UDP 改变网关和网卡名字
网络编程 UDP 改变网关和网卡名字 在程序里动态改变网关和网卡名字 1,改变网卡名字 #include <stdio.h> #include <string.h> #incl ...
- c/c++ 网络编程 UDP 改变网卡的硬件地址
网络编程 UDP 改变网卡的硬件地址 在程序里动态改变网卡的硬件地址 1,取得网卡的硬件地址 #include <stdio.h> #include <string.h> #i ...
- c/c++ 网络编程 UDP 改变IP地址
网络编程 UDP 改变IP地址 在程序里动态改变主机的IP地址 1,改变ipv4的地址 #include <stdio.h> #include <string.h> #incl ...
- c/c++ 网络编程 UDP 用if_nameindex和ioctl取得主机网络信息
网络编程 UDP 用if_nameindex和ioctl取得主机网络信息 getifaddrs函数取得的东西太多了,如果只想取得网卡名字和网卡编号可以用下面的2个函数. 1,if_nameindex ...
- c/c++ 网络编程 UDP 主机网络信息取得
网络编程 UDP 主机网络信息取得 1,if_nametoindex 通过网卡名字取得网卡编号 2,if_indextoname 通过网卡编号取得网卡名字 #include <stdio.h&g ...
- c/c++ 网络编程 UDP 发送端 bind 作用
网络编程 UDP 发送端 bind 作用 upd 发送端 调用bind函数的效果:把socket特定到一个指定的端口,如果不调用bind,内核会随机分配一个端口. upd 发送端 调用bind函数的目 ...
随机推荐
- mybatis-generator 自动生成mybatis代码与xml
一.pxm.xml中引入mybatis-generator并配置 在build节点下的plugins下添加org.mybatis.generator 示例代码如下: <?xml version= ...
- FastAPI路由与请求处理进阶指南:解锁企业级API开发黑科技 🔥
title: FastAPI路由与请求处理进阶指南:解锁企业级API开发黑科技 date: 2025/3/3 updated: 2025/3/3 author: cmdragon excerpt: 5 ...
- sql---索引总结
索引:是为了提高数据查询的效率 常见模型: 哈希表(以键值对key-value存储数据的结构) 适应场景:哈希表这种结构适用于只有等值查询的场景 思路:把值放在数组里,用一个哈希函数把key换算成一个 ...
- 07_读写文件open(filename, mode, encoding=None)
读写文件open(filename, mode, encoding=None) mode mode 权限 r 只读 w 只写(会从头开始覆盖覆盖写当前文件内容) a 追加写(从文件内容的末尾追加写内容 ...
- 在Linux系统中下载`gcc-linaro-7.2.1-2017.11-x86_64_aarch64-linux-gnu`工具链
要在Linux系统中下载gcc-linaro-7.2.1-2017.11-x86_64_aarch64-linux-gnu工具链,你可以按照以下步骤进行操作: 点击查看代码 1. **打开终端**:你 ...
- FastAPI 表单参数与文件上传完全指南:从基础到高级实战 🚀
title: FastAPI 表单参数与文件上传完全指南:从基础到高级实战 date: 2025/3/8 updated: 2025/3/8 author: cmdragon excerpt: 本教程 ...
- Laravel11 从0开发 Swoole-Reverb 扩展包(二) - Pusher 协议介绍
Pusher 协议概述 Pusher 协议 是一种用于实时 Web 通信的协议,它基于 WebSocket 技术,并提供了一套 发布-订阅(Pub/Sub)模式,用于让客户端(如浏览器.移动端.后端服 ...
- Major、Minor、Build Number及Revision 版本号注解含义
版本号 版本号由二至四个部分组成:主版本号.次版本号.内部版本号和修订号. 主版本号和次版本号两个部分为必选,内部版本号和修订号两个部分为可选.只有在未定义内部版本号部分时,修订号部分才为可选.所有定 ...
- 一款HTML转Markdown格式的工具
Markdown格式不仅对写博客的人非常友好和方便,对AI也是如此. 目前AI大语言模型的输出基本都是Markdown格式,这就意味着AI是能充分理解Markdown格式的,这一点非常重要. Mark ...
- 分布式一致性算法-Paxos、Raft、ZAB、Gossip
为什么需要一致性 数据不能存在单个节点(主机)上,否则可能出现单点故障. 多个节点(主机)需要保证具有相同的数据. 一致性算法就是为了解决上面两个问题. 一致性算法的定义 一致性就是数据保持一致, ...