网络基础(全)

协议

”协议“就是一种约定。
那么协议需要需要管理吗?
答案是当然需要管理呀。

  1. 操作系统要进行协议管理——先描述,在组织
  2. 协议本质就是软件,软件是可以进分层的
  3. 协议在设计的时候,就是被层状的划分的
  4. 为什么要划分为层状结呢?——场景复杂,功能解耦,便于人们进行各种维护。那么我们也需要对网络协议进行分层

通信的复杂,本质是和距离成正相关的。
image.png

其中物理层对应着硬件,数据链路层对应的驱动,传输层、网络层对应着操作系统,应用层可以理解为对应的系统调用。

OSI七层模型

自从而上分为:
物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。
这是理论模型。

TCP/IP五层(或四层)模型

image.png
image.png
这是实际实现的时候形成的模型
该模型把应用层,表示层,会话层。合并成一个应用层。
每层都有自己的协议方案,每层协议都要有自己的报头
在从上到下交付数据的时候,就要添加报头;从下到上递交数据的时候,就要去除报头。
如下图:
image.png
在局域网中,两台主机是可以直接通信的,局域网中表示主机的唯一性:MAC地址
为什么局域网中的两台主机可以直接通信,因为它们是用以太网进行连接的。
image.png这个命令查看网络的一些信息
如果是两个局域网中的主机要进行通信的话,那么我们要用路由器进行从中间接收转发数据
而路由器在网络层,传入路由器的时候要进行解包,传出的时候要重新打包。

如何理解MAC地址和IP地址
MAC是作为中转的地址,而IP是用来标志起始和终止的地址。

下面对应我们要学习的每一个协议,我们都要问这2个问题:

  1. 报文是要被封装的,如何解包?
  2. 决定我们的有效载荷交付给上一层的哪一个协议的问题?

端口号

端口号是传输层协议的内容。
端口号是2个字节的整数,端口号是用来标识一个进程的,告诉操作系统,当前这个数据是要交给哪一个进程来处理。
IP地址+端口号能够标识网络上某台主机的某个进程。
一个端口号只能被一个进程占用,而一个进程可以绑定多个端口号

IP+端口号这种我们就称为套接字。

关于大小端传输的问题,要求为大端传输。
下面这些函数,可以进行大小端的转换。

#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);

常见的套接字(socket):

  1. 域间socket——用于本地通信
  2. 原始socket
  3. 网络socket

虽然有3个,但是系统只提供了一个通用的接口

TCP协议:

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议:

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

socket编程接口

image.png

  • socket函数,创建成功返回一个文件描述符On success, a file descriptor for the new socket is returned,第一个参数为域,第二个参数为类型——udp为数据报,tcp为字节流SOCK_STREAM

0.0.0.0表示任意ip都可以连接
send——给tcp用的
sendto——给udp用的

  • struct sockaddr_in

image.png
image.png
点分十进制的IP地址我们要转换成4字节,还要转换成网络数据

我们看第2个接口中的第二个参数为一个结构体的指针,
Linux给的是通用的接口,传入struct sockaddr_in为网域,struct sockaddr_un为域间image.png

image.png
udp代码:
头文件

#include <iostream>
#include <string>
#include "log.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>



class UdpServe
{
public:
    UdpServe(uint16_t port,std::string ip = "") :_port(port), _ip(ip),_sock(-1) {}
    ~UdpServe() 
    {
        if(_sock>=0)
        close(_sock);
    }
    void Init()
    {
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if(_sock<0)
        {
            logMessage(FATAL,"%d-%s",errno,strerror(errno));
            exit(1);
        }
        struct sockaddr_in local;
        //先全部设置成0
        bzero(&local,sizeof local);
        local.sin_family=AF_INET;
        local.sin_addr.s_addr=_ip.empty()?INADDR_ANY:inet_addr( _ip.c_str());
        local.sin_port=htons(_port);

        //进行绑定
        if(bind(_sock,(struct sockaddr*)&local,sizeof local)<0)
        {
            logMessage(FATAL,"%s-%d-%s","bind失败",errno,strerror(errno));
            exit(2);
        }
        logMessage(NORMAL,"成功-%s",strerror(errno));

    }
    void Start()
    {
        char buffer[64];
        while(true)
        {
            struct sockaddr_in peer;
            bzero(&peer,sizeof peer);
            socklen_t len=sizeof peer;

            ssize_t s=recvfrom(_sock,buffer,sizeof(buffer)-1,0,( struct sockaddr *)&peer,&len);
            std::string str;
            if(s>0)
            {
                buffer[s]=0;
                FILE* pf= popen(buffer,"r");
                while(fgets(buffer,sizeof buffer,pf))
                {
                    str+=buffer;
                }

                fclose(pf);
                //解析出来从网络来的数据的端口
                // uint16_t cli_port=ntohs( peer.sin_port);
                // std::string cli_ip=inet_ntoa(peer.sin_addr);
                // printf("[%s:%d]:%s\n",cli_ip.c_str(),cli_port,buffer);


            }
            //把收到的再重新发给它
            // sendto(_sock,buffer,sizeof buffer,0,(struct sockaddr*)&peer,len);
            sendto(_sock,str.c_str(),str.size(),0,(struct sockaddr*)&peer,len);
        }
    }

private:
    std::string _ip;
    uint16_t _port;
    int _sock;
};

.cc

#include "udpserve.hpp"
#include <memory>


int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        std::cout<<"参数不对"<<std::endl;
        exit(1);
    }
    uint16_t port=atoi(argv[1]);
    std::unique_ptr<UdpServe> svr(new UdpServe(port));
    svr->Init();
    svr->Start();
    return 0;
}

客户端:

#include <iostream>
#include <string>
#include "log.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    if(argc!=3)
    {
        logMessage(FATAL,"%d-%s",errno,strerror(errno));
        exit(1);
    }
    //客户端不需要绑定,第一次发生的时候会自动绑定
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        logMessage(FATAL,"%d-%s",errno,strerror(errno));
        exit(2);
    }
    struct sockaddr_in serve;
    bzero(&serve,sizeof serve);
    serve.sin_family=AF_INET;
    serve.sin_addr.s_addr= inet_addr(argv[1]);
    serve.sin_port=htons(atoi(argv[2]));
    char buffer[64];
    while(true)
    {
        std::cout<<"请输入你的消息:";
        std::string message;
        std::getline(std::cin,message);

        sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&serve,sizeof serve);

        //回显
        struct sockaddr_in temp;
        socklen_t len=sizeof(temp);

        ssize_t s=recvfrom(sock,buffer,sizeof buffer,0,(struct sockaddr*)&temp,&len);

        if(s>0)
        {
            buffer[s]=0;
            std::cout<<"回显:"<<buffer<<std::endl;
        }
    }
    close(sock);

}

image.png
image.png这个可以直接不用写客户端进行测试

字节流

我们调用send,write等函数,不是直接向另一台主机发送的,而是把这些数据拷贝到内核中的缓冲区,什么时候发送、发送多少给另一台主机由os自己确定,发送的次数和接受的次数没有任何关系,这就是面向字节流的。

