使用UDP协议进行数据传输是不需要服务器和客户端进行连接的,只需要在同一局域网内使用相同的端口就可以进行数据的传输,是因为UDP不是面向连接的,传输数据的安全性较低,适用于传输小数据信息,但其传输效率高,常用于图片和媒体流的传输以及广播和组播的通信。
编写UDP协议服务器主要分为5个步骤:
1:创建套接字
2:绑定服务器自身 IP和端口号
3:接收数据
4:发送数据
5:关闭套接字
现在对每一步需要使用到的函数及函数形参和返回值作详细说明:
1:创建套接字
创建套接字使用socket函数进行创建,该函数返回值为一个大于等于3的整数,是一个文件描述符,由于0,1,2分别被系统中的文件描述符0(标准输入),2(标准输出),3(标准错误输出)占用,所以新创建的描述符一般为3,函数原型及使用方式如下:
int socket(int domain, int type, int protocol); //函数原型
int sfd=socket(AF_INET,SOC_DGRAM,0); //使用方法
if(sfd==-1)
{
perror("socket failed\n");
exit(-1);
}
函数形参解释:
domain : 如果使用的网络协议为IPV4版本,参数设置为AF_INET ,如果为IPV6版本,参数设置为AF_INET6,这里使用的是IPV4版本。
type : 如果用于TCP服务器的 编写,参数设置为SOCK_STREAM,如果是用于UDP服务器编写就设置为SOCK_DGRAM,我们选择后者。
protocol : 一般为0
函数正确返回一个可用的套接字。
2,绑定服务器IP和端口号
这一步的目的是将服务器的自身IP地址和端口号共享在网络中供其他客户端连接。使用bind 函数进行绑定。函数原型和使用方式如下:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //函数原型
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(8888);
addr.sin_addr.s_addr=inet_addr("0.0.0.0"); //这里将其赋值为"0.0.0.0"表示系统自动分配可用地址
socklen_t len=sizeof(addr);
if(bind(sfd,(struct sockaddr*)&addr,len)==-1) //函数使用方式
{
perror("bind failed\n");
exit(-1);
}
bind函数中第二个参数有点复杂,详细解释一下参数。
sockfd : 第一步中使用socket函数返回的返回值传参即可
addr : 该参数应为(struct sockaddr* ) 类型,是一个结构体地址类型,使用man 手册查看结构体原型为:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
需要注意的是绑定时需要的是服务器的IP和端口号,而该结构体中没有这个选项,这时候就需要使用到另外一个结构体:
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 */
};
没错,主角就是这个结构体,它的结构体成员满足需求,所以在使用前提前定义一个该类型的结构体变量进行赋值,第一个成员sin_family 表示地址协议族,这里使用AF_INET代表IPV4协议族,第二个成员为服务器的端口号,一般我们设置其为整数,为主机字节序,但函数中需要的是网络字节序,所以需要一个函数 htons() 进行转换后进行赋值,对第三个成员赋值时会发现该成员的类型为结构体类型,所以我们需要再通过man手册查看结构体原型 (属于是套娃了,那就一层一层拿开),结构体原型如下:
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
这个结构体中只有一个成员且不是结构体类型,说明套娃结束,还需注意的是我们在对其赋值时一般是以"*.*.*.* ",样式进行赋值,例如"192.168.2.88",实际这是一个字符串类型,计算机不能识别其为一个地址类型,所以利用一个函数将其转换为网络字节序后进行赋值,inet_addr() ,由于该成员是我们定义的结构体的成员的成员,所以需要两个" . "进行引用,所有成员都赋值完后,最后一步就是将我们定义的结构体变量进行强制类型转换 (请注意函数原型中需要的参数类型为(struct sockaddr*) 类型,但是我们定义的 结构体类型是struct addr_in 类型,所以类型不匹配),直接使用 (struct addr*)&addr进行强转后使用。
第三个参数socklen_t len :代表第二个参数中结构体的大小,所以提前定义一个变量为socklen_t 类型进行赋值。
绑定成功后其实就已经将IP和端口号共享在网络中了。
3,接收数据
UDP接收数据使用recvfrom 函数,原型和使用方式如下:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); //函数原型
char buf[32]={0}; //用于存放发送方的消息
struct sockaddr_in send_addr; //用于存放发送方的IP和端口号
socklen_t len1=sizeof(send_addr); //表示结构体大小
int ret=recvfrom(sfd ,buf ,sizeof(buf) ,0 ,(struct sockaddr*)&send_addr ,&len1 ); //函数使用方式
if(ret==0)
{
printf("leved \n");
}
else if(ret < 0)
{
perror("recv failed\n");
exit(-1);
}
其实看懂上一步中的函数参数说明后这个函数就变得很简单,只是多加了一个字符数组用于存放客户端发送的信息,其他的按照函数原型的形参类型进行调整即可,该函数成功返回读取的字节个数错误返回 -1。就不再进行赘述。
4,发送数据
我们需要实现的是一个能够进行交互的服务器,所以除了有接收客户端的信息外还应该具备能够给客户端发送信息的功能,这里使用与发送函数为一组的函数 sendto() ,函数原型如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); //函数原型
char buf1[32]={0};
fgets(buf,sizeof(buf),stdin); //从终端输入数据后存入buf1中
int num=sendto(sfd ,buf ,strlen(buf1)-1 ,0 ,(struct sockaddr*)&send_addr ,len1); //函数使用方式
if(num<0)
{
perror("send failed\n");
exit(-1);
}
这个函数跟发送函数一样,也不进行赘述。
5,关闭套接字
使用close 函数关闭即可。
int close(int fd); //函数原型
close(sfd); //使用方式
创建一个简单的UDP服务器完成,我们可以将其放入一个循环内,使其能够完成不断与终端进行交互的功能,整体代码如下:
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//创建套接字
int sfd;
sfd=socket(AF_INET,SOCK_DGRAM,0);
// printf("sfd=%d\n",sfd);
//绑定IP和端口号
struct sockaddr_in udp_addr;
udp_addr.sin_family=AF_INET;
udp_addr.sin_port=htons(8888);
udp_addr.sin_addr.s_addr=inet_addr("0.0.0.0");
socklen_t len=sizeof(udp_addr);
if(-1==bind(sfd,(struct sockaddr*)&udp_addr,len))
{
perror("bind failed\n");
exit(-1);
}
//接收数据
struct sockaddr_in caddr; //用于保存发送端的IP和端口号
socklen_t len1=sizeof(caddr);
while(1)
{
char buf[32]={0};
int ret;
ret=recvfrom(sfd, buf, sizeof(buf), 0,(struct sockaddr *)&caddr, &len1); //接收客户端发送的消息
if(ret==-1)
{
perror("recvfrom failed\n");
exit(-1);
}
printf("numbers of recv :%d",ret);
printf("recv :%s\n",buf);
if(strncmp(buf,"exit",4)==0)
{
close(sfd);
exit(-1);
}
memset(buf,0,sizeof(buf));
//发送数据
int ret1;
char buf1[32]={0};
printf("请输入发送给客户端的消息: ");
fgets(buf1,sizeof(buf),stdin);
ret1=sendto(sfd, buf, strlen(buf)-1, 0,(struct sockaddr *)&caddr,len1); //发送消息给客户端
if(ret1==-1)
{
perror("sendto failed\n");
exit(-1);
}
printf("numbers of send :%d\n",ret1);
memset(buf1,0,sizeof(buf1));
}
close(sfd);
return 0;
}
接下来利用小程序进行测试功能。
测试成功!,UDP服务器的编写到此结束,下一章会更新UDP客户端的编写。