当前位置: 首页 > news >正文

Linux网络:使用UDP实现网络通信(网络套接字的创建绑定)

文章目录

      • 1. UDP网络程序的服务端
        • 1.1 封装一个UdpServer类
        • 1.2 获取socket套接字
        • 1.3 绑定套接字

  • 序:在上一章中,先介绍网络基础,然后阐述以太网通信原理、局域网通信的数据封装分用及跨网络靠路由器通信,指出IP和Mac地址作用。接着聚焦网络套接字,说明端口号用于标识主机网络进程,而本章将从UDP协议的角度去理解网络通信的原理。

传输层提供的协议有TCP和UDP两种,提供数据收发的服务,TCP叫做传输控制协议,有链接(确保通信信道的可靠性,收发两端能正常交流),有可靠性(传输层),只有成功建立链接才能进行通信,面向字节流,UDP叫做用户数据报协议,无连接(不需要确保通信信道的可靠性,直接发),不可靠性,面向数据报(发出去的数据有明显的边界,意思就是要发数据就发完整的数据,要么就不发)
其中的udp的不可靠性,并不意味着就是不好了,这是一个中性词,不可靠意味着简单,方便,而要维持可靠性,就必然需要花费一定的成本和资源,所以可靠性的负担也是要被考虑到的地方,所以对于选择tcp还是udp进行通信,还是要因地制宜!!!

在这里插入图片描述

H代表主机,n表示网络,l表示long四字节,s表示short两字节
第一个是32位的,主机转网络序列。
第二个是16位的,主机转网络序列。
第三个是32位的,网络转主机序列。
第四个是16位的,网络转主机序列。

网络套接字

在这里插入图片描述

套接字编程的种类:

  1. 域间套接字编程—>同一个机器内
  2. 原始套接字编程—>网络工具(抓包… )
  3. 网络套接字编程—>用户间的网络通信

想将三个网络接口统一抽象化—>参数的类型必须是统一的

1. UDP网络程序的服务端

1.1 封装一个UdpServer类

要想启动服务端,服务端至少要一个构造,一个析构,一个初始化和一个运行的接口!!!

class UdpServer{
public:UdpServer(){}void Init(){}void Run(func_t func){}~UdpServer(){}
private:};
1.2 获取socket套接字

想要使用UDP套接字,就必须先获取一个套接字,要用到下面的一个函数来获取套接字

在这里插入图片描述

需要包含下面两个库
#include<sys/types.h>
#include<sys/socket.h>int socket(int domain,int type,int protocol);

该函数的第一个参数表示创建的套接字的域(IPv4还是IPv6,在udp场景中使用AF_INET或者PF_INET选项(IPv4)),不同场景要创建不同种类的套接字
在这里插入图片描述

第二个参数表示当前socket对应的类型,例如SOCK_STREM表示的就是流式套接字,SOCK_DGRAM表示用户数据报套接字,显然,之前提到的UDP是面向用户数据报的,TCP是面向字节流的,对于不同的场景填充不同的套接字类型,所以在UDP的场景下我们选择SOCK_DGRAM的套接字类型
在这里插入图片描述

第三个参数表示协议类型,当前默认不用考虑,直接填0

返回值RETURN_VALUE,如果成功了套接字被返回,失败了-1被返回,错误码被设置!这说明该返回值就是一个文件,所以打开一个套接字就是打开一个文件(一切皆文件),类比文件描述符,要想进行网络上的通信就必须经过“网络文件描述符”
在这里插入图片描述