守护进程

  • 前台进程:和终端相关联的进程,
  • 在用xshell登录终端,只允许一个前台进程和多个后台进程
  • 进程除了有自己的pid,ppid,还有一个组id

image.png

  • SID叫做绘画id,TTY用来判断是前台还是后台进程
  • 在命令行中,同时用管道启动多个进程,多个进程是兄弟关系,父进程都是bash,就可以用匿名管道来进行通信
  • 而同时被创建的多个进程可以成为一个进程组的概念,组长一般是第一个进程。
  • 任何一层登录,登录的用户,需要有多个进程组,来给这个用户提供服务,用户可以自己启动多个进程,或者进程组。我们把给用户提供服务的进程,或者用户自己启动的所有的进程或者服务,整体属于一个叫做会话的机制中。
  • 如何将自己变成自称会话呢?——setsid(),这就是我们所说的守护进程。

setsid要成功被调用,必须保证当前进程不是进程组的组长,可以用创建子进程的方法来实现。

  • 守护进程不能直接向显示器打印消息,一旦打印就会出现问题,可以进程就会被终止

在Linux系统中有这样一个文件/dev/null,我们可以向它里面放数据,它就相当于一个无底洞,不做任何数据的保留。

下面让我们自己写一个守护进程的小代码:

  1. 捕捉一些信号,不能让进程无辜退出
  2. 不能让自己成为组长
  3. 对标准输入,标准输出,标准错误进行重定向,让守护进程不能直接向显示器打印消息
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void Daemon()
{
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    if (fork() > 0)
        exit(1);

    setsid();

    int fd = open("/dev/null", O_RDWR);
    if (fd > 0)
    {
        dup2(0, fd);
        dup2(1, fd);
        dup2(2, fd);
    }
}

HTTP协议

应用层:就是程序员基于socket接口之上编写的具体逻辑,做的很多工作,都是和文本处理相关的——协议分析与处理
那么HTTP协议一定具备大量的文本分析和协议处理

认识URL

平时我们是的网址就是URL
https://gitee.com/maoleblog/high-concurrency-memory-pool
前面是协议,然后是域名(域名后面需要有端口),后面的就是web
字符对应ASCII转成对应的16进制,然后再加一个%。这就是urlencode(编码),对应的urldecode就是译码的过程。

那么http是如何进行请求的呢?
首先客户端发送一个http请求给服务器,服务器把这个请求处理好之后发给客户端就完成了请求。当然客户端在发送请求之前,肯定完成了3次握手的动作。
http协议是应用层协议,底层采用的是TCP。

  1. http请求和响应的报文格式

请求格式:
第一行为请求行——包括请求的方法,url,http协议的版本,它们之间用空格隔开,后面是\r\n
后面的若干行是请求报头,这些行也是以\r\n结尾,是k,v的形式,请求报头也就是属性字段。
再后面的一行就是空行
后面的就是正文部分
响应格式:
第一行为状态行:——包括http的版本,状态码,状态码描述,它们之间也是用空格分开的,后面就是\r\n
后面的多行就是响应报头,和请求格式一样
再后面也是空行,也就是\r\n
最后就是正文部分。

大致描述如下:
image.png
其实上面的就是字节流,为了方便才这样画的
对于上面的正文部分,我们怎么才能知道正文部分已经读取完毕呢?当然在外面的报头里面会有包含正文的长度,Cotent-Length

下面我们就见一见它的格式:
image.png

HTTP的方法

在请求行中的方法有很多个,其中最常见的方法为GETPOST方法
这两个方法都是获取资源。
它们之间的不同就是:
GET方法把请求会转成url的一部分
POST方法会会把这种请求放到正文中。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>这是一个博客</h1>
    <p>这是一个博客主页,是用来测试的哦!</p>
    <form action="/login" method="GET">
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" required><br><br>

        <label for="password">密码:</label>
        <input type="password" id="password" name="password" required><br><br>

        <input type="submit" value="登录">
    </form>
</body>

</html>

上面这是一个表单,当我们的method是GET方法的时候,请求的结构是什么样子的呢?
image.png
就会显示在这里,说明GET是通过URL传参的。
当方法变成POST的时候
image.png
就会显示在这里,POST通过http正文提交参数的。
GET方法通过url传参,回显输入的私密信息,不够私密
POST方法通过正文提交参数,不会回显,私密性有保证
但是不管是GET还是POST方法,都是不安全的,只有加密和解密才是安全的。

HTTP的状态码

image.png
这里的重定向分为永久性的重定向和临时性的重定向
image.png
需要加上location字段
比如下面的这样:

std::string head="HTTP/1.1 302 Found\r\n";
head+="location:http://baidu.com/\r\n";

这种请求的主要过程就是:
客户端发送http请求到服务器,服务器没有该请求,状态码为301,告诉客户端一个新的地址,客户端再发送请求,服务端直接给显示新的地址。就是在请求报头中添加的location:http://baidu.com/\r\n后面加上跟随的网址就行。
请求报头中还有Content-Length表示的是正文的长度;Content-Type数据类型——比如text/html类型;还有Connection表示连接状态——keep-alive为长连接,close为短连接。
长连接可以一次连接可以多个请求和响应,短连接一次连接一次请求响应,也就是每次请求都要重新建立连接。

还有一个需要了解的就是Cookie
http协议的特征就是

  1. 简单快速
  2. 无连接
  3. 无状态

无状态就是当我们关闭我们访问网址的时候,再次访问的时候需要继续登录,所以要登录一次不需要再次登录的话,那么我们就要有一次文件来存储登录的状态,这个就是cookie文件(内存级别,硬盘级别)。
当用户请求的时候,服务器端可以设置Set-Cookie来保存用户的登录状态,当该用户再次登录的时候,会携带着Cookie字段,这样服务器端就直接在它对应的数据库中查找就行,用户就可以直接登录了,不需要再次登录。

如果在用户登陆的时候,服务器端不进行对用户的信息进行转换的话,(直接明文的形式)就泄漏个人信息,所以客户端要转成自己的id,比如是一段数字来表明该用户的唯一性。即使黑客那着这个id来访问服务器,那么服务器端只要检测到异常的信息,就阻断它就可以。

那么我们来写一下程序,来看看这个cooki。
image.png
这是我们在报头中添加的。
image.png

HTTPS是什么

HTTPS也是应用层协议,是在HTTP协议的解除上添加的一个加密层。

常见的加密方式

对称加密

单密钥进行加密解密

特点:算法公开,计算量小,加密速度快,加密效率高

非对称加密

有两个密钥,一个是公开密钥,一个是私有密钥

特点:算法强度复杂,加密速度密钥对称加密的速度快

数字指纹,数字摘要

对信息用hash函数生成一串固有长度的数字摘要(也叫做数字指纹)

HTTPS的工作过程探究

  • 只使用对称密钥,在客户端和服务端传送密钥的时候就有可能被中间人截获密钥
  • 只使用非对称密钥,服务端的公钥会被中间人获取,从而替换成自己的公钥,导致不安全
  • 双方都使用密钥或者使用对称和不对称的方式,如果中间人一开始就进行攻击了,也是不安全的。

