一、什么是UDP?
1.UDP协议:
是面向无连接的用户数据报协议,在传输数据前不需要建立连接,且目的主机收到UDP报文后,也不需要给出任何确认。
2.特点:
1、相比TCP速度更快
2、广播和多播应用必须使用UDP
3、简单的请求和应答应用程序可以使用UDP
3.C/S架构:
C/S架构指的是客户端-服务器架构(Client-Server Architecture),它是一种常见的网络通信模型。
-
客户端(Client):客户端是发起通信请求的一方。它创建一个UDP套接字(socket),并通过该套接字向服务器发送数据报。客户端通常负责发送请求、接收响应以及处理服务器返回的数据。
-
服务器(Server):服务器是接收并处理客户端请求的一方。它创建一个UDP套接字,并在指定的端口上监听客户端的请求。当服务器接收到客户端发送的数据报时,会根据请求进行相应的处理,并将处理结果发送回客户端
疑问:client 能否接收数据?server 能否发送数据呢?
其实在网络编程开发中 client 和 server 双方既可以有发送数据还可以接收数据;一般认为提供服务的一方为 server;而接受服务的另一方为 clien。
注意点:
①服务器之所以要bind,因为本地端口port需要固定,不是随机;
② 服务器也可以给客服端发送数据;
③客户端也可以bind,但一般不这样。
二、案例
使用UDP,实现简单的QQ功能,要求:能够指定发给某个IP。
1.发送函数
void *send_deal(void *arg)
{
//从参数中获取套接字描述符
int sockfd = *(int *)arg;
//定义目的地址结构体
struct sockaddr_in dst_addr;
//清零地址结构
bzero(&dst_addr,sizeof(dst_addr));
//设置地址族为IPV4
dst_addr.sin_family = AF_INET;
while(1)
{
char buf[128] = "";
//读取用户数据
fgets(buf,sizeof(buf),stdin);
//去掉末尾换行符
buf[strlen(buf)-1]=0;
//如果用户改变目的地址
if(strncmp(buf,"sayto",5)==0)
{
unsigned short port = 0;
char ip[16] = "";
//从字符串中解析IP地址
sscanf(buf,"sayto %s %hu",ip,&port);
//设置端口
dst_addr.sin_port = htons(port);
//设置IP地址
inet_pton(AF_INET,ip,&dst_addr.sin_addr.s_addr);
continue;
}
//发送数据
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&dst_addr,sizeof(dst_addr));
//用户输入q,退出循环
if(strcmp(buf,"q")==0)
{
break;
}
}
return NULL;
}
2.接收函数
//接受函数
void *recv_deal(void *arg)
{
//从参数中获取套接字描述符
int sockfd = *(int *)arg;
while (1)
{
//客户端结构体
struct sockaddr_in cli_addr;
//结构体的长度
socklen_t cli_len = sizeof(cli_addr);
unsigned short port = 0;//存储端口数量
char ip[16] = "";//存储IP
unsigned char buf[512]= "";//接收的数据
int len = 0;//接收的长度
//接收数据
len = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cli_addr,&cli_len);
if(len>=0)//接收成功
{
port = ntohs(cli_addr.sin_port);//得到端口号
inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr,ip,16);
printf("%s:%hu %s\n",ip,port,buf);
}
}
return NULL;
}
3.主函数
//检查命令行参数数量
if(argc!=2)
{
printf("./a.out 8000\n");
return 0;
}
//创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
//检查套接字是否创建成功
if(sockfd<0)
{
perror("sockfd");
return 0;
}
//定义服务器地址结构体并初始化为0
struct sockaddr_in my_addr;
bzero(&my_addr,sizeof(my_addr));
//设置地址族为IPV4
my_addr.sin_family = AF_INET;
//设置端口号,使用命令行参数,并转换为网络字节序
my_addr.sin_port = htons(atoi(argv[1]));
//绑定到所有的网络接口
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定地址到套接字
int ret = bind(sockfd,(struct sockaddr *)&my_addr,sizeof(my_addr));
//检查是否绑定成功
if(ret<0)
{
perror("bind");
close(sockfd);//关闭套接字
return 0;
}
//创建发送线程
pthread_t send_tid;
pthread_create(&send_tid,NULL,send_deal,(void *)&sockfd);
//创建接收线程
pthread_t recv_tid;
pthread_create(&recv_tid,NULL,recv_deal,(void *)&sockfd);
//主线程等待发送线程结束
pthread_join(send_tid,NULL);
//取消接收线程
pthread_cancel(recv_tid);
//主线程等待接收线程结束
pthread_join(recv_tid,NULL);
//关闭套接字
close(sockfd);
return 0;
主要用到头文件:
// 导入标准输入输出库
#include <stdio.h>
// 导入套接字相关库
#include <sys/socket.h>
// 导入UNIX标准库,包含了close函数
#include <unistd.h>
// 导入网络地址库,包括sockaddr_in结构体
#include <netinet/in.h>
// 导入字符串处理函数,例如bzero
#include <string.h>
// 导入IP地址处理函数,例如inet_pton
#include <arpa/inet.h>
// 导入线程库
#include <pthread.h>
// 导入标准库,包含了atoi函数
#include <stdlib.h>
可以使用网络调试助手,运行就能实现啦。
注:
编译时如没有线程相应的库,线程函数的程序在 pthread 库中,故链接时要加上参数-lpthread。
改变IP和端口号:sayto 具体IP和端口号port;
结束:输入q。