kehUDP编程—QQ聊天室
概述:
UDP是User Datagram Protocol --用户数据报协议,是一个简单的面向数据报的运输层协议,是一种无连接的协议。UDP不提供可靠性的传输,它只是把应用程序传给IP层的数据报发送出去,但不保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个联系,且没有超时重发等机制,故而传输速度很快。d
特点:
- 邮件系统服务模式的抽象
- 每个分组都携带完整的目的地址
- 发送数据之前不需要建立连接
- 不对数据包的顺序进行检查,不能保证分组的先后顺序
- 不进行分组出错的恢复和重传
- 不保证数据传输的可靠性
在网络质量不好的情况下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:
它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频,视频和普通数据在传送时使用 UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接受结果产生太大影响。比如我们聊天用的ICQ和QQ就使用的UDP协议
UDP编程的C/S架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KF3AV12z-1656939566349)(D:\图片\image-20220704180859668.png)]
对比于写信模型。客户端相当于寄信人,要想成功给人寄信,信封上必须写上对方的地址
UDP客户端程序
socket创建通信的套接字
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
参数:domain协议族:协议AF_INET IPv4 AF_INET6 IPv6
type类型:SOCK_DGRAM(UDP的套接字),SOCK_STREAM(TCP套接字),SOCK_RAW(原始套接字)
protocol协议类别:(0,IPPROTO_TCP,IPPROTO_UDP)
返回值:
>0 通信的文件描述符
<0 创建失败
sendto()发送
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd,//套接字
const void*buf,//发送数据缓冲区
size_t nbytes,//发送数据缓冲区的大小
int flags,//一般为0
const struct sockaddr*to,//指向目的主机地址结构体的指针
socklen_t addrlen)//to 所指向内容的长度
功能:向to结构体指针中指定的ip,发送UDP数据,可以发送0长度的UDP数据包
返回值:
成功:发送数据的长度
失败:-1
IPv4地址结构
存放IPv4协议通信的所有地址信息
#include<netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family;//2 字节 协议AF_INET AF_INET6
in_port_t sin_port;//2 字节 端口
struct in_addr sin_addr;//4 字节 IP地址(32位无符号整数)
char sin_zero[8]//8 字节 全写0
};
struct in_addr:
{
in_addr_t s_addr;//4 字节
};
通用地址结构(类型转换)
struct sockaddr{
sa_family_t sa_family;//2字节
char sa_data[14]//14字节
}
DUP客户端编程流程,有点类似于写信的过程:
找个邮政工作人员(socket())-----信封上写上地址同时里面装上信件内容并且投递(sendto())------对方接受(recvform())-----收工(close())
recvfrom()收到
ssize_t recvfrom(int sockfd,//套接字
void *buf,//接受数据缓冲区
size_t nbytes,//接受数据缓冲区的大小
int flags,//套接字标志(常用0)
struct sockaddr*from,//源地址结构体指针,用来保存数据的来源
socklen_t*addrlen)//from所指内容的长度
功能:接受UDP数据,并将源地址信息保存在from指向的结构中,默认的情况下,如果没有接受到数据,这个函数会阻塞,直到有数据到来
客户端程序发送接受
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
unsigned short port=8080;//服务器端口
char*server_ip="192.168.1.101";//服务器ip地址
int sockfd;
sockfd=socket(AF_INET,SOCK_DGRAM,0);//创建UDP套字
//udp客户端 发送消息 给服务器
//定义一个IPv4地址结构 存放服务器的地址信息(目的主机)
struct sockaddr_in ser_addr;
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family=AF_INET;
ser_addr.sin_port=htons(port);//服务器端口
//服务器的ip地址
inet_pton(AF_INET,server_ip,&ser_addr.sin_addr.s_addr);
//发送数据
sendto(sockfd,"hello word",strlen("hello word"),0,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
//接收数据
//定义一个IPv4地址结构 存放发送者的信息
while(1)
{
struct sockaddr_in from_addr;
socklen_t from_len=sizeof(from_addr);
unsigned char buf[1500]="";
int len=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&from_addr,&from_len);
char ip[16]="";
inet_ntop(AF_INET,&from_addr.sin_addr.s_addr,ip,16);
printf("消息来自于%s %hu---->",ip,ntohs(from_addr.sin_port));
printf("len:%d msg:%s\n",len,buf);
}
//关闭套接字
close(sockfd);
return 0;
}
UDP注意事项:
- 本地ip,本地端口(我是谁)
- 目的ip,目的端口(发给谁)
- 在客户端的代码中,我们只设置了目的ip,目的端口
- 客户端的本地IP,本地port是我们调用sendto的时候Linux系统底层自动给客户端分配的;分配端口的方式为随机分配,及每次运行系统给的port不一样
bind给udp套接字绑定固定的port,ip信息
#include<sys/socket.h>
int bind(int socket,const struct sockaddr*address)
socklen_t address_len
返回值:
成功:0
失败为-1
UDP编程–QQ
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<pthread.h>
#include<arpa/inet.h>
#include<unistd.h>
void *send_function(void *arg)
{
//获取套接字
int sockfd=*(int*)arg;
//定义目的地址
struct sockaddr_in dst_addr;
bzero(&dst_addr,sizeof(dst_addr));
dst_addr.sin_family=AF_INET;
while(1)
{
//获取键盘输入
char buf[128]="";
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//判断是否是ip port
//sayto ip port
if(strncmp(buf,"sayto",5)==0)
{
char ip[16]="";
unsigned short port=0;
sscanf(buf,"sayto %s %hu",ip,&port);
dst_addr.sin_port=htons(port);
inet_pton(AF_INET,ip,&dst_addr.sin_addr.s_addr);
continue;
}
else
{
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&dst_addr,sizeof(dst_addr));
if(strcmp(buf,"bye")==0)
{
break;
}
}
}
return NULL;
}
void *recv_function(void *arg)
{
int sockfd=*(int*)arg;
while(1)
{
struct sockaddr_in from_addr;
socklen_t from_len=sizeof(from_addr);
unsigned char buf[1500]="";
char ip[16]="";
int len=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&from_addr,&from_len);
printf("%s %hu:%s\n",inet_ntop(AF_INET,&from_addr.sin_addr.s_addr,ip,16),ntohs(from_addr.sin_port),buf);
if(strcmp(buf,"bye")==0)
{
break;
}
}
return NULL;
}
int main(int argc,char const*argv[])
{
//判断参数,./a.out 8080
if(argc!=2)
{
printf("./a.out 8080\n");
return 0;
}
//创建udp套接字
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
//bind绑定固定的端口,ip
struct sockaddr_in my_addr;
bzero(&my_addr,sizeof(my_addr));
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(atoi(argv[1]));
my_addr.sin_addr.s_addr=htonl(INADDR_ANY);
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));
//创建发送线程
pthread_t send_tid;
pthread_create(&send_tid,NULL,send_function,&sockfd);
//创建接受线程
pthread_t recv_tid;
pthread_create(&recv_tid,NULL,recv_function,&sockfd);
pthread_join(send_tid,NULL);
pthread_join(recv_tid,NULL);
//关闭套接字
close(sockfd);
return 0;
}
ead_t send_tid;
pthread_create(&send_tid,NULL,send_function,&sockfd);
//创建接受线程
pthread_t recv_tid;
pthread_create(&recv_tid,NULL,recv_function,&sockfd);
pthread_join(send_tid,NULL);
pthread_join(recv_tid,NULL);
//关闭套接字
close(sockfd);
return 0;
}