网络初识

前言

之前对于网络这块的知识一直是零零散散的,这次抽空好好的做个总结。

先给出OSI的7层网络模型与TCP/IP四层模型以及对应的网络协议

网络分层以及对应的协议.png

先说OSI七层网络模型:

  • 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由 1、0 转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的模数转换与数模转换)。这一层的数据叫做比特
  • 数据链路层:主要将从物理层接收的数据进行 MAC 地址(网卡的地址)的封装与解封装。常把这一层的数据叫做。在这一层工作的设备是交换机,数据通过交换机来传输。
  • 网络层:主要将从下层接收到的数据进行 IP 地址(例 192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包
  • 传输层:定义了一些传输数据的协议和端口号(WWW 端口 80 等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与 TCP 特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如 QQ 聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段进行传输,到达目的地址后在进行重组。常常把这一层数据叫做
  • 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或或者接受会话请求(设备之间需要互相认识可以是 IP 也可以是 MAC 或者是主机名)
  • 表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等))
  • 应用层 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(你就把它理解成我们在电脑屏幕上可以看到的东西.就 是终端应用)。

再以TCP/IP分层模型来说:

  • 数据链路层(Network Access Layer)在 TCP/IP 参考模型中并没有详细描述,只是指出主机必须使用某种协议与网络相连。
  • 网络层(Internet Layer)是整个体系结构的关键部分,其功能是使主机可以把分组发往任何网络,并使分组独立地传向目标。这些分组可能经由不同的网络,到达的顺序和发送的顺序也可能不同。高层如果需要顺序收发,那么就必须自行处理对分组的排序。互联网层使用因特网协议(IP,Internet Protocol)。
  • 传输层(Tramsport Layer)使源端和目的端机器上的对等实体可以进行会话。在这一层定义了两个端到端的协议:传输控制协议(TCP,Transmission Control Protocol)和用户数据报协议(UDP,User Datagram Protocol)。TCP 是面向连接的协议,它提供可靠的报文传输和对上层应用的连接服务。为此,除了基本的数据传输外,它还有可靠性保证、流量控制、多路复用、优先权和安全性控制等功能。UDP 是面向无连接的不可靠传输的协议,主要用于不需要 TCP 的排序和流量控制等功能的应用程序。
  • 应用层(Application Layer)包含所有的高层协议,包括:虚拟终端协议(TELNET,TELecommunications NETwork)、文件传输协议(FTP,File Transfer Protocol)、电子邮件传输协议(SMTP,Simple Mail Transfer Protocol)、域名服务(DNS,Domain Name 13/04/2018 Page 161 of 283 Service)、网上新闻传输协议(NNTP,Net News Transfer Protocol)和超文本传送协议(HTTP,HyperText Transfer Protocol)等。

TCP与UDP的区别
TCP UDP
面向连接 无连接
可靠,保证顺序,不会丢包 不可靠,不保证顺序,会丢包
需要资源多,效率低,速度慢 需要资源少,效率高,速度快
只支持点对点通信 支持一对一、一对多、多对一、多对多的通信模式
是面向字节流的 UDP是面向报文的
有拥塞控制机制 没有拥塞控制,适合媒体通信
TCP的报文格式
TCP报文结构.png
  • 端口号:识别同一台计算机里的不同程序进程,TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接
  • 序号和确认号:TCP可以可靠传输的关键部分。序号是本报文发送数据组的第一个字节的序号,在TCP传输的流中每一个字节就是一个序号。eg.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。
  • 数据偏移/首部长度:4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。
  • 保留:为将来定义新的用途保留,现在一般置0。
  • 控制位:URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。
    1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
    2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
    3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
    4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
    5)SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
    6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
  • 窗口 滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。
  • 校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。
  • 紧急指针:只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
  • 选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。
  • 数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。

TCP的三次握手与四次挥手过程,各个状态名称与含义
三次握手.png
  • 三次握手(我要和你建立链接,你真的要和我建立链接么,我真的要和你建立链接,成功)

第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。

第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

  • 四次挥手
    这是由于 TCP 的半关闭造成的。因为 TCP 连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭。这个单方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个 FIN 来向另一方通告将要终止这个方向的连接。
    四次挥手.png

第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。此时TCP链接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。

第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

为什么TCP链接需要三次握手,两次不可以么,为什么?

为了防止已失效的链接请求报文突然又传送到了服务端,因而产生错误。

客户端发出的连接请求报文并未丢失,而是在某个网络节点长时间滞留了,以致延误到链接释放以后的某个时间才到达Server。这是,Server误以为这是Client发出的一个新的链接请求,于是就向客户端发送确认数据包,同意建立链接。若不采用“三次握手”,那么只要Server发出确认数据包,新的链接就建立了。由于client此时并未发出建立链接的请求,所以其不会理睬Server的确认,也不与Server通信;而这时Server一直在等待Client的请求,这样Server就白白浪费了一定的资源。若采用“三次握手”,在这种情况下,由于Server端没有收到来自客户端的确认,则就会知道Client并没有要求建立请求,就不会建立链接。