class UdpServer{
public:void Init(){//1.创建udp socket_sockfd=socket(AF_INET,SOCK_DGRAM,0);//用的网络协议IPv4,SOCK_DGRAM数据报(无连接,不稳定)//因为#define AF_INET PF_INET,所以用PF_INET也是一样的if(_sockfd < 0) {//使用日志系统对错误信息进行打印log(Fatal,"socket create error, _sockfd: %d", _sockfd);exit(SOCKET_ERR);}log(Info,"socket create success, _sockfd: %d", _sockfd);}
private://添加私有成员int _sockfd; //网络文件描述符
};
1.3 绑定套接字

绑定套接字要用到bind函数:
在这里插入图片描述

第一个参数:要绑定的套接字(sockfd)
第二个参数:一个结构体,该结构体是本文一开始讲到的通用接口,所有的套接字接口都是struct sockaddr,其参数传递的时候都是struct sockaddr * ,但是我们现在使用的是网络通信,所以我们使用的是struct sockaddr_in这样的结构,要传参数的时候直接强转就行了。
在这里插入图片描述
第三个参数:第二个参数的结构体的长度

在绑定的时候,必须要知道当前服务的端口号和IP地址,IP确定唯一的一台主机,端口号确定该主机上对应的一个服务

uint16_t defaultport = 5070;
std::string defaultip = "0.0.0.0";class UdpServer{
public:UdpServer(const uint16_t &port = defaultport,const std::string &ip = defaultip):_port(port),_ip(ip){}private://添加私有成员std::string _ip; //IP地址, 0.0.0.0 任意地址绑定uint16_t _port;  //表示服务器进程的端口号
};

虽然bind的第二个参数要传的是struct sockaddr,但我们实际要用的是struct sockaddr_in,但是又由于网络接口统一抽象化,所以只需要将struct sockaddr_in填充完后再强转就行了。

#include<netinet/in.h> //要使用该结构体所要包含的头文件
#include<arpa/inet.h>  //主机网络序列转换所需要用到的头文件

初始化struct sockaddr_in结构体
在这里插入图片描述

struct sockaddr_in结构体的各个参数介绍:
在这里插入图片描述

其中的sin_zero表示这个结构体的填充字段,没啥用,就是占位符。其中的sin,s表示socket,in表示inet。

在这里插入图片描述

sin_addr表示IP地址,但是,如果直接将服务器的IP地址传给sin_addr的话,是不行的,因为该参数还是一个结构体,这个结构体里面的in_addr_t才是能传递参数的网络序列,所以要想将IP地址传入进去,第一步就是要将string类型的IP地址转化为uint32_t的类型,第二步是将uint32_t转化为网络序列
在这里插入图片描述

问题一:如何将一个字符串的IP地址转化为一个整数?又怎么转换回来?

使用inet_addr函数,可以直接将字符串转化为4字节整数,并且转化为网络序列。

sin_port表示该服务器的端口号,该参数的类型是in_port_t,而该类型就是uint16_t,但是到这一步还是不可以直接将服务器的端口号传进去,需要注意的是,端口号需要保证是网络字节序列,因为端口号是要给对方发送的,所以还需要将主机序列转化为网络序列,使用函数htons
在这里插入图片描述

sin_family表示要表明自己的结构体所对应的类型,也就是套接字的域,因为我们使用的UDP的套接字的域是IPv4,所以这里直接把AF_INET传过去就行

问题二:但是我们并没有在struct sockaddr_in的里面看到famliy啊?

在这里插入图片描述

我们可以看到该结构体中有一个像这样的宏,传一个_sin进去。
在这里插入图片描述

**该宏定义是意思就是用sa_fanily_t sa_prefix##family代替前面__SOCKADDR_COMMON(sin_ ) **

问题三:其中的“##”是什么意思?

可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称为记号粘合

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
这里我们想想,写⼀个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。

比如

int int_max(int x, int y)
{return x>y?x:y;
}float float_max(float x, float y)
{return x>yx:y;
}

但是这样写起来太繁琐了,现在我们这样写代码试试:

//宏定义
#define GENERIC_MAX(type)        \
type type##_max(type x, type y)  \
{                                \return (x>y?x:y);            \
}

所以这块宏定义就是在sin_后面加了一个family变成了sin_family来表示该参数,这也就是sin_family的由来

class UdpServer{
public:void Init(){//1.创建udp socket//...//2.绑定bind socket//2.1 准备数据struct sockaddr_in local;//memset(&local,0,sizeof(local));bzero(&local,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的//local.sin_addr.s_addr = inet_addr(_ip.c_str());//1.string -> uint32_t 2. uint32_t必须是网络序列的//如果IP要被网络使用,IP也必须是网络序列的local.sin_addr.s_addr=INADDR_ANY;//(INADDR_ANY就是000000000全零)//2.2 开始绑定 if(bind(_sockfd, (const struct sockaddr*)&local, sizeof(local)) < 0){log(Fatal,"bind error,errno: %d ,strerror: %s", errno, strerror(errno));exit(BIND_ERR);}log(Info,"bind success, _sockfd: %d",_sockfd);}
private:int _sockfd; //网络文件描述符std::string _ip; //IP地址, 0.0.0.0 任意地址绑定uint16_t _port;  //表示服务器进程的端口号
};

总结:

本文主要介绍了传输层的TCP与UDP协议特点:TCP可靠、面向连接、字节流;UDP不可靠、无连接、面向数据报。重点讲解UDP服务端编程:创建socket(AF_INET/SOCK_DGRAM)、绑定IP和端口(使用struct sockaddr_in,调用bind),涉及htons、inet_addr等网络字节序转换,强调代码封装与关键步骤实现。

http://www.dtcms.com/a/403520.html

相关文章:

  • 第一次培训
  • 端侧多模态大模型MiniCPM-V 4.5技术报告解析
  • 【环境配置】macbook 配置环境变量
  • 位运算解题1:找数组中唯一成对的那个数
  • 织梦网做网站洛阳网站制作哪家好
  • 动态ip建设网站深圳英文网站建设去哪家
  • 【香橙派开发笔记】初次使用与ubuntu系统安装
  • SAP GUI Script简单案例
  • Nestjs框架: 番外篇之如何基于VsCode调试nodejs项目
  • 手机app网站铜仁做网站公司
  • 基于Cesium倾斜摄影数据的多区域裁剪 / 挖空
  • PyTorch 中可以实现张量形状的改变的有几种方式
  • 三亚市住房与城乡建设局网站企业展馆展厅设计公司
  • Linux----使用vim编辑器
  • [Spring_SpringBoot_Redis] Redis stater
  • 超细整理,保险寿险项目测试项目分析+面试(详细)
  • 濮阳网站设计公司wordpress的编辑器在哪个目录
  • 【Linux】如何通过uptime查看系统负载是否过高?
  • PostgreSQL 16 Administration Cookbook 读书笔记:第10章 Performance and Concurrency
  • Elasticsearch 7.15索引模板介绍
  • 做海外房产最好的网站济南企业网站建设
  • STM32学习-UART串口通信:物理层/协议层/UART基本架构/代码实战
  • [C++——lesson11.static关键字]
  • 小说网站开发流程具体app开发公司 上海
  • 【阿里DeepResearch】写作组件WebWeaver详解
  • 汽车面向服务架构(SOA)网络安全对策
  • 视频网站用php做做视频网站需要什么职位工作
  • Git 无法访问 GitHub(Recv failure: Connection was reset)问题解决教程
  • 佛山网站设计是学校网站建设项目需求报告
  • (8)ASP.NET Core2.2 中的MVC路由一