I/O
1、什么是 IO?
I/O为input(输入)/ output(输出)的简称
input
代表将数据从其他地方写入程序,例如从文件中读取文件,将文件内容保存在程序内存中
output
代表程序将数据输出到某些地方,例如利用程序向文件中保存内容等,在Linux系统中,有一切皆文件的概念,不管对于任何文件,设备,网络设备等,在Linux下都被当做文件来进行处理;
2、常用的 IO 类有哪些?
深入理解Java中的IO
在整个Java.io包中最重要的就是5个类和一个接口。
5个类指的是: File、InputStream、OutputStream、Writer、Reader;
一个接口指的是: Serializable
常用的IO 类
FileInputStream
FileOutputStream
BufferedInputStream
BufferedOutputStream
FileReader
FileWriter
BufferedReader
BufferedWriter
常用流的介绍
1)对文件进行操作:FileInputStream
(字节输入流),FileOutputStream
(字节输出流),FileReader
(字符输入流),FileWriter
(字符输出流)
2)对管道进行操作:PipedInputStream
(字节输入流),PipedOutStream
(字节输出流),PipedReader
(字符输入流),PipedWriter
(字符输出流)
PipedInputStream的一个实例要和PipedOutputStream的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。
3)字节/字符数组:ByteArrayInputStream
,ByteArrayOutputStream
,CharArrayReader
,CharArrayWriter
是在内存中开辟了一个字节或字符数组。
4)Buffered缓冲流:BufferedInputStream
,BufferedOutputStream
,BufferedReader
,BufferedWriter
,是带缓冲区的处理流,缓冲区的作用的主要目的是:避免每次和硬盘打交道,提高数据访问的效率。
5)转化流:InputStreamReader
:在读入数据的时候将字节转换成字符。OutputStreamWriter
:在写出数据的时候将字符转换成字节。
6)数据流:DataInputStream
,DataOutputStream
。因为平时若是我们输出一个8个字节的long类型或4个字节的float类型,那怎么办呢?可以一个字节一个字节输出,也可以把转换成字符串输出,但是这样转换费时间,若是直接输出该多好啊,因此这个数据流就解决了我们输出数据类型的困难。数据流可以直接输出float类型或long类型,提高了数据读写的效率。
7)打印流:printStream
,printWriter
,一般是打印到控制台,可以进行控制打印的地方和格式,其中的 print方法不会抛出异常,可以通过checkError方法来查看异常。
8)对象流:ObjectInputStream
,ObjectOutputStream
,把封装的对象直接输出,而不是一个个在转换成字符串再输出。
9)RandomAccessFile随机访问文件
10)ZipInputStream、ZipOutputStream读取zip文档 getNextEntry、putNextEntry 得到或创建ZipEntry对象。
3、你怎么理解 IO、BIO、NIO、AIO?
-
IO: 阻塞IO。
-
BIO: 同步阻塞IO。
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO: 同步非阻塞IO。
服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到
多路复用器
上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这需要用户进行不停的去询问。NIO的包括三个核心概念:缓冲区(Buffer)
、通道(Channel)
、选择器(Selector)
。
- AIO: 异步非阻塞IO。
最大的特性时具有异步能力,这种能力对 socket 与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。
4、什么是比特(Bit)、字节(Byte)、字符(Char)?
位(bit):是计算机中存储数据的最小单位,是二进制数中的一个位数,值为“0”或“1”。
字节(byte):计算机中存储数据的单元,是一个8位的二进制数,一个具体的存储空间。如0x01,0xFA。。。(计算机内部,一个字节可表示一个数据、一个英文字母,两个字节可表示一个汉字。1B=8bit)
字符(char):人们使用的一个记号,只是抽象意义上的一个符号。如‘1’,‘中’,‘¥’。。。
ASCII 编码 一个英文字母(不分大小写)占1个字节的空间,一个中文汉字占2个字节的空间。
GB 2312/GBK 编码中,一个汉字字符存储需要2个字节。
UTF-8 编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节
5、Java 有哪几种类型的流?
首先应该从两个角度来看:
从输入输出方面来讲: Java中有 输入流
和 输出流
从流的编码方式来讲: Java中有 字节流
(byte)和 字符流
(char)
输出流:OutputStream, OutputStreamReder
输入流:InputStream, InputStreamReader
对于字节流而言:主要继承的抽象类为 InputStream和OutputStream
对于字符流而言:主要继承的抽象类为 InputStreamReader和OutputStreamReder
6、字节流和字符流的区别?
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区
的
字节流与字符流主要的区别是他们的的处理方式
字节流是最基本的,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的
但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化
这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联
在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的
在从字节流转化为字符流时,实际上就是byte[]转化为String时,
public String(byte bytes[], String charsetName)
有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的lang
而在字符流转化为字节流时,实际上是String转化为byte[]时,
byte[] String.getBytes(String charsetName)
也是一样的道理
至于java.io中还出现了许多其他的流,按主要是为了提高性能和使用方便,
如BufferedInputStream,PipedInputStream等
7、Java 序列化是什么?
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
8、怎么序列化一个对象?
序列化和反序列化的详解
实现序列化:
-
1)用流操作序列化: 让类实现Serializable接口,标注该类对象是可被序列, 使用输出流ObjectOutputStream/ 输入流ObjcetInputStream 进行序列化和反序列化
-
2)JSON工具: JSON.toJSONString(obj对象) 进行序列化, JSON.parseObject(str, 类.class) 进行反序列化 (String类实现了Serializable接口)
序列化的意义
-
①将对象转为字节流
存储到硬盘
上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。 -
②序列化成字节流形式的对象可以进行
网络传输
(二进制形式),方便了网络传输。 -
③通过序列化可以在进行远程调用时
进程间传递对象
。
9、Java 有哪两种序列化方式?
-
Serializable
:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。 -
Externalizable
:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;
但是如果你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient
(瞬态的变量)就可以实现了。
10、怎么控制类中的某些变量不被序列化?
-
Externalizable
接口:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性; -
Serializable
接口,并在要隐藏的属性前面加上transient
(瞬态的变量)就可以实现了。
11、静态变量能不能被序列化?阐述静态变量和实例变量的区别。
静态变量(static
)不属于对象,属于类。不能被序列化。
还有瞬态的变量(transient
)也不能被序列化 。如果您不想序列某变量/字段就将其标记为瞬态。比如银行余额,信用卡详细信息等。
这里的不能序列化的意思,是序列化信息中不包含这个静态成员域
静态变量和实例变量的区别
静态变量: 是被static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝, 可以实现让多个对象共享内存, 同时也存在线程安全问题;
实例变量: 就是相当于该类的属性, 必须依存于某一实例, 需要先创建对象然后通过对象才能访问到它。
区别如下:
1,两个变量的生命周期不同。
成员变量: 随着对象的创建而存在,随着对象的回收而释放
静态变量: 随着类的加载而存在,随着类的卸载而释放(类不消失一直存在(不考虑回收机制),生命周期长)
2,调用方式不同。
成员变量: 只能被对象调用
静态变量: 可以被对象调用,还可以被类名调用(建议使用类名调用,便于区分)
3,内存分配方式不同。
成员变量: 必须创建了实例对象, 并初始化
静态变量: 不用创建任何实例对象,静态变量就会在类加载的时候被分配空间
4,数据存储位置不同。
成员变量: 数据存储在堆内存的对象中,所以也叫对象的特有数据。
静态变量: 数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据
12. 写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。
public static int countWordInFile(String filename, String word) {
int counter = 0;
try (FileReader fr = new FileReader(filename)) {
try (BufferedReader br = new BufferedReader(fr)) {
String line = null;
while ((line = br.readLine()) != null) {
int index = -1;
while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
counter++;
line = line.substring(index + word.length());
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return counter;
}
13. 如何用Java代码列出一个目录下所有的文件?
class Test12 {
public static void main(String[] args) {
File f = new File("/Users/Hao/Downloads");
for(File temp : f.listFiles()) {
if(temp.isFile()) {
System.out.println(temp.getName());
}
}
}
}
如果需要对文件夹继续展开,代码如下所示:
class Test12 {
public static void main(String[] args) {
showDirectory(new File("/Users/Hao/Downloads"));
}
public static void showDirectory(File f) {
_walkDirectory(f, 0);
}
private static void _walkDirectory(File f, int level) {
if(f.isDirectory()) {
for(File temp : f.listFiles()) {
_walkDirectory(temp, level + 1);
}
}
else {
for(int i = 0; i < level - 1; i++) {
System.out.print("\t");
}
System.out.println(f.getName());
}
}
}
在Java 7中可以使用NIO.2的API来做同样的事情,代码如下所示:
class ShowFileTest {
public static void main(String[] args) throws IOException {
Path initPath = Paths.get("/Users/Hao/Downloads");
Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}
});
}
}
14、用Java的套接字编程实现一个多线程的回显(echo)服务器。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class EchoServer {
private static final int ECHO_SERVER_PORT = 6789;
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) {
System.out.println("服务器已经启动...");
while(true) {
Socket client = server.accept();
new Thread(new ClientHandler(client)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static class ClientHandler implements Runnable {
private Socket client;
public ClientHandler(Socket client) {
this.client = client;
}
@Override
public void run() {
try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter pw = new PrintWriter(client.getOutputStream())) {
String msg = br.readLine();
System.out.println("收到" + client.getInetAddress() + "发送的: " + msg);
pw.println(msg);
pw.flush();
} catch(Exception ex) {
ex.printStackTrace();
} finally {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:上面的代码使用了Java 7的TWR语法,由于很多外部资源类都间接的实现了AutoCloseable接口(单方法回调接口),因此可以利用TWR语法在try结束的时候通过回调的方式自动调用外部资源类的close()方法,避免书写冗长的finally代码块。(java TWR是怎么优雅我们的代码的?) 此外,上面的代码用一个静态内部类实现线程的功能,使用多线程可以避免一个用户I/O操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。
下面是一段回显客户端测试代码:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class EchoClient {
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost", 6789);
Scanner sc = new Scanner(System.in);
System.out.print("请输入内容: ");
String msg = sc.nextLine();
sc.close();
PrintWriter pw = new PrintWriter(client.getOutputStream());
pw.println(msg);
pw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
System.out.println(br.readLine());
client.close();
}
}
如果希望用NIO的多路复用套接字实现服务器,代码如下所示。NIO的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class EchoServerNIO {
private static final int ECHO_SERVER_PORT = 6789;
private static final int ECHO_SERVER_TIMEOUT = 5000;
private static final int BUFFER_SIZE = 1024;
private static ServerSocketChannel serverChannel = null;
private static Selector selector = null; // 多路复用选择器
private static ByteBuffer buffer = null; // 缓冲区
public static void main(String[] args) {
init();
listen();
}
private static void init() {
try {
serverChannel = ServerSocketChannel.open();
buffer = ByteBuffer.allocate(BUFFER_SIZE);
serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT));
serverChannel.configureBlocking(false);
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void listen() {
while (true) {
try {
if (selector.select(ECHO_SERVER_TIMEOUT) != 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handleKey(key);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void handleKey(SelectionKey key) throws IOException {
SocketChannel channel = null;
try {
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
channel = serverChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
channel = (SocketChannel) key.channel();
buffer.clear();
if (channel.read(buffer) > 0) {
buffer.flip();
CharBuffer charBuffer = CharsetHelper.decode(buffer);
String msg = charBuffer.toString();
System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
channel.write(CharsetHelper.encode(CharBuffer.wrap(msg)));
} else {
channel.close();
}
}
} catch (Exception e) {
e.printStackTrace();
if (channel != null) {
channel.close();
}
}
}
}
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public final class CharsetHelper {
private static final String UTF_8 = "UTF-8";
private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
private CharsetHelper() {
}
public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
return encoder.encode(in);
}
public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
return decoder.decode(in);
}
}
15、XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?
XML文档定义方式:有两种定义形式,dtd文档类型定义和schema模式
本质区别:schema本身是xml的,可以被XML解析器解析(这也是从DTD上发展schema的根本目的)
普通区别:
-
schema 是内容开放模型,可扩展,功能性强,而DTD可扩展性差。
-
shema 支持丰富的数据类型,而 DTD不支持元素的数据类型,对属性的类型定义也很有限。
-
schema 支持命名空间机制,而DTD不支持。
-
schema 可针对不同情况对整个XML 文档或文档局部进行验证;而 DTD缺乏这种灵活性。
-
schema 完全遵循XML规范,符合XML语法,可以和DOM结合使用,功能强大;而DTD 语法本身有自身的语法和要求,难以学习。
解析XML文档方式:
-
DOM解析: DOM的全称是Document Object Model,也即文档对象模型。在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树),应用程序正是通过对这个对象模型的操作,来实现对XML文档数据的操作。通过DOM接口,应用程序可以在任何时候访问XML文档中的任何一部分数据,因此,这种利用DOM接口的机制也被称作随机访问机制。
-
SAX解析:SAX的全称是Simple APIs for XML,也即XML简单应用程序接口。与DOM不同,SAX提供的访问模式是一种顺序模式,这是一种快速读写XML数据的方式。当使用SAX分析器对XML文档进行分析时,会触发一系列事件,并激活相应的事件处理函数,应用程序通过这些事件处理函数实现对XML文档的访问,因而SAX接口也被称作事件驱动接口。
-
JDOM解析:JDOM采用了Java中的Collection架构来封装集合,是Java爱好者更加熟悉的模式
-
DOM4J解析:xml解析器一次性把整个xml文档加载进内存,然后在内存中构建一颗Document的对象树,通过Document对象,得到树上的节点对象,通过节点对象访问(操作)到xml文档的内容
16、你在项目中哪些地方用到了XML?
XML的主要作用:数据交换
和信息配置
。
在做数据交换时,XML将数据用标签组装起来,然后压缩打包加密后通过网络传送给接收者,接收者解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON(JavaScript Object Notation)取而代之。
当然,目前很多软件仍然使用XML来存储配置信息,我们在很多项目中通常也会将作为配置信息的硬代码写在XML文件中,Java的很多框架也是这么做的,而且这些框架都选择了dom4j作为处理XML的工具,因为Sun公司的官方API实在不怎么好用。
补充:现在有很多时髦的软件(如Sublime)已经开始将配置文件书写成JSON格式,我们已经强烈地感受到XML的另一项功能也将逐渐被业界抛弃。
网络编程
1、OSI 的七层模型都有哪些?TCP/IP五层模型?四层?
2、TCP/IP是什么?
TCP/IP是一个协议族,而不是单独的协议。包括arp、ip、icmp、tcp、udp、http、ftp等协议
3、TCP 和 UDP 协议的区别?
- 1、TCP面向连接的; UDP是无连接的,即发送数据之前不需要建立连接
- 2、TCP提供可靠的服务, 传送的数据,无差错,不丢失,不重复,且按序到达; UDP尽最大努力交付,不保证可靠交付
- 3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流; UDP是面向报文的, UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
- 4、每一条TCP连接只能是点到点的; UDP支持一对一,一对多,多对一和多对多的交互通信
- 5、TCP首部开销20到80字节; UDP的首部开销小,只有8个字节
- 6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
TCP报文结构:
UDP报文结构:
4、TCP三次握手; 两次不行吗?
TCP/IP详解
图解TCP/IP详解
seq
:(Sequence Number):本报文段数据的第一个字节的序号
ack
:(Acknowledgment Number):确认号——期望收到对方下个报文段的第一个数据字节的序号
SYN
(synchronize):请求同步标志——用于建立和释放连接,当SYN=1时,表示建立连接。
ACK
(acknowledge):确认标志——仅当 ACK=1时确认号字段才有效。建立 TCP 连接后,所有报文段都必须把 ACK 字段置为 1。
FIN
(Finally):结束标志——用于释放连接,当 FIN=1,表明发送方已经发送完毕,要求释放TCP连接。
三次握手流程
第一次握手
:客户端向服务器端发送连接请求SYN包 (SYN=1, seq=x),并进入SYN_SEND状态, 等待服务器回应;
第二次握手
:服务器端收到请求包后,将客户端的请求包 SYN=1(seq=x)放入到自己的未连接队列,此时服务器需要发送两个包(SYN包和ACK包)给客户端(SYN=1, ACK=1, seq=y, ack=x+1), 此时服务器进入SYN_RECV状态。
第三次握手
:客户端收到服务器的包后,知道服务器同意建立连接;向服务器发送连接建立的确认ACK包 (ACK=1, seq=x+1, ack=y+1),告诉服务器已经收到你的确认信息了,我们可以正式链接, 进行数据通信了。服务器收到后,此时服务器与客户端进入ESTABLISHED状态,开始进行数据传送。
为什么要三次握手?
握手的过程实际上是在通知对方自己的初始化序号seq
(Initial Sequence Number),简称ISN,也就是上图中的x和y。x和y会被当作之后传输数据的一个依据,以保证TCP报文在传输过程中不会混乱。
解决两个问题:
1、避免连接请求的数据包丢失
假设连接途中,客户端网络不稳定出现丢包,服务端根据seq=x来确定客户端请求到第几个包。然后告诉客户端你从第seq=x个包开始发送给我,之前的不用发送了,我这里有记录了。
2、数据传输过程因为网络并发量很大在某结点被阻塞
传输过程因为网络并发量很大在某结点被阻塞了,Server端将先后收到2次请求,并持续等待两个Client请求向他发送数据,但是Cient端实际上只有一次请求,而Server端却有2个响应,极端的情况可能由于Client端多次重新发送请求数据而导致Server端最后建立了N多个响应在等待,因而造成极大的资源浪费!
三次握手的seq与ack确定了包的顺序。客户端每次请求时,询问服务端说这是第一号包,服务端收到后告诉客服端下次你给我的只能是二号包(别的都不要),同时给返回到客户端的包作标记:这是我返回给你的一号包。这样,出现阻塞时,根据包的序号就知道要响应的是几号包。
如果没有第三次握手, 服务端会认为客户端没有收到服务端返回的确认包, 所以会重试发送第二次握手的包, 重试次数*
5、TCP四次挥手
四次挥手流程
第一次挥手
:客户端向服务器端发送一个断开请求FIN包(结束标志FIN=1, seq=u),并进入FIN-WAIT-1状态。
第二次挥手
:服务器收到FIN包后,先向客户端发送确认ACK包 (确认标志ACK=1, seq=v, ack=u+1),向客户端表明已知道了其断开请求, 服务器进入CLOSE-WAIT状态, 但是不会立即断开, 而是继续传送剩下的数据。客户端收到服务器的确认包后, 进入FIN-WAIT-2状态, 并继续接收服务器传送的数据。
第三次挥手
:服务器将剩下的数据传送完毕后, 向客户端发送断开请求FIN包和确认ACK包(FIN=1, ACK=1, seq=w, ack=u+1), 并进入LAST-ACK状态。
第四次挥手
:客户端收到服务器的FIN包后,知道服务器已经做好断开连接的准备了;向服务器发送断开确认ACK包 (ACK=1, seq=u+1, ack=w+1),进入TIME-WAIT状态, 等待2MSL时间后进入CLOSED状态。服务器收到客户端的断开确认ACK包后, 直接进入CLOSED状态。
为什么要四次挥手?
tcp关闭连接需要四次握手原因:TCP连接是全双工通道,需要双向关闭。
client向server发送关闭请求,表示client不再发送数据,server响应。此时server端仍然可以向client发送数据,待server端发送数据结束后,就向client发送关闭请求,然后client确认。
6、如果一个客户端和服务器建立了TCP连接(三次握手之后),之后他就宕机了,服务器会知道吗?
对于通信双方出现的宕机,对端是无法感知的。只能采取保活措施进行规避:1、采用tcp固有机制keepalive;2、应用层自定义保活包。
7、TCP 粘包/拆包的原因及解决方法?
TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。
TCP粘包/拆包的原因:
-
应用程序写入的字节
大于
套接字发送缓冲区
的大小,会发生拆包
现象, -
应用程序写入数据
小于
套接字缓冲区
大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包
现象; -
进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度
>
MSS的时候将发生拆包 -
以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。
解决方法
-
消息定长:FixedLengthFrameDecoder类
-
包尾增加特殊字符分割:
行分隔符类:LineBasedFrameDecoder
或自定义分隔符类 :DelimiterBasedFrameDecoder
将消息分为消息头和消息体:LengthFieldBasedFrameDecoder类。分为有头部的拆包与粘包、长度字段在前且有头部的拆包与粘包、多扩展头部的拆包与粘包。
8、滑动窗口
滑动窗口概念不仅存在于数据链路层,也存在于传输层,两者有不同的协议,但基本原理是相近的。其中一个重要区别是,一个是针对于帧的传送,另一个是字节数据的传送。
滑动窗口协议(Sliding Window Protocol),该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。
滑动窗口引入
IP层协议属于不可靠的协议,IP层并不关系数据是否发送到了对端,TCP通过确认机制来保证数据传输的可靠性,在比较早的时候使用的是send--wait--send
的模式,其实这种模式叫做stop-wait模式,发送数据方在发送数据之后会启动定时器,但是如果数据或者ACK丢失,那么定时器到期之后,收不到ACK就认为发送出现状况,要进行重传。这样就会降低了通信的效率,如下图所示,这种方式被称为 positive acknowledgment with retransmission (PAR)
滑动窗口
可以假设一下,来优化一下PAR效率低的缺点,比如我让发送的每一个包都有一个id,接收端必须对每一个包进行确认,这样设备A一次多发送几个片段,而不必等候ACK,同时接收端也要告知它能够收多少,这样发送端发起来也有个限制,当然还需要保证顺序性,不要乱序,对于乱序的状况,我们可以允许等待一定情况下的乱序,比如说先缓存提前到的数据,然后去等待需要的数据,如果一定时间没来就DROP掉,来保证顺序性!
在TCP/IP协议栈中,滑动窗口的引入可以解决此问题,先来看从概念上数据分为哪些类
-
Sent and Acknowledged:这些数据表示已经发送成功并已经被确认的数据,比如图中的前31个bytes,这些数据其实的位置是在窗口之外了,因为窗口内顺序最低的被确认之后,要移除窗口,实际上是窗口进行合拢,同时打开接收新的带发送的数据
-
Send But Not Yet Acknowledged:这部分数据称为发送但没有被确认,数据被发送出去,没有收到接收端的ACK,认为并没有完成发送,这个属于窗口内的数据。
-
Not Sent,Recipient Ready to Receive:这部分是尽快发送的数据,这部分数据已经被加载到缓存中,也就是窗口中了,等待发送,其实这个窗口是完全由接收方告知的,接收方告知还是能够接受这些包,所以发送方需要尽快的发送这些包
-
Not Sent,Recipient Not Ready to Receive: 这些数据属于未发送,同时接收端也不允许发送的,因为这些数据已经超出了发送端所接收的范围
对于接收端也是有一个接收窗口的,类似发送端,接收端的数据有3个分类,因为接收端并不需要等待ACK所以它没有类似的接收并确认了的分类,情况如下
-
Received and ACK Not Send to Process:这部分数据属于接收了数据但是还没有被上层的应用程序接收,也是被缓存在窗口内
-
Received Not ACK: 已经接收并,但是还没有回复ACK,这些包可能输属于Delay ACK的范畴了
-
Not Received:有空位,还没有被接收的数据。
发送窗口 和 可用窗口
对于发送方来讲,窗口内的包括两部分,就是发送窗口(已经发送了,但是没有收到ACK),可用窗口,接收端允许发送但是没有发送的那部分称为可用窗口。
-
Send Window : 20个bytes 这部分值是有接收方在三次握手的时候进行通告的,同时在接收过程中也不断的通告可以发送的窗口大小,来进行适应
-
Window Already Sent: 已经发送的数据,但是并没有收到ACK。
滑动窗口原理
TCP并不是每一个报文段都会回复ACK的,可能会对两个报文段发送一个ACK,也可能会对多个报文段发送1个ACK【累计ACK】,比如说发送方有1/2/3 三个报文段,先发送了2,3 两个报文段,但是接收方期望收到1报文段,这个时候2,3报文段就只能放在缓存中等待报文1的空洞被填上,如果报文1,一直不来,报文2/3也将被丢弃,如果报文1来了,那么会发送一个ACK对这3个报文进行一次确认。
举一个例子来说明一下滑动窗口的原理:
-
假设32~45 这些数据,是上层Application发送给TCP的,TCP将其分成四个Segment来发往internet
-
seg1
(32 ~ 34)
, seg2(35 ~ 36)
, seg3(37~ 41)
, seg4(42 ~ 45)
这四个片段,依次发送出去,此时假设接收端之接收到了seg1 seg2 seg4 -
此时接收端的行为是回复一个ACK包说明已经接收到了32~36的数据,并将seg4进行缓存(保证顺序,产生一个保存seg3 的hole)
-
发送端收到ACK之后,就会将32~36的数据包从发送并没有确认切到发送已经确认,提出窗口,这个时候窗口向右移动
-
假设接收端通告的Window Size仍然不变,此时窗口右移,产生一些新的空位,这些是接收端允许发送的范畴
-
对于丢失的seg3,如果超过一定时间,TCP就会重新传送(重传机制),重传成功会seg3 seg4一块被确认,不成功,seg4也将被丢弃
就是不断重复着上述的过程,随着窗口不断滑动,将真个数据流发送到接收端,实际上接收端的Window Size通告也是会变化的,接收端根据这个值来确定何时及发送多少数据,从对数据流进行流控。原理图如下图所示:
滑动窗口动态调整
主要是根据接收端的接收情况,动态去调整Window Size,然后来控制发送端的数据流量
- 客户端不断快速发送数据,服务器接收相对较慢,看下实验的结果
a. 包175,发送ACK携带WIN = 384,告知客户端,现在只能接收384个字节
b. 包176,客户端果真只发送了384个字节,Wireshark也比较智能,也宣告TCP Window Full
c. 包177,服务器回复一个ACK,并通告窗口为0,说明接收方已经收到所有数据,并保存到缓冲区,但是这个时候应用程序并没有接收这些数据,导致缓冲区没有更多的空间,故通告窗口为0, 这也就是所谓的零窗口,零窗口期间,发送方停止发送数据
d. 客户端察觉到窗口为0,则不再发送数据给接收方
e. 包178,接收方发送一个窗口通告,告知发送方已经有接收数据的能力了,可以发送数据包了
f. 包179,收到窗口通告之后,就发送缓冲区内的数据了.
总结一点,就是接收端可以根据自己的状况通告窗口大小,从而控制发送端的接收,进行流量控制
9、介绍各种网络协议。
网络协议与标准是基于OSI七层模型,每一层都有其对应的协议。
下面挑几个熟悉的协议标准:
一,TCP/IP
TCP/IP是分层协议,如层次图所示:从底层到应用层,分别是物理层,链路层,网络层,传输层,应用层。数据是层层封装,封装的方式一般都是在原有数据的前面加一个数据控制头,数据封装格式如数据封装图所示。
二,HTTP协议
从上面两个图,可以看到,HTTP(hypertexttransfer protocol ,超文本传输协议)是基于TCP协议可靠传输的应用层协议。简单理解,HTTP是一种请求响应式协议,一个客户机和服务器建立连接后,发送一个请求给服务器;服务器接到请求后,给予相应的响应信息。
HTTP是一种无状态协议,即不保存状态,每当有新的请求发送时,就会产生对应的新响应,协议本身并不保留之前的一切请求或响应报文的信息,这样可以更快的处理大量事物,确保协议的可伸缩性,但同时也带来一些不便,比如,用户登录到一家购物网站,如果他跳转到该网站其他页面,也是希望能继续保持登录状态,为了实现期望的保持状态功能,于是引入Cookie技术,(Cookie技术通过在请求和响应报文中写入Cookie信息来控制客户端的状态。Cookie会根据从服务器端发送的响应报文内的一个叫做Set-Cookie的首部字段信息,通知客户端保存Cookie,当客户端下次再往服务器端发送请求时,客户端会自动在请求报文中加入Cookie值后发送出去。服务器端发现客户端发送过来的Cookie后,会去检和比对服务器上的记录,最后得到之前的状态信息。)
HTTP使用明文,本身不具备加密的功能,同时它所基于的TCP/IP网也是很容易被窃听的网络,所以即使传输的报文经过加密,也只是在破解上增加障碍而已。HTTP协议没有加密机制,后来人们引入SSL(secure socketlayer ,安全套接层)和TLS(Transport layer security,安全传输层协议)结合使用的手段,也就是现在常说的HTTPS,简而言之,HTTPS就是身披SSL协议的HTTP了。在HTTP协议通信时,本身是不存在确认通信方的步骤,也就是说,任何人都可以发起请求,服务器不管对方是谁都会响应。所以会出现一些常见的隐患:
-
无法确定请求发送至目标的Web服务器是否是按真实意图返回响应的那台服务器。即我发送给A服务器的请求,被一台伪装成A的服务器给接收了。
-
无法确定响应返回到的客户端是否是按真实意图接收响应的那个客户端。即服务器想发送给我的东西,被一个伪装成我的人给拿走了。
-
无法确定正在通信的对方是否具备访问权限。因为某些Web服务器上保存着重要的信息,只想发给特定用户通信的权限。
-
无法判定请求是来自何方,出自谁手。
-
即使是无意义的请求也会照单全收,无法阻止海量请求下的DoS攻击。
所以在HTTP上引入SSL后,SSL不仅提供加密处理,而且用了一种被称为证书的手段,可用户确定方,可以减少信息泄露的危险性。 另外由于HTTP协议无法证明报文的完整性,因此在请求或响应传输中,即使内容遭到篡改也无从得知。比如,有可能你从服务器请求一张正常的图片,结果在传输过程中被人改成了一张限制级的图片,结果你就被查水表了。
三,Telnet协议
Telnet协议是TCP/IP协议族,是Internet远程登录服务标准协议,应用这个协议,能够把本地用户所使用的计算机变成远程主机系统的一个终端。具体说来,它实现三种基本服务。
其一,Telnet定义一个网咯虚拟终端为远的系统提供一个标准接口。客户机只需构造使用标准接口的程序即可。
其二,Telnet包括一个允许客户机和服务器协商选项的机制,而且还提供一组标准选项。
其三,Telnet对称处理连接的两端。
四,FTP协议
FTP是网络共享文件的传输协议,目标是提高文件的共享性和可靠地传输数据。相比HTTP协议,FTP协议要相对复杂,她的命令和数据会分开传送。在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据。FTP使用 2 个端口,一个数据端口和一个命令端口(也叫做控制端口)。这两个端口一般是21(命令端口)和 20 (数据端口)。控制 Socket 用来传送命令,数据 Socket 是用于传送数据。每一个 FTP 命令发送之后,FTP 服务器都会返回一个字符串,其中包括一个响应代码和一些说明信息。其中的返回码主要是用于判断命令是否被成功执行了。有主动模式和被动模式两种。
主动模式 (PORT),客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出“port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。
被动模式 (PASV),为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。第一个端口连接服务器的21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 开头的信息,在括号中有以逗号隔开的六个数字,前四个指服务器的地址,最后两个,将倒数第二个乘 256 再加上最后一个数字,这就是FTP 服务器开放的用来进行数据传输的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口号是 p1*256+p2,ip 地址为h1.h2.h3.h4。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。
10、DNS域名解析
从客户端到本地DNS服务器是属于递归查询
,而DNS服务器之间就是的交互查询就是迭代查询
。
-
浏览器缓存: 浏览器先检查
浏览器缓存
中有没有被解析过的这个域名对应的ip地址,如果有,解析结束。同时域名被缓存的时间也可通过TTL属性来设置。 -
系统缓存: 如果浏览器缓存中没有(专业点叫还没命中),浏览器会检查操作系统缓存中有没有对应的已解析过的结果。而操作系统也有一个域名解析的过程。在windows中可通过c盘里一个叫
hosts 文件
来设置,如果你在这里指定了一个域名对应的ip地址,那浏览器会首先使用这个ip地址。但是这种操作系统级别的域名解析规程也被很多黑客利用,通过修改你的hosts文件里的内容把特定的域名解析到他指定的ip地址上,造成所谓的域名劫持。所以在windows7中将hosts文件设置成了readonly,防止被恶意篡改。 -
如果hosts文件还没有命中域名,才会真正的请求
本地域名服务器
(Local DNS)来解析这个域名,这台服务器一般在你的城市的某个角落,距离你不会很远,并且这台服务器的性能都很好,一般都会缓存域名解析结果,大约80%的域名解析到这里就完成了。 -
如果本地DNS服务器本地区域文件与缓存没有命中,则根据本地DNS服务器的设置(是否设置转发器)进行查询
- 如果未用转发模式,
1.本地DNS
就把请求发至13个根DNS
,根DNS服务器收到请求后会判断这个域名()是谁来授权管理,并会返回一个负责该顶级域名服务器
的一个IP地址(gTLD Server,国际顶级域名服务器,如 等)。
- 本地DNS服务器收到IP信息后,再发送请求给上一步返回的gTLD。这台负责域的服务器收到请求后,如果自己无法解析,它就会找一个管理域的下一级DNS服务器地址(http://qq)给本地DNS服务器。
- 当本地DNS服务器收到这个地址后,就会找http://qq域服务器,重复上面的动作,进行查询,直至找到www.qq主机。
- 如果用的是转发模式,
此本地DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
-
本地DNS缓存这个域名和对应的ip
-
本地DNS把解析的结果返回给用户,用户根据TTL值缓存到本地系统缓存中,域名解析过程至此结束
其中顶级域名分为:国家顶级域名、通用顶级域名、反向域名。
国家顶级域名:中国:cn, 美国:us,英国uk…
通用顶级域名:com 公司企业 edu教育机构 gov政府部门 int国际组织 mil军事部门 net网络 org非盈利组织…
反向域名:只有一个arpa,用于PTR查询(IP地址转换为域名) 。
DNS 查询的顺序
1.是否是本机
2.是否是缓存
3. HOSTS文件查询
4. DNS服务器查询
11、DNS在网络层用哪个协议,为什么?
域名解析是将域名映射为IP地址的过程。DNS是应用层协议,client端(一般指浏览器)构建DNS查询请求,依次被传输层,网络层,数据链路层等封装传送到达DNS服务器端,最终client端接收到DNS响应消息
DNS 在传输层主要基于UDP协议
一次UDP名字服务器交换可以短到两个包:一个查询包、一个响应包。一次TCP交换则至少包含9个包:三次握手初始化TCP会话、一个查询包、一个响应包以及四次分手的包交换。
考虑到效率原因,TCP连接的开销大得,故采用UDP作为DNS的运输层协议,这也将导致只有13个根域名服务器的结果。
DNS 在网络层基于IP/IPv6协议
12、介绍HTTPS协议,详述SSL建立连接过程。
HTTPS是在HTTP的基础上和ssl/tls证书结合起来的一种协议,保证了传输过程中的安全性,减少了被恶意劫持的可能.很好的解决了解决了http的三个缺点(被监听、被篡改、被伪装)
-
在使用HTTPS是需要保证服务端配置正确了对应的安全证书
-
客户端发送请求到服务端
-
服务端返回公钥和证书到客户端
-
客户端接收后会验证证书的安全性,如果通过则会随机生成一个随机数,用公钥对其加密,发送到服务端
-
服务端接受到这个加密后的随机数后会用私钥对其解密得到真正的随机数,随后用这个随机数当做私钥对需要发送的数据进行对称加密
-
客户端在接收到加密后的数据使用私钥(即生成的随机值)对数据进行解密并且解析数据呈现结果给客户
-
SSL加密建立
对称加密和非对称加密
对称加密
: 即加密的密钥和解密的密钥相同,
非对称加密
: 非对称加密将密钥分为公钥和私钥,公钥可以公开,私钥需要保密,客户端公钥加密的数据,服务端可以通过私钥来解密
13、http请求从输入url开始都经过什么过程?
- 浏览器地址栏输入url
- 浏览器会先查看浏览器缓存–系统缓存–路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步
- 域名解析(DNS)获取相应的 ip (详见第10题: DNS域名解析)
- 浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手
- 握手成功,浏览器向服务器发送http请求,请求数据包
- 服务器请求数据,将数据返回到浏览器
- 浏览器接收响应,读取页面内容,解析html源码,生成DOm树
- 解析css样式、浏览器渲染,js交互
- 请求页面中需要的js脚本和图片或者样式表
14、http 协议的理解
1.超文本的传输协议,是用于从万维网服务器超文本传输到本地资源的传输协议
2.基于TCP/IP通信协议来传递数据(HTML,图片资源)
3.基于运用层的面向对象的协议,由于其简洁、快速的方法、适用于分布式超媒体信息系统
4.http请求信息request:请求行(request line)、请求头部(header),空行和请求数据四部分构成
请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本.
请求头部,用来说明服务器要使用的附加信息
空行,请求头部后面的空行是必须的
请求数据也叫主体,可以添加任意的其他数据。
5.http响应信息Response: 状态行、消息报头、空行和响应正文
状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成
消息报头,用来说明客户端要使用的一些附加信息
空行,消息报头后面的空行是必须的
响应正文,服务器返回给客户端的文本信息。
15、http拥塞控制
TCP的拥塞控制
拥塞:即对资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变坏,整个网络的吞吐量随之负荷的增大而下降。
拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。
流量控制:指点对点通信量的控制,是端到端正的问题。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
拥塞控制代价:需要获得网络内部流量分布的信息。在实施拥塞控制之前,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外的开销。拥塞控制还需要将一些资源分配给各个用户单独使用,使得网络资源不能更好地实现共享。
几种拥塞控制方法
慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。
慢开始 和 拥塞避免
发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。
发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
慢开始算法:当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是 先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。
拥塞避免算法:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。
16、http 和 https 的区别
https:是以安全为目标的HTTP通道,简单讲是HTTP的安全版本,通过SSL加密
http:超文本传输协议。是一个客服端和服务器端请求和应答的标准(tcp),使浏览器更加高效,使网络传输减少
17、http和https tls处于哪一层(7层中的哪一层)
http和https 是应用层
SSL(Secure Socket Layer安全套接层)以及其继承者TLS(Transport Layer Security 传输层安全)是为了网络通信安全 提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。
18、发消息时 网络传输过程中(只说应用层)信息怎么保证不被窃取
一般采用双向证书SSL通信
19、介绍一下SSL / TLS
20、密钥泄漏怎么办?
预防私钥泄密最好的方法从来都是把密钥保存在线下,将您的私钥存储在外部硬件令牌上, 不给他人任何机会获取。
更多推荐
IO / 网络编程
发布评论