【Linux】网络基础与socket编程基础

一.网络发展

计算机的出现是在网络之前的。而网络产生之初就是为了解决局部计算机无法交互的问题。所以,网络在诞生之初,最先出现的就是我们的局域网LAN,用来结局局部多台计算机的通信问题。

而随着时间的推移,局域网已经不能满足我们的通信要求,于是就诞生出了广域网WAN,广域网可以将远隔千里之外的计算机连接起来。

计算机是人类的工具,而人类之间需要进行交互合作,所以这就要求计算机需要进行交互合作,也就说,网络的产生是必然的。

二.网络协议

网络的存在是为了将计算机联通起来,而计算机可能来自不同的计算机厂商,导致计算机可能有差异,所以计算机进行网络通信时就会产生出入。所以,为了解决计算机因硬件不同导致的通信障碍,就有了网络协议。

而最著名的就是ISO组织指定的OSI网络协议。OSI有7层协议,且协议之间分层。

在网络角度,OSI七层协议是非常完美的,只不过会话、表示、应用三层无法直接融入操作操作系统,所以在具体实现的时候,就将其压缩为了一层,变为5层协议。

三.TCP/IP五层协议

OSI只是给出标准,并没有具体实现。而在TCP/IP协议中,就是将最上面3层合并为了一层。

1.为什么要有tcp/ip协议?

当我们在进行本机通信的时候,我们可以将一个计算机理解为一个网络结构,计算机在运行的时候,其实就是cpu、内存、磁盘等设备直接在进行交互合作,这不过它们是通过线连接起来的。

而网络通信,就是五湖四海的计算机连接起来。这些计算机就好比我们的cpu、内存、磁盘...而网络通信与本机通信的区别就在于这个线变长了。所以这就会导致一系列问题:

  • 如何处理数据
  • 数据丢失怎么办
  • 怎么定位目标主机位置
  • 怎么解决当下我要去那里呢

所以,为了解决这上面的问题,就有了TCP/IP协议。

2.TCP/IP协议与操作系统的关系

在网络通信过程中,有可能你是Linux系统,而我们Windows系统,我们之间要进行通信,这就要求我们的网络协议栈是一致的。也就是说该协议的实现是一致的。这样才能保证不同操作系统之间的通信问题。

在传输层最出名的就是tcp协议,而网络层最出名的就是ip协议。而这两层是内置在操作系统内部的。它们整个网络协议栈的核心,所以将该网络协议栈叫做tcp/ip协议。

网络协议栈的前四层,分别用来解决刚才网络通信的4个问题:

  • 应用层用来解决如何处理数据的问题
  • 传输层解决数据丢失的问题
  • 网络层解决定位目标主机位置的问题
  • 数据链路层解决当下要去那里的问题

3.究竟什么是协议

协议其实就是一个结构体!!! 在两个主机在进行通信的时候,其实就是A主机填充结构化数据,然后通过网络协议栈发送到网络,再通过网络发送到B主机的网卡。B主机在从底向上将该结构化数据拿出来,因为该结构体是一个协议,所以AB主机都能认识,B主机就可以拿到A主机发送的结构化数据。

因为协议是分层的,所以每一层都有协议,同层之间可以认识对方的协议。

四.网络传输基本流程

1.局域网通信

0x1.mac地址

局域网中,可能会存在多个主机,我们需要确定我们的消息是发送给哪一个主机的。所以我们需要有一个东西来标识一个局域网中唯一的主机。这个东西就是mac地址,而mac地址其实是在网卡上的,mac地址一般在全球都是唯一的。

0x2.局域网通信标准

以太网、无线LAN,令牌环网

0x3.以太网进行网络通信

为了保证数据能够发给指定的主机,所以在以太网中发送数据的时候要包含对方主机的mac地址。

  • 在以太网中,任意时刻,只允许一台机器向网络中发送数据
  • 如果有多台同时发送,会发生数据干扰,我们称之为数据碰撞
  • 所有发送数据的主机都要进行碰撞检测和碰撞避免
  • 没有交换机的情况下,一个以太网,就是一个碰撞域
  • 局域网通信的过程中,一个主机发送的报文所有的主机都可以收到,主机对收到的报文确认是否是发送给自己的,通过目标mac地址判定,如果是则向上传递,反之丢弃

局域网中,所有的数据都要经过网络,所有对于以太网来说,他其实就是一个临界资源。

