网络 UDP协议(C++|代码通过udp协议实现客户端与服务端之间的通信)

socket套接字编程就是在网络程序中编写代码,通过一套套的接字接口实现网络程序的编写。

udp通信编程

udp是无连接、不可靠、面向数据报的传输层协议。
特点:支持多对多连接交互通信。尽最大努力交付,不保证可靠性,但速度最快
在这里插入图片描述

各端的操作流程:

服务端操作流程:

  1. 创建套接字端口:在内核中创建socket结构体,关联进程与网卡之间的联系
  2. 为套接字绑定地址信息:网络通信中的数据都必须带有源端IP、源端端口、对端IP、对端端口、协议。在内核创建的socket结构体中描述IP地址端口以及协议,(必须主动绑定,告诉客户端自己的地址信息,如果不绑定客户端就不知道该发往哪个服务端了)为了告诉操作系统发往哪个IP地址,哪个端口的数据是交给我来处理的
  3. 接收数据:告诉操作系统发往哪个地址和端口的数据应该交给我处理,操作系统一旦接收到发往这个地址和端口的数据,就会将这条数据放到对应的socket的接收缓冲区中,然后服务端从对应的socket的接收缓冲区中取出数据。
  4. 发送数据:将数据写入内核中的socket发送缓冲区中,操作系统选择合适的时候将数据封装发送出去
  5. 关闭套接字:释放资源

在这里插入图片描述

客户端操作流程:第2,3步与服务端不同

  1. 创建套接字:在内核中创建socket结构体,关联进程与网卡之间的联系
  2. 为套接字绑定地址信息:描述在内核中创建的socket结构体的源端地址信息;发送的数据中源端地址信息就是绑定的地址信息(不推荐主动绑定地址,降低端口冲突的概率,从而确保数据发送的安全性)
  3. 发送数据:将数据放到socket的发送缓冲区中,操作系统选择合适时候封装数据并发送数据。若socket发送数据的时候还没绑定地址,则操作系统会选择合适的地址进行绑定。
  4. 接收数据:将数据写入内核中的socket发送缓冲区中,操作系统选择合适的时候将数据封装发送出去
  5. 关闭套接字:释放资源

socket接口介绍

1、创建套接字int socket(int domain, int type, int protocol) 参数内容(domian:地址域(本地通信-AF_LOCAL、IPv4-AF_INET、IPv6-AF_INET6等)确定本次socket通信使用哪种协议版本的地址结构,不同的协议版本有不同的地址结构;type:套接字类型(流式套接字-SOCK_STREAM、数据报套接字-SOCK_DGRAM等);protocol:协议类型(TCP-IPPROTO_TCP、UDP-IPPROTO_UDP) ,默认为0-流式默认TCP,数据报默认UDP)
返回值:文件描述符-非负整数, 套接字所有其他接口的操作句柄,失败返回-1

2、为套接字绑定地址信息int bind(int sockfd, struct sockaddr *addr, socklen_t len)参数内容(sockfd:创建套接字返回的操作句柄;addr:要绑定的地址信息;len:要绑定的地址信息长度)
中间的参数结构体有很多种

struct sockaddr
{
	sa_family_t sa_family;
	char sa_data[14];
};

struct sockaddr_in
{
	sa_family sin_family;//地址域
	in_port_t sin_port;//端口号
	struct in_addr sin_addr;//IP地址
};

struct sockaddr_in6
{
	sa_family sin_family;//地址域
	in_port_t sin6_port;//端口号
	uint32_t sin6_flowinfo;
	struct in6_addr, sin6_addr;//IP地址
	unit32_t sin6_scope_id;
}

struct sockaddr_un
{
	__SOCKADDR_COMMON (sun_);
	char sun_path[108];
};

在这里插入图片描述

不同的地址结构,有统一的一个信息:前两个字节是地址域类型,bind可以绑定不同的地址结构,为了实现接口统一,因此用户定义地址结构的时候,需要定义自己需要的地址结构,例如IPv4就使用struct sockaddr_in,但是进行绑定的时候,统一类型强转成为sockaddr*类型
简单解析bind接口的实现

bind(fd,addr, len)
{
	if(addr->sa_family==AF_INET)
	{
		//绑定IPv4地址信息,这个结构体按照sockaddr_in进行解析
	}
	else if (addr->sa_family==AF_INET6)
	{
		//绑定IPv6地址信息,这个结构体按照sockaddr_in6进行解析
	}
	else if ...
}

