云南网站建设哪家好,搜索引擎优化方案模板,网站建设和网络优化的区别,系统开发的需求分析阶段的重要工作之一是一、网络结构模式
1、C/S 结构
1#xff09;、简介
服务器 - 客户机#xff0c;即 Client - Server#xff08;C/S#xff09;结构。C/S 结构通常采取两层结构。服务器负责数据的管理#xff0c;客户机负责完成与用户的交互任务。客户机是因特网上访问别人信息的机器、简介
服务器 - 客户机即 Client - ServerC/S结构。C/S 结构通常采取两层结构。服务器负责数据的管理客户机负责完成与用户的交互任务。客户机是因特网上访问别人信息的机器服务器则是提供信息供人访问的计算机。
客户机通过局域网与服务器相连接受用户的请求并通过网络向服务器提出请求对数据库进行操作。服务器接受客户机的请求将数据提交给客户机客户机将数据进行计算并将结果呈现给用户。服务器还要提供完善安全保护及对数据完整性的处理等操作并允许多个客户机同时访问服务器这就对服务器的硬件处理数据能力提出了很高的要求。
在C/S结构中应用程序分为两部分服务器部分和客户机部分。服务器部分是多个用户共享的信息与功能执行后台服务如控制共享数据库的操作等客户机部分为用户所专有负责执行前台功能在出错提示、在线帮助等方面都有强大的功能并且可以在子程序间自由切换。
2、优点
能充分发挥客户端 PC 的处理能力很多工作可以在客户端处理后再提交给服务器所以 C/S 结构客户端响应速度快
操作界面漂亮、形式多样可以充分满足客户自身的个性化要求
C/S 结构的管理信息系统具有较强的事务处理能力能实现复杂的业务流程
安全性较高C/S 一般面向相对固定的用户群程序更加注重流程它可以对权限进行多层次校验提供了更安全的存取模式对信息安全的控制能力很强一般高度机密的信息系统采用 C/S 结构适宜。
3、缺点
客户端需要安装专用的客户端软件。首先涉及到安装的工作量其次任何一台电脑出问题如病毒、硬件损坏都需要进行安装或维护。系统软件升级时每一台客户机需要重新安装其维护和升级成本非常高
对客户端的操作系统一般也会有限制不能够跨平台。
2、B/S 结构
1、简介
B/S 结构Browser/Server浏览器/服务器模式是 WEB 兴起后的一种网络结构模式WEB 浏览器是客户端最主要的应用软件。这种模式统一了客户端将系统功能实现的核心部分集中到服务器上简化了系统的开发、维护和使用。客户机上只要安装一个浏览器如 Firefox 或 Internet Explorer服务器安装 SQL Server、Oracle、MySQL 等数据库。浏览器通过 Web Server 同数据库进行数据交互。
2、优点
B/S 架构最大的优点是总体拥有成本低、维护方便、 分布性强、开发简单可以不用安装任何专门的软件就能实现在任何地方进行操作客户端零维护系统的扩展非常容易只要有一台能上网的电脑就能使用。
3、缺点
通信开销大、系统和数据的安全性较难保障; 个性特点明显降低无法实现具有个性化的功能要求 协议一般是固定的http/https 客户端服务器端的交互是请求-响应模式通常动态刷新页面响应速度明显降低。
二、MAC 地址
网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件又称为网络适配器或网络接口卡NIC。其拥有 MAC 地址属于 OSI 模型的第 2 层它使得用户可以通过电缆或无线相互连接。每一个网卡都有一个被称为 MAC 地址的独一无二的 48 位串行号。
网卡的主要功能
数据的封装与解封装链路管理数据编码与译码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mXBm6lFL-1676445475538)(4-Linux网络编程.assets/image-20221005080831933.png)]
MAC 地址Media Access Control Address直译为媒体存取控制位址也称为局域网地址、以太网地址、物理地址或硬件地址它是一个用来确认网络设备位置的位址由网络设备制造商生产时烧录在网卡中。在 OSI 模型中第三层网络层负责 IP 地址第二层数据链路层则负责 MAC位址 。MAC 地址用于在网络中唯一标识一个网卡一台设备若有一或多个网卡则每个网卡都需要并会有一个唯一的 MAC 地址。
MAC 地址的长度为 48 位6个字节通常表示为 12 个 16 进制数如00-16-EA-AE-3C-40 就是一个MAC 地址其中前 3 个字节16 进制数 00-16-EA 代表网络硬件制造商的编号它由IEEE电气与电子工程师协会分配而后 3 个字节16进制数 AE-3C-40 代表该制造商所制造的某个网络产品如网卡的系列号。只要不更改自己的 MAC 地址MAC 地址在世界是唯一的。形象地说MAC 地址就如同身份证上的身份证号码具有唯一性。
Windows系统 MAC 地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOguKkfC-1676445475539)(4-Linux网络编程.assets/image-20221005101457689.png)]
Linux系统 MAC 地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XvcfNLHM-1676445475539)(4-Linux网络编程.assets/image-20221005101350328.png)]
三、IP 地址
1、简介
IP 协议是为计算机网络相互连接进行通信而设计的协议。在因特网中它是能使连接到网上的所有计算机网络实现相互通信的一套规则规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统只要遵守 IP 协议就可以与因特网互连互通。各个厂家生产的网络系统和设备如以太网、分组交换网等它们相互之间不能互通不能互通的主要原因是因为它们所传送数据的基本单元技术上称之为“帧”的格式不同。IP 协议实际上是一套由软件程序组成的协议软件它把各种不同“帧”统一转换成“IP 数据报”格式这种转换是因特网的一个最重要的特点使所有各种计算机都能在因特网上实现互通即具有“开放性”的特点。正是因为有了 IP 协议因特网才得以迅速发展成为世界上最大的、开放的计算机通信网络。因此IP 协议也可以叫做“因特网协议”。
IP 地址Internet Protocol Address是指互联网协议地址又译为网际协议地址。IP 地址是 IP协议提供的一种统一的地址格式它为互联网上的每一个网络和每一台主机分配一个逻辑地址以此来屏蔽物理地址的差异。
IP 地址是一个 32 位的二进制数通常被分割为 4 个“ 8 位二进制数”也就是 4 个字节。IP 地址通常用“点分十进制”表示成a.b.c.d的形式其中a,b,c,d都是 0~255 之间的十进制整数。例点分十进IP地址100.4.5.6实际上是 32 位二进制数01100100.00000100.00000101.00000110。
2、IP 地址编址方式
最初设计互联网络时为了便于寻址以及层次化构造网络每个 IP 地址包括两个标识码ID即网络ID 和主机 ID。同一个物理网络上的所有主机都使用同一个网络 ID网络上的一个主机包括网络上工作站服务器和路由器等有一个主机 ID 与其对应。Internet 委员会定义了 5 种 IP 地址类型以适合不同容量的网络即 A 类~ E 类。 其中 A、B、C 3类如下表格由 InternetNIC 在全球范围内统一分配D、E 类为特殊地址。
类 别最大网络数IP地址范围单个网段最大 主机数私有IP地址范围A126(2^7-2)1.0.0.1- 126.255.255.2541677721410.0.0.0- 10.255.255.255B16384(2^14)128.0.0.1- 191.255.255.25465534172.16.0.0- 172.31.255.255C2097152(2^21)192.0.0.1- 223.255.255.254254192.168.0.0- 192.168.255.255
①、A类IP地址
一个 A 类 IP 地址是指 在 IP 地址的四段号码中第一段号码为网络号码剩下的三段号码为本地计算机的号码。如果用二进制表示 IP 地址的话A 类 IP 地址就由 1 字节的网络地址和 3 字节主机地址组成网络地址的最高位必须是“0”。A 类 IP 地址中网络的标识长度为 8 位主机标识的长度为 24 位A类网络地址数量较少有 126 个网络每个网络可以容纳主机数达 1600 多万台。
A 类 IP 地址 地址范围 1.0.0.1 - 126.255.255.254二进制表示为00000001 00000000 00000000 00000001 - 01111111 11111111 11111111 11111110。最后一个是广播地址。
A 类 IP 地址的子网掩码为 255.0.0.0每个网络支持的最大主机数为 256 的 3 次方 - 2 16777214 台。
②、B类IP地址
一个 B 类 IP 地址是指在 IP 地址的四段号码中前两段号码为网络号码。如果用二进制表示 IP 地址的话B 类 IP 地址就由 2 字节的网络地址和 2 字节主机地址组成网络地址的最高位必须是“10”。B 类 IP地址中网络的标识长度为 16 位主机标识的长度为 16 位B 类网络地址适用于中等规模的网络有16384 个网络每个网络所能容纳的计算机数为 6 万多台。
B 类 IP 地址地址范围 128.0.0.1 - 191.255.255.254 二进制表示为10000000 00000000 00000000 00000001 - 10111111 11111111 11111111 11111110。 最后一个是广播地址。
B 类 IP 地址的子网掩码为 255.255.0.0每个网络支持的最大主机数为 256 的 2 次方 - 2 65534 台。
③、C类IP地址
一个 C 类 IP 地址是指在 IP 地址的四段号码中前三段号码为网络号码剩下的一段号码为本地计算机的号码。如果用二进制表示 IP 地址的话C 类 IP 地址就由 3 字节的网络地址和 1 字节主机地址组成网络地址的最高位必须是“110”。C 类 IP 地址中网络的标识长度为 24 位主机标识的长度为 8 位C 类网络地址数量较多有 209 万余个网络。适用于小规模的局域网络每个网络最多只能包含254台计算机。
C 类 IP 地址范围 192.0.0.1-223.255.255.254 二进制表示为: 11000000 00000000 00000000 00000001 - 11011111 11111111 11111111 11111110。
C类IP地址的子网掩码为 255.255.255.0每个网络支持的最大主机数为 256 - 2 254 台。
④、D类IP地址
D 类 IP 地址在历史上被叫做多播地址multicast address即组播地址。在以太网中多播地址命名了一组应该在这个网络中应用接收到一个分组的站点。多播地址的最高位必须是 “1110”范围从224.0.0.0 - 239.255.255.255。
⑤、特殊的网址
每一个字节都为 0 的地址 “0.0.0.0” 对应于当前主机 IP 地址中的每一个字节都为 1 的 IP 地址 “255.255.255.255” 是当前子网的广播地址 IP 地址中凡是以 “11110” 开头的 E 类 IP 地址都保留用于将来和实验使用。 IP地址中不能以十进制 “127” 作为开头该类地址中数字 127.0.0.1 到 127.255.255.255 用于回路测试如127.0.0.1可以代表本机IP地址。
⑥、子网掩码 子网掩码subnet mask又叫网络掩码、地址掩码、子网络遮罩它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。子网掩码不能单独存在它必须结合 IP 地址一起使用。子网掩码只有一个作用就是将某个 IP 地址划分成网络地址和主机地址两部分。 子网掩码是一个 32 位地址用于屏蔽 IP 地址的一部分以区别网络标识和主机标识并说明该 IP地址是在局域网上还是在广域网上。 子网掩码是在 IPv4 地址资源紧缺的背景下为了解决 lP 地址分配而产生的虚拟 lP 技术通过子网掩码将A、B、C 三类地址划分为若干子网从而显著提高了 IP 地址的分配效率有效解决了 IP 地址资源紧张的局面。另一方面在企业内网中为了更好地管理网络网管人员也利用子网掩码的作用人为地将一个较大的企业内部网络划分为更多个小规模的子网再利用三层交换机的路由功能实现子网互联从而有效解决了网络广播风暴和网络病毒等诸多网络管理方面的问题。
在大多数的网络教科书中一般都将子网掩码的作用描述为通过逻辑运算将 IP 地址划分为网络标识(Net.ID) 和主机标识(Host.ID)只有网络标识相同的两台主机在无路由的情况下才能相互通信。
根据 RFC950 定义子网掩码是一个 32 位的 2 进制数 其对应网络地址的所有位都置为 1对应于主机地址的所有位置都为 0。子网掩码告知路由器地址的哪一部分是网络地址哪一部分是主机地址使路由器正确判断任意 IP 地址是否是本网段的从而正确地进行路由。网络上数据从一个地方传到另外一个地方是依靠 IP 寻址。从逻辑上来讲是两步的。第一步从 IP 中找到所属的网络好比是去找这个人是哪个小区的第二步再从 IP 中找到主机在这个网络中的位置好比是在小区里面找到这个人。
子网掩码的设定必须遵循一定的规则。与二进制 IP 地址相同子网掩码由 1 和 0 组成且 1 和 0 分别连续。子网掩码的长度也是 32 位左边是网络位用二进制数字 “1” 表示1 的数目等于网络位的长度右边是主机位用二进制数字 “0” 表示0 的数目等于主机位的长度。这样做的目的是为了让掩码与 IP 地址做按位与运算时用 0 遮住原主机数而不改变原网络段数字而且很容易通过 0 的位数确定子网的主机数 2 的主机位数次方 - 2因为主机号全为 1 时表示该网络广播地址全为 0 时表示该网络的网络号这是两个特殊地址。通过子网掩码才能表明一台主机所在的子网与其他子网的关系使网络正常工作。
四、端口
1、简介
“端口” 是英文 port 的意译可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口其中虚拟端口指计算机内部或交换机路由器内的端口不可见是特指TCP/IP协议中的端口是逻辑意义上的端口。例如计算机中的 80 端口、21 端口、23 端口等。物理端口又称为接口是可见端口计算机背板的 RJ45 网口交换机路由器集线器等 RJ45 端口。电话使用 RJ11 插口也属于物理端口的范畴。
如果把 IP 地址比作一间房子端口就是出入这间房子的门。真正的房子只有几个门但是一个 IP地址的端口可以有 65536即2^16个之多端口是通过端口号来标记的端口号只有整数范围是从 0 到655352^16-1。
2、端口类型
1、周知端口Well Known Ports
周知端口是众所周知的端口号也叫知名端口、公认端口或者常用端口范围从 0 到 1023它们紧密绑定于一些特定的服务。例如 80 端口分配给 WWW 服务21 端口分配给 FTP 服务23 端口分配给Telnet服务等等。我们在 IE 的地址栏里输入一个网址的时候是不必指定端口号的因为在默认情况下WWW 服务的端口是 “80”。网络服务是可以使用其他端口号的如果不是默认的端口号则应该在地址栏上指定端口号方法是在地址后面加上冒号“:”半角再加上端口号。比如使用 “8080” 作为 WWW 服务的端口则需要在地址栏里输入“网址:8080”。但是有些系统协议使用固定的端口号它是不能被改变的比如 139 端口专门用于 NetBIOS 与 TCP/IP 之间的通信不能手动改变。
2、注册端口Registered Ports
端口号从 1024 到 49151它们松散地绑定于一些服务分配给用户进程或应用程序这些进程主要是用户选择安装的一些应用程序而不是已经分配好了公认端口的常用程序。这些端口在没有被服务器资源占用的时候可以用用户端动态选用为源端口。
3、动态端口 / 私有端口Dynamic Ports / Private Ports
动态端口的范围是从 49152 到 65535。之所以称为动态端口是因为它一般不固定分配某种服务而是动态分配。
五、网络模型
1、OSI 七层参考模型
七层模型亦称 OSIOpen System Interconnection参考模型即开放式系统互联。参考模型是国际标准化组织ISO制定的一个用于计算机或通信系统间互联的标准体系一般称为 OSI 参考模型或七层模型。
它是一个七层的、抽象的模型体不仅包括一系列抽象的术语或概念也包括具体的协议。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRRYABit-1676445475540)(4-Linux网络编程.assets/image-20221005084155799.png)]
1、物理层主要定义物理设备标准如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流就是由1、0转化为电流强弱来进行传输到达目的地后再转化为 1、0也就是我们常说的数模转换与模数转换。这一层的数据叫做比特。
2、数据链路层建立逻辑连接、进行硬件地址寻址、差错校验等功能。定义了如何让格式化数据以帧为单位进行传输以及如何让控制对物理介质的访问。将比特组合成字节进而组合成帧用MAC地址访问介质。
3、网络层进行逻辑地址寻址在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加而网络层正是管理这种连接的层。
4、传输层定义了一些传输数据的协议和端口号 WWW 端口 80 等如TCP传输控制协议传输效率低可靠性强用于传输可靠性要求高数据量大的数据UDP用户数据报协议与TCP 特性恰恰相反用于传输可靠性要求不高数据量小的数据如 QQ 聊天数据就是通过这种方式传输的。 主要是将从下层接收的数据进行分段和传输到达目的地址后再进行重组。常常把这一层数据叫做段。
5、会话层通过传输层端口号传输端口与接收端口建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求。
6、表示层数据的表示、安全、压缩。主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等也就是把计算机能够识别的东西转换成人能够能识别的东西如图片、声音等。
7、应用层网络服务与最终用户的一个接口。这一层为用户的应用程序例如电子邮件、文件传输和终端仿真提供网络服务。
2、TCP/IP 四层模型
1、简介
现在 Internet因特网使用的主流协议族是 TCP/IP 协议族它是一个分层、多协议的通信体系。TCP/IP协议族是一个四层协议系统自底而上分别是数据链路层、网络层、传输层和应用层。每一层完成不同的功能且通过若干协议来实现上层协议使用下层协议提供的服务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NB2ZFwMH-1676445475540)(4-Linux网络编程.assets/image-20221005085459006.png)]
TCP/IP 协议在一定程度上参考了 OSI 的体系结构。OSI 模型共有七层从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。但是这显然是有些复杂的所以在 TCP/IP 协议中它们被简化为了四个层次。 1应用层、表示层、会话层三个层次提供的服务相差不是很大所以在 TCP/IP 协议中它们被合并为应用层一个层次。 2由于传输层和网络层在网络协议中的地位十分重要所以在 TCP/IP 协议中它们被作为独立的两个层次。 3因为数据链路层和物理层的内容相差不多所以在 TCP/IP 协议中它们被归并在网络接口层一个层次里。只有四层体系结构的 TCP/IP 协议与有七层体系结构的 OSI 相比要简单了不少也正是这样TCP/IP 协议在实际的应用中效率更高成本更低。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFDYtKPO-1676445475541)(4-Linux网络编程.assets/image-20221005090050550.png)]
2、四层介绍
1、应用层应用层是 TCP/IP 协议的第一层是直接为应用进程提供服务的。 1对不同种类的应用程序它们会根据自己的需要来使用应用层的不同协议邮件传输应用使用了 SMTP 协议、万维网应用使用了 HTTP 协议、远程登录服务应用使用了有 TELNET 协议。 2应用层还能加密、解密、格式化数据。 3应用层可以建立或解除与其他节点的联系这样可以充分节省网络资源。
2、传输层作为 TCP/IP 协议的第二层运输层在整个 TCP/IP 协议中起到了中流砥柱的作用。且在运输层中 TCP 和 UDP 也同样起到了中流砥柱的作用。
3、网络层网络层在 TCP/IP 协议中的位于第三层。在 TCP/IP 协议中网络层可以进行网络连接的建立和终止以及 IP 地址的寻找等功能。
4、网络接口层在 TCP/IP 协议中网络接口层位于第四层。由于网络接口层兼并了物理层和数据链 路层所以网络接口层既是传输数据的物理媒介也可以为网络层提供一条准确无误的线路。
六、协议
1、简介
协议网络协议的简称网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定计算机之间才能相互通信交流。它的三要素是语法、语义、时序。
为了使数据在网络上从源到达目的网络通信的参与方必须遵循相同的规则这套规则称为协议protocol它最终体现为在网络上传输的数据包的格式。
协议往往分成几个层次进行定义分层定义是为了使某一层协议的改变不影响其他层次的协议。
2、常见协议
应用层常见的协议有FTP协议File Transfer Protocol 文件传输协议、HTTP协议Hyper Text Transfer Protocol 超文本传输协议、NFSNetwork File System 网络文件系统。
传输层常见协议有TCP协议Transmission Control Protocol 传输控制协议、UDP协议User Datagram Protocol 用户数据报协议。
网络层常见协议有IP 协议Internet Protocol 因特网互联协议、ICMP 协议Internet Control Message Protocol 因特网控制报文协议、IGMP 协议Internet Group Management Protocol 因特网组管理协议。
网络接口层常见协议有ARP协议Address Resolution Protocol 地址解析协议、RARP协议Reverse Address Resolution Protocol 反向地址解析协议。
3、UDP协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JDZXf1QV-1676445475541)(4-Linux网络编程.assets/image-20221005091759064.png)]
1、源端口号发送方端口号 2、目的端口号接收方端口号 3、长度UDP用户数据报的长度最小值是 8 仅有首部 4、校验和检测UDP用户数据报在传输中是否有错有错就丢弃
4、TCP协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PPx3DlIZ-1676445475542)(4-Linux网络编程.assets/image-20221005091819442.png)]
1、源端口号发送方端口号 2、目的端口号接收方端口号 3、序列号本报文段的数据的第一个字节的序号 4、确认序号期望收到对方下一个报文段的第一个数据字节的序号 5、首部长度数据偏移TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远即首部长度。单位32位即以 4 字节为计算单位 6、保留占 6 位保留为今后使用目前应置为 0 7、紧急 URG 此位置 1 表明紧急指针字段有效它告诉系统此报文段中有紧急数据应尽快传送 8、确认 ACK仅当 ACK1 时确认号字段才有效TCP 规定在连接建立后所有传达的报文段都必须把 ACK 置1 9、推送 PSH当两个应用进程进行交互式的通信时有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下TCP 就可以使用推送push操作这时发送方TCP 把 PSH 置 1并立即创建一个报文段发送出去接收方收到 PSH 1 的报文段就尽快地即“推送”向前交付给接收应用进程而不再等到整个缓存都填满后再向上交付 10、复位 RST用于复位相应的 TCP 连接 11、同步 SYN仅在三次握手建立 TCP 连接时有效。当 SYN 1 而 ACK 0 时表明这是一个连接请求报文段对方若同意建立连接则应在相应的报文段中使用 SYN 1 和 ACK 1。因此SYN 置1 就表示这是一个连接请求或连接接受报文 12、终止 FIN用来释放一个连接。当 FIN 1 时表明此报文段的发送方的数据已经发送完毕并要求释放运输连接 13、窗口指发送本报文段的一方的接收窗口而不是自己的发送窗口 14、校验和校验和字段检验的范围包括首部和数据两部分在计算校验和时需要加上 12 字节的伪头部 15、紧急指针仅在 URG 1 时才有意义它指出本报文段中的紧急数据的字节数紧急数据结束后就是普通数据即指出了紧急数据的末尾在报文中的位置注意即使窗口为零时也可发送紧急数据 16、选项长度可变最长可达 40 字节当没有使用选项时TCP 首部长度是 20 字节
5、IP协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5kVJbB2u-1676445475542)(4-Linux网络编程.assets/image-20221005092037868.png)]
1、版本IP 协议的版本。通信双方使用过的 IP 协议的版本必须一致目前最广泛使用的 IP 协议版本号为 4即IPv4) 2、首部长度单位是 32 位4 字节 3、服务类型一般不适用取值为 0 4、总长度指首部加上数据的总长度单位为字节 5、标识identificationIP 软件在存储器中维持一个计数器每产生一个数据报计数器就加 1并将此值赋给标识字段 6、标志flag目前只有两位有意义。 标志字段中的最低位记为 MF。MF 1 即表示后面“还有分片”的数据报。MF 0 表示这已是若干数据报片中的最后一个。 标志字段中间的一位记为 DF意思是“不能分片”只有当 DF 0 时才允许分片 7、片偏移指出较长的分组在分片后某片在源分组中的相对位置也就是说相对于用户数据段的起点该片从何处开始。片偏移以 8 字节为偏移单位。 8、生存时间TTL表明是数据报在网络中的寿命即为“跳数限制”由发出数据报的源点设置这个字段。路由器在转发数据之前就把 TTL 值减一当 TTL 值减为零时就丢弃这个数据报。 9、协议指出此数据报携带的数据时使用何种协议以便使目的主机的 IP 层知道应将数据部分上交给哪个处理过程常用的 ICMP(1)IGMP(2)TCP(6)UDP(17)IPv641 10、首部校验和只校验数据报的首部不包括数据部分。 11、源地址发送方 IP 地址 12、目的地址接收方 IP 地址
6、以太网帧协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZXwUZj4-1676445475543)(4-Linux网络编程.assets/image-20221005092135220.png)]
类型0x800表示 IP、0x806表示 ARP、0x835表示 RARP
7、ARP协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ociiUtF6-1676445475543)(4-Linux网络编程.assets/image-20221005092157300.png)]
1、硬件类型1 表示 MAC 地址 2、协议类型0x800 表示 IP 地址 3、硬件地址长度6 4、协议地址长度4 5、操作1 表示 ARP 请求2 表示 ARP 应答3 表示 RARP 请求4 表示 RARP 应答
8、封装
上层协议是如何使用下层协议提供的服务的呢其实这是通过封装encapsulation实现的。应用程序数据在发送到物理网络上之前将沿着协议栈从上往下依次传递。每层协议都将在上层数据的基础上加上自己的头部信息有时还包括尾部信息以实现该层的功能这个过程就称为封装。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahefsmGu-1676445475544)(4-Linux网络编程.assets/image-20221005092227755.png)]
9、分用
当帧到达目的主机时将沿着协议栈自底向上依次传递。各层协议依次处理帧中本层负责的头部数据以获取所需的信息并最终将处理后的帧交给目标应用程序。这个过程称为分用demultiplexing。分用是依靠头部信息中的类型字段实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sNofUI4C-1676445475544)(4-Linux网络编程.assets/image-20221005092243009.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4FkfvsV-1676445475545)(4-Linux网络编程.assets/image-20221005092246686.png)]
网络通信的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7VbHEHMV-1676445475545)(4-Linux网络编程.assets/image-20221005155232846.png)]
arpAddress resolution protocol地址解析协议协议通过IP地址查找MAC地址
在数据链路层我们需要网络层输出来的数据IP数据报进行封装成帧我们需要获取目标的 MAC 地址此时就需要通过 arp 协议来通过 IP 地址查找目标端的 MAC 地址。
我们可以通过 arp -a 来通过IP地址查看MAC地址
C:\Users\qykhhrarp -a接口: 192.168.23.1 --- 0xcInternet 地址 物理地址 类型192.168.23.254 00-50-56-e9-7d-f4 动态192.168.23.255 ff-ff-ff-ff-ff-ff 静态224.0.0.22 01-00-5e-00-00-16 静态224.0.0.251 01-00-5e-00-00-fb 静态224.0.0.252 01-00-5e-00-00-fc 静态239.255.255.250 01-00-5e-7f-ff-fa 静态255.255.255.255 ff-ff-ff-ff-ff-ff 静态接口: 192.168.182.1 --- 0x10Internet 地址 物理地址 类型192.168.182.10 00-0c-29-b7-d2-3e 动态192.168.182.255 ff-ff-ff-ff-ff-ff 静态224.0.0.22 01-00-5e-00-00-16 静态224.0.0.251 01-00-5e-00-00-fb 静态224.0.0.252 01-00-5e-00-00-fc 静态239.255.255.250 01-00-5e-7f-ff-fa 静态arp地址解析协议的工作原理
每台主机在其ARP缓存区中维护一个arp列表当源主机想发送数据给目的主机的时候先在自己的arp列表中查找是否有该mac地址如果有就之间将数据发送给目的主机,如果没有就向本地网段发送一个ARP请求广播包来查询目的之举对应的mac地址。
当本地网络中的所有主机都接受到该arp数据包时首先检查数据包中的IP是否是自己的IP地址如果不是则忽略该数据包。如果是取出数据包中的源主机的IP和MAC地址并写入自己的arp列表中并将自己的mac地址发送给源主机。源主机收到相应包之后先将目的主机中的IP和mac写入自己的arp列表中并将数据发送给目的主机。如果没有响应包则表示ARP查询失败。【广播发送ARP请求单播发送响应】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BxwYTso1-1676445475546)(4-Linux网络编程.assets/image-20221005160455433.png)]
七、socket 通信
1、socket 介绍
所谓 socket套接字就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
一个套接字就是网络上进程通信的一端提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲套接字上联应用进程下联网络协议栈是应用程序通过网络协议进行通信的接口是应用程序与网络协议根进行交互的接口。
socket 可以看成是两个网络应用程序进行通信时各自通信连接中的端点这是一个逻辑上的概念。它是网络环境中进程间通信的 API也是可以被命名和寻址的通信端点使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 socket 中该 socket 通过与网络接口卡NIC相连的传输介质将这段信息送到另外一台主机的 socket 中使对方能够接收到这段信息。socket 是由 IP 地址和端口结合的提供向应用层进程传送数据包的机制。
socket 本身有“插座”的意思在 Linux 环境下用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件那么理所当然的我们可以使用文件描述符引用套接字。与管道类似的Linux 系统将其封装成文件的目的是为了统一接口使得读写套接字和读写文件的操作一致。
区别是管道主要应用于本地进程间通信而套接字多应用于网络进程间数据的传递。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqHBVvVR-1676445475546)(4-Linux网络编程.assets/image-20221005163112703.png)]
// 套接字通信分两部分
- 服务器端被动接受连接一般不会主动发起连接
- 客户端主动向服务器发起连接
socket是一套通信的接口Linux 和 Windows 都有但是有一些细微的差别。2、字节序
1、简介
现代 CPU 的累加器一次都能装载至少4 字节这里考虑 32 位机即一个整数。那么这 4 字节在内存中排列的顺序将影响它被累加器装载成的整数的值这就是字节序问题。在各种计算机体系结构中对于字节、字等的存储机制有所不同因而引发了计算机通信领域中一个很重要的问题即通信双方交流的信息单元比特、字节、字、双字等等应该以什么样的顺序进行传送。如果不达成一致的规则通信双方将无法进行正确的编码/译码从而导致通信失败。
字节序顾名思义字节的顺序就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。
字节序分为大端字节序Big-Endian 和小端字节序Little-Endian。大端字节序是指一个整数的最高位字节23 ~ 31 bit存储在内存的低地址处低位字节0 ~ 7 bit存储在内存的高地址处小端字节序则是指整数的高位字节存储在内存的高地址处而低位字节则存储在内存的低地址处。
2、字节序举例
小端字节序
0x 01 02 03 04 - ff 255 内存的方向内存的高位 ----- 内存的低位
0x 01 02 03 04 进行小端字节序排序结果为04 03 02 01
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XwuakLIA-1676445475552)(4-Linux网络编程.assets/image-20221005164620586.png)]
上图的字节流为0x 11 22 33 44 12 34 56 78
大端字节序
0x 01 02 03 04 内存的方向内存的低位 ----- 内存的高位
0x 01 02 03 04 进行大端字节序排序结果为01 02 03 04
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eIuWRjy1-1676445475553)(4-Linux网络编程.assets/image-20221005165146373.png)]
上图的字节流为0x 12 34 56 78 11 22 33 44
通过代码检测当前主机的字节序
/*
字节序字节在内存中存储的顺序
小端字节序数据的高位字节存储在内存的高位地址低位字节存储在内存的低位地址
大端字节序数据的高位字节存储在内存的低位地址低位字节存储在内存的高位地址
*/
// 通过代码检测当前主机的字节序
#include stdio.h
int main()
{// 定义一个联合体(共用体)// 结构体和共用体的区别在于结构体的各个成员会占用不同的内存互相之间没有影响而共用体的所有成员占用同一段内存修改一个成员会影响其余所有成员。union{short value; // 2个字节char bytes[sizeof(short)]; // char[2] 2个字节} test;test.value 0x0102;if((test.bytes[0] 1) (test.bytes[1] 2)){printf(大端字节序\n);}else if((test.bytes[0] 2) (test.bytes[1] 1)){printf(小端字节序\n);}else{printf(未知字节序\n);}return 0;
}qykhhrqykhhr:~/Linux/lesson31$ gcc -o byteorder byteorder.c
qykhhrqykhhr:~/Linux/lesson31$ ./byteorder
小端字节序3、字节序转换函数
当格式化的数据在两台使用不同字节序的主机之间直接传递时接收端必然错误的解释之。解决问题的方法是发送端总是把要发送的数据转换成大端字节序数据后再发送而接收端知道对方传送过来的数据总是采用大端字节序所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换小端机转换大端机不转换。
网络字节顺序是 TCP/IP 中规定好的一种数据表示格式它与具体的 CPU 类型、操作系统等无关从而可以保证数据在不同主机之间传输时能够被正确解释网络字节顺序采用大端排序方式。
BSD Socket提供了封装好的转换接口方便程序员使用。包括从主机字节序到网络字节序的转换函数htons、htonl从网络字节序到主机字节序的转换函数ntohs、ntohl。
h - host 主机主机字节序
to - 转换成什么
n - network 网络字节序
s - short unsigned short / l - long unsigned int#include arpa/inet.h
// 转换端口
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 网络字节序 - 主机字节序
// 转IP
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 网络字节序 - 主机字节序/*
网络通信时需要将主机字节序转换成网络字节序大端另外一旦获取到数据以后根据情况将网络字节序转换成主机字节序。
#include arpa/inet.h
// 转换端口short
uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
uint16_t ntohs(uint16_t netshort); // 网络字节序 - 主机字节序
// 转IPlong
uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
uint32_t ntohl(uint32_t netlong); // 网络字节序 - 主机字节序
*//*
规定网络是用的大端序列但是我们机器一般都是小端排序所以我们在发送数据和接受数据时需要进行转换
*/
#include arpa/inet.h
#include stdio.h
int main()
{// htons 转换端口unsigned short a 0x0102;printf(a %x\n,a);unsigned short b htons(a);printf(b %x\n,b);printf(\n);// htonl 转换IPchar buf[4] {192,168,1,100};// 将 buf 的地址转为int *并去除int类型的4个字节数据// 取char数组前四个字节组成一个int数int num *(int *)buf;int sum htonl(num);// 将 int * 转为 char *unsigned char *p (char *)sum;printf(%d %d %d %d\n,*p, *(p 1), *(p 2), *(p 3));printf(\n);// ntohl 将网络字节序大端转为主机字节序小端unsigned char buf1[4] {10,1,168,192};int num1 *(int *)buf1;int sum1 ntohl(num1);unsigned char * p1 (unsigned char *)sum1;printf(%d %d %d %d\n,*p1, *(p1 1), *(p1 2), *(p1 3));// ntohsreturn 0;
}qykhhrqykhhr:~/Linux/lesson31$ ./bytetrans
a 102
b 201100 1 168 192192 168 1 103、socket 地址
// socket地址其实是一个结构体封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。
// 客户端 - 服务器IP, Port1、通用 socket 地址
socket 网络编程接口中表示 socket 地址的是结构体 sockaddr其定义如下
#include bits/socket.h
struct sockaddr {sa_family_t sa_family;char sa_data[14];
};
typedef unsigned short int sa_family_t;sa_family 成员是地址族类型sa_family_t的变量。地址族类型通常与协议族类型对应。常见的协议族protocol family也称 domain和对应的地址族入下所示
协议族地址族描述PF_UNIXAF_UNIXUNIX本地域协议族PF_INETAF_INETTCP/IPv4协议族PF_INET6AF_INET6TCP/IPv6协议族
宏 PF_* 和 AF_* 都定义在 bits/socket.h 头文件中且后者与前者有完全相同的值所以二者通常混用。
sa_data 成员用于存放 socket 地址值。但是不同的协议族的地址值具有不同的含义和长度如下所示
协议族地址值含义和长度PF_UNIX文件的路径名长度可达到108字节PF_INET16 bit 端口号和 32 bit IPv4 地址共 6 字节PF_INET616 bit 端口号32 bit 流标识128 bit IPv6 地址32 bit 范围 ID共 26 字节
由上表可知14 字节的 sa_data 根本无法容纳多数协议族的地址值。因此Linux 定义了下面这个新的通用的 socket 地址结构体这个结构体不仅提供了足够大的空间用于存放地址值而且是内存对齐的。
#include bits/socket.h
struct sockaddr_storage
{sa_family_t sa_family;unsigned long int __ss_align;char __ss_padding[ 128 - sizeof(__ss_align) ];
};
typedef unsigned short int sa_family_t;2、专用 socket 地址
很多网络编程函数诞生早于 IPv4 协议那时候都使用的是 struct sockaddr 结构体为了向前兼容现在 sockaddr 退化成了void *的作用传递一个地址给函数至于这个函数是 sockaddr_in 还是 sockaddr_in6由地址族确定然后函数内部再强制类型转化为所需的地址类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WA10Z2oj-1676445475553)(4-Linux网络编程.assets/image-20221005171151814.png)]
UNIX 本地域协议族使用如下专用的 socket 地址结构体
#include sys/un.h
struct sockaddr_un
{sa_family_t sin_family;char sun_path[108];
};TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体它们分别用于 IPv4 和 IPv6
#include netinet/in.h
struct sockaddr_in
{sa_family_t sin_family; /* __SOCKADDR_COMMON(sin_) */in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of struct sockaddr. */unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -sizeof (in_port_t) - sizeof (struct in_addr)];
};
struct in_addr
{in_addr_t s_addr;
};
struct sockaddr_in6
{sa_family_t sin6_family;in_port_t sin6_port; /* Transport layer port # */uint32_t sin6_flowinfo; /* IPv6 flow information */struct in6_addr sin6_addr; /* IPv6 address */uint32_t sin6_scope_id; /* IPv6 scope-id */
};
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))所有专用 socket 地址以及 sockaddr_storage类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr强制转化即可因为所有 socket 编程接口使用的地址参数类型都是 sockaddr。
4、IP地址转换字符串ip- 整数 主机、网络字节序的转换
通常人们习惯用可读性好的字符串来表示 IP 地址比如用点分十进制字符串表示 IPv4 地址以及用十六进制字符串表示 IPv6 地址。但编程中我们需要先把它们转化为整数二进制数方能使用。而记录日志时则相反我们要把整数表示的 IP 地址转化为可读的字符串。下面 3 个函数可用于用点分十进制字符串表示的 IPv4 地址和用网络字节序整数表示的 IPv4 地址之间的转换
#include arpa/inet.h
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);下面这对更新的函数也能完成前面 3 个函数同样的功能并且它们同时适用 IPv4 地址和 IPv6 地址
#include arpa/inet.h
// p:点分十进制的IP字符串n:表示network网络字节序的整数
int inet_pton(int af, const char *src, void *dst);af:地址族 AF_INET AF_INET6src:需要转换的点分十进制的IP字符串dst:转换后的结果保存在这个里面
// 将网络字节序的整数转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族 AF_INET AF_INET6src: 要转换的ip的整数的地址dst: 转换成IP地址字符串保存的地方size第三个参数的大小数组的大小返回值返回转换后的数据的地址字符串和 dst 是一样的将点分十进制的IP字符串转换为网络字节序的整数
/*
#include arpa/inet.h
// p:点分十进制的IP字符串n:表示network网络字节序的整数
int inet_pton(int af, const char *src, void *dst);af:地址族 AF_INET AF_INET6src:需要转换的点分十进制的IP字符串dst:转换后的结果保存在这个里面
// 将网络字节序的整数转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族 AF_INET AF_INET6src: 要转换的ip的整数的地址dst: 转换成IP地址字符串保存的地方size第三个参数的大小数组的大小返回值返回转换后的数据的地址字符串和 dst 是一样的
*/
#include stdio.h
#include arpa/inet.h
int main()
{// 创建一个IP字符串点分十进制的IP地址字符串char buf[] 192.168.1.4;unsigned int num 0;// 将点分十进制的IP字符串转换为网络字节序的整数// 192.168.1.4 ---- 19216814inet_pton(AF_INET,buf,num);unsigned char * p (unsigned char *)num;printf(%d %d %d %d\n,*p , *(p 1), *(p 2),*(p 3));printf(\n);// 将网络字节序的IP整数转换成点分十进制的IP字符串// 19216814 ---- 192.168.1.4char ip[16] ;const char * str inet_ntop(AF_INET,num,ip,16);printf(%s\n,str);return 0;
}qykhhrqykhhr:~/Linux/lesson31$ gcc -o iptrans iptrans.c
qykhhrqykhhr:~/Linux/lesson31$ ./iptrans
192 168 1 4也可以使用 char buf[1024] {0}; 数组来接受转换后的结果。
#include stdio.h
#include arpa/inet.h
int main()
{// 创建一个 IP 字符串点分十进制的IP地址字符串char buf[] {192.168.182.10};unsigned char p[1024] {0};// 将点分十进制的IP字符串转换为网络字节序inet_pton(AF_INET,buf,p);printf(%d %d %d %d\n,*p,*(p 1),*(p 2),*(p 3));printf(\n);// 将网络字节序的IP证书转换成点分十进制的IP字符串char ip[16] {0};const char * str inet_ntop(AF_INET,p,ip,16);printf(%s\n,str);return 0;
}qykhhrqykhhr:~/Linux2/4-Linux网络编程$ ./3-inet_pton
192 168 182 10192.168.182.105、TCP通信流程
// TCP 和 UDP - 传输层的协议
UDP:用户数据报协议面向无连接可以单播多播广播 面向数据报不可靠
TCP:传输控制协议面向连接的可靠的基于字节流仅支持单播传输UDP TCP
是否创建连接 无连接 面向连接
是否可靠 不可靠 可靠的
连接的对象个数 一对一、一对多、多对一、多对多 支持一对一
传输的方式 面向数据报 面向字节流
首部开销 8个字节 最少20个字节
适用场景 实时应用视频会议直播 可靠性高的应用文件传输[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHnFUGnk-1676445475554)(4-Linux网络编程.assets/image-20221005171617187.png)]
// TCP 通信的流程
// 服务器端 被动接受连接的角色
1. 创建一个用于监听的套接字- 监听监听有客户端的连接- 套接字这个套接字其实就是一个文件描述符
2. 将这个监听文件描述符和本地的IP和端口绑定IP和端口就是服务器的地址信息- 客户端连接服务器的时候使用的就是这个IP和端口
3. 设置监听监听的fd开始工作
4. 阻塞等待当有客户端发起连接解除阻塞接受客户端的连接会得到一个和客户端通信的套接字fd
5. 通信- 接收数据- 发送数据
6. 通信结束断开连接6. 套接字函数// 客户端
1. 创建一个用于通信的套接字fd
2. 连接服务器需要指定连接的服务器的 IP 和 端口
3. 连接成功了客户端可以直接和服务器通信- 接收数据- 发送数据
4. 通信结束断开连接6、套接字函数
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h // 包含了这个头文件上面两个就可以省略
int socket(int domain, int type, int protocol);- 功能创建一个套接字- 参数- domain: 协议族AF_INET : ipv4AF_INET6 : ipv6AF_UNIX, AF_LOCAL : 本地套接字通信进程间通信- type: 通信过程中使用的协议类型SOCK_STREAM : 流式协议SOCK_DGRAM : 报式协议- protocol : 具体的一个协议。一般写0- SOCK_STREAM : 流式协议默认使用 TCP- SOCK_DGRAM : 报式协议默认使用 UDP- 返回值- 成功返回文件描述符操作的就是内核缓冲区。- 失败-1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命名- 功能绑定将fd 和本地的IP 端口进行绑定- 参数- sockfd : 通过socket函数得到的文件描述符- addr : 需要绑定的socket地址这个地址封装了ip和端口号的信息- addrlen : 第二个参数结构体占的内存大小
int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn- 功能监听这个socket上的连接- 参数- sockfd : 通过socket()函数得到的文件描述符- backlog : 未连接的和已经连接的和的最大值 5
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);- 功能接收客户端连接默认是一个阻塞的函数阻塞等待客户端连接- 参数- sockfd : 用于监听的文件描述符- addr : 传出参数记录了连接成功后客户端的地址信息ipport- addrlen : 指定第二个参数的对应的内存大小- 返回值- 成功 用于通信的文件描述符- -1 失败
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);- 功能 客户端连接服务器- 参数- sockfd : 用于通信的文件描述符- addr : 客户端要连接的服务器的地址信息- addrlen : 第二个参数的内存大小- 返回值成功 0 失败 -1
ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据TCP 通信实现服务器端
// TCP 通信的服务器端#include stdio.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include stdlib.hint main() {// 1.创建socket(用于监听的套接字)int lfd socket(AF_INET, SOCK_STREAM, 0);if(lfd -1) {perror(socket);exit(-1);}// 2.绑定struct sockaddr_in saddr;saddr.sin_family AF_INET;// inet_pton(AF_INET, 192.168.193.128, saddr.sin_addr.s_addr);saddr.sin_addr.s_addr INADDR_ANY; // 0.0.0.0saddr.sin_port htons(9999);int ret bind(lfd, (struct sockaddr *)saddr, sizeof(saddr));if(ret -1) {perror(bind);exit(-1);}// 3.监听ret listen(lfd, 8);if(ret -1) {perror(listen);exit(-1);}// 4.接收客户端连接struct sockaddr_in clientaddr;int len sizeof(clientaddr);int cfd accept(lfd, (struct sockaddr *)clientaddr, len);if(cfd -1) {perror(accept);exit(-1);}// 输出客户端的信息char clientIP[16];inet_ntop(AF_INET, clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));unsigned short clientPort ntohs(clientaddr.sin_port);printf(client ip is %s, port is %d\n, clientIP, clientPort);// 5.通信char recvBuf[1024] {0};while(1) {// 获取客户端的数据int num read(cfd, recvBuf, sizeof(recvBuf));if(num -1) {perror(read);exit(-1);} else if(num 0) {printf(recv client data : %s\n, recvBuf);} else if(num 0) {// 表示客户端断开连接printf(clinet closed...);break;}char * data hello,i am server;// 给客户端发送数据write(cfd, data, strlen(data));}// 关闭文件描述符close(cfd);close(lfd);return 0;
}TCP 通信实现客户端
// TCP通信的客户端#include stdio.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include stdlib.hint main() {// 1.创建套接字int fd socket(AF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);exit(-1);}// 2.连接服务器端struct sockaddr_in serveraddr;serveraddr.sin_family AF_INET;inet_pton(AF_INET, 192.168.182.10, serveraddr.sin_addr.s_addr);serveraddr.sin_port htons(9999);int ret connect(fd, (struct sockaddr *)serveraddr, sizeof(serveraddr));if(ret -1) {perror(connect);exit(-1);}// 3. 通信char recvBuf[1024] {0};while(1) {char * data hello,i am client;// 给客户端发送数据write(fd, data , strlen(data));sleep(1);int len read(fd, recvBuf, sizeof(recvBuf));if(len -1) {perror(read);exit(-1);} else if(len 0) {printf(recv server data : %s\n, recvBuf);} else if(len 0) {// 表示服务器端断开连接printf(server closed...);break;}}// 关闭连接close(fd);return 0;
}7、TCP 三次握手
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6gKQ7FJV-1676445475554)(4-Linux网络编程.assets/image-20221006100007383.png)]
TCP 是一种面向连接的单播协议在发送数据前通信双方必须在彼此间建立一条连接。所谓的“连接”其实是客户端和服务器的内存里保存的一份关于对方的信息如 IP 地址、端口号等。
TCP 可以看成是一种字节流它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中双方需要交换一些连接的参数。这些参数可以放在 TCP 头部。
TCP 提供了一种可靠、面向连接、字节流、传输层的服务采用三次握手建立一个连接。采用 四次挥手来关闭一个连接。
三次握手的目的是保证双方之间建立了连接。
三次握手发生在客户端连接的时候当调用 connect()底层会通过 TCP 协议进行三次握手。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uF3ClSf-1676445475555)(4-Linux网络编程.assets/image-20221005172318814.png)]
16 位端口号port number告知主机报文段是来自哪里源端口以及传给哪个上层协议或应用程序目的端口的。进行 TCP 通信时客户端通常使用系统自动选择的临时端口号。
32 位序号sequence number一次 TCP 通信从 TCP 连接建立到断开过程中某一个传输方向上的字节流的每个字节的编号。假设主机 A 和主机 B 进行 TCP 通信A 发送给 B 的第一个TCP 报文段中序号值被系统初始化为某个随机值 ISNInitial Sequence Number初始序号值。那么在该传输方向上从 A 到 B后续的 TCP 报文段中序号值将被系统设置成 ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如某个 TCP 报文段传送的数据是字节流中的第 1025 ~ 2048 字节那么该报文段的序号值就是 ISN 1025。另外一个传输方向从B 到 A的 TCP 报文段的序号值也具有相同的含义。
32 位确认号acknowledgement number用作对另一方发送来的 TCP 报文段的响应。其值是收到的 TCP 报文段的序号值 标志位长度SYNFIN 数据长度 。假设主机 A 和主机 B 进行TCP 通信那么 A 发送出的 TCP 报文段不仅携带自己的序号而且包含对 B 发送来的 TCP 报文段的确认号。反之B 发送出的 TCP 报文段也同样携带自己的序号和对 A 发送来的报文段的确认序号。
4 位头部长度head length标识该 TCP 头部有多少个 32 bit(4 字节)。因为 4 位最大能表示15所以 TCP 头部最长是60 字节。
6 位标志位包含如下几项
URG 标志表示紧急指针urgent pointer是否有效。ACK 标志表示确认号是否有效。我们称携带 ACK 标志的 TCP 报文段为确认报文段。PSH 标志提示接收端应用程序应该立即从 TCP 接收缓冲区中读走数据为接收后续数据腾出空间如果应用程序不将接收到的数据读走它们就会一直停留在 TCP 接收缓冲区中。RST 标志表示要求对方重新建立连接。我们称携带 RST 标志的 TCP 报文段为复位报文段。SYN 标志表示请求建立一个连接。我们称携带 SYN 标志的 TCP 报文段为同步报文段。FIN 标志表示通知对方本端要关闭连接了。我们称携带 FIN 标志的 TCP 报文段为结束报文段。16 位窗口大小window size是 TCP 流量控制的一个手段。这里说的窗口指的是接收通告窗口Receiver WindowRWND。它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据这样对方就可以控制发送数据的速度。16 位校验和TCP checksum由发送端填充接收端对 TCP 报文段执行 CRC 算法以校验TCP 报文段在传输过程中是否损坏。注意这个校验不仅包括 TCP 头部也包括数据部分。这也是 TCP 可靠传输的一个重要保障。16 位紧急指针urgent pointer是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。因此确切地说这个字段是紧急指针相对当前序号的偏移不妨称之为紧急偏移。TCP 的紧急指针是发送端向接收端发送紧急数据的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o85AkTkc-1676445475555)(4-Linux网络编程.assets/image-20221005172452511.png)]
第一次握手1.客户端将SYN标志位置为12.生成一个随机的32位的序号seqJ 这个序号后边是可以携带数据数据的大小
第二次握手1.服务器端接收客户端的连接 ACK12.服务器会回发一个确认序号 ack客户端的序号 数据长度 SYN/FIN(按一个字节算)3.服务器端会向客户端发起连接请求 SYN14.服务器会生成一个随机序号seq K
第三次握手1.客户单应答服务器的连接请求ACK12.客户端回复收到了服务器端的数据ack服务端的序号 数据长度 SYN/FIN(按一个字节算)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a68gLDCJ-1676445475556)(4-Linux网络编程.assets/image-20221006100022320.png)]
8、TCP 滑动窗口
滑动窗口Sliding window是一种流量控制技术。早期的网络通信中通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况同时发送数据导致中间节点阻塞掉包谁也发不了数据所以就有了滑动窗口机制来解决此问题。滑动窗口协议是用来改善吞吐量的一种技术即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包称窗口尺寸。
TCP 中采用滑动窗口来进行传输控制滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时发送方一般不能再发送数据报。 滑动窗口是 TCP 中实现诸如 ACK 确认、流量控制、拥塞控制的承载结构。
窗口理解为缓冲区的大小 滑动窗口的大小会随着发送数据和接收数据而变化。 通信的双方都有发送缓冲区和接收数据的缓冲区 服务器
发送缓冲区发送缓冲区的窗口接收缓冲区接收缓冲区的窗口
客户端
发送缓冲区发送缓冲区的窗口接收缓冲区接收缓冲区的窗口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdNJFLtA-1676445475556)(4-Linux网络编程.assets/image-20221005172523163.png)]
发送方的缓冲区白色格子空闲的空间灰色格子数据已经被发送出去了但是还没有被接收紫色格子还没有发送出去的数据
接收方的缓冲区白色格子空闲的空间紫色格子已经接收到的数据[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnIuSXME-1676445475557)(4-Linux网络编程.assets/image-20221005172527522.png)]
# mss: Maximum Segment Size(一条数据的最大的数据量)
# win: 滑动窗口
1. 客户端向服务器发起连接客户端的滑动窗口是4096一次发送的最大数据量是1460
2. 服务器接收连接情况告诉客户端服务器的窗口大小是6144一次发送的最大数据量是1024
3. 第三次握手
4. 4-9 客户端连续给服务器发送了6k的数据每次发送1k
5. 第10次服务器告诉客户端发送的6k数据以及接收到存储在缓冲区中缓冲区数据已经处理了一些,窗口增大了窗
口大小是2k
6. 第11次服务器告诉客户端发送的6k数据以及接收到存储在缓冲区中缓冲区数据已经处理了4k窗口增大,窗
口大小是4k
7. 第12次客户端给服务器发送了1k的数据
8. 第13次客户端主动请求和服务器断开连接并且给服务器发送了1k的数据
9. 第14次服务器回复ACK 8194, a:同意断开连接的请求 b:告诉客户端已经接受到方才发的2k的数据 c:滑动窗口2k
10.第15、16次通知客户端滑动窗口的大小
11.第17次第三次挥手服务器端给客户端发送FIN,请求断开连接
12.第18次第四次挥手客户端同意了服务器端的断开请求9、TCP 四次挥手
四次挥手发生在断开连接的时候在程序中当调用了close()会使用TCP协议进行四次挥手。
客户端和服务器端都可以主动发起断开连接谁先调用close()谁就是发起。
因为在TCP连接的时候采用三次握手建立的的连接是双向的在断开的时候需要双向断开。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2UUXMyQ-1676445475557)(4-Linux网络编程.assets/image-20221006100030267.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ce9JzQko-1676445475557)(4-Linux网络编程.assets/image-20221005172848794.png)]
10、TCP 通信并发
要实现TCP通信服务器处理并发的任务使用多线程或者多进程来解决。
思路
1. 一个父进程多个子进程
2.父进程负责等待并接受客户端的连接
3.子进程完成通信接受一个客户端连接就创建一个子进程用于通信。多进程版本实现服务端可以处理多个客户端
server_process.c
// 多进程版本实现服务端可以处理多个客户端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h
#include signal.h
#include sys/types.h
#include sys/stat.h
#include wait.h
#include errno.h// 回收子进程资源
void recyleChild(int arg)
{while (1){int ret waitpid(-1,NULL,WNOHANG);if(ret -1){// 所有的子进程都回收了break;}else if (ret 0){// 还有子进程活着break;}else if(ret 0){// 被回收了printf(子进程 %d 被回收了\n,ret);}}
}
int main()
{// 注册 SIGCHLD 信号子进程结束时给父进程发送的信号实现子进程结束回收资源struct sigaction act;act.sa_flags 0;sigemptyset(act.sa_mask);act.sa_handler recyleChild;sigaction(SIGCHLD,act,NULL);// 1、创建 socketint lfd socket(PF_INET,SOCK_STREAM,0);if(lfd -1){perror(socket);exit(-1);}// 绑定struct sockaddr_in saddr;saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY; //0.0.0.0saddr.sin_port htons(9999);int ret bind(lfd,(struct sockaddr *)saddr,sizeof(saddr));if(ret -1){perror(bind);exit(-1);}// 监听ret listen(lfd,128);if(ret -1){perror(listen);exit(-1);}// 不断循环等待接受客户端连接while (1){// 接受连接struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd,(struct sockaddr *)cliaddr,len);if(cfd -1){// EINTR系统调用被有效连接到达之前捕获的信号中断参见信号7。if(errno EINTR){continue;}perror(accept);exit(-1);}// 每一个连接进来创建一个子进程跟客户端通信pid_t pid fork();if(pid 0){// 子进程// 获取客户端的信息char cliIP[16];inet_ntop(AF_INET,cliaddr.sin_addr.s_addr,cliIP,sizeof(cliIP));unsigned short cliPort ntohs(cliaddr.sin_port);printf(client ip is %s,port is %d\n,cliIP,cliPort);// 接受客户端发来的数据char recvBuf[1024];while (1){int len read(cfd,recvBuf,sizeof(recvBuf));if(len -1){perror(read);exit(-1);}else if(len 0){printf(recv client %s\n,recvBuf);}else{printf(client closed...\n);}write(cfd,recvBuf,strlen(recvBuf) 1);}close(cfd);exit(0);// 退出当前子进程}}close(lfd);return 0;
}client.c
// TCP通信的客户端#include stdio.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include stdlib.hint main() {// 1.创建套接字int fd socket(AF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);exit(-1);}// 2.连接服务器端struct sockaddr_in serveraddr;serveraddr.sin_family AF_INET;inet_pton(AF_INET, 192.168.182.10, serveraddr.sin_addr.s_addr);serveraddr.sin_port htons(9999);int ret connect(fd, (struct sockaddr *)serveraddr, sizeof(serveraddr));if(ret -1) {perror(connect);exit(-1);}// 3. 通信// char recvBuf[1024] {0};// 这里不初始化但是计算长度时需要加1字符串结束符\0char recvBuf[1024];int i 0;while(1) {sprintf(recvBuf,data:%d\n,i);// 给客户端发送数据长度需要加1字符串结束符write(fd, recvBuf , strlen(recvBuf) 1);sleep(1);int len read(fd, recvBuf, sizeof(recvBuf));if(len -1) {perror(read);exit(-1);} else if(len 0) {printf(recv server %s\n, recvBuf);} else if(len 0) {// 表示服务器端断开连接printf(server closed...);break;}}// 关闭连接close(fd);return 0;
}多线程版本实现服务端可以处理多个客户端
server_thread.c
// 多线程版本实现服务端可以处理多个客户端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h
#include pthread.h// 封装子线程通信时需要的基本信息
struct sockInfo
{int fd;// 通信的文件描述符pthread_t tid; // 线程号struct sockaddr_in addr;// 客户端信息
};// 支持同时128个客户端连接
struct sockInfo sockinfos[128];void * working(void * arg)
{// 子线程和客户端进行通信 cfd、客户端信息、线程号// 子进程// 获取客户端的信息struct sockInfo * pinfo (struct sockInfo *)arg;char cliIP[16];inet_ntop(AF_INET,pinfo-addr.sin_addr.s_addr,cliIP,sizeof(cliIP));unsigned short cliPort ntohs(pinfo-addr.sin_port);printf(client ip is %s,port is %d\n,cliIP,cliPort);// 接受客户端发来的数据char recvBuf[1024];while (1){int len read(pinfo-fd,recvBuf,sizeof(recvBuf));if(len -1){perror(read);exit(-1);}else if(len 0){printf(recv client %s\n,recvBuf);}else{printf(client closed...\n);break;}write(pinfo-fd,recvBuf,strlen(recvBuf) 1);}close(pinfo-fd);return NULL;
}
int main()
{// 初始化数据int max sizeof(sockinfos) / sizeof(sockinfos[0]);for(int i 0; i max; i){bzero(sockinfos[i],sizeof(sockinfos[i]));// 初始化为 -1 表示当前的文件描述符可用sockinfos[i].fd -1;// 表示当前线程号可用sockinfos[i].tid -1;}// 1、创建 socketint lfd socket(AF_INET,SOCK_STREAM,0);if(lfd -1){perror(socket);exit(-1);}// 2、绑定struct sockaddr_in sact;sact.sin_addr.s_addr INADDR_ANY;// 0.0.0.0sact.sin_family AF_INET;sact.sin_port htons(9999);int ret bind(lfd,(struct sockaddr *)sact,sizeof(sact));if(ret -1){perror(bind);exit(-1);}// 3、监听ret listen(lfd,128);if(ret -1){perror(listen);exit(-1);}// 循环等待客户端连接一旦一个客户端连接进来就创建一个子线程进行通信while (1){struct sockaddr_in cliaddr;int len sizeof(cliaddr);// 接受连接int cfd accept(lfd,(struct sockaddr *)cliaddr, len);struct sockInfo * pinfo;for(int i 0; i max; i){// 如果有当前位置的文件描述符可用就进行赋值// 从这个数组中找到一个可用的sockInfo元素if(sockinfos[i].fd -1){pinfo sockinfos[i];break;}// 如果到最后一个还没有可用资源就睡眠1s然后再重新进入循环判断if(i max - 1){sleep(1);i 0;}}pinfo-fd cfd;memcpy(pinfo-addr,cliaddr,len);pthread_t tid;// 创建子线程pthread_create(pinfo-tid,NULL,working,pinfo);// 设置线程分离pthread_detach(pinfo-tid);}return 0;
}client.c
// TCP通信的客户端#include stdio.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include stdlib.hint main() {// 1.创建套接字int fd socket(AF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);exit(-1);}// 2.连接服务器端struct sockaddr_in serveraddr;serveraddr.sin_family AF_INET;inet_pton(AF_INET, 192.168.182.10, serveraddr.sin_addr.s_addr);serveraddr.sin_port htons(9999);int ret connect(fd, (struct sockaddr *)serveraddr, sizeof(serveraddr));if(ret -1) {perror(connect);exit(-1);}// 3. 通信// char recvBuf[1024] {0};// 这里不初始化但是计算长度时需要加1字符串结束符\0char recvBuf[1024];int i 0;while(1) {sprintf(recvBuf,data:%d\n,i);// 给客户端发送数据长度需要加1字符串结束符write(fd, recvBuf , strlen(recvBuf) 1);int len read(fd, recvBuf, sizeof(recvBuf));if(len -1) {perror(read);exit(-1);} else if(len 0) {printf(recv server %s\n, recvBuf);} else if(len 0) {// 表示服务器端断开连接printf(server closed...);break;}sleep(1);}// 关闭连接close(fd);return 0;
}11、TCP 状态转换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nNwRCaW7-1676445475558)(4-Linux网络编程.assets/image-20221005172908424.png)] 第一次挥手丢失会发生什么 答主动断开方重传FIN请求报文 第二次挥手丢失会发生什么 答由于ACK报文不会重传所以主动断开方会重传FIN报文 第三次挥手丢失会发生什么 答被动断开方会重传FINACK报文 第四次挥手丢失会发生什么 答同第三次挥手 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SQcOrQLq-1676445475558)(4-Linux网络编程.assets/image-20221005172913632.png)]
2MSLMaximum Segment Lifetime
主动断开连接的一方, 最后进出入一个 TIME_WAIT状态, 这个状态会持续: 2msl
msl: 官方建议: 2分钟, 实际是30s
当 TCP 连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK 后连接的主动关闭方必须处于TIME_WAIT 状态并持续 2MSL 时间。
这样就能够让 TCP 连接的主动关闭方在它发送的 ACK 丢失的情况下重新发送最终的 ACK。
主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK它们并不消耗序列号被动关闭方也不会重传而是因为被动关闭方重传了它的 FIN。事实上被动关闭方总是重传 FIN 直到它收到一个最终的 ACK。
半关闭
当 TCP 链接中 A 向 B 发送 FIN 请求关闭另一端 B 回应 ACK 之后A 端进入 FIN_WAIT_2 状态并没有立即发送 FIN 给 AA 方处于半连接状态半开关此时 A 可以接收 B 发送的数据但是 A 已经不能再向 B 发送数据。
从程序的角度可以使用 API 来控制实现半连接状态
#include sys/socket.h
int shutdown(int sockfd, int how);sockfd: 需要关闭的socket的描述符how: 允许为shutdown操作选择以下几种方式:SHUT_RD(0) 关闭sockfd上的读功能此选项将不允许sockfd进行读操作。该套接字不再接收数据任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。SHUT_WR(1): 关闭sockfd的写功能此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。SHUT_RDWR(2):关闭sockfd的读写功能。相当于调用shutdown两次首先是以SHUT_RD,然后以SHUT_WR。使用 close 中止一个连接但它只是减少描述符的引用计数并不直接关闭连接只有当描述符的引用计数为 0 时才关闭连接。shutdown 不考虑描述符的引用计数直接关闭描述符。也可选择中止一个方向的连接只中止读或只中止写。
注意: 1、如果有多个进程共享一个套接字close 每被调用一次计数减 1 直到计数为 0 时也就是所用进程都调用了 close套接字将被释放。 2、在多进程中如果一个进程调用了 shutdown(sfd, SHUT_RDWR) 后其它的进程将无法进行通信。但如果一个进程 close(sfd) 将不会影响到其它进程。
12、端口复用
端口复用最常用的用途是:
防止服务器重启时之前绑定的端口还未释放程序突然退出而系统没有释放端口
#include sys/types.h
#include sys/socket.h
// 设置套接字的属性不仅仅能设置端口复用
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t
optlen);
参数- sockfd : 要操作的文件描述符- level : 级别 - SOL_SOCKET (端口复用的级别)- optname : 选项的名称- SO_REUSEADDR- SO_REUSEPORT- optval : 端口复用的值整形- 1 : 可以复用- 0 : 不可以复用- optlen : optval参数的大小
端口复用设置的时机是在服务器绑定端口之前。
setsockopt();
bind();例子
// 设置端口复用
int optval 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEPORT,optval,sizeof(saddr));常看网络相关信息的命令 netstat 参数
-a 所有的socket-p 显示正在使用socket的程序的名称-n 直接使用IP地址而不通过域名服务器
设置端口复用
server.c
#include stdio.h
#include ctype.h
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.hint main(int argc, char *argv[]) {// 创建socketint lfd socket(PF_INET, SOCK_STREAM, 0);if(lfd -1) {perror(socket);return -1;}struct sockaddr_in saddr;saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY;saddr.sin_port htons(9999);//int optval 1;//setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, optval, sizeof(optval));int optval 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, optval, sizeof(optval));// 设置端口复用int optval 1;setsockopt(lfd,SOL_SOCKET,SO_REUSEPORT,optval,sizeof(saddr));// 绑定int ret bind(lfd, (struct sockaddr *)saddr, sizeof(saddr));if(ret -1) {perror(bind);return -1;}// 监听ret listen(lfd, 8);if(ret -1) {perror(listen);return -1;}// 接收客户端连接struct sockaddr_in cliaddr;socklen_t len sizeof(cliaddr);int cfd accept(lfd, (struct sockaddr *)cliaddr, len);if(cfd -1) {perror(accpet);return -1;}// 获取客户端信息char cliIp[16];inet_ntop(AF_INET, cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));unsigned short cliPort ntohs(cliaddr.sin_port);// 输出客户端的信息printf(clients ip is %s, and port is %d\n, cliIp, cliPort );// 接收客户端发来的数据char recvBuf[1024] {0};while(1) {int len recv(cfd, recvBuf, sizeof(recvBuf), 0);if(len -1) {perror(recv);return -1;} else if(len 0) {printf(客户端已经断开连接...\n);break;} else if(len 0) {printf(read buf %s\n, recvBuf);}// 小写转大写for(int i 0; i len; i) {recvBuf[i] toupper(recvBuf[i]);}printf(after buf %s\n, recvBuf);// 大写字符串发给客户端ret send(cfd, recvBuf, strlen(recvBuf) 1, 0);if(ret -1) {perror(send);return -1;}}close(cfd);close(lfd);return 0;
}client.c
#include stdio.h
#include arpa/inet.h
#include stdlib.h
#include unistd.h
#include string.hint main() {// 创建socketint fd socket(PF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);return -1;}struct sockaddr_in seraddr;inet_pton(AF_INET, 127.0.0.1, seraddr.sin_addr.s_addr);seraddr.sin_family AF_INET;seraddr.sin_port htons(9999);// 连接服务器int ret connect(fd, (struct sockaddr *)seraddr, sizeof(seraddr));if(ret -1){perror(connect);return -1;}while(1) {char sendBuf[1024] {0};fgets(sendBuf, sizeof(sendBuf), stdin);write(fd, sendBuf, strlen(sendBuf) 1);// 接收int len read(fd, sendBuf, sizeof(sendBuf));if(len -1) {perror(read);return -1;}else if(len 0) {printf(read buf %s\n, sendBuf);} else {printf(服务器已经断开连接...\n);break;}}close(fd);return 0;
}八、IO 多路复用
1、I/O多路复用I/O多路转接
I/O 多路复用使得程序能同时监听多个文件描述符能够提高程序的性能Linux 下实现 I/O 多路复用的系统调用主要有 select、poll 和 epoll。
阻塞等待BIO模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wY90uz85-1676445475558)(4-Linux网络编程.assets/image-20221007082119276.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PUECjNY-1676445475559)(4-Linux网络编程.assets/image-20221007082202341.png)]
非阻塞忙轮询
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBe2GR9M-1676445475559)(4-Linux网络编程.assets/image-20221007082225596.png)]
NIO 模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EL1ESIoC-1676445475560)(4-Linux网络编程.assets/image-20221007082253771.png)]
IO多路转接技术
第一种select/poll
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqhgFYBK-1676445475560)(4-Linux网络编程.assets/image-20221007082314972.png)]
第二种epoll
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBQjVKgL-1676445475560)(4-Linux网络编程.assets/image-20221007082328320.png)]
2、select
主旨思想
1、首先要构造一个关于文件描述符的列表将要监听的文件描述符添加到该列表中。 2、调用一个系统函数监听该列表中的文件描述符直到这些描述符中的一个或者多个进行I/O操作时该函数才返回。
这个函数是阻塞函数对文件描述符的检测的操作是由内核完成的
3、在返回时它会告诉进程有多少哪些描述符要进行I/O操作。
// sizeof(fd_set) 128字节 总共是1024位最多可保存1024个文件描述符
#include sys/time.h
#include sys/types.h
#include unistd.h
#include sys/select.h
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);- 参数- nfds : 委托内核检测的最大文件描述符的值 1- readfds : 要检测的文件描述符的读的集合委托内核检测哪些文件描述符的读的属性- 一般检测读操作- 对应的是对方发送过来的数据因为读是被动的接收数据检测的就是读缓冲区- 是一个传入传出参数- writefds : 要检测的文件描述符的写的集合委托内核检测哪些文件描述符的写的属性- 委托内核检测写缓冲区是不是还可以写数据不满的就可以写- exceptfds : 检测发生异常的文件描述符的集合- timeout : 设置的超时时间struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};- NULL : 永久阻塞直到检测到了文件描述符有变化- tv_sec 0 tv_usec 0 不阻塞- tv_sec 0 tv_usec 0 阻塞对应的时间- 返回值 :- -1 : 失败- 0(n) : 检测的集合中有n个文件描述符发生了变化
// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);
// 判断fd对应的标志位是0还是1 返回值 fd对应的标志位的值0返回0 1返回1
int FD_ISSET(int fd, fd_set *set);
// 将参数文件描述符fd 对应的标志位设置为1
void FD_SET(int fd, fd_set *set);
// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);select()工作过程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mxnqfP5R-1676445475561)(4-Linux网络编程.assets/image-20221007084844901.png)]
使用 select I/O 多路复用技术实现通信
select.c
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include arpa/inet.h
#include sys/select.hint main()
{// 1、创建 socketint lfd socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_addr.s_addr INADDR_ANY;saddr.sin_port htons(9999);saddr.sin_family AF_INET;// 2、绑定bind(lfd,(struct sockaddr *)saddr,sizeof(saddr));// 3、监听listen(lfd,8);// 创建一个fd_set的集合存放的是需要检查的文件描述符fd_set rdset,tmp;FD_ZERO(rdset);FD_SET(lfd,rdset);int maxfd lfd;while (1){tmp rdset;// 调用select系统函数让内核检测哪些文件描述符有数据int ret select(maxfd 1,tmp,NULL,NULL,NULL);if(ret -1){perror(select);exit(-1);}else if(ret 0){// 超时返回continue;}else if(ret 0){// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变if(FD_ISSET(lfd, tmp)){// 表示有新的客户端连接进来了struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd,(struct sockaddr *)cliaddr,len); // 获取新连接的客户端的信息char cliIP[16] {0};inet_ntop(AF_INET,cliaddr.sin_addr.s_addr,cliIP,sizeof(cliIP));unsigned short cliPort ntohs(cliaddr.sin_port);// 输出客户端的信息printf(clients ip is %s, and port is %d\n, cliIP, cliPort );// 将新的文件描述符加入到集合中FD_SET(cfd,rdset);// 更新最大的文件描述符maxfd maxfd cfd ? maxfd : cfd;}// 遍历剩余的文件描述符的缓冲区变化for(int i lfd 1; i maxfd; i){if(FD_ISSET(i,tmp)){// 说明这个文件描述符对应的客户端发送来了数据char buf[1024] {0};int len read(i,buf,sizeof(buf));if(len -1){perror(read);exit(-1);}else if(len 0){printf(client closed...\n);close(i);// 将断开连接的客户端的文件描述符从集合中移除FD_CLR(i,rdset);}else if (len 0){printf(server read buf %s\n,buf);// 长度加1是为了将字符串结束符\0的长度加上write(i,buf,strlen(buf) 1);}}}}}close(lfd);return 0;
}client.c
#include stdio.h
#include arpa/inet.h
#include stdlib.h
#include unistd.h
#include string.hint main() {// 创建socketint fd socket(PF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);return -1;}struct sockaddr_in seraddr;inet_pton(AF_INET, 127.0.0.1, seraddr.sin_addr.s_addr);seraddr.sin_family AF_INET;seraddr.sin_port htons(9999);// 连接服务器int ret connect(fd, (struct sockaddr *)seraddr, sizeof(seraddr));if(ret -1){perror(connect);return -1;}int num 0;while(1) {char sendBuf[1024] {0};// fgets(sendBuf, sizeof(sendBuf), stdin);sprintf(sendBuf,send data %d,num);write(fd, sendBuf, strlen(sendBuf) 1);// 接收int len read(fd, sendBuf, sizeof(sendBuf));if(len -1) {perror(read);return -1;}else if(len 0) {printf(read buf %s\n, sendBuf);} else {printf(服务器已经断开连接...\n);break;}sleep(1);}close(fd);return 0;
}select()的缺点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5uk3oNF-1676445475561)(4-Linux网络编程.assets/image-20221007112630024.png)]
缺点 1、每次调用select都需要把fd集合从用户态拷贝到内核态这个开销在fd很多时会很大 2、同时每次调用select都需要在内核遍历传递进来的所有fd这个开销在fd很多时也很大 3、select支持的文件描述符数量太小了默认是1024 4、fds集合不能重用每次都需要重置
3、poll
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QeU0RRwN-1676445475562)(4-Linux网络编程.assets/image-20221007112721668.png)]
#include poll.h
struct pollfd {int fd; /* 委托内核检测的文件描述符 */short events; /* 委托内核检测文件描述符的什么事件 */short revents; /* 文件描述符实际发生的事件 */
};
struct pollfd myfd;
myfd.fd 5;
myfd.events POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);- 参数- fds : 是一个struct pollfd 结构体数组这是一个需要检测的文件描述符的集合- nfds : 这个是第一个参数数组中最后一个有效元素的下标 1- timeout : 阻塞时长0 : 不阻塞-1 : 阻塞当检测到需要检测的文件描述符有变化解除阻塞0 : 阻塞的时长- 返回值-1 : 失败0n : 成功,n表示检测到集合中有n个文件描述符发生变化[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hh1tTdA9-1676445475562)(4-Linux网络编程.assets/image-20221006202402893.png)]
使用 poll I/O 多路复用技术实现通信
poll.c
#include stdio.h
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.h
#include poll.hint main() {// 创建socketint lfd socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in saddr;saddr.sin_port htons(9999);saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY;// 绑定bind(lfd, (struct sockaddr *)saddr, sizeof(saddr));// 监听listen(lfd, 8);// 初始化检测的文件描述符数组struct pollfd fds[1024];for(int i 0; i 1024; i) {fds[i].fd -1;fds[i].events POLLIN;}fds[0].fd lfd;int nfds 0;while(1) {// 调用poll系统函数让内核帮检测哪些文件描述符有数据int ret poll(fds, nfds 1, -1);if(ret -1) {perror(poll);exit(-1);} else if(ret 0) {continue;} else if(ret 0) {// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变if(fds[0].revents POLLIN) {// 表示有新的客户端连接进来了struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd, (struct sockaddr *)cliaddr, len);// 获取新连接的客户端的信息char cliIP[16] {0};inet_ntop(AF_INET,cliaddr.sin_addr.s_addr,cliIP,sizeof(cliIP));unsigned short cliPort ntohs(cliaddr.sin_port);// 输出客户端的信息printf(clients ip is %s, and port is %d\n, cliIP, cliPort );// 将新的文件描述符加入到集合中for(int i 1; i 1024; i) {if(fds[i].fd -1) {fds[i].fd cfd;fds[i].events POLLIN;break;}}// 更新最大的文件描述符的索引nfds nfds cfd ? nfds : cfd;}for(int i 1; i nfds; i) {if(fds[i].revents POLLIN) {// 说明这个文件描述符对应的客户端发来了数据char buf[1024] {0};int len read(fds[i].fd, buf, sizeof(buf));if(len -1) {perror(read);exit(-1);} else if(len 0) {printf(client closed...\n);close(fds[i].fd);fds[i].fd -1;} else if(len 0) {printf(read buf %s\n, buf);write(fds[i].fd, buf, strlen(buf) 1);}}}}}close(lfd);return 0;
}client.c
#include stdio.h
#include arpa/inet.h
#include stdlib.h
#include unistd.h
#include string.hint main() {// 创建socketint fd socket(PF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);return -1;}struct sockaddr_in seraddr;inet_pton(AF_INET, 127.0.0.1, seraddr.sin_addr.s_addr);seraddr.sin_family AF_INET;seraddr.sin_port htons(9999);// 连接服务器int ret connect(fd, (struct sockaddr *)seraddr, sizeof(seraddr));if(ret -1){perror(connect);return -1;}int num 0;while(1) {char sendBuf[1024] {0};// fgets(sendBuf, sizeof(sendBuf), stdin);sprintf(sendBuf,send data %d,num);write(fd, sendBuf, strlen(sendBuf) 1);// 接收int len read(fd, sendBuf, sizeof(sendBuf));if(len -1) {perror(read);return -1;}else if(len 0) {printf(read buf %s\n, sendBuf);} else {printf(服务器已经断开连接...\n);break;}sleep(1);}close(fd);return 0;
}4、epoll
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQuoLsaV-1676445475563)(4-Linux网络编程.assets/image-20221007112741197.png)]
#include sys/epoll.h
// 创建一个新的epoll实例。在内核中创建了一个数据这个数据中有两个比较重要的数据一个是需要检测的文件描述符的信息红黑树还有一个是就绪列表存放检测到数据发送改变的文件描述符信息双向链表。
int epoll_create(int size);- 参数size : 目前没有意义了(底层实现方式发生了变化为了兼容)。随便写一个数必须大于0- 返回值-1 : 失败 0 : 文件描述符操作epoll实例的
typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
};
常见的Epoll检测事件- EPOLLIN- EPOLLOUT- EPOLLERR// 对epoll实例进行管理添加文件描述符信息删除信息修改信息
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);- 参数- epfd : epoll实例对应的文件描述符- op : 要进行什么操作EPOLL_CTL_ADD: 添加EPOLL_CTL_MOD: 修改EPOLL_CTL_DEL: 删除- fd : 要检测的文件描述符- event : 检测文件描述符什么事情
// 检测函数
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);- 参数- epfd : epoll实例对应的文件描述符- events : 传出参数保存了发送了变化的文件描述符的信息- maxevents : 第二个参数结构体数组的大小- timeout : 阻塞时间- 0 : 不阻塞- -1 : 阻塞直到检测到fd数据发生变化解除阻塞- 0 : 阻塞的时长毫秒- 返回值- 成功返回发送变化的文件描述符的个数 0- 失败 -1使用epoll完成通信
#include stdio.h
#include arpa/inet.h
#include stdlib.h
#include unistd.h
#include string.h
#include sys/epoll.hint main()
{// 1、创建socketint lfd socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port htons(9999);saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY;// 2、绑定bind(lfd,(struct sockaddr *)saddr,sizeof(saddr));// 3、监听listen(lfd,8);// 调用 epoll_create() 创建一个 epoll 实例int epfd epoll_create(100);// 将监听的文件描述符相关的检测信息添加到 epoll 实例中struct epoll_event epev;epev.data.fd lfd;epev.events EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,epev);struct epoll_event epevs[1024];while (1){int ret epoll_wait(epfd,epevs,1024,-1);if(ret -1){perror(epoll_wait);exit(-1);}printf(ret %d\n,ret);for(int i 0; i ret; i){if(epevs[i].data.fd lfd){// 监听的文件描述符有数据到达有客户端连接struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd,(struct sockaddr *)cliaddr,len);// 将返回的文件描述符添加到 epoll 实例中epev.data.fd cfd;epev.events EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,epev);}else {// 只针对 EPOLLIN 进行操作if(epevs[i].events EPOLLOUT){continue;}// 除了监听文件描述符其他文件描述符有数据都是代表需要通信char buf[1024] {0};int len read(epevs[i].data.fd,buf,sizeof(buf));if(len -1){perror(read);exit(-1);}else if(len 0){printf(client closed...\n);epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);close(epevs[i].data.fd);exit(-1);}else if(len 0){printf(read buf %s\n, buf);write(epevs[i].data.fd,buf,strlen(buf) 1);}}}}close(lfd);close(epfd);return 0;
}Epoll 的工作模式
LT 模式 水平触发
假设委托内核检测读事件 - 检测fd的读缓冲区 读缓冲区有数据 - epoll检测到了会给用户通知
用户不读数据数据一直在缓冲区epoll 会一直通知用户只读了一部分数据epoll会通知缓冲区的数据读完了不通知 LTlevel - triggered是缺省的工作方式并且同时支持 block 和 no-block socket。在这种做法中内核告诉你一个文件描述符是否就绪了然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作内核还是会继续通知你的。 #include stdio.h
#include arpa/inet.h
#include stdlib.h
#include unistd.h
#include string.h
#include sys/epoll.hint main()
{// 1、创建socketint lfd socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port htons(9999);saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY;// 2、绑定bind(lfd,(struct sockaddr *)saddr,sizeof(saddr));// 3、监听listen(lfd,8);// 调用 epoll_create() 创建一个 epoll 实例int epfd epoll_create(100);// 将监听的文件描述符相关的检测信息添加到 epoll 实例中struct epoll_event epev;epev.data.fd lfd;epev.events EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,epev);struct epoll_event epevs[1024];while (1){int ret epoll_wait(epfd,epevs,1024,-1);if(ret -1){perror(epoll_wait);exit(-1);}printf(ret %d\n,ret);for(int i 0; i ret; i){if(epevs[i].data.fd lfd){// 监听的文件描述符有数据到达有客户端连接struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd,(struct sockaddr *)cliaddr,len);// 将返回的文件描述符添加到 epoll 实例中epev.data.fd cfd;epev.events EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,epev);}else {// 只针对 EPOLLIN 进行操作if(epevs[i].events EPOLLOUT){continue;}// 除了监听文件描述符其他文件描述符有数据都是代表需要通信char buf[5] {0};int len read(epevs[i].data.fd,buf,sizeof(buf));if(len -1){perror(read);exit(-1);}else if(len 0){printf(client closed...\n);epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);close(epevs[i].data.fd);exit(-1);}else if(len 0){printf(read buf %s\n, buf);write(epevs[i].data.fd,buf,strlen(buf) 1);}}}}close(lfd);close(epfd);return 0;
}ET 模式边沿触发
假设委托内核检测读事件 - 检测fd的读缓冲区读缓冲区有数据 - epoll检测到了会给用户通知
用户不读数据数据一致在缓冲区中epoll下次检测的时候就不通知了用户只读了一部分数据epoll不通知缓冲区的数据读完了不通知 ETedge - triggered是高速工作方式只支持 no-block socket。在这种模式下当描述符从未就绪变为就绪时内核通过 epoll 告诉你。然后它会假设你知道文件描述符已经就绪并且不会再为那个文件描述符发送更多的就绪通知直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意如果一直不对这个 fd 作 IO 操作从而导致它再次变成未就绪内核不会发送更多的通知only once。 ET 模式在很大程度上减少了 epoll 事件被重复触发的次数因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候必须使用非阻塞套接口以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。 struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
};
常见的Epoll检测事件- EPOLLIN- EPOLLOUT- EPOLLERR- EPOLLETstruct epoll_event epev;
// 将返回的文件描述符添加到 epoll 实例中
epev.data.fd cfd;
// 设置边沿触发
epev.events EPOLLIN | EPOLLET;#include stdio.h
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/epoll.h
#include fcntl.h
#include errno.hint main() {// 创建socketint lfd socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in saddr;saddr.sin_port htons(9999);saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY;// 绑定bind(lfd, (struct sockaddr *)saddr, sizeof(saddr));// 监听listen(lfd, 8);// 调用epoll_create()创建一个epoll实例int epfd epoll_create(100);// 将监听的文件描述符相关的检测信息添加到epoll实例中struct epoll_event epev;epev.events EPOLLIN;epev.data.fd lfd;epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, epev);struct epoll_event epevs[1024];while(1) {int ret epoll_wait(epfd, epevs, 1024, -1);if(ret -1) {perror(epoll_wait);exit(-1);}printf(ret %d\n, ret);for(int i 0; i ret; i) {int curfd epevs[i].data.fd;if(curfd lfd) {// 监听的文件描述符有数据达到有客户端连接struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd, (struct sockaddr *)cliaddr, len);// 设置cfd属性非阻塞int flag fcntl(cfd, F_GETFL);flag | O_NONBLOCK;fcntl(cfd, F_SETFL, flag);epev.events EPOLLIN | EPOLLET; // 设置边沿触发epev.data.fd cfd;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, epev);} else {if(epevs[i].events EPOLLOUT) {continue;} // 循环读取出所有数据// ET边缘触发模式下epoll 只会通知一次如果一次缓冲区中的内容没有读取完就需要重复读取否则只有等下次有消息时 epoll 通知后再从缓冲区中获取上次的数据// 循环读取出缓冲区的所有数据需要设置 read 为非阻塞需要设置通信的文件描述符为非阻塞fcntl(cfd,F_SETFL,flag)char buf[5];int len 0;while( (len read(curfd, buf, sizeof(buf))) 0) {// 打印数据// printf(recv data : %s\n, buf);write(STDOUT_FILENO, buf, len);write(curfd, buf, len);}if(len 0) {printf(client closed....);}else if(len -1) {if(errno EAGAIN) {printf(data over.....);}else {perror(read);exit(-1);}}// // 除了监听文件描述符其他文件描述符有数据都是代表需要通信// char buf[5] {0};// int len read(epevs[i].data.fd,buf,sizeof(buf));// if(len -1)// {// perror(read);// exit(-1);// }// else if(len 0)// {// printf(client closed...\n);// epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);// close(epevs[i].data.fd);// exit(-1);// }// else if(len 0)// {// printf(read buf %s\n, buf);// write(epevs[i].data.fd,buf,strlen(buf) 1);// }}}}close(lfd);close(epfd);return 0;
}九、UDP 通信、本地套接字
1、UDP
1.1、UDP通信
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B8iUqbMn-1676445475563)(4-Linux网络编程.assets/image-20221007165131392.png)]
#include sys/types.h
#include sys/socket.h
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);- 参数- sockfd : 通信的fd- buf : 要发送的数据- len : 发送数据的长度- flags : 0- dest_addr : 通信的另外一端的地址信息- addrlen : 地址的内存大小
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr,socklen_t *addrlen);- 参数- sockfd : 通信的fd- buf : 接收数据的数组- len : 数组的大小- flags : 0- src_addr : 用来保存另外一端的地址信息不需要可以指定为NULL- addrlen : 地址的内存大小udp_server.c
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include arpa/inet.hint main()
{// 1、创建一个通信的 socketint fd socket(PF_INET,SOCK_DGRAM,0);if(fd -1){perror(socket);exit(-1);}struct sockaddr_in saddr;saddr.sin_addr.s_addr INADDR_ANY;saddr.sin_port htons(9999);saddr.sin_family AF_INET;// 2、绑定int ret bind(fd,(struct sockaddr *)saddr,sizeof(saddr));if(ret -1){perror(bind);exit(-1);}// 3、通信while (1){char recvbuf[128];char clientIP[16];struct sockaddr_in cliaddr;int len sizeof(cliaddr);//接受数据int num recvfrom(fd,recvbuf,sizeof(recvbuf),0,(struct sockaddr *)cliaddr,len);printf(client IP : %s, port : %d\n,inet_ntop(AF_INET,cliaddr.sin_addr.s_addr,clientIP,sizeof(clientIP)),ntohs(cliaddr.sin_port));printf(client say : %s\n,recvbuf);// 发送数据sendto(fd,recvbuf,strlen(recvbuf) 1,0,(struct sockaddr *)cliaddr,len);}close(fd);return 0;
}udp_client.c
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include arpa/inet.hint main()
{// 1、创建一个通信的 socketint fd socket(PF_INET,SOCK_DGRAM,0);if(fd -1){perror(socket);exit(-1);}// 连接的服务端的地址struct sockaddr_in saddr;inet_pton(AF_INET,127.0.0.1,saddr.sin_addr.s_addr);saddr.sin_port htons(9999);saddr.sin_family AF_INET;//2、通信int num 0;while (1){// 发送数据char sendbuf[128];sprintf(sendbuf,hello, I am client %d\n,num);sendto(fd,sendbuf,strlen(sendbuf) 1,0,(struct sockaddr *)saddr,sizeof(saddr));//接受数据int num recvfrom(fd,sendbuf,sizeof(sendbuf),0,NULL,NULL);printf(server say : %s\n,sendbuf);sleep(1);}close(fd);return 0;
}1.2、广播
向子网中多台计算机发送消息并且子网中所有的计算机都可以接收到发送方发送的消息每个广播消息都包含一个特殊的IP地址这个IP中子网内主机标志部分的二进制全部为1。 a.只能在局域网中使用。 b.客户端需要绑定服务器广播使用的端口才可以接收到广播消息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3Ecpjzu-1676445475563)(4-Linux网络编程.assets/image-20221007165239915.png)]
// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t
optlen);- sockfd : 文件描述符- level : SOL_SOCKET- optname : SO_BROADCAST- optval : int类型的值为1表示允许广播- optlen : optval的大小bro_server.c
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include arpa/inet.hint main()
{// 1、创建一个通信的 socketint fd socket(PF_INET,SOCK_DGRAM,0);if(fd -1){perror(socket);exit(-1);}// 2、设置广播属性int op 1;setsockopt(fd,SOL_SOCKET,SO_BROADCAST,op,sizeof(op));// 3、创建一个广播的地址struct sockaddr_in cliaddr;cliaddr.sin_port htons(9999);cliaddr.sin_family AF_INET;inet_pton(AF_INET,192.168.182.255,cliaddr.sin_addr.s_addr);// 3、通信int num 0;while (1){char sendBuf[128];sprintf(sendBuf,hello, client...%d\n,num);// 发送数据sendto(fd,sendBuf,strlen(sendBuf) 1,0,(struct sockaddr *)cliaddr,sizeof(cliaddr));printf(广播的数据%s\n,sendBuf);sleep(1);}close(fd);return 0;
}bro_client.c
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include arpa/inet.hint main()
{// 1、创建一个通信的 socketint fd socket(PF_INET,SOCK_DGRAM,0);if(fd -1){perror(socket);exit(-1);}// 2、客户端绑定本地的IP和端口struct sockaddr_in addr;addr.sin_family AF_INET;addr.sin_port htons(9999);addr.sin_addr.s_addr INADDR_ANY;// 3、绑定int ret bind(fd,(struct sockaddr *)addr,sizeof(addr));if(ret -1){perror(bind);exit(-1);}//2、通信int num 0;while (1){char buf[128];//接受数据int num recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);printf(server say : %s\n,buf); }close(fd);return 0;
}1.3、组播(多播)
单播地址标识单个 IP 接口广播地址标识某个子网的所有 IP 接口多播地址标识一组 IP 接口。
单播和广播是寻址方案的两个极端要么单个要么全部多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收也就是说由运行相应多播会话应用系统的主机上的接口接收。另外广播一般局限于局域网内使用而多播则既可以用于局域网也可以跨广域网使用。 a.组播既可以用于局域网也可以用于广域网 b.客户端需要加入多播组才能接收到多播的数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5OQrP7Fr-1676445475564)(4-Linux网络编程.assets/image-20221007165329140.png)]
组播地址
IP 多播通信必须依赖于 IP 多播地址在 IPv4 中它的范围从 224.0.0.0 到 239.255.255.255 并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类
IP地址说明224.0.0.0~224.0.0.255局部链接多播地址是为路由协议和其它用途保留的地址路由 器并不转发属于此范围的IP包224.0.1.0~224.0.1.255预留多播地址公用组播地址可用于Internet使用前需要申请224.0.2.0~238.255.255.255预留多播地址用户可用组播地址(临时组地址)全网范围内有效239.0.0.0~239.255.255.255本地管理组播地址可供组织内部使用类似于私有 IP 地址不 能用于 Internet可限制多播范围
设置组播
int setsockopt(int sockfd, int level, int optname,const void *optval,socklen_t optlen);// 服务器设置多播的信息外出接口- level : IPPROTO_IP- optname : IP_MULTICAST_IF- optval : struct in_addr// 客户端加入到多播组- level : IPPROTO_IP- optname : IP_ADD_MEMBERSHIP- optval : struct ip_mreqstruct ip_mreq
{/* IP multicast address of group. */struct in_addr imr_multiaddr; // 组播的IP地址/* Local IP address of interface. */struct in_addr imr_interface; // 本地的IP地址
};typedef uint32_t in_addr_t;
struct in_addr
{in_addr_t s_addr;
};multi_server.c
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include arpa/inet.hint main()
{// 1、创建一个通信的 socketint fd socket(PF_INET,SOCK_DGRAM,0);if(fd -1){perror(socket);exit(-1);}// 2、设置多播的属性设置外出接口struct in_addr imr_multiaddr;// 初始化多播地址inet_pton(AF_INET,239.0.0.10,imr_multiaddr);setsockopt(fd,IPPROTO_IP,IP_MULTICAST_IF,imr_multiaddr,sizeof(imr_multiaddr));// 3、初始化客户端信息struct sockaddr_in cliaddr;cliaddr.sin_port htons(9999);cliaddr.sin_family AF_INET;inet_pton(AF_INET,239.0.0.10,cliaddr.sin_addr.s_addr);// 3、通信int num 0;while (1){char sendBuf[128];sprintf(sendBuf,hello, client...%d\n,num);// 发送数据sendto(fd,sendBuf,strlen(sendBuf) 1,0,(struct sockaddr *)cliaddr,sizeof(cliaddr));printf(组播的数据%s\n,sendBuf);sleep(1);}close(fd);return 0;
}multi_client.c
#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include arpa/inet.hint main()
{// 1、创建一个通信的 socketint fd socket(PF_INET,SOCK_DGRAM,0);if(fd -1){perror(socket);exit(-1);}// 2、客户端绑定本地的IP和端口struct sockaddr_in addr;addr.sin_family AF_INET;addr.sin_port htons(9999);addr.sin_addr.s_addr INADDR_ANY;// 3、绑定int ret bind(fd,(struct sockaddr *)addr,sizeof(addr));if(ret -1){perror(bind);exit(-1);}struct ip_mreq op;// 设置组播的IP地址inet_pton(AF_INET,239.0.0.10,op.imr_multiaddr.s_addr);// 设置本地IP地址op.imr_interface.s_addr INADDR_ANY;// 加入到多播组setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,op,sizeof(op));//2、通信int num 0;while (1){char buf[128];//接受数据int num recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);printf(server say : %s\n,buf); }close(fd);return 0;
}2、本地套接字
本地套接字的作用本地的进程间通信 有关系的进程间的通信 没有关系的进程间的通信 本地套接字实现流程和网络套接字类似一般呢采用TCP的通信流程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AA366VYi-1676445475564)(4-Linux网络编程.assets/image-20221007165502755.png)]
// 本地套接字通信的流程 - tcp
// 服务器端
1. 创建监听的套接字int lfd socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的套接字文件 - server端struct sockaddr_un addr;// 绑定成功之后指定的sun_path中的套接字文件会自动生成。bind(lfd, addr, len);
3. 监听listen(lfd, 100);
4. 等待并接受连接请求struct sockaddr_un cliaddr;int cfd accept(lfd, cliaddr, len);
5. 通信接收数据read/recv发送数据write/send
6. 关闭连接close();// 客户端的流程
1. 创建通信的套接字int fd socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的IP 端口struct sockaddr_un addr;// 绑定成功之后指定的sun_path中的套接字文件会自动生成。bind(lfd, addr, len);
3. 连接服务器struct sockaddr_un serveraddr;connect(fd, serveraddr, sizeof(serveraddr));
4. 通信接收数据read/recv发送数据write/send
5. 关闭连接close();// 头文件: sys/un.h
#define UNIX_PATH_MAX 108
struct sockaddr_un {sa_family_t sun_family; // 地址族协议 af_localchar sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远0
};[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wAq528qM-1676445475565)(4-Linux网络编程.assets/image-20221007201551950.png)]
ipc_server.c
#include stdio.h
#include string.h
#include unistd.h
#include stdlib.h
#include arpa/inet.h
#include sys/un.hint main() {unlink(server.sock);// 1.创建监听的套接字int lfd socket(AF_LOCAL, SOCK_STREAM, 0);if(lfd -1) {perror(socket);exit(-1);}// 2.绑定本地套接字文件struct sockaddr_un addr;addr.sun_family AF_LOCAL;strcpy(addr.sun_path, server.sock);int ret bind(lfd, (struct sockaddr *)addr, sizeof(addr));if(ret -1) {perror(bind);exit(-1);}// 3.监听ret listen(lfd, 100);if(ret -1) {perror(listen);exit(-1);}// 4.等待客户端连接struct sockaddr_un cliaddr;int len sizeof(cliaddr);int cfd accept(lfd, (struct sockaddr *)cliaddr, len);if(cfd -1) {perror(accept);exit(-1);}printf(client socket filename: %s\n, cliaddr.sun_path);// 5.通信while(1) {char buf[128];int len recv(cfd, buf, sizeof(buf), 0);if(len -1) {perror(recv);exit(-1);} else if(len 0) {printf(client closed....\n);break;} else if(len 0) {printf(client say : %s\n, buf);send(cfd, buf, len, 0);}}close(cfd);close(lfd);return 0;
}ipc_client.c
#include stdio.h
#include string.h
#include unistd.h
#include stdlib.h
#include arpa/inet.h
#include sys/un.hint main() {unlink(client.sock);// 1.创建套接字int cfd socket(AF_LOCAL, SOCK_STREAM, 0);if(cfd -1) {perror(socket);exit(-1);}// 2.绑定本地套接字文件struct sockaddr_un addr;addr.sun_family AF_LOCAL;strcpy(addr.sun_path, client.sock);int ret bind(cfd, (struct sockaddr *)addr, sizeof(addr));if(ret -1) {perror(bind);exit(-1);}// 3.连接服务器struct sockaddr_un seraddr;seraddr.sun_family AF_LOCAL;strcpy(seraddr.sun_path, server.sock);ret connect(cfd, (struct sockaddr *)seraddr, sizeof(seraddr));if(ret -1) {perror(connect);exit(-1);}// 4.通信int num 0;while(1) {// 发送数据char buf[128];sprintf(buf, hello, i am client %d\n, num);send(cfd, buf, strlen(buf) 1, 0);printf(client say : %s\n, buf);// 接收数据int len recv(cfd, buf, sizeof(buf), 0);if(len -1) {perror(recv);exit(-1);} else if(len 0) {printf(server closed....\n);break;} else if(len 0) {printf(server say : %s\n, buf);}sleep(1);}close(cfd);return 0;
}
文章转载自: http://www.morning.wyrkp.cn.gov.cn.wyrkp.cn http://www.morning.kflzy.cn.gov.cn.kflzy.cn http://www.morning.ktskc.cn.gov.cn.ktskc.cn http://www.morning.jcpq.cn.gov.cn.jcpq.cn http://www.morning.gwjqq.cn.gov.cn.gwjqq.cn http://www.morning.pbzgj.cn.gov.cn.pbzgj.cn http://www.morning.sdhmn.cn.gov.cn.sdhmn.cn http://www.morning.lmqfq.cn.gov.cn.lmqfq.cn http://www.morning.bpmfz.cn.gov.cn.bpmfz.cn http://www.morning.cmqrg.cn.gov.cn.cmqrg.cn http://www.morning.hhboyus.cn.gov.cn.hhboyus.cn http://www.morning.lphtm.cn.gov.cn.lphtm.cn http://www.morning.tlbdy.cn.gov.cn.tlbdy.cn http://www.morning.trjdr.cn.gov.cn.trjdr.cn http://www.morning.lprfk.cn.gov.cn.lprfk.cn http://www.morning.bmsqq.cn.gov.cn.bmsqq.cn http://www.morning.hwlmy.cn.gov.cn.hwlmy.cn http://www.morning.hdwjb.cn.gov.cn.hdwjb.cn http://www.morning.lkgqb.cn.gov.cn.lkgqb.cn http://www.morning.zhengdaotang.cn.gov.cn.zhengdaotang.cn http://www.morning.ntzbr.cn.gov.cn.ntzbr.cn http://www.morning.zkpwk.cn.gov.cn.zkpwk.cn http://www.morning.tjpmf.cn.gov.cn.tjpmf.cn http://www.morning.qjrjs.cn.gov.cn.qjrjs.cn http://www.morning.wqkfm.cn.gov.cn.wqkfm.cn http://www.morning.xcnwf.cn.gov.cn.xcnwf.cn http://www.morning.xzqzd.cn.gov.cn.xzqzd.cn http://www.morning.tbplf.cn.gov.cn.tbplf.cn http://www.morning.zymgs.cn.gov.cn.zymgs.cn http://www.morning.gxcym.cn.gov.cn.gxcym.cn http://www.morning.rwyw.cn.gov.cn.rwyw.cn http://www.morning.mhcys.cn.gov.cn.mhcys.cn http://www.morning.qcfcz.cn.gov.cn.qcfcz.cn http://www.morning.jfsbs.cn.gov.cn.jfsbs.cn http://www.morning.c7622.cn.gov.cn.c7622.cn http://www.morning.trsfm.cn.gov.cn.trsfm.cn http://www.morning.wyzby.cn.gov.cn.wyzby.cn http://www.morning.xjqhh.cn.gov.cn.xjqhh.cn http://www.morning.brbmf.cn.gov.cn.brbmf.cn http://www.morning.nyfyq.cn.gov.cn.nyfyq.cn http://www.morning.lggng.cn.gov.cn.lggng.cn http://www.morning.gwqcr.cn.gov.cn.gwqcr.cn http://www.morning.bxqpl.cn.gov.cn.bxqpl.cn http://www.morning.pskjm.cn.gov.cn.pskjm.cn http://www.morning.rqbr.cn.gov.cn.rqbr.cn http://www.morning.wqcz.cn.gov.cn.wqcz.cn http://www.morning.pngph.cn.gov.cn.pngph.cn http://www.morning.rlbc.cn.gov.cn.rlbc.cn http://www.morning.yxyyp.cn.gov.cn.yxyyp.cn http://www.morning.hnkkf.cn.gov.cn.hnkkf.cn http://www.morning.rbgwj.cn.gov.cn.rbgwj.cn http://www.morning.gmgyt.cn.gov.cn.gmgyt.cn http://www.morning.kxrld.cn.gov.cn.kxrld.cn http://www.morning.sqmbb.cn.gov.cn.sqmbb.cn http://www.morning.tpps.cn.gov.cn.tpps.cn http://www.morning.lsnnc.cn.gov.cn.lsnnc.cn http://www.morning.hyxwh.cn.gov.cn.hyxwh.cn http://www.morning.hghhy.cn.gov.cn.hghhy.cn http://www.morning.dxrbp.cn.gov.cn.dxrbp.cn http://www.morning.kqpq.cn.gov.cn.kqpq.cn http://www.morning.crxdn.cn.gov.cn.crxdn.cn http://www.morning.yzfrh.cn.gov.cn.yzfrh.cn http://www.morning.hydkd.cn.gov.cn.hydkd.cn http://www.morning.spfh.cn.gov.cn.spfh.cn http://www.morning.przc.cn.gov.cn.przc.cn http://www.morning.mtsck.cn.gov.cn.mtsck.cn http://www.morning.mtrfz.cn.gov.cn.mtrfz.cn http://www.morning.qgjp.cn.gov.cn.qgjp.cn http://www.morning.wphzr.cn.gov.cn.wphzr.cn http://www.morning.xbbrh.cn.gov.cn.xbbrh.cn http://www.morning.mdwb.cn.gov.cn.mdwb.cn http://www.morning.yktr.cn.gov.cn.yktr.cn http://www.morning.nnykz.cn.gov.cn.nnykz.cn http://www.morning.btgxf.cn.gov.cn.btgxf.cn http://www.morning.wdshp.cn.gov.cn.wdshp.cn http://www.morning.tbrnl.cn.gov.cn.tbrnl.cn http://www.morning.kltmt.cn.gov.cn.kltmt.cn http://www.morning.dqwkm.cn.gov.cn.dqwkm.cn http://www.morning.wnmdt.cn.gov.cn.wnmdt.cn http://www.morning.dqpd.cn.gov.cn.dqpd.cn