Linux网络编程

目录

一.前置知识

1.认识协议、报文、封装、解包、局域网、ip地址、mac地址

2.认识OSI五层模型

二.宏观层面去看待网络协议栈和OS之间的关联

1.明确网络协议栈和OS之间的关联

2.在通信双方主机的OSI中报文的流动循迹

三.OSI各层协议的源码级理解

socket网络套接字及其编程接口

1.创建套接字

a.套接字编程的种类

b.端口号

2.绑定套接字

网络字节序、主机字节序

库函数——完成网络字节序和主机字节序间转换

*大小端存储(题外话)

3.监听套接字

4.服务器阻塞式接收请求连接

5.客户端向服务器发送建立连接请求

6.关于struct sockaddr、struct sockaddr_in、struct sockaddr_un

四.文章小结


一.前置知识

1.认识协议、报文、封装、解包、局域网、ip地址、mac地址

①什么是协议??
---- 在计算机当中,协议就是双方约定好了的结构体对象。
②什么是报文??
---- 本质就是一个结构体,这个结构体内有诸多描述其自身的“属性字段”,即报头,报文=报头 + 有效载荷。
③至于封装、解包
---- 它们本质就是填充或读取结构体中的报头字段,通过分析这些属性信息,来判断将有效载荷交付给上层的哪个协议。
④啥是局域网??
---- 听名字就知道,它是指一个辐射范围较小的网络区域,在这个区域内的主机,其ip地址都是私有地址,局域网的存在,本质是因为公有ip地址有限,运营商不能给每家每户的每台设备都分配一个公有ip,所以私有ip、局域网这个概念应势而生。
ip地址
---- 在跨网络数据传输的过程中,由于经过的局域网不同,mac地址会不断改变,但IP地址不会改变,它可以协助我们进行路径选择,其本质是一个4字节32位的整数(uint32_t)。
mac地址
---- 保证在同一个局域网内,源mac地址和目的mac地址能被物理层的驱动程序识别,使数据能够从一个主机发送到另一个主机。

2.认识OSI五层模型

OSI五层模型 —— 网络分层实现各层协议间的高内聚、低耦合
a.应用层(***),用户可以在该层确定需要发送的报文,并对报文进行序列化、反序列化式封装、解包,这是通信双方所交互数据的“起始点”和“终止点”。   
b.传输层(***),在通信时,保证数据交互的“可靠性”,并通过目的端口(port),来确定报文最终发送给目标主机中的哪一个“目标进程”。                              
c.网络层(***),确定目标主机的“位置”,并决定将报文交付给上层的“哪一个协议”。                                                                                                 
d.数据链路层(**),实现数据帧级别的报文传输,通俗来讲,就是实现两个主机间的报文交互。                                                                                          
e.物理层(*),最底层,也是真正的报文传输,该层不做深入了解。                                                                                                                                 

二.宏观层面去看待网络协议栈和OS之间的关联

1.明确网络协议栈和OS之间的关联

用户层面对的是 上层用户传输层和网络层对应的是 操作系统内核层面的源码,而 数据链路层物理层则针对主机的 底层驱动和电子设备
两台主机进行跨网络通信时,报文的传输路径(忽略中间设备,如:路由器、交换机等),如下图:

2.在通信双方主机的OSI中报文的流动循迹

数据包的流动:
    用户写入数据资源(有效载荷),传递给应用层——>为保证接收方应用层能读懂这份资源,用户层要在这份资源中,添加报头——>同理,传输层添加报头——>网络层添加报头(包括源IP和目的IP)——>以太网封装报头,通过mac地址找到目标路由器——>路由器解包以太网封装的报头,拿到目的IP地址,重新再封装一个令牌环报头——>找到下一个路由器或目标主机——>层层解包,目标用户拿到发送者发送的数据资源。
对上图进行总结(重点):
1.任一层的协议,都要提供一种能力,将报头和有效载荷分离的能力。
2.任一层的协议,都要在报头中提供,决定将自己的有效载荷交付给上层的哪个协议的能力。
以太网通信的前提:局域网内的每台主机,都要一个能够标识自身唯一性的东西,即mac地址。
IP协议屏蔽了底层网络的差异化,靠的就是工作在IP层的路由器!
上述所将,只是十分粗略的阐述的报文在通信双方主机的OSI间的流动,想要了解各协议层的具体功能,以及想要知道 跨网络通信源码级数据传输,就得提前认识一个东西,即socket。

三.OSI各层协议的源码级理解

socket网络套接字及其编程接口

--- 简单来讲,套接字就是两台主机进行跨网络通信的桥梁。通信双方所交互的数据一定会经由一个网卡,在Linux系统下,一切皆文件,网卡也可以看做文件,而socket就是这个文件的入口!

1.创建套接字