3、接收数据,接收发送者地址便于回复ssize_t recvfrom(int sockfd, char *buf, int len, int flag, struct sockaddr *peer_addr, socklen *addrlen) 参数内容(sockfd:创建套接字返回的操作句柄;buf:一块缓冲区,用于接收从接收缓冲区中取出数据;len:想要接收的数据长度;flag:操作选项标志,默认为0,表示阻塞操作;peer_addr:发送方的地址信息;addrlen:想要获取的地址信息长度以及返回实际长度)
返回值:成功返回实际接收到的数据字节长度,失败返回-1

4、发送数据ssize_t sendto(int sockfd, char *data, int len, int flag, struct sickaddr *peer_addr, socklen_t addrlen) 参数内容(socket:socket操作句柄;data:要发送的数据地址;len:要发送数据长度;flag:默认为0,表示阻塞操作;peer_addr:接收方的地址信息;addrlen:地址信息长度)
返回值:成功返回实际发送的数据字节长度,失败返回-1

5、关闭套接字int close(int fd)

网络字节序的转换接口
uint32_t htonl(uint32_t hostlong) 主机字节序到网络字节序的转换
uint16_t htons(uint16_t hostshort)
uint32_t ntohl(uint32_t netlong)网络字节序到主机字节序的转换
uint16_t ntohs(uint16_t netshort)

in_addr_t inet_addr(const char *cp)将字符串的点分十进制IP地址转换成为网络字节序的整数IP地址
char *inet_ntoa(struct in_addr in)将网络字节序的整数IP地址转化为字符串点分十进制IP地址
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size)将网络字节序的整数IP地址转化为字符串IP地址-兼容IPv4和IPv6
int inet_pton(int af, const char *src, void *dst)将字符串的IP地址转换成为网络字节序的整数IP地址-兼容IPv4和IPv6

udp客户服务端代码实现

使用c++封装UdpSocket类,实例化的每一个对象都是一个udp通信套接字,并且能够通过成员接口实现udp通信流程
udpsocket.hpp

//udpsocket.hpp
#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
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(const string &ip, uint16_t port)
		{
			//定义IPv4地址结构体
			struct sockaddr_in addr;
			//地址信息赋值
			addr.sin_family = AF_INET;
			addr.sin_port = htons(port);//将主机字节序短整型型数据转化为网络字节序数据
			addr.sin_addr.s_addr = inet_addr(ip.c_str());//将字符串IP地址转化为网络字节序IP地址
			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(string *buf, string *ip=NULL, uint16_t *port=NULL)
		{
			struct sockaddr_in peer_addr;//用于接收发送端的地址信息
			socklen_t len = sizeof(struct sockaddr_in);
			char tmp[4096] = {0};
			int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer_addr, &len);
			if (ret < 0)
			{
				perror("recvfrom error");
				return false;
			}
			buf->assign(tmp, ret);//assign从指定字符串中截取指定长度的数据到buf中
			if (port != NULL)
			{
				*port = ntohs(peer_addr.sin_port);//网络字节序到主机字节序的转换
			}
			if (ip != NULL)
			{
				*ip = inet_ntoa(peer_addr.sin_addr);//网络字节序到字符串IP地址的转换
			}
			return true;
		}
		//发送数据
		bool Send(const string &data, string &ip, const uint16_t port)
		{
			struct sockaddr_in addr;
			addr.sin_family = AF_INET;
			addr.sin_port = htons(port);
			addr.sin_addr.s_addr = inet_addr(ip.c_str());
			socklen_t len = sizeof(struct sockaddr_in);
			int ret = sendto(_sockfd, data.c_str(), data.size(), 0, (struct sockaddr*)&addr, len);
			if (ret < 0)
			{
				perror("sendto error");
				return false;
			}
			return true;
		}
		//关闭套接字
		bool Close()
		{
			if (_sockfd > 0)
			{
				close(_sockfd);
				_sockfd = -1;
			}
			return true;
		}
	private:
		int _sockfd;
};


udp_srv.cpp

#include <iostream>
#include <string>
#include "udpsocket.hpp"
using namespace std;