TCP协议如何来保证传输的可靠性
  • 数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时TCP发送数据端超时后会重发数据;
  • 对失序数据包重排序:既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层;
  • 丢弃重复数据:对于重复数据,能够丢弃重复数据;
  • 应答机制:当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒;
  • 超时重发:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段;
  • 流量控制:TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP使用的流量控制协议是可变大小的滑动窗口协议。
TCP的拥塞处理

从输入网址到获得页面的过程

(1). 浏览器查询 DNS,获取域名对应的IP地址:具体过程包括浏览器搜索自身的DNS缓存、搜索操作系统的DNS缓存、读取本地的Host文件和向本地DNS服务器进行查询等。对于向本地DNS服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询;

(2). 浏览器获得域名对应的IP地址以后,浏览器向服务器请求建立链接,发起三次握手;

(3). TCP/IP链接建立起来后,浏览器向服务器发送HTTP请求;

(4). 服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器;

(5). 浏览器解析并渲染视图,若遇到对js文件、css文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源;

(6). 浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。

Http怎么处理长连接

在HTTP1.0和HTTP1.1协议中都有对长连接的支持。其中HTTP1.0需要在request中增加Connection: keep-alive header才能够支持,而HTTP1.1默认支持。

C/S模式下使用socket通信,几个关键函数

编写一个基于UDP传输协议的网络编程:
先构建服务端

package com.suxin.socket.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Mysever {

    /**
     *  1.创建服务端+端口
     *  2.准备接受容器
     *  3.封装成包
     *  4.接收数据
     *  5.分析数据
     *  6.释放资源
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        //创建服务端以及端口
        DatagramSocket server = new DatagramSocket(8899);
        // 构造接收器
        byte[] container = new byte[1024];
        // 使用构造器打包
        DatagramPacket packet = new DatagramPacket(container,container.length);
        boolean flag = true;
        while(flag) {
            server.receive(packet);//接受数据
            byte[] bytes = packet.getData();
            int len = packet.getLength();
            String s = new String(bytes,0,len);
            System.out.println(s);
            if(s.equals("quit server")) {
                flag = false;
            }
        }
        server.close();
    }
}

继续构建客户端:

package com.suxin.socket.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.Scanner;

public class Myclient {
    
    public static void main(String[] args) throws IOException {
        //创建服务端以及端口
        DatagramSocket client = new DatagramSocket(6666);
        //准备数据
        Scanner sc = new Scanner(System.in);
        boolean flag = true;
        while(flag) {
            System.out.println("请输入传输数据(以enter结束):");
            String message = sc.nextLine();
            byte[] b = message.getBytes();
            //打包发送
            DatagramPacket packet = new DatagramPacket(b,b.length,new InetSocketAddress("127.0.0.1",8899));
            client.send(packet);
            System.out.println("数据:"+message+"已经发送");
            if(message.equals("close")) {
                flag = false;
            }
        }
        client.close();
    }
}

如果我们关闭服务端,运行客户端,依然可以成功。但是此时并没有消息显示。说明即使在服务端不开启的情况下,基于这种协议的依然可以执行,但是数据会丢失。注意:数据传输的过程中,必须使用字节数组。输入quit server以后还是可以继续发送消息的。

编写一个基于TCP协议的网络编程:
服务端:

package com.suxin.socket.tcp;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8899);

        Socket socket = server.accept();//等待接受外部请求
        System.out.println("客户端建立连接");
        String message = "欢迎使用";
        
        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
        dataOutputStream.writeUTF(message);

        dataOutputStream.flush();
    }
}

客户端:

package com.suxin.socket.tcp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class MyClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8899);
        DataInputStream input=new DataInputStream(socket.getInputStream());
        String echo = input.readUTF();
        System.out.println(echo);
        DataOutputStream output = new DataOutputStream(socket.getOutputStream());
        output.writeUTF("i love u");
        System.out.println("[i love u]数据已经发送");
    }
}

以上就是UDP/IP与TCP/IP的基本实现。但是,我们知道,TCP/Ip的实现仅仅是实现了一个客户端的连接,这是不符合实际的,实际情况下服务器一般都是有多个客户端的连接。这也很好解决,我们只需要在服务器端加一个循环,对于每一次来自客户端的请求,服务器端的Accept()方法都会返回一个Socket对象,这样就实现了多个连接。


参考链接
UDP/IP与TCP/IP协议 带编程解读
网络相关的面试题
面试/笔试第一弹 —— 计算机网络面试问题集锦
3分钟看懂什么是三次握手/四次挥手
java socket编程

推荐阅读更多精彩内容