CA认证

服务端在使用https前,需要向CA机构申请一份数字证书,证书包含申请者的信息、公钥信息等。

那么认证是怎么玩的呢?

我们的数据经过哈希散列得到一串哈希值的字符,这串字符通过私钥加密形成签名,数据加签名就是数据签名。而如何保证签名是正确的呢?数据进行hash散列成散列值,然后签名继续解密,比较这两个结果释放正确。

因为私钥中间人是不知道的,所以保证了数据的安全。

所以https的工作流程用的就是非对称加密+对称加密+证书认证的方式

进程的标准输入和命令行参数

命令行参数:在命令行中输入的,然后传入main函数中的
xargs就可以把进程的标准输入变成命令行参数。

传输层和网络层属于内核层,数据链路层属于驱动程序。
我们之前写的应用都是用的系统调用的接口,本质就是把数据拷贝到内核的缓冲区中,而数据往上递交的时候,就体现了端口的作用,提交的时候也是提交到用户定义的缓冲区中

netstat

netstat是一个查看网络状态的。
参数:

n:把能显示的数字全部转成数字
l:仅列出有在监听的服务状态
p:显示建立相关链接得到程序名
t:显示tcp相关的
u:显示udp相关的
a:显示全部的

pidof

通过进程名查看进程的id
pidof 进程名

我们用源ip,源端口,目的IP,目的端口 ,还有协议号(TCP/UDP)五元组来标识一个通信
一个进程可以绑定多个端口号,一个端口号只能绑定一个进程。

为什么不用pid来当端口呢?

因为不是所有的进程都提供网络服务(如果用了,系统还要去进行判断,就增加了成本),还有就是系统和网络进行解耦

报文是怎么发送到对方的主机上的呢?

当报文发送的时候,会建立连接,生成一个文件描述符(sock就是文件描述符),就把收到的信息放到缓冲区中,而上层应用在读取的时候,进程里面就有对应的文件描述符表,就可以对文件进行读写操作,也就实现了网络通信。那么,怎么通过端口找到对应的进程呢?os会把端口和进程进行哈希映射,这样通过端口就可以找到进程了。

udp理解

udp报文的格式,前8字节是udp报头,后面就是数据的部分

  • udp如何进行报头分离的——固定长度的报头(8字节)
  • 如何交付呢?——把报头提取出来之后,就知道了端口号(就可以递交给对应的进程),就知道了总报文的大小(就你提取有效载荷的部分了,也就是正文部分)

所以udp是有能力将报文一个一个正确接受的(因为固定大小的长度)
报文可以理解为位段
udp的特点:

无连接:知道对端的IP和端口号就直接进行传输,不需要进行连接
不可靠:没有确认机制,没有重传机制,容易造成丢包
面向数据报:不够灵活,没有控制读写数据的次数和数量

在8字节的报头中,16位的udp长度最大是64k(包括的是整个数据报),是很小的数字,当我们传输打印64k的,就要分包多次发送。

我们用的系统的接口函数,本质是拷贝,把数据拷贝到内核的缓冲区,这样缓冲区是传输层协议提供的。

udp没有真正意义上的发送缓冲区,sendto的数据会直接交给内核,内核就给了网络层,(给了就马上给下一层);udp有接收缓冲区,但是不保证接受报文的顺序性,当缓冲区满了的时候,再接受就会导致丢包。由于这个原因,所以udp是全双工的,发送不会影响接受缓冲区,接受和发送没有影响,就是全双工的啦。

udp报文是固定长度的大小,udp是全双工的,没有发送缓冲区,会直接把数据直接发送给内核,然后由内核进行发送。

tcp理解

image.png
TCP是如何向上交付的呢?——从固定大小的2个字节中就可以获得目的端口
TCP是如何解包的呢?——因为tcp的头部信息不是固定的,所以我们先把20字节获得,然后就知道了数据偏移的4个比特位,就可以算出报头的大小,也就可以把报头解包了。

4位比特表示的数是0到15,它的单位4字节,所以报头最大为60字节。(报头的大小[20,60])

如何理解可靠性:

当客户端发送一个消息的时候,客户端是不知道服务端有没有收到,当服务端应答的时候,服务端是不知道客户端有没有收到;当客户端接收到的时候,此时客户端就知道了刚才发的消息服务端收到了。

TCP协议的确认应答机制:只要一个报文收到了应答,就可以保证发的消息对方收到了。
为了这种机制,TCP协议中就有序号和确认序号这两个字段了。
客户端发送报文,可能会是发送多个报文,那么服务器就要对每个报文进行应答。
发送报文的顺序可能不是有序的,这种情况没有问题,因为有序号,可以对序号进行排序,然后就知道哪个报文先发的了。
发送的时候会携带着序列号,到服务端的时候会进行应答——确认号就是对应的应答号加1
确认序号表示的含义为确认序号之前的数据已经全部收到了。
比如客户端发送了几个报文,序列号分别为1000,3000,2000。服务端收到了之后,确认号就分别为1001,3001,2001。但是先发送给客户端的报文确认序列号为2001,那么就表示2001之前的报文就收到了——1000,2000收到了,3000的报文要再等等;如果此时1000报文其实服务端是没有收到的话,就会导致丢失,序号的设定就是允许部分确认丢失的或者不给应答的。
为什么要有两个字段的序号呢?——因为不仅仅客户端会发送,服务端也会发送报文,所以要2个,TCP是全双工的,下面是全双工的一个示例图

当客户端或者服务端发太快或太慢的时候,怎么才能让它们发送变慢或变快呢?

那么着就是流量控制了,报头中的16位窗口大小所决定的。
当客户端发太快的时候,服务端接收不了这么快,服务端就要告诉客户端接收缓冲区还剩多少的空间,以便客户端控制发送的速度。反之服务部端发送太快(慢),客户端也要再窗口中告诉服务端字节的接收能力。

下面来讲一下标记位:

再客户端发送报文的时候,报文是又很多种的,比如:常规报文,建立链接的报文,断开链接的报文,确认的报文等等。那么我们怎么才能知道是哪种报文呢?——这就需要用标记位来进行标记。

  • SYN:该报文是一个链接报文
  • FIN:该报文是一个断开链接请求的报文
  • ACK:确认应答标记位,凡是该报文具有应答特征,该标记位都会白设置成1。大部分网络报文ACK都是设置成1的,但是第一个链接请求报文肯定不是1.

  1. 如何理解链接呢?

可能会有大量的客户端去链接服务端,所以服务端肯定存在着大量的链接,那么操作系统就要管理这些链接;而这些所谓的链接本质就是内核的一种数据结构,建立连接成功的时候,就是在内存种创建链接对象,多个对象用某种数据结构来管理它。所以说建立链接是需要成本的——cpu,内存成本

  1. 如何理解3次握手

