UDP 是发短信,TCP 是打电话。
- TCP 打电话:必须先拨号(connect)、接通(accept),才能说话,全程不断开。
- UDP 发短信 :不需要接通,直接填对方号码 → 发送 ,对方直接收。没有连接、没有握手、没有断开。
1、UDP 最核心特点
- 无连接不用 listen、不用 accept、不用 connect(服务端不用)。
- 数据报模式一条一条发,一条一条收。
- 不可靠发出去不管对方收没收到。
- 速度快
- 支持一对多、多对多(广播、组播)
2、UDP 服务端 固定 4 步
1. socket()
创建 UDP 套接字
socket(AF_INET, SOCK_DGRAM, 0);
SOCK_DGRAM→ 代表 UDP
2. bind()
绑定 IP 和端口(和 TCP 一模一样)告诉系统:我要在这个端口收数据。
3. recvfrom()
阻塞接收数据 谁发来都行,来了就收。同时获取客户端 IP + 端口。
4. close()
关闭套接字。
3、UDP 客户端 固定 3 步
- socket()
- sendto () → 直接发给服务器
- close()
**客户端甚至可以不用 bind!**系统自动分配端口。
4、UDP 最重要的两个函数
1. recvfrom(服务端收数据)
recvfrom(
sockfd,
buf,
len,
0,
(struct sockaddr *)&client_addr, // 客户端地址
&addrlen
);
作用:阻塞等数据,收到数据,同时知道谁发的。
2. sendto(客户端发数据)
sendto(
sockfd,
buf,
len,
0,
(struct sockaddr *)&server_addr, // 目的地址
addrlen
);
作用:直接把数据发给目标 IP + 端口
5、UDP 服务端流程
plaintext
socket → bind → 循环 recvfrom 收数据 → 处理
没有 listen! 没有 accept! 没有连接!
谁发数据给这个端口,服务器就收谁的。
6、TCP vs UDP 函数对比
| 步骤 | TCP | UDP |
|---|---|---|
| 创建 | socket(...SOCK_STREAM...) | socket(...SOCK_DGRAM...) |
| 绑定 | bind | bind |
| 监听 | listen | 无 |
| 连接 | accept / connect | 无 |
| 收数据 | read | recvfrom |
| 发数据 | write | sendto |
服务器端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_PORT 5001
#define BUFSIZE 1024
#define QUIT_STR "QUIT"
int main()
{
int fd = -1;
struct sockaddr_in sin;
//1.socket
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket");
exit(1);
}
int b_reuser = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuser,sizeof(int));
//2.bind
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = INADDR_ANY;
if(bind(fd,(struct sockaddr*)&sin,sizeof(sin)) < 0)
{
perror("bind");
exit(1);
}
//3.recvfrom
char buf[BUFSIZE];
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1)
{
bzero(buf,BUFSIZE);
if(recvfrom(fd,buf,BUFSIZE-1,0,(struct sockaddr*)&cin,&addrlen) < 0)
{
perror("recvfrom");
continue;
}
char ipv4_addr[16];
if(!inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
{
perror("inet_ntop");
exit(1);
}
printf("receive from(%s,%d),data:%s\n",ipv4_addr,ntohs(cin.sin_port),buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client(%s:%d)is exiting!\n",ipv4_addr,ntohs(cin.sin_port));
}
}
close(fd);
return 0;
}
①socket () 创建 UDP 套接字
fd = socket(AF_INET, SOCK_DGRAM, 0);
重点:
- AF_INET:IPv4
- SOCK_DGRAM :数据报 → UDP
- 之前 TCP 是
SOCK_STREAM
这一句决定了是 UDP 还是 TCP!
②setsockopt()端口复用
int b_reuser = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuser, sizeof(int));
作用:
允许程序重启后,立刻绑定同一个端口不加这句,程序退出后,端口会被占用几十秒。
③bind()绑定IP+端口
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
bind(fd, (struct sockaddr*)&sin, sizeof(sin));
和 TCP 完全一样!
- 绑定端口 5001
- 绑定所有网卡 INADDR_ANY
④最核心:UDP 怎么收数据?UDP 没有 accept!直接收!
while(1)
{
bzero(buf, BUFSIZE);
// 阻塞等待接收 UDP 数据报
recvfrom(
fd,
buf,
BUFSIZE-1,
0,
(struct sockaddr*)&cin,
&addrlen
);
}
recvfrom () 函数
函数作用:
阻塞等待客户端发来 UDP 数据报 谁发来的?发来什么内容?全部拿到!
参数:
fdUDP 套接字
buf接收数据的缓冲区
BUFSIZE-1最大接收长度
0默认为 0
&cin 用来保存客户端的 IP 和端口
&addrlen地址长度
⑤接收后打印客户端信息
inet_ntop(AF_INET, &cin.sin_addr, ipv4_addr, sizeof(ipv4_addr));
printf("receive from(%s,%d), data:%s\n",
ipv4_addr,
ntohs(cin.sin_port),
buf);
⑥判断退出
if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))
{
printf("client exiting\n");
}
收到 QUIT 就提示客户端退出。
客户端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<strings.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#define SREV_PORT 5001
#define SERV_IP_ADDR "192.168.88.129"
#define QUIT_STR "QUIT"
#define BUFSIZE 1024
int main(int argc,char **argv)
{
if(argc != 3)
{
exit(1);
}
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket");
exit(1);
}
int port = -1;
port = atoi(argv[2]);
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
char buf[BUFSIZE];
while(1)
{
if(fgets(buf,BUFSIZE-1,stdin) == NULL)
{
perror("fgets");
continue;
}
sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&sin,sizeof(sin));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client is exited!\n");
break;
}
}
close(fd);
return 0;
}
①主函数&命令行参数
int main(int argc,char **argv)
{
if(argc != 3)
{
exit(1);
}
作用:必须传入 2 个参数:服务器 IP + 端口
②创建UDP套接字
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket");
exit(1);
}
重点:
AF_INET:IPv4SOCK_DGRAM:UDP 数据报 (TCP 是SOCK_STREAM)
③读取命令行端口
int port = atoi(argv[2]);
把命令行的字符串端口→转成数字
④配置服务器地址(填短信收件人)
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
作用:
填写目标服务器的 IP 和端口。
sin:服务器地址结构体argv[1]:服务器 IPport:服务器端口
⑤循环发送数据
char buf[BUFSIZE];
while(1)
{
// 从键盘读入数据
if(fgets(buf,BUFSIZE-1,stdin) == NULL)
{
perror("fgets");
continue;
}
// 发送数据(UDP 核心函数)
sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&sin,sizeof(sin));
// 如果输入 QUIT,退出
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client is exited!\n");
break;
}
}
sendto()函数
sendto(
fd, // UDP 套接字
buf, // 要发送的数据
strlen(buf),// 数据长度
0, // 默认为 0
(struct sockaddr*)&sin, // 服务器地址
sizeof(sin) // 地址长度
);
作用:
直接把数据发送给指定的服务器! 不需要连接!不需要 connect!
参数:
fdUDP 套接字
buf接收数据的缓冲区
BUFSIZE-1最大接收长度
0默认为 0
&cin 用来保存客户端的 IP 和端口
&addrlen地址长度
⑥关闭套接字&退出
close(fd);
return 0;
执行结果
