1. 原理
UDP数据通讯分服务端(软件)和客户端
服务端(软件)(服务器)先运行,服务端,不需要事先知道客户端IP和port
客户端(软件)(客户端机器)后运行,一定是客户端先给服务端发
包,客户端一定先知道服务端的IP和port
2. 创建流程
服务端操作步骤
1)创建套接字,socket函数实现(可理解为创建一个接口,实现两个进程间通信)。
2)把ip地址和端口绑定到一起(和socket接口绑定),以方便接收数据。
3)接收客户端发送的数据包。
4)发送数据包给客户端。
5)关闭套接字。
客户端操作步骤(向服务端发送请求,故一定知道服务器的ip地址和端口)。
1)创建套接字,socket函数实现(可理解为创建一个接口,实现两个进程间通信)。
2)向服务端指定的ip和端口发送数据包。
3)接收服务端发来的数据包。
4)关闭socket。
图示:
3. 函数
3.1 创建socket
a. 头文件
#include <sys/types.h>
#include <sys/socket.h>
b. 函数原型
int socket(int domain, int type, int protocol);
int domain:地址族,具体参数可看下图,此处我们用AF_INET。
int type:使用协议类型,具体有如下几种类型
SOCK_STREAM 流式套接字(TCP)
SOCK_DGRAM 报文套接字(UDP)
SOCK_RAW 原始套接字: (IP,ICMP)
int protocol:协议编号,可配置为0(系统自己识别)。
c. 返回值
成功返回得到的文件描述符。
失败返回 -1。
3.2 绑定IP和port
a. 头文件
#include <sys/types.h>
#include <sys/socket.h>
b. 函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int sockfd:socket创建的文件描述符。
*const struct sockaddr addr:把IP和地址设置到对应的结构体中去。
对于该结构体我们通常使用以下结构体:
其中需要注意的是struct in_addr也是一个结构体
struct in_addr {
uint32_t s_addr; // 存储 IP 地址(网络字节序)
};
结构体仅包含一个名为 s_addr 的无符号 32 位整数型成员,用于存储 IP 地址。
socklen_t addrlen:表示 addr 参数对应类型的地址信息结构体的大小
c. 返回值
成功返回0。
失败返回 -1 ,并设置errno。
errno类型
EACCES:被绑定的地址是受保护的地址,仅超级用户能够
访问,例:普通用户将socket绑定到知名服务端口(端口号
为0~1023)上时,bind将返回EACCES错误。
EADDRINUSE:被绑定的地址正在使用中,例:将socket绑定到一个处于TIME_WAIT状态的socket地址。
3.3 接收数据
a.函数原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
int sockfd:套接字。
void *buf:数据存放的首地址,通常定义一个数组。
size_t len:期待接收的数据大小。
int flags:操作方式 0 表示默认操作。
struct sockaddr *src_addr: 获得发送方地址,谁发送的获得谁
的地址。
socklen_t *addrlen:值结果参数,必须进行初始化, 表示
表示对方实际地址的大小。
b. 返回值
成功返回实际接收的字节数,失败返回-1。
注意点
因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址。即参数src_addr所指的内容,addrlen参数则指定该地址的长度。
3.4 发送数据
a.函数原型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
int sockfd:套接字。
void *buf:数据存放的首地址,通常定义一个数组。
size_t len:期待发送的数据大小。
int flags:操作方式 0 表示默认操作。
const struct sockaddr *dest_addr: 向指定的地址发送数据。
socklen_t addrlen:发送的地址的大小。
b. 返回值
成功返回实际发送的字节数,失败返回-1。
客户端代码
#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(server_addr);
char buf[100]={0};
int n = 0;
if(argc != 3)
{
fprintf(stderr,"Usage : %s ip port\n",argv[0]);
return -1;
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
bzero(&server_addr,addrlen);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
while(1)
{
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&server_addr,addrlen);
//向服务器发送数据,sendto();
if(n < 0)
{
perror("sendto");
return -1;
}
bzero(buf,sizeof(buf));
n = recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
//接收服务器的数据,recvfrom()
if(n < 0)
{
perror("recvfrom");
return -1;
}
printf("Recv from IP: %s\n",inet_ntoa(server_addr.sin_addr));
printf("Recv from port: %d\n",ntohs(server_addr.sin_port));
printf("recv_buf: %s\n",buf);
}
close(sockfd);
return 0;
}
服务器代码
#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(server_addr);
char buf[100]={0};
int n = 0;
if(argc != 3)
{
fprintf(stderr,"Usage : %s ip port\n",argv[0]);
return -1;
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);//生成套接字
if(sockfd < 0)//判断套接字是否生产成功
{
perror("socket");
return -1;
}
bzero(&server_addr,addrlen);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));//命令行中的端口填充进结构体
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//命令行中的IP地址填充进结构体
if(bind(sockfd,(struct sockaddr*)&server_addr,addrlen) < 0)
//使用bind()函数,将套接字文件描述符和一个服务器地址类型变量进行绑定
{
perror("bind");
return -1;
}
while(1)
{
bzero(buf,sizeof(buf));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&addrlen);
//接受客户端发送的数据,使用recvfrom()函数
//接收客户端的网络数据
if(n < 0)
{
perror("recvfrom");
return -1;
}
printf("client IP: %s\n",inet_ntoa(client_addr.sin_addr));
printf("client port: %d\n",ntohs(client_addr.sin_port));
printf("recv_buf: %s\n",buf);
n = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,addrlen);
//向客户端发送数据,使用sendto()函数向服务器主机发送数据
if(n < 0)
{
perror("sendto");
return -1;
}
}
close(sockfd);
return 0;
}
该代码实现了在简单的UDP通信,通过命令行传参绑定IP地址和端口号,服务器做简单的回显。值得注意的是,客户端和服务端都需在最后关闭套接字。