客户端发送SYN链接请求(进入SYN_SENT状态,后面的状态看上面的图片),服务端收到确认应答发送SYN+ACK,客户端收到并发送ACK给服务端,服务端收到就完成了3次握手,这3次握手包括客户端的3次,服务端的3次。
为什么要3次握手?

  1. 服务端可以把嫁接同等成本给客户端

如果只是1次握手,那么客户端一种发送链接请求,就会使服务端崩掉。

如果只是2次握手,当服务端确认应答的时候,客户端丢弃,也会使服务端崩掉
如果是4次,同2次一样;如果是5次,增加了更多的成本,没有必要
2. 验证全双工

  • 客户端请求连接,服务端确认应答——客户端发送缓冲区是正常
  • 客户端确认应答,发送ACK——服务端的发送缓冲区,接收缓冲区去正常的
  • 服务端接收到ACK——客户端的接收缓冲区是正常的。

是不是3次握手一定要保证成功呢?——不一定
如果前2次握手没有成功,接收链接没有成功,可以继续链接或者终止。
主要的是第3次,当客户端第3次发送ACK进行握手的时候,在客户端那里,客户端会觉得建立成功了,但是服务端有没有可能没有收到呢?是有可能的,如果没有收到,在服务端看来是没有建立成功的,但是客户端是觉得成功的。所以说3次握手是不一定会保证成功的。
因为客户端觉得是建立成功了,所以会发送数据,服务器就会感觉奇怪,因为服务器没有收到ACK,所以服务器就会发送RST(连接重置)。——其实像这种情况是很少见的,主要是因为客户端在发送数据的时候,报头中就会带有ACK的。发生这种情况主要是服务端挂掉了,才会连接重置。

当服务器端的缓冲区被存满的时候,也就是16位窗口为0了,然后客户端就不能再发报文,客户端一等再等,还是0,那么客户端就会发送PSH(督促对方尽快将数据进行向上交付),来催促服务端尽快向上交付。

因为tcp是具有按序到达机制的(无序排序就行,因为有序列号),那么我们发送的时候,被对方上层读到的时候,必须也是要有先后顺序的。但我想插队怎么办呢?就需要URG(紧急标志位)。
紧急标志位需要配合16位紧急指针来使用,紧急指针存的是有效载荷从0位置的偏移地址,从偏移地址处读一个字节,其实这个主要是为了检测服务端的状态,可以在询问服务端的时候快速告诉我当前的服务端的状态。

  1. 如何理解4次挥手

4次挥手也就是关闭2个通信信道(缓冲区)

下面是4次挥手的过程:

客户端发起关闭连接,发送的FIN,客户端进入FIN_WAIT_1状态
服务端接收到,并且发送应答(ACK),服务端进入CLOSE_WAIT状态
客户端收到确认应答,进入FIN_WAIT_2状态
服务端发送FIN断开连接,进入LAST_ACK状态
客户端收到应答,发送ACK,客户端进入TIME_WAIT状态
服务端收到ACK,进入CLOSED状态
一段时间(2MSL,MLS是客户端到服务端发送的最长时间) 过后,客户端也进入CLOSED状态。

从上面我们也可以看出来,可以3次挥手,就是在服务端确认应答的时候,ACK、FIN可以一起发过去。

断开连接就一定可以成功吗?——不一定

如果我们发现服务器具有大量的CLOST_WAIT状态的时候,是什么原因呢?原因就是你写的应用层代码有bug,你的连接没有关闭,就是sock没有关闭。

虽然4次挥手已经完成,但主动断开链接的一方要维持一段时间的TIME_WAIT状态,在该状态下,ip、端口依然是被占用的。但是为什么保持这个状态一段时间呢?为了保证历史报文能够在网络中消散,还有一种就是万一服务端没有收到ACK,服务端可以给客户端发FIN让客户端重传。
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
用这个函数设置一下sock属性,就可以在断开连接的时候,不需要等待一定的时间就可以重新建立连接。

理解确认应答机制(ACK)

我们可以把发送缓冲区看做一个char类型的数组,每个字节对应一个序列下标,比如我们发送了一段数据段,存到了数组中的下标为100的位置,那么序列号就是100。

一台主机发送数据段,对方主机会有2种情况

  1. 没有应答ACK
  2. 有应答,但是应答丢包了

这两种情况都要进行重传,什么时候重传呢?
TCP为了保证无论是什么状态下都可以比较高效的通信,会动态的就算这个最大超时时间。
在Linux中,超时会以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍——第一次重传500ms,如果还没有应答,就2500之后继续重传,如果还没有就4500ms进行重传。当次数太多的时候,就会强行关闭连接。
按照上面来说,如果是第2种情况下,就会导致对方主机收到很多重复的数据,那该怎么办呢?
因为发送是有编号的,接收到相同的编号就可以抛弃了。

流量控制

接收端的处理速度是有限的,如果发送端发的太快就会打满缓冲区,这个时候如果继续发送,就会造成丢包,进而引起一系列的问题。
所以我们可以根据窗口来了解到发送、接收端的承受能力如何。
那么第一次我们怎么才能知道接收方的大小呢?——在3次握手的时候就会告知了
当接收端的缓冲区满的时候,我们怎么知道接收端是否又有空间了呢?

当过了重发的超时时间的时候,要是没有接收到对方发过来的更新报文,就会发送窗口探测来询问对方,从应答中就可以了解到。
或者是在没有发起窗口探测的时候,就已经发起了窗口的更新通知,就可以继续发送报文了。

滑动窗口

什么是滑动窗口呢?
我们可以把发送缓冲区分为4个部分:已经发送并且收到应答,可以直接发送并且暂时不需要应答,尚未发送,剩余部分。
image.png
可以直接发送,暂时不需要应答的就是滑动窗口。这种设计可以让发送端马上发送下一条数据
滑动窗口也是有上限的:它的上限是对方的接受能力
滑动窗口既想给对方推送更多的数据,又要保证对方来得及接受。

我们来思考一下下面的问题:

  1. 滑动窗口必须向右移动吗?——不一定,还有可能是不动的
  2. 滑动窗可以为0吗?——可以,在对方不能接收的时候
  3. 如果没有收到开始的报文,而是收到了中间的报文,有影响吗?——没有影响,如果开始的报文收到了,没有应答,只应答了中间的,窗口移到中间就行,如果开始的报文丢失了,直接重传就行
  4. 超时重传背后的意义:就是没有收到应答是时候,数据必须暂时保存起来
  5. 滑动窗口一直往右滑动,会越界吗?——不会,它是一个环形的数组

发送端发送了多个报文,但是前面的部分报文丢失,那么应答的时候只能应答前面丢失部分之前的报文,当向发送端应答3次,发送端就意识到之后的报文存在丢包的问题,就会重新发送,这就是快重传
快重传不是超时重传,它俩是协作的关系,比如快重传的时候丢包了,就有可能超时而进行超时重传了。

拥塞控制

我们上面讲了:超时重传、快重传、流量控制、链接管理、滑动窗口、去重和按序到达、序号机制、确认应答。它们解决的都是端对端的可靠性问题。可以涉及到网络的问题。
在网络中传输的过程中:

