网络编程基础——UDP编程(2)
1 UDP的概念
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议。
UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
许多应用只支持UDP,如:多媒体数据流,不产生任何额外的数据,即使知道有破坏的包也不进行重发。当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择。
2 UDP编程主要函数与步骤
1 socket
int socket(int domain, int type, int protocol);
功能:创建一个用来进程通信的套接字,返回文件描述符
参数:
domain:AF_INET IPv4协议族
type:SOCK_STREAM 流式套接字 tcp传输协议
SOCK_DGRAM 数据报套接字 udp传输协议
SOCK_RAW 原始套接字
protocol:默认为0
返回值:
成功返回套接字新文件描述符
失败返回-1
2 bind
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:将套接字与IP地址端口绑定在一起
参数:
sockfd:文件描述符
addr:结构体空间首地址
addrlen:信息的长度
返回值:
成功返回0
失败返回-1
3 sendto
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:给另一个套接字发送消息
参数:
sockfd:套接字文件描述符
buf:要发送数据存放空间的首地址
len:要发送数据的长度
flags:发送属性 默认为0
dest_addr:目的地址
addrlen:目的地址信息长度
返回值:
成功返回发送字节个数
失败返回-1
4 recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd:套接字文件描述符
buf:存放接收到数据空间的首地址
len:最多允许接收的字节数
flags:属性 默认为0
src_addr:存放发送端地址信息空间首地址
addrlen:想要接收发送端地址大小的变量空间首地址
返回值:
成功返回实际接收字节数
失败返回-1
**注意:该函数具有阻塞功能**
5 htons与ntohs
uint16_t htons(uint16_t hostshort);
功能:将本地字节序转换为网络字节序
参数:hostshort:本地端口号
返回值:返回网络字节序端口号
uint16_t ntohs(uint16_t netshort);
功能:将网络字节序转换为本地字节序
参数:netshort:网络端口号
返回值:返回本地字节序端口号
6 套接字结构体
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
3 用UDP编程实现客户端与服务端轮询单次交互
这里我的ip地址为图中的192.168.1.117,所以在已下代码都用的这个地址
头文件
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/epoll.h>
#endif
用户端代码
#include "head.h"
int main(int argc, char const *argv[])
{
// 初始化结构体套接字
struct sockaddr_in cli;
struct sockaddr_in ser = {
.sin_family = AF_INET,
.sin_port = htons(50000),
.sin_addr.s_addr = inet_addr("192.168.1.117"),//这里你需要改为自己的ip
};
//==1==
int sock_fd_a = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sock_fd_a)
{
perror("fail to socket");
return -1;
}
while (1)
{
//==2==
char sendbuf[1024] = {0};
fgets(sendbuf, sizeof(sendbuf),stdin);
ssize_t nret1 = sendto(sock_fd_a, sendbuf, strlen(sendbuf) + 1, 0, (struct sockaddr *)&ser, sizeof(ser));
//==3==
char recbuf[1024] = {0};
ssize_t nret2 = recvfrom(sock_fd_a, recbuf, sizeof(recbuf), 0, NULL, NULL);
printf("ser->cli:%s", recbuf);
}
//==4==
close(sock_fd_a);
return 0;
}
客户端代码
#include "head.h"
int main(int argc, char const *argv[])
{
// 初始化结构体套接字
struct sockaddr_in cli;
struct sockaddr_in ser = {
.sin_family = AF_INET,
.sin_port = htons(50000),
.sin_addr.s_addr = inet_addr("192.168.1.117"),
};
//==1==
int sock_fd_b = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sock_fd_b)
{
perror("fail to socket");
return -1;
}
//==2==
int bret = bind(sock_fd_b, (struct sockaddr *)&ser, sizeof(ser));
if (-1 == bret)
{
perror("fail to bind");
return -1;
}
while (1)
{
//==3==
char recbuf[1024] = {0};
socklen_t serlen = sizeof(ser);
ssize_t nret2 = recvfrom(sock_fd_b, recbuf, sizeof(recbuf), 0, (struct sockaddr *)&ser, &serlen);
printf("cli->ser:%s", recbuf);
//==4==
char sendbuf[1024] = {0};
fgets(sendbuf, sizeof(sendbuf),stdin);
ssize_t nret1 = sendto(sock_fd_b, sendbuf, strlen(sendbuf) + 1, 0, (struct sockaddr *)&ser, sizeof(ser));
}
close(sock_fd_b);
return 0;
}
4 用UDP编程实现客户端与服务端文件的传输(这里用的是图片文件,头文件与上面的头文件相同、UDP的丢包现象属于正常,可加些许延迟改善)
客户端(用到一些文件io知识,之前的博客有提到)
#include "head.h"
int main(int argc, char const *argv[])
{
struct stat file_stat;
struct sockaddr_in cli;
struct sockaddr_in ser = {
.sin_family = AF_INET,
.sin_port = htons(50000),
.sin_addr.s_addr = inet_addr("192.168.1.117"),
};
int fd_src = open("./src.jpg",O_RDONLY);//打开文件
if(-1 == fd_src)
{
perror("fail to open");
return -1;
}
int stat_ret = fstat(fd_src,&file_stat);//获取文件状态
if(-1 == stat_ret)
{
perror("fail to fstat");
close(fd_src);
return -1;
}
int fd_cli = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd_cli)
{
perror("fail to socket");
return -1;
}
//发送文件大小
sendto(fd_cli,&(file_stat.st_size),sizeof(&file_stat.st_size),0,(struct sockaddr *)&ser,sizeof(ser));
printf("%ld\n",file_stat.st_size);
char R_file_buf[1024] = {0};
ssize_t nret = 0;
while (1)
{
nret = read(fd_src,R_file_buf,sizeof(R_file_buf));
printf("%ld\n",nret);
if(nret <= 0)
{
break;
}
sendto(fd_cli,R_file_buf,nret,0,(struct sockaddr *)&ser,sizeof(ser));
}
close(fd_src);
return 0;
}
服务端
#include "head.h"
int main(int argc, char const *argv[])
{
struct sockaddr_in cli;
struct sockaddr_in ser = {
.sin_family = AF_INET,
.sin_port = htons(50000),
.sin_addr.s_addr = inet_addr("192.168.1.117"),
};
int fd_dst = open("./dst.jpg",O_RDWR | O_CREAT | O_TRUNC,0664);//打开文件
if(-1 == fd_dst)
{
perror("fail to open");
return -1;
}
int fd_ser = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == fd_ser)
{
perror("fail to socket");
close(fd_dst);
return -1;
}
int ret_bint = bind(fd_ser,(struct sockaddr *)&ser,sizeof(ser));
if(-1 == ret_bint)
{
perror("fail to bind");
close(fd_dst);
return -1;
}
//接收文件大小
char file_size[64] = {0};
socklen_t clilen = sizeof(cli);
ssize_t ret_re = recvfrom(fd_ser,file_size,sizeof(file_size),0,(struct sockaddr *)&cli,&clilen);
ssize_t size = 0;
char tmpbuff[1024] = {0};
while (1)
{
ssize_t nret = recvfrom(fd_ser,tmpbuff,sizeof(tmpbuff),0,(struct sockaddr *)&cli,&clilen);
write(fd_dst,tmpbuff,nret);
if(nret != 1024)
{
break;
}
}
close(fd_ser);
}
结果图
5 UDP编程的大概框架
客户端 | 服务端 |
---|---|
1 socket | 1 socket |
2 sendto | 2 bind |
3 recvfrom | 3 recvfrom |
4 close | 4 sendto |
5close |