int socket(int domain,int type, int protocol); 
*第一个参数 domain:套接字的域,如:AF_UNIX,用于本地通信;AF_INET,用于网络通信
*第二个参数 type:表示套接字的类型,如:SOCK_STREAM,流式套接字;SOCK_DGRAM,数据报套接字
第三个参数 protocol:协议类型,填0就行
返回值:成功,返回文件描述符;失败,返回-1,并且错误码被设置
所以说,创建一个套接字本质是打开一个文件,文件的struct file在底层指向的是网卡。
示例:
a.套接字编程的种类
①域间套接字编程(AF_UNIX)
--- 用在同一个机器内(本地通信),以文件路径的方式,标记一份公共资源,然后以套接字的方式让我们双方通信。
②原始套接字编程
--- 通常用来编写一些网络工具(绕过传输层甚至网络层,使用底层接口)
*③网络套接字编程(AF_INET)
--- 使用传输层,进行不同主机间的跨网络通信。
套接字创建完了,下一步,绑定套接字,但在此之前,我们还需要认识另一个东西,端口号port。
b.端口号
--- 本质是一个 2字节16位的整数(uint16_t)
端口号有啥用??
--- 用来标识一个进程,通常在传输层使用,它可以告诉操作系统,将有效载荷交付给上层的哪个进程;
进程pid就能标识进程的唯一性,为啥还要弄出来个端口号??
a.进程pid每次运行生成的 值都是变化的,用进程pid来进行网络通信的话, 会带来诸多麻烦
b.用端口号进行网络通信,可以 避免pid在进程和网络通信间出现高耦合情况,即避免进程pid的修整影响到网络通信。
OK,认识完端口号,咱们开始绑定套接字。

2.绑定套接字

在写套接字的时候,一般要把我们的 主机IP地址和端口号这样的套接字信息,通过系统调用接口,和我们打开的 网络套接字进行绑定,这就要用到bind。
int bind(int socket,const struct sockaddr* address, socklen_t address_len);
第一个参数,socket:表示要对哪一个套接字进行bind操作。
第二个参数,address:绑定ip和端口号
第三个三数,address_len:表示address的长度
示例:
网络字节序、主机字节序
--- 由于主机字节序不统一,或为大端存储,或为小端存储,所以不同主机间进行数据交互可能出现乱码,故需要对发送到网络中的数据进行统一要求,这就是网络字节序的由来。
网络字节序是这样要求的:发送主机将发送缓冲区中的数据按 内存地址从低到高的顺序发出;接收主机把从网络上接收到的字节数据按内存地址 从低到高的顺序保存保存在接收缓冲区中。
因此,网络数据流的地址应该这样的:先发出的数据是低地址,后发出的数据是高地址。
TCP/IP协议规定:网络数据流应采用大端字节序,即低地址处存放高字节数据
不管这台主机是大端机还是小端机,都会按照这个TCP/IP协议规定的网络字节序来发送、接受数据
如果当前发送主机是小端,就需要先将数据转成大端,否则就忽略,直接发送即可。
库函数——完成网络字节序和主机字节序间转换
#include<arpa/inet.h>
uint32_t  htonl(uint32_t  hostlong);    —— 对长整型数据,主机字节序转网络字节序
uint16_t  htons(uint16_t  hostshort);  —— 对短整型数据,主机字节序转网络字节序
uint16_t  ntohl(uint32_t  netlong);      ——对长整型数据,网络字节序转主机字节序
uint16_t  ntohs(uint16_t  netshort);    ——对短整型数据,网络字节序转主机字节序
记忆方法:h表示host(主机),n表示network(网络),l表示32位长整数,s表示16位短整数
*大小端存储(题外话)
以一个整型数据为例,其大小是4个字节,在 内存中要保存的话,就得 占据4个字节大小的空间,而这4个字节的空间里, 每个字节之间都有高地址、低地址这样的概念,即 内存中的存储空间有高地址和低地址之分。而但对这个整型数据而言,如:0x11223344,每个字节都处于这个 数据的不同全职位上,并且 全职位也有高权职位、低权职位之分,所以,在内存中,我们把“ 全职位较高字节的数据存储在 内存中较低的地址处”称为 大端存储,将“ 全职位较低字节的数据存储在 内存中较低的地址处”称为 小端存储。
示例:
OK,回归正题,绑定完套接字之后,对TCP服务器来说,需要将套接字置为监听状态,即listen。

3.监听套接字

int listen(int socket, int backlog);//开始监听socket(TCP,服务器)
第一个参数,socket:表示想要置为监听状态的目标套接字
第二个参数,backlog:表示TCP协议中, 全链接队列的最大长度-1。(这里讲不清它的意义,会在后续的文章中讲到它)
示例:

4.服务器阻塞式接收请求连接

int accept(int socket, struct sockaddr* address, socklen_t* address_len);
第一个参数,socket:一般是监听套接字的fd,即文件描述符。
第二个参数,address:输出型参数,存放客户端的主机信息,如ip地址,端口号。
第三个参数,address_len:表示address的长度。
示例:

5.客户端向服务器发送建立连接请求

int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
第一个参数,socket:客户端套接字的fd,即文件描述符。
第二个参数,address:输出型参数,存放服务端的主机信息,如ip地址,端口号。
第三个参数,address_len:表示address的长度。
示例:

6.关于struct sockaddr、struct sockaddr_in、struct sockaddr_un

像bind、connect、accept这样的函数,都有sockaddr*类型的参数,而我们所创建出来的套接字要么是struct sockaddr_in(网络套接字)类型,要么是struct sockaddr_un(域间套接字)类型,为了能让同一个函数的同一个参数能够接收两个不同类型的参数,我们引入struct sockaddr类型,通俗来讲,struct sockaddr是一个通用类型,通过指针强转来实现这两种类型数据的传递,是C式风格的多态。

四.文章小结

本文主要讲了一些学习网络协议的前置知识,包括有关协议的部分专用名称、宏观层面去看待网络协议栈、了解socket网络套接字,学习完本章的知识点,博主会在下一篇文章中以代码的形式“手撕UDP协议和TCP协议”,并完成两台主机间跨网络通信,帮助大家深度理解UDP、TCP的底层实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雨中豪杰ˇ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值