当前位置:网站首页>套接字编程Udp篇
套接字编程Udp篇
2022-06-12 11:11:00 【Diligent_wu】
网络通信过程模拟

五元组:一台主机的每一条数据必须包含五条信息,源端公网IP、源端port、对端公网IP、对端port、协议方案
UDP通信编程
udp通信编程的流程:
模拟socket端口绑定过程:
socket接口
int socket(int domain,int type,int protocol);
int bind(int sockfd,struct sockaddr* _addr,socklen_t len);
ssize_t recvfrom(int sockfd,char *buf,int len,int flag,struct sockaddr *peer_addr,socklen_t *addrlen )
ssize_t sendto(int sockfd,char* data,int len, int flag,struct sockaddr* peer_addr,socklen_t addlen);
1.创建套接字
int socket(int domain,int type,int protocol);
domain: 地址域 - 确定本次socket通信使用哪种协议版本的地址结构 - 不同的协议有不同的地址结构 -- AF_INET IPV4网络协议;
type: 套接字类型(流式套接字SOCK_STREAM / 数据报式套接字SOCK_DGRAM(datagram) )
protocol: 协议类型(一般就是传输层协议的选择IPPROTO_TCP = 6 /IPPROTO_UDP = 17)默认为0,type给了之后,protocol就可以给0了。
protocol 在 /usr/include/netinet/in.h 下面定义
返回值:返回一个文件描述符(套接字描述符) – 非负整数,通过这个描述符可以找到对应的socket结构体。失败返回-1。
2.为套接字绑定地址信息
int bind(int sockfd,struct sockaddr* _addr,socklen_t len);
sockfd: 创建套接字返回的操作句柄
addr: 要绑定的地址信息
len:要绑定的地址信息长度
返回值:绑定成功返回0,绑定失败返回-1.
地址结构:(/usr/include/netinet/in.h:221、240)
struct sockaddr{
sa_family_t sa_family;
char sa_data[4];
}