两个主机之间的通信,其实就是两个协议栈在通信!!因为网络通信中,数据只能通过网卡发送到网络中,而用户发送的数据必须得经过整个协议栈才能达到网卡,发送网络中。而主机B从网络中接收的数据,不是接收到就结束了,目的是为了处理数据。所以,网卡在拿到数据之后,需要按照自底向上的顺序将数据传给用户。

对于网络协议栈来说,每一层都有对应的协议,我们在发送数据的时候,要按照自顶向下的顺序对数据进行封装。对于接收端来说,网卡收到数据之后,需要自底向上对数据进行解包和分用。

对于发送的报文来说,每一层都要对报文添加本层的报头,也就是协议。在任意一层,添加的报头叫做协议报头,剩下的部分都是有效载荷。

对于接收端来说,拿到报文之后,首先要对报文进行解包,即将本层的协议包头与有效载荷分离,然后在进行分用,即将有效载荷传递给上层协议。

因为同层之间有相同的协议,所以是可以根据协议报头进行解包和分用的。在整个通信过程来看,可以认为是同层之间在进行通信。

细节1:为什么在发送报文的时候要进行自顶向下进行封装?

发送数据到网络中,只能通过网卡发送,并且OS系统是网卡的管理者,所以发送数据必须经过操作系统,而贯穿了操作系统也就意味着贯穿了整个协议栈。之所以要进行封装是因为,添加了协议报头,在接受端的每一层都能够识别和正确接收数据。
细节2:不考虑应用层协议,所以的协议的报头必须具有能和有效载荷分离的能力。报头中必须包含如何将自己的有效载荷交给上层的某个具体协议。(同层之间可能有多个协议,所以在传递时要传递给指定的协议)
细节3:底层收到报文,发现不是发给我的,在数据链路层直接对报文进行丢弃。

网卡有一种混杂模式,开启之后,底层就会将所有收到的数据都进行解包和分用,不管数据是不是发送给我的。

为什么要将不同层的网络协议称为网络协议栈?

发送数据的过程要进行封装,而封装的过程其实就是进行压栈的过程,而接收数据,对数据进行解包和分用其实就是弹栈的过程。

2.跨网络通信

0x1.ip地址

ip地址是在全球范围内,用来标识唯一的一台主机的。但mac地址不也是标识唯一主机的么?两这有什么区别么?

简单理解:ip地址是长期目标,mac地址是短期目标。

在跨网络通信的时候,我们要发送数据给其他主机时,发送的数据中要包含自己的ip地址和对方主机的ip地址。但两个主机并不在同一个局域网中,所以无法直接发送。所以,我们发送的消息还包含下一个要去的位置的mac地址。比如是一个路由器。由该路由器,在发送到目标主机。

总结:源id,目的ip---从哪里来,到哪里去

源mac,目的mac---上一次从哪里来,接下来要到那里去。

ip分为ipv4和ipv6,ipv4地址是一个4字节的整数,32位;ipv6地址是16字节,128位。已经有了ipv4,为什么还要由ipv6呢?

ipv6其实是为了用来解决ipv4地址不足的问题。但目前ipv6并没有普遍,当前使用最多的是公网内网来解决ipv4地址不足的问题。

0x2.跨网络通信的过程

我们现在的广域网其实是在局域网的基础上建立起来的。我们在跨网络通信的时候,有可能两台主机位于不同类型的局域网中,而我们在数据传递的时候,就要借助路由器来连接两个局域网。

跨网络传输中,数据会发送给当前主机所在局域网中的所有主机,但没有目标主机的ip地址,所以此时主机就会将目标mac地址设置为路由器,由该路由器拿着源ip地址和目标ip地址,在该路由器连接的另一片局域网中查找,如果找到了,就将源mac地址设为自己的mac地址,目标mac地址设为目标主机的mac地址。这样就完成了一次跨网络通信。 再来结合封装,解包和分用来体会跨网络传输的过程。可以看到,ip地址在数据传递的时候是一直保持不变的,mac地址一直在发生变化。mac地址只在本局域网内有效。

所以,我们在跨网络通信其实就是一直在爬楼梯。

五.socket编程

在系统层面,进程是用户运行起来的程序,本质上是为了帮助人去完成某种任务的。所以,在系统层面,只要进程拿到了数据,本质上就是人拿到了数据。

而在网络部分,将数据发送到主机并不是目的,最终的目的是要对这些数据进行处理。而处理数据是用户进行处理的,而进程是人类的代表,所以最终就要将这些数据交给进程来处理。

