南宁网站建设牛易飞,怎么做网站推广软件,电影网站建设策划书,房地产网站开发毕业设计 文件操作—IO
文件在计算机中可以代表很多东西
在操作系统中, 文件主要是指硬盘文件
硬盘主要分为机械硬盘和固态硬盘。机械硬盘通过磁头在旋转的磁盘上读取数据#xff0c;适合顺序读取。而固态硬盘则使用闪存芯片来存储数据#xff0c;没有机械部件#xff0c;因此读… 文件操作—IO
文件在计算机中可以代表很多东西
在操作系统中, 文件主要是指硬盘文件
硬盘主要分为机械硬盘和固态硬盘。机械硬盘通过磁头在旋转的磁盘上读取数据适合顺序读取。而固态硬盘则使用闪存芯片来存储数据没有机械部件因此读取速度更快且更耐用。尽管固态硬盘的读取速度相比机械硬盘有很大提升但与内存相比其速度仍然较慢。
内存和硬盘的区别:
内存读取速度快, 硬盘读取速度慢内存会因为断电操作而失去信息, 硬盘则会在较长时间内存储数据
文件系统是操作系统的一个重要组成部分它以多叉树的形式组织文件方便用户通过路径来描述和定位文件。路径分为绝对路径和相对路径。
绝对路径通常以盘符如C:或D:后接反斜杠\及文件夹路径为开头对文件进行精确的位置描述。
相对路径通常是以当前目录作为基准目录使用…(上一级目录)或.(本机目录,可以省略不写)来描述目标文件的位置。
文件大体上可以分为多种类型其中最常见的是二进制文件和文本文件。文本文件主要由字符构成用于存储人类可读的文本信息而二进制文件则包含二进制编码的数据可以是程序、图像、音频等各种类型的数据。
Reader reader new FileReader(D:/test.txt);while (true) {int c reader.read();if(c-1) {break;}System.out.print((char)(c));}方法的返回值是int类型, 因为当文件读取完之后需要返回-1来通知程序, 因为char的对应字符表的值都是非负的
try {while (true) {char[] cbuf new char[3];int n reader.read(cbuf);if(n-1) {break;}for (int i 0; i n; i) {System.out.print(cbuf[i]);}}} finally {reader.close();}传入char类型的数组, 将传入的值存放到数组中, 实际上read(char[] cbuf)方法会根据当前可用的字符数来填充数组并返回实际读取的字符数。如果文件内容少于数组长度它只会读取并返回实际的内容长度如果文件内容大于数组长度则需要多次调用read()方法来读取剩余内容, 使用try-finally可以避免当程序读取过程中因为报出异常而中止使得未执行close()方法
每个进程都有文件描述符表, 使用顺序表, 当获取文件时会存放进去一个, 但是文件描述符表的数组长度有限, 所以一直开启而不关闭就会出错
Java中的char类型固定为两个字节UTF-16编码而中文字符在UTF-8编码中可能需要多个字节。但是当使用FileReader或指定UTF-8编码的InputStreamReader读取文件时Java会自动进行编码转换将UTF-8编码的字节序列转换为UTF-16编码的char序列。
try(Reader reader new FileReader(D:/test.txt)) {while (true) {char[] cbuf new char[3];int n reader.read(cbuf);if(n-1) {break;}for (int i 0; i n; i) {System.out.print(cbuf[i]);}}}也可以使用try来对创建的对象进行包裹, 在程序退出无论正常还是异常退出都会对其进行关闭, 但是传入的类需要实现Closeable接口
public static void main(String[] args) {try (OutputStream outputStream new FileOutputStream(d:/test.txt)) {// 这就相当于把字节流转成字符流了.PrintWriter writer new PrintWriter(outputStream);writer.println(hello);writer.flush();} catch (IOException e) {e.printStackTrace();}}PrintWriter使用了内部缓存来存储数据以减少直接写入输出流在这里是FileOutputStream的次数从而提高性能。这是因为从内存到硬盘的I/O操作通常比内存中的操作要慢得多。通过将多个小的写入操作合并成一个大的写入操作PrintWriter减少了这种性能开销。
当调用flush()方法时PrintWriter会将其内部缓存中的数据写入到输出流中。如果不调用flush()那么缓存中的数据可能不会被写入到输出流直到PrintWriter对象被垃圾回收或显式关闭时才会自动调用flush()。因此在需要确保数据立即写入输出流的情况下显式调用flush()是很重要的。
网络初识
网络通信基础
IP地址
在互联网上进行数据传输就像发送快递一样需要知道收件人的地址。在网络中这个地址就是IP地址。
IP地址使用32位二进制数表示通常由4个字节组成。为了方便阅读和记忆我们通常将IP地址转换为4个0~255之间的十进制数字表示这4个数字之间用点.分隔。这种表示方法称为点分十进制表示法。
虽然知道了IP地址但网络需要精确到哪一个进程需要接收这部分信息这就需要使用端口号来确定。
端口号
为了确定主机上哪个进程负责接收外界信息我们需要使用端口号进行进一步的标识。每个端口号只能绑定一个进程以确保端口号的唯一性。然而一个进程可以绑定多个端口号。端口号的范围通常是065535其中0通常不被使用而11023是保留给一些众所周知的服务的如FTP、HTTP等。这些系统进程会绑定到这些特定的端口上。
协议
网络本质上是通过光或电信号进行数据传输的而协议则是网络的一个重要组成部分。协议是网络中数据发送者和接收者之间共同遵守的规则和约定用于确保数据的正确传输。
协议五元组是用于标识网络中的数据流的包括以下几个要素
目的IP数据包的目标地址即接收方的IP地址。目的端口数据包的目标端口即接收进程绑定的端口号。源IP数据包的源地址即发送方的IP地址。源端口数据包的源端口即发送进程绑定的端口号。协议类型数据包所使用的网络协议类型如TCP、UDP等。
协议分层
由于网络通信的复杂性有时需要将协议拆分成多个小协议来分别处理不同的任务。随着小协议的增多为了方便管理和维护需要对这些协议进行分层管理。
分层管理的好处主要体现在以下几个方面
不同层级之间彼此独立封装性较好一层的变化对其他层的影响较小提高了系统的稳定性和可维护性。每一层的协议可以灵活切换和替换便于技术的更新和升级。上层协议通过调用下层协议提供的服务来实现其功能下层协议为上层协议提供必要的支持和保障。
通过分层管理可以更加高效地组织和管理网络协议促进网络通信的顺利进行。
TCP/IP五层网络模型
TCP/IP五层网络模型从底到顶分别为物理层、数据链路层、网络层、传输层和应用层。物理层是连接网络通信的基础设备它负责传输原始的比特流就好比公路、铁路这些基础设施。数据链路层在物理层之上它负责将网络层交下来的IP数据报组装成帧在两个相邻节点间的链路上实现无差错的帧传输并进行流量控制。网络层负责为分组交换网上的不同主机提供通信服务它确定使用哪一条路径将数据包从源主机发送到目的主机。传输层为应用进程之间提供端到端的逻辑通信它确定起点和终点确保数据的可靠传输。应用层则是对于应用程序上的数据使用它负责处理特定的应用程序协议请求及响应。
在大多数现代计算机系统中操作系统与网络接口卡NIC及其驱动程序协同工作以支持网络协议栈的实现。操作系统主要提供对网络协议栈的支持特别是在网络层、传输层和应用层。网络接口卡NIC及其驱动程序则负责实现物理层和数据链路层的功能。
集线器Hub是工作在物理层的设备它简单地将一个端口的信号复制到其他所有端口允许所有连接的设备在同一时刻进行通信但所有的设备都共享同一个带宽。
封装和分用
当你在QQ中发送一则消息给你的同学时QQ会生成消息内容并通过套接字接口将数据传递给网络协议栈进行封装。首先在传输层数据会被封装成传输层的数据报包括报头包含源端口和目的端口、传输层协议信息等和载荷即QQ生成的消息内容。然后数据报被传递到网络层在网络层数据报会被进一步封装加上IP报头包含源IP地址、目的IP地址、生存时间、协议类型等信息形成IP数据报。接下来IP数据报被传递到数据链路层在数据链路层会在IP数据报的前后分别加上源MAC地址和目的MAC地址形成以太网帧。最后以太网帧被传递到物理层通过光/电信号进行传输。从上层到下层数据经过层层封装确保能够在网络中正确传输。
接收方在接收到传输的光/电信号后会进行一一分用。首先物理层将光/电信号转换成比特流然后传递给数据链路层。在数据链路层接收方会检查MAC地址确保帧是发送给自己的然后去掉MAC头部和尾部将载荷即IP数据报传递给网络层。在网络层接收方会根据IP报头中的信息去掉IP报头将载荷即传输层数据报传递给传输层。在传输层接收方根据端口号将数据报传递给对应的应用程序如QQ。最后QQ程序对载荷进行分析提取出消息内容并进行展示。这个过程就是数据的分用过程确保数据能够正确地从网络层传输到目标应用程序。
网络编程
通过网络两个主机之间可以进行通信基于这种通信功能我们可以实现各种应用需求。在进行网络编程时操作系统会提供API应用程序接口。这些API是应用程序与网络通信之间的桥梁使得应用程序能够与网络建立联系并进行数据交换。在传输层主要的协议有TCP传输控制协议和UDP用户数据报协议。为了支持这两种协议操作系统分别提供了不同的API接口
TCP和UDP的区别与相同点:
二者都支持全双工通信这意味着在一个信道上可以同时进行数据的发送和接收。这种特性使得它们能够满足多种应用场景的需求。TCP是有连接的协议它在通信过程中需要建立连接确保双方都已准备好进行数据传输。这种连接机制使得TCP能够提供可靠的数据传输服务。相比之下UDP是无连接的协议它不需要建立连接就可以直接发送数据报。这种无连接特性使得UDP在实时性要求较高或可以容忍一定数据丢失的场景中更为适用。TCP是可靠传输协议它通过序列号、确认应答、超时重传等机制来保证数据的可靠到达。当数据在传输过程中因网络问题而丢失或损坏时TCP会尝试重新传输这些数据以确保数据的完整性和顺序性。而UDP则不提供这样的可靠性保证它发送的数据报可能会因为网络问题而丢失且UDP不会进行重传。在传输方式上TCP是基于字节流的传输协议它将数据分割成小的TCP数据段进行传输并在接收端按序重新组装成完整的字节流。而UDP则是基于数据报的传输协议它将数据封装在UDP数据报中直接发送每个数据报都有固定的大小限制并且UDP不保证数据报的顺序或可靠性。
UDP回声服务器的实现
import java.io.IOException; // 导入I/O异常类用于处理输入输出错误
import java.net.DatagramPacket; // 导入DatagramPacket类用于UDP通信中封装数据
import java.net.DatagramSocket; // 导入DatagramSocket类用于UDP通信中创建套接字
import java.net.SocketException; // 导入Socket异常类用于处理套接字相关的错误 // 定义一个名为UdpEchoServer的公共类实现UDP回声服务器
public class UdpEchoServer { private DatagramSocket socket; // 私有成员变量用于接收和发送UDP数据包的套接字 // 构造函数初始化UdpEchoServer对象并绑定到指定端口 public UdpEchoServer(int port) throws SocketException { // 初始化DatagramSocket对象并绑定到指定端口准备接收客户端的数据 socket new DatagramSocket(port); } // 定义一个start方法用于启动服务器并监听客户端的请求 public void start() throws IOException { // 无限循环持续监听客户端的请求 while (true) { // 创建一个长度为4096字节的数组用于存储接收到的数据 byte[] buffer new byte[4096]; // 创建一个DatagramPacket对象用于接收客户端发送的数据 DatagramPacket requestPacket new DatagramPacket(buffer, buffer.length); // 调用socket的receive方法接收客户端发送的数据包 socket.receive(requestPacket); // 将接收到的字节数组转换为字符串 String request new String(requestPacket.getData(), 0, requestPacket.getLength()); // 调用process方法处理请求并获取响应 String response process(request); // 创建一个新的DatagramPacket对象用于封装要发送给客户端的响应数据 DatagramPacket responsePacket new DatagramPacket(response.getBytes(), response.length(), requestPacket.getAddress(), requestPacket.getPort()); // 调用socket的send方法发送响应给客户端 socket.send(responsePacket); // 打印接收到的请求和发送的响应信息 System.out.printf([%s:%d] req%s, resp%s\n, requestPacket.getAddress().getHostAddress(), requestPacket.getPort(), request, response); } } // 定义一个私有方法process用于处理请求并返回响应 private String process(String request) { // 这里只是简单地将请求内容作为响应返回实际中可以根据需求进行更复杂的处理 return request; } // 主方法程序的入口点 public static void main(String[] args) { try { // 创建一个UdpEchoServer对象并监听1314端口 UdpEchoServer server new UdpEchoServer(1314); // 调用start方法启动服务器 server.start(); } catch (IOException e) { // 捕获并打印可能出现的I/O异常 e.printStackTrace(); } }
}Socket是操作系统提供的一个通信机制它允许不同进程之间进行数据交换。在操作系统层面Socket可以被视为一种特殊的文件描述符它提供了对底层网络通信的抽象。在Java中我们使用Socket类来创建和管理网络通信的端点使得开发者可以像操作文件一样来进行网络通信。 UDP简单回声客户端的实现
import java.io.IOException; // 导入I/O异常类用于处理输入输出错误 import java.net.*; // 导入网络编程相关的所有类 import java.util.Scanner; // 导入Scanner类用于读取用户输入 // 定义一个名为UdpEchoClient的公共类
public class UdpEchoClient { private DatagramSocket socket; // 私有成员变量用于发送和接收UDP数据包的套接字 private String serverIP; // 私有成员变量存储服务器IP地址 private int serverPort; // 私有成员变量存储服务器端口号 // 构造函数初始化UdpEchoClient对象 UdpEchoClient(String ip, int port) throws SocketException { socket new DatagramSocket(); // 创建一个新的DatagramSocket对象用于网络通信 serverIP ip; // 设置服务器IP地址 serverPort port; // 设置服务器端口号 } // 定义一个start方法用于启动客户端并接收用户输入、发送请求、接收响应 public void start() throws IOException { System.out.println(客户端启动); // 打印客户端启动信息 Scanner scanner new Scanner(System.in); // 创建一个Scanner对象用于从控制台读取用户输入 while (true) { // 无限循环持续等待用户输入 System.out.println(输入请求: ); // 提示用户输入请求 System.out.print(-); // 打印箭头提示用户输入位置 String request scanner.next(); // 读取用户输入的请求 // 创建一个DatagramPacket对象用于封装发送给服务器的请求数据 // 把请求发送给服务器 DatagramPacket requestPacket new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIP), serverPort); // 使用socket发送请求数据包 // 等待服务器的回应 socket.send(requestPacket); // 创建一个DatagramPacket对象用于接收服务器的响应数据 DatagramPacket responsePacket new DatagramPacket(new byte[4096], 4096); // 使用socket接收服务器的响应数据包 socket.receive(responsePacket); // 将响应数据包的内容转换为字符串 String response new String(responsePacket.getData(), 0, responsePacket.getLength()); // 打印服务器返回的响应 System.out.println(response); } } // 主方法程序的入口点 public static void main(String[] args) throws IOException { // 创建一个UdpEchoClient对象设置服务器IP为本地地址127.0.0.1端口号为1314 UdpEchoClient client new UdpEchoClient(127.0.0.1, 1314); // 调用start方法启动客户端 client.start(); }
}TCP回声服务器的实现
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket socket null;// 构造函数,初始化服务器套接字,监听指定端口TcpEchoServer(int port) throws IOException {socket new ServerSocket(port);}public void start() throws IOException {System.out.println(服务器启动);// 创建线程池,用于处理客户端连接ExecutorService service Executors.newCachedThreadPool();while (true) {// 不断接受客户端的连接请求Socket clientSocket socket.accept();// 将客户端连接请求提交给线程池处理service.submit(new Runnable() {Overridepublic void run() {try {// 处理客户端连接processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}}});}}// 处理客户端连接的方法private void processConnection(Socket clientSocket) throws IOException {System.out.printf([%s:%d] 客户端上线\n, clientSocket.getInetAddress(), clientSocket.getPort());// 获取客户端的输入流和输出流try (InputStream inputStream clientSocket.getInputStream();OutputStream outputStream clientSocket.getOutputStream()) {Scanner scanner new Scanner(inputStream);while (true) {// 检查客户端是否还有输入if (!scanner.hasNext()) {System.out.printf([%s:%d] 客户端下线\n, clientSocket.getInetAddress(), clientSocket.getPort());break;}// 获取客户端的输入String receive scanner.next();// 处理客户端的输入String request process(receive);// 将处理结果写入输出流PrintWriter printWriter new PrintWriter(outputStream);printWriter.println(request);printWriter.flush();System.out.printf([%s:%d] receive%s request%s\n, clientSocket.getInetAddress(),clientSocket.getPort(),receive, request);}} catch (IOException e) {e.printStackTrace();} finally {// 关闭客户端连接clientSocket.close();}}// 处理客户端输入的方法,这里只是简单地返回输入的字符串private String process(String s) {return s;}public static void main(String[] args) throws IOException {// 创建服务器对象,监听1314端口TcpEchoServer server new TcpEchoServer(1314);// 启动服务器server.start();}
}TCP简单回声客户端的实现
import java.io.*; // 导入Java的输入输出流相关的库 import java.net.Socket; // 导入Java的Socket类用于建立网络连接 import java.util.Scanner; // 导入Java的Scanner类用于从控制台读取输入 public class TcpEchoClient { // 声明一个名为TcpEchoClient的公共类 Socket socket null; // 定义一个Socket对象初始化为null String ip ; // 定义一个字符串变量用于存储服务器的IP地址 int port 0; // 定义一个整型变量用于存储服务器的端口号 // 构造函数用于创建TcpEchoClient对象时初始化Socket连接 TcpEchoClient(String serverIp, int port) throws IOException { socket new Socket(serverIp, port); // 使用给定的IP地址和端口号创建一个新的Socket连接 } // 定义一个名为start的方法用于启动客户端并处理与服务器的交互 public void start() { Scanner scanner new Scanner(System.in); // 创建一个Scanner对象用于从控制台读取输入 System.out.printf(客户端启动\n); // 打印客户端启动到控制台 try ( // 使用try-with-resources语句自动关闭资源 InputStream inputStream socket.getInputStream(); // 获取Socket的输入流 OutputStream outputStream socket.getOutputStream() // 获取Socket的输出流 ) { while (true) { // 无限循环保持客户端运行 PrintWriter printWriter new PrintWriter(outputStream); // 创建一个PrintWriter对象用于向服务器发送数据 Scanner scannerNetWork new Scanner(inputStream); // 创建一个Scanner对象用于从Socket的输入流读取数据 System.out.printf(- ); // 打印- 到控制台提示用户输入 String send scanner.next(); // 从控制台读取用户输入的数据 printWriter.println(send); // 将用户输入的数据发送到服务器 printWriter.flush(); // 刷新输出流确保数据被发送 String response scannerNetWork.next(); // 从Socket的输入流读取服务器的响应 System.out.println(response); // 打印服务器的响应到控制台 } } catch (IOException e) { // 捕获IO异常 e.printStackTrace(); // 打印异常的堆栈信息 } } // 主方法程序的入口点 public static void main(String[] args) throws IOException { TcpEchoClient client new TcpEchoClient(127.0.0.1, 1314); // 创建一个TcpEchoClient对象连接到本地的1314端口 client.start(); // 启动客户端 }
}网络原理
网络通信依赖于协议而管理这些协议的最佳方式是采用分层结构。TCP/IP分层协议模型主要分为物理层、数据链路层、网络层、传输层和应用层。
物理层定义了数据传输的电气、机械和时序接口特性包括电缆规格、接口卡、数据编码方式等以确保数据在设备之间的正确传输。数据链路层在物理层服务的基础上负责将网络层交下来的IP数据报组装成帧在两个相邻节点间的链路上透明地传输帧并进行差错控制和流量控制。网络层主要负责路径规划决定数据从源主机到目标主机之间所经过的路由路径。传输层为应用进程之间提供端到端的逻辑通信对报文进行分割与重组并以适当的顺序传输给目标系统的对应层次。应用层为特定应用程序提供网络服务。它关心的是应用程序如何使用网络服务而不是数据如何在网络中传输。
应用层
应用层中的自定义协议方式
XML一种古老的标记语言使用标签来管理数据。HTML就是基于XML发展而来的但HTML对标签的使用有严格的限制。XML的优势在于可读性较好但占用的网络带宽较大效率不高。JSON使用大括号包裹数据内部通过键值对的形式表示键值对之间用逗号分隔键和值之间用冒号分隔。JSON的可读性较好同样占用的网络带宽较大但因其简洁明了的结构目前已成为流行的数据交换格式。ProtobufferProtocol Buffers由Google开发的一种数据序列化协议类似于XML、JSON、YAML等。它使用二进制格式对数据进行序列化和反序列化因此具有高效的传输效率和数据压缩率。同时它支持多种编程语言具有良好的跨平台性和跨语言性。然而由于其使用二进制格式可读性不如XML和JSON因此在开发时可能需要借助工具进行序列化和反序列化的处理。
DNS应用层协议
DNS域名系统是应用层协议的一部分用于将便于记忆的域名转换为IP地址后者描述了设备在网络上的位置。由于IP地址难以记忆我们通常使用域名来访问网站。为了管理这些映射关系DNS系统利用一组分布式服务器来存储域名与IP地址的对应关系。
鉴于单一服务器面对大量并发访问时可能会资源不足甚至崩溃DNS架构采用了多级缓存和服务器镜像技术来分散负载。首先许多运营商和大型网络服务提供商部署了镜像服务器来存储根服务器和其他关键DNS服务器的数据。当用户尝试访问某个域名时他们会被引导至最近的镜像服务器这样可以减少对核心DNS服务器的访问压力。
此外DNS查询结果通常会在用户的本地计算机或网络中进行缓存这意味着一旦域名被解析相同的DNS请求在缓存有效期内可以直接从本地获取答案无需重复向DNS服务器发起请求。这种缓存机制进一步减少了对DNS服务器的访问频率从而提高了系统的整体效率和可靠性。
Http协议
HTTP协议超文本传输协议是基于请求与响应模型的无状态协议其中请求和响应的结构存在明显区别。
请求结构
首行 由三部分组成用空格分隔。第一部分是请求方法如GET、POST等第二部分是请求的资源URL第三部分是HTTP版本如HTTP/1.1。请求头 由多个键值对构成每对键值用冒号加空格分隔。每个键值对占用一行。请求头包含客户端环境信息、请求参数等。空行 一个单独的空行表示请求头部的结束。正文Body 请求的内容区域可以包含用户提交的数据。不是所有请求都有正文例如GET请求通常没有正文。
响应结构
首行 由三部分组成用空格分隔。第一部分是HTTP版本号第二部分是状态码如200、404等第三部分是状态消息如OK、Not Found。响应头 结构与请求头类似由键值对组成包含服务器的类型、内容类型、编码方式等信息。空行 标志着响应头部的结束。正文 响应的内容可以包括HTML、图片、文本等多种格式的数据根据请求的不同响应正文的内容和格式也会有所不同。
URL的基本格式
编辑
URL统一资源定位符是用于描述网络资源位置的标准方式。它的基本格式包括以下几个部分
协议方案名 指明使用的网络协议如HTTP、HTTPS、FTP等。登录信息 可选用于身份认证通常包括用户名和密码但现代网页很少在URL中直接使用通常通过网页表单或API进行身份认证。服务器网址 资源所在的服务器地址通常是域名或直接的IP地址。服务器端口号 可选指定服务器上的端口对于采用标准端口的协议如HTTP的80端口或HTTPS的443端口通常省略。带层次的文件路径 描述资源在服务器上的位置类似文件系统的路径但可能指向硬盘上的文件、内存中的资源或其他形式的数据。查询字符串 可选以问号?开头后接一个或多个键值对每对键值用等号连接不同键值对之间用和号分隔。用于传递额外参数或进行数据检索。片段标识符 可选以井号#开始用于直接定位到网页的特定部分常见于技术文档或长文章中。
URL中的特殊字符如空格、问号等需要使用百分号编码URL编码转换以避免解析错误。这种编码方式将特殊字符转化为%后跟两位十六进制数的形式。 GET 和 POST 的区别
用途 GET 用于从服务器请求数据。通常用于检索信息而不会影响资源的状态。POST 用于向服务器提交数据通常会导致服务器状态的变化或产生副作用例如用户登录、数据提交和文件上传等。 安全性 GET 由于请求的数据会附加在 URL 上敏感数据可能会被浏览器缓存或保存在浏览器历史记录中这可能导致安全问题。不推荐使用 GET 方法发送敏感信息。POST 数据包含在请求的正文body中不会在 URL 中显示适用于传输敏感或大量数据。 幂等性 GET 是幂等的意味着多次执行相同的 GET 请求理论上应该得到相同的结果并且不会改变服务器的状态。POST 通常不是幂等的多次提交相同的 POST 请求可能会每次都在服务器上产生副作用。 缓存 GET 请求的响应通常可以被缓存除非特定的缓存指令禁止。POST 由于 POST 请求可能改变服务器状态响应通常不被缓存
Header
Host主机 主机头通常与URL匹配但由于代理或负载均衡器的存在可能会有所不同。它指定了请求的目标主机通常在虚拟主机中使用。Content-Length和Content-Type 这两个属性确实只在HTTP请求中存在消息主体时才会出现。Content-Length指定消息主体的长度以减少分段传输。Content-Type指定正在发送的资源的媒体类型影响服务器对请求的处理方式。User-Agent用户代理 尽管其重要性已经降低但用户代理仍然有助于确定浏览器的功能并处理特定的业务逻辑。最初用于适应不同浏览器的能力但由于标准化的改进和浏览器功能的统一性其重要性已大大降低。Referer引用页 记录了前一个地址通常在首次进入浏览器首页时不存在。它提供了关于请求来源的信息有助于进行分析和跟踪。CookieCookie 由于安全问题网站无法直接访问用户的文件系统。因此临时数据如登录会话被存储在浏览器中以Cookie的形式存在。这些通常由服务器或网页自动生成以键值对的形式组织并根据域名存储在浏览器的文件夹中在后续请求中随请求一起发送到服务器。
https的加密
HTTP协议传递的数据不进行加密一旦被截获信息便可以直接被读取。相对地HTTPS增加了数据传输的安全性通过使用对称加密和非对称加密。非对称加密中公钥用于加密数据私钥用于解密私钥保存在服务器上而公钥则是公开的。通常在建立HTTPS连接时使用非对称加密传送对称密钥因为非对称加密虽然安全但处理速度慢不适合大量数据传输。使用对称密钥加密数据传输提高效率。
在实际应用中可能遇到中间人攻击。攻击者在用户与服务器之间截获并篡改信息用户可能接收到攻击者提供的伪造公钥。为防止此类攻击HTTPS协议采用数字证书验证公钥的合法性。数字证书由可信的证书颁发机构CA签发用户通过浏览器内置的CA公钥验证证书的真实性确保所使用的公钥属实
HTTP响应状态码
2XX类 表示请求成功。这类状态码表明请求已被服务器正确接收、理解和处理。例如200 OK 是成功响应的标准代码。3XX类 表示需要进行重定向。这些状态码告知客户端需要进一步操作以完成请求。例如301 Moved Permanently 指资源已永久改变位置。4XX类 表示客户端错误。这类状态码指出请求含有错误或无法被执行。404 Not Found 表示服务器找不到请求的资源403 Forbidden 表示没有权限访问请求的资源。5XX类 表示服务器错误。这类状态码表明服务器在处理请求时内部发生错误。例如500 Internal Server Error 表示服务器遇到错误无法完成请求。
UDP协议
学习UDP协议主要需要了解其报文结构。UDP报文由报头和载荷两部分组成。载荷是应用层的数据而报头则包含了源端口、目的端口、报文长度和校验和等信息。其中源端口和目的端口各占2个字节用于标识发送方和接收方的应用程序报文长度字段占2个字节表示整个UDP数据报包括报头和载荷的总长度其值在8UDP报头最小长度到65535字节之间校验和字段占2个字节用于检验UDP数据报在传输过程中是否出现错误。
由于网络通信过程中可能会受到各种干扰导致信息传递错误因此UDP协议使用了校验和来检验数据报的完整性。UDP的校验和是基于二进制反码求和的算法对报头和载荷中的每个16位字进行求和然后将结果取反码作为校验和。接收方在收到数据报后会重新计算校验和并与发送方提供的校验和进行比较以验证数据的正确性。
为了提高校验和的可靠性现在常使用MD5等更复杂的算法来计算校验和。MD5算法具有以下特点
定长无论输入数据的大小如何MD5算法都会生成一个固定长度的哈希值128位。离散性MD5算法具有很好的离散性即使输入数据只有微小的差异计算出的哈希值也会有很大的不同。这种特性使得MD5算法非常适合用于哈希表、数据去重等场景。不可逆性在现有计算技术下通过MD5哈希值很难反推出原始数据。这种特性使得MD5算法在密码存储、文件完整性校验等领域得到广泛应用。
TCP协议
确认应答
TCP协议的核心功能是提供可靠的数据传输服务。在TCP协议中发送方在向接收方传输数据后接收方会返回一个确认信息ACK给发送方以告知数据是否已成功接收。这种确认机制确保了数据的可靠传输。
在TCP中数据被分割成一个个的报文段segment每个报文段都有唯一的序列号。当接收方收到数据后它会返回一个包含下一个期望接收的字节序列号的确认信息ACK。这样即使在网络中数据包的传输路径不同、传输速率不同接收方也能根据序列号正确地重新组装数据。
为了避免数据包乱序到达的问题即后发的数据包先到达接收方TCP使用序列号来确保数据包按照正确的顺序被接收方重新组装。
超时重传
在网络环境中当负载过大或线路拥塞时数据包可能会在传输过程中滞留而无法准时到达接收端或者在途中被丢弃。为了保证信息的可靠传输网络协议如TCP会采用超时重传机制。由于丢包的概率通常不是很高因此重传的数据包通常能够成功送达。
超时的时间长度是根据实际情况动态调整的。在发生第一次超时后下一次的超时时间限制会增加这是为了应对可能存在的更多不可预测的网络状况。然而超时时间会有一个最大的时间限制一旦超过这个限制数据包将被视为无法送达并且发送方将放弃该数据包。
导致超时重传的原因可能是数据包在发送时丢失也可能是接收方返回的应答在传输过程中丢失。由于发送方无法直接区分这两种情况因此无论是哪种情况它都会尝试对数据包进行重传。这可能会导致数据包被传输两次但TCP协议具有去重和排序机制能够识别并处理重复的数据包确保数据的完整性和顺序性。TCP的缓冲区会存储数据包及其编号当收到相同编号的数据包时会进行去重处理并按照正确的顺序将数据包传递给接收端。
连接管理
连接管理主要负责管理TCP连接的建立和断开。在建立连接之前通信双方需要确认彼此的网络通畅以及传输能力是否正常这个过程类似于开黑前测试麦克风以确保双方的通信能够正常进行。
三次握手建立连接
TCP连接的建立通常是由客户端发起。首先客户端向服务器发送一个SYNSynchronize Sequence Numbers信号这个信号并不包含应用层的数据只是一个通知告知服务器客户端想要建立连接。服务器收到SYN信号后会返回一个SYNACKSynchronize Acknowledgment信号给客户端表示服务器接收到了客户端的请求并且服务器本身也准备发送SYN信号以测试自己的传输能力。最后客户端在收到服务器的SYNACK信号后会发送一个ACKAcknowledgment信号给服务器表示已经收到并确认了服务器的SYN信号。
注意在三次握手中第二个步骤中服务器返回的SYN和ACK信号并不是被合并成一次发送的而是作为同一个TCP报文段的不同部分被发送的。SYN和ACK是TCP报文段中的标志位用于标识报文段的类型。
三次握手的作用
确认双方接收和传输能力是否正常运行。初始化序列号以便后续通信中能够正确识别并组装数据包。确保网络通畅为数据传输做好准备。
四次挥手断开连接
TCP连接的断开可以由客户端或服务器中的任何一方发起。当一方想要断开连接时会向对方发送一个FINFinish信号告知对方即将关闭连接。接收方在收到FIN信号后会先发送一个ACK信号给发起方表示已经收到并确认了结束连接的请求。然后接收方在完成自己的数据传输后也会向对方发送一个FIN信号告知对方自己也准备关闭连接。发起方在收到对方的FIN信号后会再次发送一个ACK信号给对方表示已经收到并确认了对方的关闭连接请求。至此TCP连接被完全关闭。
注意在四次挥手中为了确保接收方能够收到ACK信号发起方会进入一个TIME_WAIT状态等待一段时间通常为2MSLMSL是报文段在网络中的最大生存时间以确保接收方能够收到ACK信号。如果在这段时间内没有收到对方的重传请求发起方才会真正关闭连接。这是为了防止由于网络拥塞等原因导致的ACK信号丢失。
滑动窗口
虽然TCP使用滑动窗口机制来提高网络信息流通的效率但在某些场景下与UDP相比由于TCP需要确保数据的可靠传输包括顺序和完整性其效率可能不及UDP。UDP不建立连接不保证数据包的顺序和可靠性因此在某些对实时性要求高且对数据可靠性要求不高的应用中UDP可能具有更高的效率。
TCP通过滑动窗口和累积确认Cumulative Acknowledgment的方式允许发送方一次性发送多个数据包而无需等待每个数据包的单独确认。接收方只需确认最近连续接收到的数据包发送方据此继续发送后续数据包。这种方式减少了网络中的往返时间RTT提高了传输效率。
关于丢包问题 丢发送的数据包 当发送方检测到某个数据包丢失通过超时或重复确认时会触发重传机制。TCP使用序列号来标识每个数据包确保即使数据包乱序到达也能按正确的顺序组装。接收方在缓冲区中按照序列号对数据包进行排序直到收到完整的连续数据包序列。 丢失返回的ack应答 TCP使用超时和重复确认来检测丢失的ACK。如果发送方在发送数据包后没有在规定时间内收到ACK或者收到重复的ACK表示某个数据包之后的数据包都已被接收但该数据包丢失则会触发快速重传机制立即重传丢失的数据包。这种方式比简单的超时重传更为高效。
在通信不频繁或数据量较小的情况下TCP的普通确认应答和超时重传机制已经足够。但在通信频繁或数据量较大的情况下滑动窗口和快速重传等机制能够显著提高传输效率。需要注意的是窗口大小的选择需要考虑到网络的带宽、延迟以及接收方的处理能力以避免因窗口过大而导致接收方处理不过来反而降低效率
流量控制
在TCP中每个socket都有数据缓冲区用于存储从发送方接收的数据包。为了降低丢包的风险发送方会根据接收方的当前可用窗口大小即接收方尚未确认的字节数来限制其发送速率。这可以类比于一个水池水池的容量对应于接收方的缓冲区大小而流入水池的水流则对应于发送方的发送速率。如果流入水池的水流太快水池就可能溢出即发生丢包。
随着数据从接收方的缓存中被读取并处理接收方会发送确认报文ACK给发送方告知其已成功接收的字节序列号。随着ACK的发送接收方的可用窗口大小即尚未确认的字节数会动态变化。发送方会结合接收方的当前可用窗口大小来调节其发送速率确保不会发送超过接收方能够处理的数据量。
当接收方的可用窗口大小为0时发送方会暂停发送数据并周期性地发送一个不包含应用层数据的窗口探测包零窗口探测来检测接收方的窗口是否已重新打开。一旦接收方确认窗口已重新打开通过发送一个带有非零窗口大小的ACK发送方就可以继续发送数据。
TCP使用16位的窗口大小字段来表示接收方的可用窗口大小。然而在某些情况下这个大小可能不足以满足高速数据传输的需求。为了支持更大的窗口大小TCP提供了窗口扩展Window Scale选项。在建立连接时发送方和接收方可以协商一个缩放因子将窗口大小字段的实际值乘以这个缩放因子从而得到一个更大的有效窗口大小。
拥塞控制
拥塞控制是TCP协议中的重要机制它主要关注在传输过程中中间节点如路由器、交换机等的负载情况以避免网络拥塞。TCP采用了四种算法来实现拥塞控制慢开始、拥塞避免、快重传和快恢复。
慢开始在建立连接或长时间未发送数据时TCP会采用慢开始算法初始发送一个很小的数据量然后逐渐增大。这样可以探测网络的可用带宽。拥塞避免当发送方探测到网络拥塞的迹象如超时或重复ACK时会降低发送速率以避免进一步的拥塞。快重传当接收方收到一个失序的数据包时会立即发送重复ACK给发送方通知其某个数据包丢失。发送方在收到一定数量的重复ACK后会立即重传丢失的数据包而不需要等待超时。快恢复与快重传配合使用当发送方收到重复ACK时会降低发送速率到慢开始阶段的一半并重新进入拥塞避免阶段。
在拥塞控制过程中TCP会根据网络的实际情况动态地调整发送速率以避免网络拥塞。
流量控制和拥塞控制的关系
流量控制和拥塞控制都会对TCP的窗口大小进行限制。流量控制是基于接收方的处理能力来限制发送方的发送速率而拥塞控制则是基于网络的拥塞情况来限制发送方的发送速率。在实际应用中TCP会取两者中的较小值作为实际的窗口大小。
延时应答
在TCP中接收方在接收到数据包后通常不会立即返回ACK应答给发送方而是会等待一段时间通常称为延迟时间以便查看是否有更多的数据包到达。如果在这段时间内又有数据包到达接收方会将这些数据包的ACK应答合并成一个应答发送给发送方。这样可以减少网络中的ACK报文数量提高网络带宽的利用率。同时由于接收方有更多的时间来读取数据缓存区使得剩余缓存区变大从而可能使得窗口变大提高了数据的传输效率。
捎带应答
捎带应答是TCP协议中的一种优化策略当接收方准备向发送方发送数据时如果它尚未发送对之前接收到的数据段的确认ACK那么接收方会将这个ACK与要发送的数据一起封装在一个TCP报文段中发送出去。这样可以将两次传输合并成一次减少了封装和分用的开销从而提高了传输效率。这种优化策略并不直接依赖于TCP的延迟确认特性而是TCP协议栈的一种实现优化
面向字节流
像TCP这种使用字节流进行传递消息的方式就存在粘包和拆包的现象。为了避免这些问题我们需要使用一种机制来明确消息的边界。这可以通过在消息中添加定长、特殊标记或长度前缀等方式来实现。而自定义协议如XML、JSON、Protocol Buffers等虽然它们定义了数据的结构和编码方式但在使用它们进行网络通信时我们仍然需要一种机制来识别消息的边界
异常的处理情况 进程崩溃 当进程崩溃时操作系统会尝试替进程关闭套接字。这通常会导致TCP正常发送FIN给对端等待对端发送FIN和ACK然后再发送ACK来关闭连接。但具体行为可能取决于操作系统和编程语言的实现。 电脑关机 如果你正常关闭电脑操作系统会负责关闭所有进程和套接字并尝试向对端发送FIN包。但是如果系统突然崩溃或电源被强行关闭可能不会发送FIN包对端将开始超时重传并最终可能进入连接重置状态。 电脑断电 电脑断电是瞬间的进程和操作系统都无法执行任何清理操作包括发送TCP的FIN包。对端将注意到连接不再活跃并开始超时重传。最终对端可能会进入连接重置状态并删除连接信息。 如果应用层实现了心跳包机制它将用于检测连接的活跃性。在多次发送心跳包没有响应后应用层将尝试断开连接。 网线断开 网线断开后TCP连接将失去通信能力。双方都会注意到连接不再活跃并开始超时重传。但是由于物理层的问题TCP的FIN包可能永远不会发送或接收。类似地应用层的心跳包也将无法发送或接收。在这种情况下双方最终都可能进入连接重置状态并删除连接信息。
心跳机制在分布式系统中常用于维护和监测节点间的连接状态确保所有组件能够有效地通信。一般而言分布式应用通常会设计自己的应用层心跳机制而不是依赖于底层传输协议如TCP的机制因为TCP协议标准中并未包含内置的心跳功能。应用层的心跳机制允许开发者自定义心跳频率和超时机制从而更适应各种不同场景的具体需求。
TCP提供可靠传输保证数据的顺序性、可靠性和数据完整性非常适合于需要高可靠性的网络通信场景。TCP通过使用确认和重传机制来保证数据包的正确传达因此广泛用于文件传输、网页浏览等场合。
相比之下UDP是一种无连接的协议它不保证数据包的顺序或可靠传达但提供了较低的延迟和较少的协议开销。这使得UDP特别适合于对实时性要求很高的应用例如视频流、VoIP语音通信以及局域网内的高效率数据传输。在机房等受控环境中数据的安全性和完整性可以通过其他方式保障因此使用UDP可以提高通信效率。
网络层
网络层主要有两个基本功能一是通过IP地址来标识网络上的设备位置确保数据包能被正确路由到目的地二是管理数据包在发送源和目的地之间的传输路径包括数据包的生成、传输以及接收过程。
IP协议详解
4位版本号 当前主流的IP协议版本为IPv4和IPv6。IPv4地址长度为32位而IPv6地址长度则扩展至128位以应对互联网快速增长的地址需求。4位首部长度 这一字段规定了IP首部的最小和最大长度。首部的基本长度是20字节通过首部长度字段可以扩展到60字节以包含更多的选项。8位服务类型 旧的IPv4头部中的服务类型字段ToS已部分被DSCP区分服务代码点和ECN显式拥塞通知所替代。ToS中的4位被用于DSCP以实现服务质量QoS管理。16位总长度 这一字段表示IP数据包的总长度包括首部和数据载荷。总长度最大可达65535字节。16位标识、3位标志位与13位片偏移 这些字段共同管理IP分片。标识字段帮助重新组装分片后的数据包而标志位中的MF更多分片和DF禁止分片控制分片行为。16位首部校验和 这是用于检测IP首部在传输过程中是否发生错误的校验和类似于TCP和UDP的校验机制。32位源IP地址与32位目的IP地址 这两个字段标识了数据包的发送者和接收者的IP地址。
NAT机制与内外网交互
NAT网络地址转换是一种广泛使用的技术它允许多个设备共享一个公共IP地址进行互联网访问同时保持内网IP地址的私有性。在NAT环境中内部设备的私有IP地址会被转换为公共IP地址同时端口号也可能被重新映射以区分来自同一内网的不同请求。这使得内网设备可以主动访问外网但外网设备无法直接访问内网设备从而增强了网络的安全性。
通过这种机制NAT不仅解决了IPv4地址短缺的问题也为网络设备提供了一层额外的安全保护
网段划分
IP地址由网络号和主机号两部分组成。在同一局域网LAN中所有设备的网络号必须相同而主机号必须唯一以确保各个设备的地址不重复。
不同局域网可以连接到同一路由器。这些局域网可以配置相同或不同的网络号这取决于网络的具体设计需求和子网掩码的配置。
子网掩码用于区分IP地址中的网络号和主机号。子网掩码中连续的‘1’位代表网络号而‘0’位代表主机号。子网掩码中的‘1’位始终位于‘0’位之前这样才能正确地区分出网络地址和主机地址。
路由选择
在网络通信中IP数据报的传输并非像地图上的已知路径那样预先规划而是根据实际网络环境动态进行探索。每个路由器都维护着一张路由表其中记录了相邻路由器及其对应的网络。当接收到数据报时路由器会根据目标IP地址查询路由表如果找到匹配项则将数据报转发到相应的网络接口如果没有匹配项则按照默认路由进行转发。
数据链路层
以太网协议是一种常见的数据链路层协议同时涵盖了物理层的功能。以太网数据帧由帧头、数据部分和帧尾组成。帧头包含了目标地址和源地址它们是MAC地址即网络地址的表示形式。以太网协议主要关注相邻节点之间的数据传输和转发。与之相反网络层则更关注数据的起始点和终点以实现跨网络的数据传输。