『网络』基于UDP协议的socket编程

概述

首先,我们知道,UDP是无连接不可靠的数据报协议,有很多场合比较适合使用UDP协议。使用UDP编写的一些常见的应用程序有:DNS(域名系统)NFS(网络文件系统)SNMP(简单网络管理协议)
下图为典型的UDP客户端/服务器程序的函数调用
在这里插入图片描述

基于UDP协议的网络通信流程

客户端流程

  1. 创建套接字
  2. 为套接字绑定地址(ip + port)信息。通常客户端不推荐用户手动绑定地址信息
  3. 发送数据如果socket还没有绑定地址,这时候操作系统会选择一个合适的地址端口进行绑定)。
  4. 接收数据
  5. 关闭套接字
    服务端流程
  6. 创建套接字。通过套接字使进程与网卡建立联系
  7. 套接字绑定地址信息(ip + port)。
  8. 接收数据
  9. 发送数据
  10. 关闭套接字

接口介绍

创建套接字:
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值