总结:网络通信的本质,其实就是两个不同主机上的进程在进行数据交互,在本质,其实就是进程间通信。而进程间通信的前提条件是人不同的进程看到同一份资源,而在网络通信中,这同一份资源就是网络!!!

上网,只有两种行为:

  • 从远端服务器获取数据到本地
  • 将自己本地的数据推送到远端服务器

而这两种行为的本质其实就是在进程IO,将数据从内存->网卡->网络,反之也一样。

1.端口号port

在一台主机上,同时会运行着多个进程,我们在发送数据时,需要将数据交给全网唯一的一台主机上的唯一的一个进程,这样对端主机才能够处理数据。

所以,端口号就是用来标识主机上唯一的一个进程的。

  • 端口号是一个2字节的16位的整数;
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
  • IP地址+端口号能够标识网络中唯一的一台主机以及该主机上唯一的一个进程;
  • 一个端口号只能被一个进程占用。

而端口号底层的实现原理可以认为是:在传输层协议中有一张hash表,存储的是端口号与对应进程的task_struct的对应关系。当收到报文进行解包和分用时,到了传输层,此时的传输层报头就是端口号,有效载荷就是对应的数据。这样就可以找到目标进程了。

端口号port VS pid,为什么不直接使用pid来标识唯一的进程的?

首先,不是所有的进程都要进行网络通信,如果以pid来标识,那么底层的hash表就会非常大,查询起来效率就变低了。

其次,pid是一个系统的概念,他只在本机有效。而且进程可能通过绑定多个不同的端口号,以提供不同的服务。使用pid的话,pid到进程,进程到pid都只有一条路。

最后,如果pid发生了改变,那么网络也得做出相应的改变,所以有了端口号,就让网络与系统解耦了。

端口号范围划分

  • 0-1023:知名端口号,这些端口号不允许被用户绑定。 HTTP,FTP,SSH等这些广为使用的应用层协议,它们使用的端口号都是固定的。
  • 1024-65535:操作系统动态分配的端口号。客户端程序的端口号,就是由操作系统从这个范围内分配的。

2.socket

ip是用来标识全网唯一的一台主机,端口号是用来标识一台主机上唯一的一个进程。所以我们使用ip+port就可以标识全网内唯一的一台主机上的唯一一个进程。

而socket = ip + port。我们将socket叫做套接字。

3.传输层典型协议代表

传输层网络协议是定义在内核中的,而操作系统内核是不允许我们普通用户访问的。所以我们要进行网络通信,就得使用操作系统提供的传输层的系统调用接口。

传输层最典型的代表就是TCP(传输控制协议)和UDP(用户数据报协议)协议。

0x1.TCP协议

  • 面向连接
  • 可靠性
  • 面向字节流

0x2.UDP协议

  • 无连接
  • 不可靠
  • 面向数据报

我们要理解,这里的可靠和不可靠并不是优缺点,而是协议的特点。

4.网络字节序

计算机有大端和小端之分,低位存放在低地址处,我们称之为小端,大端与之相反。

在网络通信过程中,通信的双方的机器可能不同。数据在机器中的存储方式不同,如果小端机器直接通过网络将数据发送出去,那么对端的大端机器读到的数据就是反着的。

所以,为了避免在网络通信中,因为大小端而导致的差异,TCP/IP协议规定,发送到网络中的数据必须是大端。如果是大端机器,则不用任何操作,直接收发即可;如果是小端,则在发送时要将小端转化为大端,在接受时,要将大端转化为小端。

对于下列接口:h表示host,即本机序列,n表示net,即网络序列。所以我们可以通过这些接口实现,本机序列与网络序列的互相转换。如果本机本来就是大端,则该函数不做任何操作。

cpp 复制代码
#include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

l表示长整型,s表示短整型。

5.socket接口

cpp 复制代码
#include <sys/types.h>          
#include <sys/socket.h>

       // 创建套接字 tcp/udp clien/server
       int socket(int domain, int type, int protocol);
        
       // 绑定套接字信息 tcp/udp client/server
       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

       // 开始监听 tcp server
       int listen(int sockfd, int backlog);

       // 接收请求 tcp server
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

       // 创建连接 tcp client
       int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockaddr结构

我们在绑定套接字,接收请求,创建连接的时候都需要传入sockaddr这个结构,要么是客户端信息,要么就是服务器信息。

通信分为本地通信和网络通信。socket接口的设计者,为了让本地通信和网络通信都有一套相同的接口,所以就设计出了类似多态的效果。sockaddr_in用来实现网络通信,sockaddr_un用来实现本地通信。