如果出现少量的丢包,可能会认为是主机的问题,然后就会重传
如果是大量的丢包,就是网络出现了问题(网络拥塞了),那么这个时候还要重传吗?这时就不能再重传了,网络有很多主机,如果都进行重传的话,网络会更加拥堵。

那么这个时候就要引入拥塞控制了。
出现这种情况,TCP会引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,然后再决定按照多大的速度传输数据。
此处我们引入拥塞窗口的概念——就是发送多大的数据去进行探测
开始发送的时候,定义窗口的大小为1,每次收到ACK应答,窗口就会翻倍(乘2),这是一种指数增长的,指数增长前期启动的比较慢,后期会爆炸式增长。(数学中叫做指数爆炸)。因为这种增长中后期太快了,所以我们会设置一个阈值,当达到这个阈值的时候,就会按照线性增长的方式进行增长。
image.png
从上图可以看出:

  1. 慢启动的阈值是窗口的最大值
  2. 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口会置成1

所以说:滑动窗口的大小是拥塞窗口与对方接收能力的最小值

为什么要用慢启动的方式呢?

  1. 前期要让网络有缓一缓的机会——解决网络拥堵
  2. 中后期,网络恢复了之后,尽可能恢复通信的过程——尽快恢复双方通信的效率

延迟应答

当对方主机接收到数据之后立刻ACK应答,这时候返回的窗口可能是比较小的。如果延迟一会,让上方应用层处理一下数据,就会使缓冲区变的更大,然后再给应答就可以提高效率,就能法更多的数据。这就是延迟应答。
延迟应答受数量的限制和时间的限制,一般每隔N(2)个包就应答一次;在快要超过最大延迟时间(超时重传的时间)的时候应答一次

捎带应答

捎带应答就是在应答的时候也把数据带过去

面向字节流

我们在应用层写的程序,调用send发送数据的时候,实际是把数据拷贝到习题的缓冲区中(TCP缓冲区),对应缓冲区来说,上面时候发,发多少由系统决定,系统只管把数据发送过去,不管是什么类型的。发送到对端的时候,读多少由上层应用决定,系统不管。这就叫做面向字节流。
UDP就是每次发一个,每次读一个。这就是面向数据报。

粘包问题

因为TCP是面向字节流的(还因为TCP中无法知道正文的大小),所以上层可以发了多个请求,而TCP一次就把这些请求发送到对端的主机上了,那么这多个请求报文就会粘在一起,因为TCP只管发送数据,不管数据的格式类型什么的,那么对于粘包问题怎么解决呢?——由上层应用解决,比如自定协议(定长报文,特殊字符什么的,只要和正文不冲突就行),又比如发送的http协议

UPD没有粘包问题,因为UDP是固定大小的报文,使得数据和数据之间有明确的边界。

TCP异常情况

  • 进程终止:文件描述符随进程,进程终止,会自动关闭文件描述符,也就是进行4次挥手,和正常的关闭没有区别
  • 机器重启:机器重启的时候,会先终止进程,和上面一样
  • 机器断电/断网:无论是服务端还是客户端,它们都会觉得链接还是正常的。如果是客户端断的,当客户端好的时候,发起连接请求的时候,服务端就会发觉之前的连接断开了,就会重新连接;其实TCP中有内置的保活定时器,会定期的询问对方是否还在。如果是服务端断的,客户端发送数据的时候,服务端会发现怎么没有3次握手就发数据了,会发送RST进行连接重置。

TCP小结

可靠性:

  • 校验和
  • 序列号(保证按序到达)
  • 确认应答
  • 超时重传
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能:

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景
UDP用于对高速传输和实时性比较高的通信领域,UDP还没有用于广播

如何用UDP实现可靠传输——参考TCP的可靠传输机制,不同的场景添加不同的机制回答就好。

思考:
accept要不要参与三次握手呢?
不需要参与3次握手,accept从底层直接获取已经建立好的链接。
所以说先建立好链接,然后才能accept获取对应的连接。
那如果我不调用accept,能建立连接成功吗?——当然可以,因为accept 只是把建立好的获得而已
让我们继续思考,如果上层来不及调用accept,并且对端还来了大量的连接,该怎么办?

这个问题就和我们去吃火锅一样,吃火锅的人多了,就会先在外面排一会队。
服务器本身要维护一个连接队列,这个连接不能太长也不能没有。而这个队列的长度和listen的第二个参数有关!
在Linux内核协议栈为一个tcp连接管理使用的两个队列

  1. 半链接队列——用来保存处于SYN_SENT和SYN_RECV状态的请求
  2. 全链接队列(accept队列)——用来保存处于ESTABLISHED状态,但是应用层还没有调用accept取走请求

半连接队列,一段时间后还存在(没有进去全连接队列),服务端就主动关闭了该对象下的请求了。
全连接队列的长度为listen的第二个参数+1。

IP协议

应用层解决的是数据使用的问题
下三层解决的是网络通信的细节,讲数据可靠的从A主机跨网络送到B主机
那么IP协议解决的是什么问题呢?它主要是提供一个能力——讲数据从A主机送达B主机的能力。有能力不一定可以做到,由TCP来保证可靠的。

image.png

  • 4位版本号指的是IP协议的版本,对于ipv4来说,就是4了。
  • 4位首部长度:IP协议报头的总大小,这里的单位是4字节,该4位表示的最大报头的大小就是60字节了,用总长度减去固定长度的大小就是选项字段了。
  • 8位服务类型:3位优先权字段(已经弃用)、4位TOS字段和1位保留字段(必须置成0)。4位TOS分别表示:最小延迟,最大吞吐量,最高可靠性,最小延迟;这四者相互冲突,只能选择其一,在网络传输中,我们要想低延迟(比如ssh这样的程序),就要选择低延迟,对于ftp这样的,最大吞吐量就是比较重要的了。
  • 总长度:单位字节。
  • 8位生存时间:指的是报文在网络传输中,最多可以经历多少个路由器,超过这个,报文直接舍弃。
  • 8位协议:指的就是tcp,upd,是什么协议就填什么协议
  • 16位首部检验和:如果检验不成功,直接舍弃报文,上层的传输层会重新发。

链路层由于物理特征的原因,一般无法转发太大的数据,链路层转发到网络的报文是有限制的(默认为1500字节)。所以网络层传输的数据大于这个,就要进行数据切片,对于的接收端就要对数据从新合成一个。

切片

  • 为什么要切片

切片主要是由于数据链路层有报文大小发送的限制,而上层的传输层又不知道,所以到网络层要进行切片,以便数据链路层发送处理

  • 什么是切片

把一个比较大的IP报文,拆分成为多个小的、满足条件的报文。分片是网络层做的,那么组装也应该是网络层做的。为什么要进行组装呢?——传输层发给网络层一个完整的报文,那么网络层往上递交的时候也应该递交应该完整的报文。IP分片和组装的行为,TCP是不知道的,也不关心。

  • 切片是怎么办到的

