文章目录
- 一、 UDP
- (一)概述
- (二)流程
- 二、收发函数
- (一)recvfrom
- (二)sendto
- 三、实现一个简单的udp服务端和客户端
- 四、实现tftp客户端协议
一、 UDP
(一)概述
UDP(User Datagram Protocol)用户数据报协议:
是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
- 注:
默认支持并发
不存在“粘包”的现象
(二)流程
服务器流程:
创建套接字–socket()
填充服务器网络信息结构体
将套接字与服务器网络信息结构体绑定–bind()
收发数据–sendto() recvfrom()
关闭套接字–close()
客户端流程:
创建套接字–socket()
填充服务器网络信息结构体
收发数据–sendto() recvfrom()
关闭套接字–close()
二、收发函数
(一)recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:在套接字上接收一条消息
参数:
前 4 个参数用法和 recv 函数的4个参数用法一模一样
后 2 个参数用法和 accept 函数的后两个参数用法一样 用来 保存发送方的网络信息结构体的
返回值:
成功 实际接收的字节数
失败 -1 重置错误码
recvfrom函数用在UDP中是不会返回0的
(二)sendto
#include <sys/types.h>
#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);
功能:向套接字上发送一条消息
参数:
前 4 个参数用法和 send 函数的4个参数用法一模一样
后 2 个参数用法和 connect 函数的后两个参数用法一样 用来 指定消息发给谁的
返回值:
成功 实际发送的字节数
失败 -1 重置错误码
三、实现一个简单的udp服务端和客户端
server.c
#include <my_head.h>
int main(int argc, char const *argv[])
{
if(3 != argc){
printf("Usage: %s IPv4 port\n",argv[0]);
exit(-1);
}
//创建套接字
int sockfd = 0;
if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0))){
ERR_LOG("socket error");
}
//填充结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(atoi(argv[2]));//将命令行第二个参数转成int,并转成网络字节序
serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t serveraddrlen = sizeof(serveraddr);
//绑定结构体
if(-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddrlen)){
ERR_LOG("bind error");
}
//客户信息结构体
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
while(1){
//收发数据
printf("等待接收数据...\n");
char buff[128]={0};
//保存发送方的网络结构体
if(-1 == recvfrom(sockfd,buff,sizeof(buff),0,(struct sockaddr *)&clientaddr,&clientaddrlen)){
ERR_LOG("recvfrom error");
}
printf("接收到数据[%s]\n",buff);
strcat(buff,"--zyx");
//指定发给谁
if(-1 == sendto(sockfd,buff,sizeof(buff),0,(struct sockaddr *)&clientaddr,clientaddrlen)){
ERR_LOG("sendto error");
}
}
return 0;
}
client.c
#include <my_head.h>
int main(int argc, char const *argv[])
{
if(3 != argc){
printf("Usage: %s IPv4 port\n",argv[0]);
exit(-1);
}
//创建套接字
int sockfd = 0;
if(-1 == (sockfd = socket(AF_INET,SOCK_DGRAM,0))){
ERR_LOG("socket error");
}
//填充结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t serveraddrlen = sizeof(serveraddr);
//可以不进行绑定,系统会自动绑定
//收发数据
char buff[128];
while(1){
printf("请输入数据:");
scanf("%s",buff);
if(-1 == sendto(sockfd,buff,sizeof(buff),0,(struct sockaddr *)&serveraddr,serveraddrlen)){
ERR_LOG("sendto error");
}
if(-1 == recvfrom(sockfd,buff,sizeof(buff),0,NULL,NULL)){
ERR_LOG("recvfrom error");
}
printf("应答消息:[%s]\n",buff);
}
return 0;
}
四、实现tftp客户端协议
TFTP概述
TFTP:简单文件传送协议
最初用于引导无盘系统,被设计用来传输小文件
特点:
基于UDP协议实现
不进行用户有效性认证
数据传输模式:
octet:二进制模式
netascii:文本模式
mail:已经不再支持
需求分析:
1、服务器在69号端口等待客户端的请求
2、服务器若批准此请求,则使用临时端口与客户端进行通信
3、每个数据包的编号都有变化(从1开始)
4、每个数据包都要得到ACK的确认如果出现超时,则需要重新发送最后的包(数据或ACK)
5、数据的长度以512Byte传输
6、小于512Byte的数据意味着传输结束
注意点:
1.tftp服务器仅在69号端口接收客户端的请求,批准请求后,使用临时端口与客户端进行通信
2.每个数据包都要得到ACK的确认
代码实现:
#include <my_head.h>
int main(int argc, char const *argv[])
{
if(2 != argc){
printf("Usage: %s IPv4\n",argv[0]);
exit(-1);
}
//创建套接字
int socketfd = 0;
if(-1 == (socketfd = socket(AF_INET,SOCK_DGRAM,0))){
ERR_LOG("socket error");
}
//填充结构体
struct sockaddr_in clentaddr;
clentaddr.sin_family=AF_INET;
clentaddr.sin_port=htons(69);
clentaddr.sin_addr.s_addr=inet_addr(argv[1]); //字符串转四字节网络序整型
socklen_t clentlen = sizeof(clentaddr);
//发送请求
char buff[600]={0};
char filename[20]={0};
printf("请输入想要下载的文件:");
scanf("%s",filename);
int dest_fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0664);
//组装请求数据包
int nbytes = sprintf(buff,"%c%c%s%c%s%c",0,1,filename,0,"octet",0);
if(-1 == sendto(socketfd,buff,nbytes,0,(struct sockaddr *)&clentaddr,clentlen)){
ERR_LOG("sendto error");
}
unsigned short option=0;
unsigned short node =0;
char data[512]={0};
char ack[4];
while(1){
//接收服务器数据
memset(buff, 0, sizeof(buff));
memset(data, 0, sizeof(data));
nbytes = recvfrom(socketfd,buff,sizeof(buff),0,(struct sockaddr *)&clentaddr,&clentlen);
//解析数据(网络字节序)
option = *(unsigned short *)buff;//操作码
node = *(unsigned short *)(buff+2);//块编号
strncpy(data,buff+4,nbytes-4);
if(5 == ntohs(option)){
//说明出错了
printf("差错码:%d; 差错信息:%s\n",ntohs(node),buff);
}else if(3 == ntohs(option)){
printf("已接收到数据\n");
//说明发送过来数据了,组装应答数据包
memset(ack,0,4);
ack[0]=0;
ack[1]=4;
*(unsigned short *)(ack+2)=node;
//发送ACK
if(-1 == sendto(socketfd,ack,4,0,(struct sockaddr *)&clentaddr,clentlen)){
ERR_LOG("sendto error");
}
//写入文件中
if(-1 == write(dest_fd,data,nbytes-4)){
ERR_LOG("write error");
}
}
if(nbytes < 516)//数据包内容不足512字节,说明文件拷贝完毕
break;
}
printf("Download over\n");
close(dest_fd);
close(socketfd);
return 0;
}