socket = ip + port。所以在网络通信的时候,我们需要绑定套接字信息。前16位用来区别是本地还是网络通信。

所以,我们在传参的时候,直接使用sockaddr接收sockaddr_in/sockaddr_un结构,传长度时要传具体的长度,这样就可以在内部将其本来的内容转换出来。

0x1.socket

创建套接字

  • domain:用来设置套接字的通信类型,是网络通信还是本地巡回。网络通信:AF_INET,本地巡回:AF_UNIX。
  • type:传输数据的类型,创建UDP套接字是使用SOCK_DGRAM,创建TCP套接字使用SOCK_STREAM。因为udp面向数据报,tcp面向字节流。
  • protocol:传0即可,通过前两个参数就已经可以确定创建的是那个套接字。

返回值:

On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.

成功返回一个文件描述符。失败返回-1.

为什么网络通信之间要先创建套接字呢?

网络通信的数据都要通过网卡进行收发,所以,我们可以简单的理解为网络通信前需要先打开网卡,而在Linux操作系统中,一切皆文件。所以,我们将要发送的数据写入到网卡文件中,所以系统就会给我们返回一个文件描述符。

0x2.bind

绑定套接字信息,不论是客户端还是服务端,在通信之间都需要创建套接字,创建套接字之后要进行绑定,绑定自己的端口号和ip地址。

对于服务器来说:不需要将ip地址固定。如果绑定了固定的ip地址,到时候就知道收到由绑定的ip地址发来的请求。所以服务端ip地址一般绑定INADDR_ANY

对于客户端来说,不需要显式的绑定自己的ip地址和端口号。首先,在同一台主机上,操作系统是知道ip地址是多少的,不需要显式指定。其次,如果显式绑定了端口号,并且多个客户端绑定了同一个端口号,就会导致无法同时启动多个客户端。

客户端的ip地址和端口号由操作系统进行随机绑定。

  • sockfd:创建套接字返回的文件描述符
  • sockaddr:绑定的套接字信息,需要填写sockaddr_in结构内部的信息。
  • addrlen:sockaddr_in的长度。

0x3.listen

udp是不面向连接的,tcp是面向连接的。所以,listen是tcp接口,目的是让服务器处于监听状态。服务器处于监听状态之后,就可以被外界连接。

  • sockfd:那个套接字将设为监听状态。监听状态必须是面向连接的。一般是服务器进行监听。
  • backlog:请求队列的长度。弱化,可以理解为底层等待连接的套接字个数。

0x4.accept

服务端获取客户端向服务端发送的连接。

  • sockfd:服务端创建的套接字
  • addr:输出型参数,获取连接上来的客户端信息
  • addrlen:输出型参数,addr的长度。

accept的返回值也是一个文件描述符。

服务端创建的文件描述符是用来获取连接的。它不给客户端提供服务。

accept返回的文件描述符是用来给客户端提供服务的。我们后序使用accept返回的文件描述符来发送数据和接收数据。

0x5.connect

客户端用来向服务端发起连接。

  • sockfd:客户端的文件描述符
  • addr:自己的ip和端口信息
  • addrlen:addr的长度。
相关推荐
和计算机搏斗的每一天23 分钟前
虚拟网络编辑器
网络
若汝棋茗33 分钟前
基于TouchSocket实现WebSocket自定义OpCode扩展协议
网络·websocket·网络协议
敖云岚1 小时前
【Linux】基于虚拟机实现网络的管理
linux·服务器·网络
洛克希德马丁1 小时前
Parsec解决PnP连接失败的问题
网络·智能路由器
-SGlow-1 小时前
Linux相关概念和易错知识点(40)(HTML资源交互、网页管理、搜索引擎)
linux·运维·服务器·网络·html·交互
利刃大大2 小时前
【网络编程】九、详解 HTTPS 加密原理
网络·网络协议·https
搬码临时工3 小时前
动态域名服务ddns怎么设置?如何使用路由器动态域名解析让外网访问内网?
服务器·网络·tcp/ip·智能路由器·访问公司内网
wudinaniya3 小时前
笔记本电脑打开网页很慢,一查ip地址网段不对怎么处理
网络·网络协议·tcp/ip·电脑
天翼云开发者社区4 小时前
办公网络流量隔离:为高效办公保驾护航
网络·安全
和计算机搏斗的每一天4 小时前
ip命令详解
服务器·网络·tcp/ip