来了解一下IP报文的第2行。

  • 16位标识:IP报文的序号。相同分片的肯定具有相同的IP报文的序号
  • 3位标志字段:第一位保留(暂时不用);第二位要是1表示禁止分片,这时如果报文的长度超过链路层的默认,就会直接丢弃报文;第三位表示更多分片,如果一个报文分片了话,1就表示后面的还有分片的报文,0就表示后面没有分片的报文。
  • 13位片位移:表示每个切片在原来没有切片的偏移大小,单位是8字节,所以除最后一个报文外,其他的报文的长度必须是8的整数倍。

下面是一些理解:

  1. 分片的行为不是主流

在网络分片和组装的过程中,应用层和传输层是不知道的。
一个完整的报文分片成多个报文,会增加丢包率,有一个切片的报文丢包了,就是整个报文丢包。
这不是网络层说的算的,要想彻底解决分片的问题,要在传输层找到答案(控制一下报文的长度)。

  1. 具有识别报文和报文的不同

根据16位标识,不同报文,标识不同;相同的报文,标识也是相同的

  1. 具有识别报文是否被分片

如果更多分片的标志位为1,说明被分片了。
如果更多分片的标志位为0,但偏移不为0,也 说明被分片了。
否则就是独立的没有被分片的报文。

  1. 识别出哪些分片是开始,哪些是中间,哪些分片是结尾

开始:更多分片为1,偏移为0
中间:更多分片为1,偏移不为0
结尾:更多分片为0,偏移不为0

  1. 异常处理:组装的过程中,缺失的分片要识别出来

当我们收到一批报文的时候,先尽可能的将报文的分片区分出来,并放在一起。
那么我们如何才能保证收全了?
首先我们把具有相同16位标识的报文放在一起,然后根据偏移量排个序。
根据:偏移量+自身的大小=下一个报文的偏移
就这样扫描整个报文,如果不匹配,中间一定会有丢失的;如果成功计算到结尾,就一定收取完整了。

分片之前,一定是一个独立的IP报。
分片之后,每一个分片都要有IP报头。
下面是一个简单的图示:

我们这个分片是对整个报文进行分片(包括IP报头的),分好的每一个报文都是满足发送要求的,第一个分片的报文的包头是直接拿下来的,当然对应的IP报头的属性需要改变一下的。

网段划分

IP地址分为2个部分:网络号和主机号
网络号:保证相互连接的两个网段具有不同的标识
主机号:同一个网段内,主机之间具有相同的 网络号,但是必须有不同的主机号。

IP的大划分可用按照国家或者地区来划分(这里指都的是公网IP)

互联网的网络地址分为A~E五类。(自己查找一下资料看看)

网络号相当于代表某个区域,主机号是网络号中的具体主机——比如前15位表示安徽的网络号,后面的位数表示主机号,它们合起来就是一个IP地址

有一种技术叫做DHCP, 能够自动的给子网内新增主机节点分配IP地址, 避免了手动管理IP的不便.
一般的路由器都带有DHCP功能. 因此路由器也可以看做一个DHCP服务器.

我们为什么要对IP进行不同的划分——更高效的定位到目标主机

IP划分的方案:

  • image.png

这种划分方案比较局限,且有浪费,使本就少的IP可用的更加少。比如A类,实际主机根本没有那么多,就造成了浪费。

  • CIDR方案:

引入一个额外的子网掩码来区分网络号和主机号
子网掩码也是一个32位的整数,通常用一连串的0结尾
将IP地址和子网掩码进行按位与操作,得到的结果就是网络号

IP地址和子网掩码还有一种更简洁的表示方法,例如140.252.20.68/24,表示IP地址为140.252.20.68, 子网掩码的高 24位是1,也就是255.255.255.0

特殊的IP地址:
将IP地址全部设为0,就成了网络号,代表这个局域网
将IP地址中的主机地址全部设成1,就成为了广播地址,用于给同一个链路中相互连接的所以主机发送数据包
127.*的IP地址用于本机换回测试,通常是127.0.0.1

ip地址数量的限制

CIDR虽然在一定程度上缓解了IP地址不够用的情况,但是IP的上限还是没有增加,下面是3种方式来解决:

  • 动态IP地址:只给接入网络的设备分配IP地址,因此同一个mac地址的设备,每次接入网络中得到的IP地址不一定是相同的。
  • NAT技术
  • IPV6

私有IP和公网IP

  1. 当我们要上网的时候,家里面会做什么呢?

首先要有家附近要有网络的覆盖,然后光纤入户,弄调制解调器(光猫),弄路由器。
配置路由器的账号、密码(用于运营商认证你们的入网)
我们设置路由器的WiFi名称和密码(让路由器认证连接我们路由器的设备)

  1. 为什么我们无法访问一些外网

是运营商不让我们访问,运营商识别是一些国外网站的IP就之间把请求的报文丢弃。

在一个组织内部组件局域网,只用于局域网内的通信,而不直接连到公网上,理论上使用任意IP地址作为私有的都可以,但RFC规定了用于组建私有IP的地址

10.*
172.16.到172.31.
192.168.*

这些范围内的都成为私有IP,其余的则为称为全局IP也就是公网IP
私有网络对应的IP是局部的,可以在不同的子网中是可以把重复出现的——IP不足的问题缓解了。

所以路由器也应该有局部的和全局的。
首先路由器要向向下找IP,也要具有向上找IP的能力。
所以路由器可以配置两个IP地址,一个是WAN口IP,一个是LAN口IP(子网IP)

  • WAN口IP——对外的,是自己所在上级子网给分配的IP
  • LAN口IP——对内的,面向的是自己构建的子网

所以路由器是具有构建子网功能的。

那我们把一个报文发送到公网的一个应用上去,比如抖音。源地址是我们的私有IP,目的地址是抖音的公网IP,当我们送到抖音 的时候,抖音把消息给我们却不知道给谁,因为私有IP太多了都是重复的。对于这个问题,肯定不是这样的,路由器会做一件事:就是把报文中的源IP替换成路由器的WAN口IP,每经过一个运营商的内网路由器,都要做这个工作(公网路由器不做),这样抖音把消息跟我们就会先给运行商的公网,然后一步步往下找我们(怎么找的后面讲)。像这种替换技术就是NAT技术。

路由

路由的过程,就是从一台路由到另一台路由的过程。
我们怎么才能知道该数据包发送到下一个地方,这就依赖每个节点内部都维护着一个路由表
路由表可以使用route命令查看
先在路由表中进行查找,找到就进行下一跳,没有找到就跳至默认的(一般是不是访问局部的,而是公网)
下一跳的路由器和当前路由器肯定属于同一个局域网。

IP没有解决设备转发的具体功能,而是提供了一种转发的策略,具体的转发由下一层决定的。

数据链路层

image.png

6字节目的地址:下一跳主机的mac地址
6字节源地址:当前主机的mac地址
类型:向上交付的类型,比如上层是IP协议,类型就是0800
数据的大小为46到1500字节
最后的4字节的CRC校验

MAC地址是用来识别数据链路层中相连的节点,通常就是我们的网卡,具有唯一的标识,在网卡出厂时就确认了,不能修改。