#define CHECK_RET(q) if((q)==false){return false;}
int main(int argc, char *argv[])
{
	//运行时有三个参数 udp_src 192.168.73.29 4096
	if (argc != 3)
	{
		cout << "Usage: ./udp_srv ip prot\n";
		return -1;
	}
	uint16_t port = stoi(argv[2]);
	string ip = argv[1];
	UdpSocket srv_sock;
	//创建套接字
	CHECK_RET(srv_sock.Socket());
	//绑定地址信息
	CHECK_RET(srv_sock.Bind(ip, port));
	while(1)
	{
		//接收数据
		string buf;
		string peer_ip;
		uint16_t peer_port;
		CHECK_RET(srv_sock.Recv(&buf, &peer_ip, &peer_port));
		cout << "client["<<peer_ip<<":"<<peer_port<<"] say: " << buf << endl;
		//发送数据
		buf.clear();
		cout << "server say: ";
		cin >> buf;
		CHECK_RET(srv_sock.Send(buf, peer_ip, peer_port));
	}
	//关闭套接字
	srv_sock.Close();
	return 0;
}


udp_cli.cpp

#include <iostream>
#include <string>
#include "udpsocket.hpp"
using namespace std;

#define CHECK_RET(q) if((q)==false){return -1;}
 main(int argc, char *argv[])
{
	if (argc != 3)
	{
		cout << "Usage: ./udpcli ip port\n";
		return -1;
	}
	string srv_ip = argv[1];
	uint16_t srv_port = stoi(argv[2]);

	UdpSocket cli_sock;
	//创建套接字
	CHECK_RET(cli_sock.Socket());
	//绑定数据(不推荐)
	while(1)
	{
		//发送数据
		cout << "client say:";
		string buf;
		cin >> buf;
		CHECK_RET(cli_sock.Send(buf, srv_ip, srv_port));
		//接收数据
		buf.clear();
		CHECK_RET(cli_sock.Recv(&buf));
		cout << "server say: " << buf << endl;
	}
	//关闭套接字
	cli_sock.Close();
	return 0;
}

makefile

all:udp_srv udp_cli
udp_srv:udp_srv.cpp
	g++ -std=c++11 $^ -o $@
udp_cli:udp_cli.cpp
	g++ -std=c++11 $^ -o $@

运行结果:
通过ifconfig命令查看网卡信息
在这里插入图片描述
先运行服务端(ip地址必须为网卡信息中的IP地址)
在这里插入图片描述
再运行客户端,并对指定服务端发送数据
在这里插入图片描述
服务端收到数据并回复
在这里插入图片描述
客户端也收到数据
在这里插入图片描述
netstat-查看网络状态信息
-a表示查看所有网络信息
-n表示查看原始ip地址信息
-p表示查看网络状态对应的进程
-t表示查看tcp的信息
-u表示查看udp的信息

推荐阅读

网络 TCP协议(C++代码|通过tcp协议实现客户端与服务端之间的通信)

  • 17
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
以下是一个简单的UDP通信程序,包括了客户端服务端代码服务端: ```c++ #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; const int BUF_SIZE = 1024; int main(int argc, char* argv[]) { if(argc != 2) { cout<<"Usage: "<<argv[0]<<" <port>"<<endl; exit(1); } int serv_sock = socket(AF_INET, SOCK_DGRAM, 0); sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr)); sockaddr_in clnt_addr; socklen_t clnt_addr_len = sizeof(clnt_addr); char buffer[BUF_SIZE]; while(true) { int str_len = recvfrom(serv_sock, buffer, BUF_SIZE, 0, (sockaddr*)&clnt_addr, &clnt_addr_len); sendto(serv_sock, buffer, str_len, 0, (sockaddr*)&clnt_addr, clnt_addr_len); } close(serv_sock); return 0; } ``` 客户端: ```c++ #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; const int BUF_SIZE = 1024; int main(int argc, char* argv[]) { if(argc != 3) { cout<<"Usage: "<<argv[0]<<" <IP> <port>"<<endl; exit(1); } int clnt_sock = socket(AF_INET, SOCK_DGRAM, 0); sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); while(true) { char message[BUF_SIZE]; cout<<"Enter message to send: "; cin>>message; sendto(clnt_sock, message, strlen(message), 0, (sockaddr*)&serv_addr, sizeof(serv_addr)); sockaddr_in from_addr; socklen_t from_addr_len = sizeof(from_addr); char buffer[BUF_SIZE]; int str_len = recvfrom(clnt_sock, buffer, BUF_SIZE, 0, (sockaddr*)&from_addr, &from_addr_len); buffer[str_len] = 0; cout<<"Message from server: "<<buffer<<endl; } close(clnt_sock); return 0; } ``` 这个程序简单地实现了一个服务端客户端之间UDP通信服务端接收来自客户端的消息,然后将其原封不动地发送回客户端客户端循环地读取用户输入并将其发送到服务端,然后等待来自服务端的响应。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteShirtI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值