概述
首先,我们知道,UDP是无连接不可靠的数据报协议,有很多场合比较适合使用UDP协议。使用UDP编写的一些常见的应用程序有:DNS(域名系统)、NFS(网络文件系统)和SNMP(简单网络管理协议)。
下图为典型的UDP客户端/服务器程序的函数调用。
基于UDP协议的网络通信流程
客户端流程:
- 创建套接字。
- 为套接字绑定地址(ip + port)信息。通常客户端不推荐用户手动绑定地址信息。
- 发送数据(如果socket还没有绑定地址,这时候操作系统会选择一个合适的地址端口进行绑定)。
- 接收数据。
- 关闭套接字。
服务端流程: - 创建套接字。通过套接字使进程与网卡建立联系。
- 为套接字绑定地址信息(ip + port)。
- 接收数据。
- 发送数据。
- 关闭套接字。
接口介绍
创建套接字:
int socket(int domain, int type, int protocol);
参数:
domin:地址域。
AF_INET:IPV4网络协议地址域。
AF_INET6:IPV6网络协议地址域。
type:套接字类型。
SOCK_STREAM:流式套接字,默认协议TCP,不支持UDP。
SOCK_DGRAM:数据报套接字,默认协议UDP,不支持TCP。
protocol:协议类型。
0:试用套接字默认协议。
6/IPPROTO_TCP:TCP协议。
17/IPPROTO_UDP:UDP协议。
返回值:套接字操作句柄(文件描述符),失败返回-1。
为套接字绑定地址信息:
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
参数:
sockfd:创建套接字返回的描述符。
addr:地址信息。
addrlen:地址信息长度。
返回值:成功返回0,失败返回-1。
接受数据:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd:操作句柄,套接字描述符。
buf:用buf存储接收的数据。
len:想要接收的数据长度。
flags:
0:默认阻塞接受。
saddr:发送端的地址信息。
addrlen:地址信息长度(输入输出型参数),不但要指定想要接收多长还要保存实际接受了多长。
返回值:实际接收的数据长度,失败返回-1。
发送数据:
ssize_t sendto(
int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen
);
参数:
socket:套接字描述符。
buf:要发送的数据。
len:要发送的数据长度。
flags:
0:默认阻塞发送。
dest_addr:目的端地址信息,标识数据要发送到哪里去。
addrlen:地址信息长度。
返回值:实际发送的数据长度,失败返回-1。
关闭套接字:
int close(int fd);
参数:
fd:套接字描述符。
返回值:成功返回0,失败返回-1。
封装UdpSocket类
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string>
#include <arpa/inet.h>
#include <unistd.h>
using std::cout;
using std::endl;
// 缓冲大小
#define BUF_SIZE 1024
// UDP socket类
class UdpSocket{
public:
// 构造函数
UdpSocket()
: _sockfd(-1)
{}
// 创建套接字
bool Socket(){
// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(_sockfd < 0){
// 套接字创建失败
perror("socket error");
return false;
}
return true;
}
// 绑定地址信息
bool Bind(std::string& ip, uint16_t port){
// IPv4地址结构
struct sockaddr_in addr;
addr.sin_family = AF_INET;
// 点分十进制IP转二进制形式
inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
// 端口号,主机字节序转网络字节序
addr.sin_port = htons(port);
// 地址空间长度
socklen_t len = sizeof(struct sockaddr_in);
// 绑定地址信息
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
// 绑定失败
perror("bind error");
return false;
}
return true;
}
// 接收
bool Recv(std::string& buf, struct sockaddr_in* saddr){
// 清空缓冲区
buf.clear();
buf.resize(BUF_SIZE);
// 地址空间大小
socklen_t len = sizeof(struct sockaddr_in);
// 阻塞接收
int ret = recvfrom(_sockfd, &buf[0], BUF_SIZE, 0,
(struct sockaddr*)saddr, &len);
if(ret < 0){
// 接收失败
perror("recvfrom error");
return false;
}
return true;
}
// 发送
bool Send(std::string& buf, struct sockaddr_in* daddr){
// 地址信息长度
socklen_t len = sizeof(struct sockaddr_in);
// 阻塞发送
int ret = sendto(_sockfd, buf.c_str(), buf.size(), 0,
(struct sockaddr*)daddr, len);
if(ret < 0){
// 发送失败
perror("sendto error");
return false;
}
return true;
}
// 关闭
bool Close(){
// 关闭套接字
int ret = close(_sockfd);
if(ret < 0){
// 套接字关闭失败
perror("close error");
return false;
}
return true;
}
private:
// 套接字描述符
int _sockfd;
};
字典服务器
下面,我们使用前面封装的UdpSocket类实现一个字典服务器,下面我们封装一个字典服务器:
UDP客户端和服务器的实现
UDP服务器
#include "udp_socket.h"
int main(int argc, char* argv[]){
bool ret;
if(argc != 3){
cout << "./udp_server ip port" << endl;
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSocket sock;
ret = sock.Socket();
if(!ret){
return -1;
}
ret = sock.Bind(ip, port);
if(!ret){
return -1;
}
while(1){
std::string buf;
struct sockaddr_in cli_addr;
ret = sock.Recv(buf, &cli_addr);
if(!ret){
return -1;
}
cout << "client say: " << buf << endl;
cout << "server say: " << endl;
fflush(stdout);
std::cin >> buf;
ret = sock.Send(buf, &cli_addr);
if(!ret){
return -1;
}
}
ret = sock.Close();
if(!ret){
return -1;
}
return 0;
}
UDP客户端
#include "udp_socket.h"
int main(int argc, char* argv[]){
bool ret;
if(argc != 3){
cout << "./udp_client ip port" << endl;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
UdpSocket sock;
ret = sock.Socket();
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &ser_addr.sin_addr);
while(1){
std::string buf;
cout << "client say: ";
fflush(stdout);
std::cin >> buf;
ret = sock.Send(buf, &ser_addr);
if(!ret){
return -1;
}
ret = sock.Recv(buf, &ser_addr);
if(!ret){
return -1;
}
cout << "server say: " << buf << endl;
}
ret = sock.Close();
if(!ret){
return -1;
}
return 0;
}