数据链路层发送的最大的数据为1500字节,这个1500字节称为以太网的最大传输单元——MTU,不同的网络类型有不同的MTU。

  • MTU对udp的影响:

一旦udp携带的数据超过1472(1500-20ip首部-8udp首部),那么就会在网络层分成多个IP数据包,就增加的丢包的概率,导致重传。这也是udp不可靠的表现。

  • MTU对tcp的影响:

当然tcp携带的数据也不能太大,也是受制于MTU的,TCP的单个数据报的最大消息长度称为MSS(1500-20ip报头-20tcp报头=1460),我们发的数据最后要低于这个值。

重新梳理局域网的通信原理

在一个局域网中有多个主机,主机h1要想给h6发送消息,那么在该局域网中的所有的主机都会收到该消息。如果局域网中在它们发送消息的时候,其他主机也发送消息,就会导致数据的碰撞。
如何避免呢?——我们有避免碰撞算法,发送的主机会休息随机的时间,然后再重新发送。

根据上面的所说:
局域网中的主机越少越好,这样就可以减少碰撞。(碰撞重发是由代价的)
在局域网中发送的数据帧越短越好,越短发送的时间就越短,就减少了碰撞的机率。
当然局域网中的主机很多的时候,我们还有交换机(划分碰撞区域)。
交换机可以把局域网中的主机隔开,比如隔开成2部分:1到3为一个部分,4到6算一个部分;当1给3发送消息的时候,数据不会推送到4到6的主机部分,如果1发送给4,交换机识别了,就放它过去。交换机这种设备也减少了碰撞的发生。

在网络转发的过程中,目的IP地址是不变的,MAC桢的报头在每一次转发经过路由器的时候都会发生变化。但是我们在封装桢的时候,目的(下一跳)MAC的地址我们怎么知道。(后面讲)

ARP协议

ARP协议是介于数据链路层和网络之间的协议。也就是地址解析协议

ARP协议的作用:建立了主机IP地址和MAC地址的映射关系

目标IP:IP=目标网络+目标地址,在往下一层封装的时候,就必须知道MAC地址,如果没有MAC地址,我们就无法封装MAC桢

image.png
其实下面的才是真正的ARP数据报的格式,上面的封装了数据链路层
image.png
注意到源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为以太网的情况
是多余的,但如果链路层是其它类型的网络则有可能是必要的。
硬件类型指链路层网络类型,1为以太网;
协议类型指要转换的地址类型,0x0800为IP地址;
硬件地址长度对于以太网地址为6字节;
协议地址长度对于和IP地址为4字节;
op字段为1表示ARP请求,op字段为2表示ARP应答

ARP的过程

  1. 先进行广播
  2. 然后再1v1的进行发送

开始的时候先把数据封装好,进行广播到各个路由器或者主机,到达每个主机的时候,先解包,然后往上层进行递交,先看op字段是请求还是应答,然后再看目标IP。不一样就抛弃。
当目标主机进行应答的时候,对方主机先看目的IP和自己的是否一样,一样就递交,不一样就抛弃。递交给上一级的时候,也是先看op字段再看IP。
image.png

思考:

  1. arp看起来至少进行一个请求和一个应答,是不是每一次发送数据都这么干呢?

arp请求成功之后,请求方会暂时讲IP和mac地址的映射关系暂时保存下来。

  1. 是不是只会在目标的子网中进行arp,其他地方会不会发生arp呢?

arp在任何地方都会发生。

arp伪装,arp攻击,让自己成为中间人?

image.png

DNS——域名到IP的映射的系统

我们访问百度的时候,访问的是她们的域名。但本质要访问到它们的IP地址,当访问域名的时候,先向域名解析服务去解析域名得到IP地址,然后再用IP进行访问了。
DNS是应用层协议,DNS底层使用udp进行解析,浏览器会缓存DNS的结果

NAT技术

NAT技术解决当前IP地址不够用的注意手段,是路由器的一个功能。

NAT能够将私有IP对外通信时转为全局IP. 也就是就是一种将私有IP和全局IP相互转化的技术方法:
很多学校, 家庭, 公司内部采用每个终端设置私有IP, 而在路由器或必要的服务器上设置全局IP;
全局IP要求唯一, 但是私有IP不需要; 在不同的局域网中出现相同的私有IP是完全不影响的

NAPT
那么问题来了, 如果局域网内, 有多个主机都访问同一个外网服务器, 那么对于服务器返回的数据中, 目的IP都是相同
的. 那么NAT路由器如何判定将这个数据包转发给哪个局域网的主机?
这时候NAPT来解决这个问题了. 使用IP+port来建立这个关联关系
**注意:**在进行转换的时候,端口也可能是被改变的

NAT技术的缺陷
无法从NAT外部向内部服务器建立连接; (从外网到内网)
装换表的生成和销毁都需要额外开销;
通信过程中一旦NAT设备异常, 即使存在热备, 所有的TCP连接也都会断开;
image.png

代理服务器

正向代理:多个经过代理服务器去访问
反向代理:请求经过代理服务器去指定让哪个机器去工作

NAT和代理服务器的区别

路由器往往都具备NAT设备的功能, 通过NAT设备进行中转, 完成子网设备和其他子网设备的通信过程.
代理服务器看起来和NAT设备有一点像. 客户端像代理服务器发送请求, 代理服务器将请求转发给真正要请求的服务器; 服务器返回结果后, 代理服务器又把结果回传给客户端.
那么NAT和代理服务器的区别有哪些呢?
从应用上讲, NAT设备是网络基础设备之一, 解决的是IP不足的问题. 代理服务器则是更贴近具体应用, 比 如通过代理服务器进行翻墙, 另外像迅游这样的加速器, 也是使用代理服务器.
从底层实现上讲, NAT是工作在网络层, 直接对IP地址进行替换. 代理服务器往往工作在应用层.
从使用范围上讲, NAT一般在局域网的出口部署, 代理服务器可以在局域网做, 也可以在广域网做, 也可以 跨网
从部署位置上看, NAT一般集成在防火墙, 路由器等硬件设备上, 代理服务器则是一个软件程序, 需要部署
在服务器上.
代理服务器是一种应用比较广的技术.
翻墙: 广域网中的代理.
负载均衡: 局域网中的代理.

翻墙

image.png
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/592295.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

综合能源系统:Modbus转IEC104网关解决方案

Modbus转IEC104网关BE102 方案概述 Modbus和IEC104是两种通信协议&#xff0c;各自适用于不同行业和场景&#xff0c;其中Modbus常见于工业自动化&#xff0c;而IEC104则主导电力行业。在某些项目中&#xff0c;需要将Modbus设备的数据传至IEC104电力平台&#xff0c;但两者协…

[嵌入式系统-65]:RT-Thread-组件:FinSH控制台, 用户与RT Thread OS实时命令行交互工具

目录 FinSH 控制台 1. FinSH 简介 2. FinSH 内置命令 - 内核代码自身提供的命令 显示线程状态 显示信号量状态 显示事件状态 显示互斥量状态 显示邮箱状态 显示消息队列状态 显示内存池状态 显示定时器状态 显示设备状态 显示动态内存状态 3. 自定义 FinSH 命令 …