struct sockaddr 这个结构体,包含了两个成员,一个是地址域,一个是数据。
我们通常使用AF_INET、AF_INET6和AF_LOCAL填充,而这三个宏所对应的就是一个一个结构体,其中包含着不同的信息。而它们结构体的第一个成员都是一样的,是一个地址域类型,且只需要保证地址域就可以让绑定不同的地址信息。
bind可以绑定不同的类型,为了实现接口统一,因此用户定义地址结构的时候,定义自己需要的地址结构(例如:ipv4就使用struct sockaddr_in),但是绑定的时候,统一会将类型强转为sockaddr*类型.
bind内部实现猜测:
bind(fd,struct sockaddr* addr,len){
if(addr->sa_family == AF_INET){//绑定IPV4地址信息,这个结构体按照sockadd_in进行解析}
else if(addr->sa_family == AF_UNET6){//IPV6,按照IPV6地址结构解析}
else if(addr->sa family == AF_LOCAL){}
3.接收数据
不仅仅要接收数据,还要知道数据是谁发的,以便于回复
ssize_t recvfrom(int sockfd,char *buf,int len,int flag,struct sockaddr *peer_addr,socklen_t *addrlen )
sockfd:套接字操作句柄
buf:接收缓冲区
len:想要接受的数据长度
flag:默认为0,表示阻塞。要是一直没数据,就一直等待。
peer_addr:发送方的地址信息
addrlen:想要获取的地址信息长度以及返回实际长度(输入输出参数)
返回值:成功返回实际接受到的数据字节长度,失败返回-1;
4.发送数据
ssize_t sendto(int sockfd,char* data,int len, int flag,struct sockaddr* peer_addr,socklen_t addlen);
data:要发送的数据首地址
peer_addr:接收方的地址信息
返回值:成功返回实际接受到的数据字节长度,失败返回-1;
5.关闭套接字
close(int sockfd);
字节序转换接口
主机字节序到网络字节序的转换:
uint16_t htons(uint16_t data); 短整型
uint32_t htonl(uint32_t data); 长整型
网络字节序到主机字节序的转换:
uint16_t ntohs(unit16_t data); 短整型
uint32_t ntohl(uint32_t data); 长整型
字符串IPV4的IP地址转换为网络字节序的IP地址:
in_addr_t 是一个uint32_t类型
in_addr_t inet_addr(char* ip);
网络字节序的整数IP地址转换为字符串的IPV4的IP地址:
char* inet_ntoa(struct in_addr_t nip);
注意:16位的转换用htons/ntohs
32位的转换用htonl/ntohl
不能混用!!!
会造成字节序紊乱。
4字节数使用htons/ntohs,只讲两个字节截出来,进行字节序转换,然后进行补全,这样造成得到的数据与预期不符。是错的。
2字节数使用htonl/ntohl,是进行4字节转换的,只有两个字节就会先补全,在转换,如果这时使用16位的接数据,就会只取低16位的数据。也是不对的。
简易UDP通信流程
socket类的封装
封装一个udpSocket类,实例化的每一个对象都是一个udp通信连接,通过这个对象的成员方法完成通信流程。
class udpSocket{
public:
udpSocket():_sockfd(-1){}
bool Socket();
bool Bind(const str::string& ip,uint16_t port);
bool Recvfrom(std::string* buf,std::string* ip,uint16_t* port);
bool sendto(std::string& data,std::string& ip,uint16_t port);
bool Close();
private:
int _sockfd;
}
#include<cstdio>
#include<unistd.h>
#include<string>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
class UdpSocket
{
public:
UdpSocket():_sockfd(-1){
}
public:
//1.创建套接字
bool Socket()
{
//socket(地址域,套接字类型,协议类型)
_sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
//2.绑定地址信息
bool Bind(const std::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());
//地址结构大小
socklen_t len = sizeof(struct sockaddr_in);
//bind(套接字描述符,地址结构,地址结构大小)
int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
if(ret < 0 )
{
perror("bind error");
return false;
}
return true;
}
//接收数据
bool Recvfrom(std::string* buf, std::string* ip = NULL,uint16_t* port = NULL)
{
//创建地址结构
struct sockaddr_in peer_addr;
socklen_t len = sizeof(sockaddr_in);
//创建缓冲区
char tmp[4096]={
0};
//recvfrom(套接字描述符,接收缓冲区,缓冲区大小,标识符,地址结构信息,地址结构大小)
ssize_t ret = recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)&peer_addr,&len) ;
buf->assign(tmp,ret); //从tmp中截取ret长度的数据放到buf中
if(ret < 0)
{
perror("recvfrom error");
return false;
}
if(ip != NULL)
{
*ip = inet_ntoa(peer_addr.sin_addr); //如果传进来的ip是NULL的,就把peer_addr的ip给它
}
if(port!= NULL)
{
*port = ntohs(peer_addr.sin_port);
}
return true;
}
//4.发送数据
bool Sendto(std::string& data,std::string& ip,uint16_t port)
{
//创建地址结构
struct sockaddr_in peer_addr;
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(port);
peer_addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
//sendto(套接字标识符,发送缓冲区,缓冲区大小,标识符,地址结构信息,地址结构大小)
ssize_t ret = sendto(_sockfd,data.c_str(),data.size(),0,(struct sockaddr*)&peer_addr,len);
if(ret < 0 )
{
perror("sendto error");
return false;
}
return true;
}
//5.关闭套接字
bool Close()
{
if(_sockfd > 0 )
{
close(_sockfd);
_sockfd = -1;
}
return true;
}
private:
int _sockfd;
};
服务端通信流程
#include<iostream>
#include<string>
#include"Udpsocket.hpp"
#define CHECK_RET(p) if((p) == false){
return false;}
int main(int argc, char*argv[])
{
//获取命令
// ./udp_srv 192.168.116.128 9000
if(argc != 3)
{
std::cout<<"Usage: ./udp_srv ip port";
return -1;
}
uint16_t port = std::stoi(argv[2]);
std::string ip = argv[1];
//演示套接字服务端流程
UdpSocket srv_socket;
//创建套接字
CHECK_RET(srv_socket.Socket());
//绑定地址信息
CHECK_RET(srv_socket.Bind(ip,port));
while(1)
{
//接收消息
std::string buf;
std::string peer_ip;
uint16_t peer_port;
CHECK_RET(srv_socket.Recvfrom(&buf,&peer_ip,&peer_port)); //接受对端地址信息
//clint[192.168.xxx.xxx 8000]say:
std::cout<<"clint["<<peer_ip<<" "<<peer_port<<"]say:"<<buf<<std::endl;
//发送消息
buf.clear();
std::cout<<"server say:";
std::cin>>buf;
CHECK_RET(srv_socket.Sendto(buf,peer_ip,peer_port)); //谁发送了数据就给谁回复
}
//关闭套接字
CHECK_RET(srv_socket.Close());
return 0;
}
客户端通信流程
#include<iostream>
#include<string>
#include"Udpsocket.hpp"
#define CHECK_RET(p) if((p) == false){
return false;}
int main(int argc,char* argv[])
{
if(argc != 3)
{
std::cout<<"Usage: ./udp_cli ip port";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = std::stoi(argv[2]);
UdpSocket cli_socket;
CHECK_RET(cli_socket.Socket());
//CHECK_RET(cli_socket.Bind(ip,port));
while(1)
{
std::cout<<"client say:";
std::string buf;
std::cin>>buf;
CHECK_RET(cli_socket.Sendto(buf,srv_ip,srv_port));
buf.clear();
CHECK_RET(cli_socket.Recvfrom(&buf));
std::cout<<"server say: "<<buf<<std::endl;
}
CHECK_RET(cli_socket.Close());
return 0;
}
边栏推荐
- 【clickhouse专栏】基础数据类型说明
- 深度学习与CV教程(14) | 图像分割 (FCN,SegNet,U-Net,PSPNet,DeepLab,RefineNet)
- Mcuxpresso develops NXP rt1060 (3) -- porting lvgl to NXP rt1060
- FPGA开发——Hello_world例程
- AI - face
- PHP uses leanclound to save associated properties
- AcWing 1921. 重新排列奶牛(环图)
- 2021-03-24
- NLP data set download address for naturallanguageprocessing
- How the ArrayList collection implements ascending and descending order
猜你喜欢

Clickhouse column basic data type description

基于C#的安全聊天工具设计

Index query efficiency of MySQL

MYSQL——内置函数

M-arch (fanwai 13) gd32l233 evaluation - some music

Flex layout

Common methods of string class

信号继电器RXSF1-RK271018DC110V

On the improvement of 3dsc by harmonic shape context feature HSC

Pseudo static setting of access database in win2008 R2 iis7.5
随机推荐
B+ 树的简单认识
Flex layout
Summary method of lamp environment deployment
AI - face
Leetcode 2169. Get operands of 0
AcWing 1986. 镜子(模拟,环图)
【clickhouse专栏】基础数据类型说明
Sendmail Dovecot 邮件服务器
Epidemic home office experience | community essay solicitation
Get start date and end date for all quarters of the year
[machine learning] practice of logistic regression classification based on Iris data set
元宇宙链游与传统游戏的区别
AcWing 128. Editor (to effectively modify the specified position in the top stack)
How to upload the video on the computer to the mobile phone without network
Malicious code analysis practice - use IDA pro to analyze lab05-01 dll
Redis summary
元宇宙系统搭建与构造
Clj3-100alh30 residual current relay
M-Arch(番外12)GD32L233评测-CAU加解密(捉弄下小编)
M-Arch(番外10)GD32L233评测-SPI驱动DS1302