【5/01-5/03】 Arxiv安全类文章速览

知识星球 首先推荐一下我们的知识星球&#xff0c;以AI与安全结合作为主题&#xff0c;包括AI在安全上的应用和AI本身的安全&#xff1b; 加入星球你将获得&#xff1a; 【Ai4sec】&#xff1a;以数据驱动增强安全水位&#xff0c;涵盖内容包括&#xff1a;恶意软件分析&…

MATLAB中功率谱密度计算pwelch函数使用详解

MATLAB中功率谱密度计算pwelch函数使用详解 目录 前言 一、pwelch函数简介 二、pwelch函数参数说明 三、pxx pwelch(x)示例 四、[pxx,f]pwelch(x,window,noverlap,nfft,fs)示例 四、[pxx,f] pwelch(x,window,noverlap,nfft,fs,freqrange,spectrumtype)示例 五、多通道功…

# cmd 报错 “npm 不是内部或外部命令,也不是可运行的程序 或批处理文件”

cmd 报错 “npm 不是内部或外部命令,也不是可运行的程序 或批处理文件” 1、报错原因分析&#xff1a; Node.js 没有安装或安装不正确。 npm 的路径没有添加到系统环境变量中。 安装 Node.js 时选择了不包含 npm 的安装选项。 2、解决方法&#xff1a; 1&#xff09;在 cm…

【房屋】租房攻略,萌新第一次租房需要考虑的要素(通勤、地段、房源)

【房屋】租房攻略&#xff0c;萌新第一次租房需要考虑的要素&#xff08;通勤、地段、房源&#xff09; 文章目录 1、位置要好&#xff08;通勤近 vs 地段好&#xff09;2、户型要好&#xff08;朝向/楼层&#xff0c;独卫/家具&#xff0c;水电费&#xff09;3、价格要便宜4、…

Github 2024-05-03 Java开源项目日报 Top9

根据Github Trendings的统计,今日(2024-05-03统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9Kotlin项目1C++项目1libGDX: 跨平台Java游戏开发框架 创建周期:4284 天开发语言:Java, C++协议类型:Apache License 2.0Star数量:2…

DDD:根据maven的脚手架archetype生成ddd多模块项目目录结构

随着领域驱动的兴起&#xff0c;很多人都想学习如何进行ddd的项目开发&#xff0c;那ddd的项目结构是怎么样的&#xff1f;又是如何结合SpringBoot呢&#xff1f;那么针对这个问题&#xff0c;笔者使用maven的archetype封装一个相对通用的ddd的项目目录&#xff0c;方便一键生成…

函数模板 template

函数模板的定义和调用 注意&#xff1a; 在调用函数模板时&#xff0c;编译器会根据调用的函数的参数类型自动推导出T的类型。 优先选择普通函数 强制调用函数模板 函数模板不能对函数的参数自动强制类型转换 myPrintAll(10,b)//普通函数&#xff0c;因为普通函数将b强制转换成…

安装vscode基础配置,es6基础语法,

https://code.visualstudio.com/ es6 定义变量 const声明常量&#xff08;只读变量&#xff09; // 1、声明之后不允许改变 const PI “3.1415926” PI 3 // TypeError: Assignment to constant variable. // 2、一但声明必须初始化&#xff0c;否则会报错 const MY_AGE /…

极简单行阅读器:上班族的摸鱼神器

在忙碌的工作日中&#xff0c;我们经常需要寻找一些方式来放松自己&#xff0c;而阅读无疑是一种既能够放松心情&#xff0c;又能增长知识的方式。今天&#xff0c;我要向大家介绍一个名为“极简单行阅读器”的神器&#xff0c;它不仅能够满足你的阅读需求&#xff0c;还能让你…

时也命也!反派失败于错估了主角的实力——早读(逆天打工人爬取热门微信文章解读)

此子断不可留 引言Python 代码第一篇 洞见 人到中年最大的清醒&#xff1a;时也&#xff0c;运也&#xff0c;命也第二篇 人民日报要闻社会政策 结尾 自知之明是最难得的知识 真正的智慧来自于对自己能力和局限的深刻理解 引言 最近在看仙葫 然后昨天晚上刷了一下这个诛仙 发现…

Qt之信号与槽

槽的本质&#xff1a;对信号响应的函数。 信号函数和槽函数通常位于某个类中&#xff0c;和普通的成员函数相⽐&#xff0c;它们的特别之处在于&#xff1a; 信号函数⽤ signals 关键字修饰&#xff0c;槽函数⽤ public slots、protected slots 或者 private slots 修饰。sign…

前端基础学习html-->表单标签

目录 表单标签&#xff1a; 表单域&#xff1a; 表单控件(表单元素)&#xff1a; 提示信息: 表单标签&#xff1a; 表单标签顾名思义就是一种表格&#xff0c;用于收集用户信息 在html&#xff0c;一个完整的表单域是由表单域&#xff0c;表单控件(表单元素)和提示信息组…

揭秘Fabric交易流程:一文带你深入了解

随着区块链技术的日益普及&#xff0c;Hyperledger Fabric作为一种联盟链解决方案&#xff0c;受到了广泛关注。那么&#xff0c;Fabric的交易流程究竟是怎样的呢&#xff1f;本文将为您一一揭晓。 1. Fabric交易的参与方 客户端&#xff1a;交易流程的发起方&#xff0c;发起…

Java web第五次作业

1.在idea中配置好数据源 2、视频案例中只给出了查询所有结果的示例&#xff0c;请自己完成添加、删除、修改操作的代码。以下供参 考。 Delete("delete from emp where id#{id}") public void delete(Integer id); 测试代码 Test public void testDelete(){ empMa…

springboot 整合 knife4j-openapi3

适用于&#xff1a;项目已使用shiro安全认证框架&#xff0c;整合knife4j-openapi3 1.引入依赖 <!-- knife4j-openapi3 --> <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-spring-boot-starter</artifa…

SpringBoot+Vue项目在线视频教育平台

一、前言介绍 本系统采用的数据库是Mysql&#xff0c;使用SpringBoot框架开发&#xff0c;运行环境使用Tomcat服务器&#xff0c;idea是本系统的开发平台。在设计过程中&#xff0c;充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面…

ThreeJS:常见几何体与基础材质入门

在前文《ThreeJS:Geometry与顶点|索引|面》中&#xff0c;我们了解了与Geometry几何体相关的基础概念&#xff0c;也尝试了如何通过BufferGeometry自定义几何体。 常见Geometry几何体 ThreeJS内部也提供了诸多封装好的几何体&#xff0c;常见的Geometry几何体如下图所示&#…

Delta lake with Java--利用spark sql操作数据1

今天要解决的问题是如何使用spark sql 建表&#xff0c;插入数据以及查询数据 1、建立一个类叫 DeltaLakeWithSparkSql1&#xff0c;具体代码如下&#xff0c;例子参考Delta Lake Up & Running第3章内容 import org.apache.spark.sql.SaveMode; import org.apache.spark.…