Week 6
week 6
完成第一版平台项目任务书
这周加了两天班,比较累
进阶知识
网络编程
TCP UDP
- 流量控制和拥塞控制
流量控制和拥塞控制是网络通信中的两个重要概念,它们的目的都是为了确保网络中数据的高效传输和避免网络拥塞。然而,它们在实现方式和使用场景上存在一定的区别。
流量控制主要是通过控制发送方的发送速率,以使网络中的数据包不会超过接收方的处理能力。这是一种点对点的控制,主要考虑的是两个直接通信节点之间的数据传输。常用的流量控制技术有滑动窗口协议和可变大小缓冲区等。例如,滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度。
拥塞控制则主要是通过控制网络中的流量,以避免网络拥塞。当网络负载过高时,可能会导致网络拥塞,影响数据包的传输。拥塞控制通过控制发送速率、调整路由等方式,使网络中的数据流量稳定在可承受范围内。常用的拥塞控制技术有拥塞避免、拥塞检测和拥塞回退等。
总的来说,流量控制和拥塞控制各有其特点。流量控制主要关注单个通信节点之间的数据传输,而拥塞控制则更注重整个网络的状态和稳定性。在实际应用中,它们常常同时存在并协同工作,以确保网络中数据传输的高效和可靠。
- tcp粘包与拆包
TCP的粘包和拆包是由于TCP协议的特性导致的。TCP是一个字节流协议,它会把发送端要传输的数据先传入缓冲区,然后根据缓冲区的大小和实际情况进行数据包的分割。在接收端,数据包到达后,TCP会将其放入接收缓冲区,然后由应用程序读取。
粘包现象发生在当发送端连续发送两个数据包,而接收端将其合并为一个数据包接收时,或者一个完整的数据包被拆分成多个数据包发送,在接收端将其合并成一个完整的数据包时。这时,接收端无法确定这些数据包属于哪个原始数据包,也无法确定原始数据包的数量,这就是TCP的粘包问题。
拆包现象发生在当发送端需要发送的数据大于缓冲区的大小时,数据会被拆分成多个数据包发送,而在接收端将其合并成一个完整的数据包时。这种情况是由于TCP每次发送数据时都有一个固定的大小,为了提高TCP传输的性能,发送端会将数据发送到一个缓冲区,当缓冲区满后,再将缓冲区中的数据发送到接收端。因此,如果发送端需要发送的内容大于缓冲区的大小,就会发生拆包的现象。
IPV4 IPV6
IPv4和IPv6是两种用于标识用户和设备在互联网上通信的IP地址格式。它们之间存在以下主要区别:
- 地址长度:IPv4地址是32位二进制数,通常表示为4个十进制数,每个数之间用点号分隔。相比之下,IPv6地址是128位二进制数,通常表示为8个16进制数,每个数之间用冒号分隔。
- 地址表示:IPv4地址是数字地址,用点分隔。而IPv6是一个字母数字地址,用冒号分隔。
- 地址结构:IPv6地址由网络前缀和接口标识两个部分组成。网络前缀有n位,相当于IPv4地址中的网络ID;接口标识有(128-n)比特,相当于IPv4地址中的主机ID。
- 兼容性:有些设备可以同时运行IPv4和IPv6两套协议栈,这种节点被称为双栈节点。这些结点既可以收发IPv4报文,也可以收发IPv6报文。
- 地址空间:IPv4地址空间有限,而IPv6地址空间非常大,可以满足互联网持续增长的需求。
HTTP
超文本传输协议(HTTP)是互联网上应用最为广泛的一种网络协议,用于浏览器和服务器之间的通信。以下是关于HTTP/1.0、HTTP/1.1和HTTP/2的一些主要特点和区别:
- HTTP/1.0:
- 简单性:请求由单行指令构成,以唯一可用方法GET开头,其后跟目标资源的路径。
- 无持久连接:每个TCP连接只能处理一个请求,完成后需要关闭。
- 不支持管线化:客户端必须等待当前请求完成后才能发出下一个请求。
- 无状态管理:无法保留之前的请求信息,例如Cookie和Session。
- HTTP/1.1:
- 继承自HTTP/1.0的简单性,同时克服了其性能问题。
- 长连接:增加Connection字段,通过设置Keep-Alive保持HTTP连接不断开,避免了每次客户端与服务器请求都要重复建立释放建立TCP连接,提高了网络的利用率。
- 管道化(pipelining):支持请求管道化,使得请求能够“并行”传输,基于HTTP/1.1的长连接,使得请求管线化成为可能。
- 增加请求头和响应头来扩充功能。
- HTTP/2:
- 多路复用:允许多个请求在同一个连接上同时进行,减少了TCP连接的数量。
- 服务器推送:允许服务器在客户端需要之前主动发送数据。
- 头部压缩:减小了数据传输的大小,提高了加载速度。
http 与 https
HTTP和HTTPS是用于在网络上进行数据传输的协议,它们在连接方式、端口使用以及数据传输安全性上存在显著差异。
- HTTP:这是互联网上最广泛使用的应用层协议,主要用于传输HTML文档。然而,HTTP协议的数据传输是明文的,这意味着在数据发送过程中可能会遭受拦截或窃取,因此存在安全风险。
- HTTPS:HTTPS是HTTP的安全版,它是建构在SSL/TLS之上的HTTP协议。与HTTP不同,HTTPS会对信息进行加密处理,以防止敏感信息的泄露。此外,“https://”前缀表明了网页是通过SSL(安全套接字层)或TLS(传输层安全)协议加密的,这为客户端和服务器之间的信息传输提供了额外的保护。
为了实现加密,HTTPS需要更多的计算资源,因此会比HTTP耗费更多的服务器资源。同时,HTTP和HTTPS也使用不同的连接方式和端口。其中,HTTP使用的是80端口,而HTTPS则使用的是443端口。
Java RMI
Java RMI,全称Java Remote Method Invocation,是Java编程语言中用于实现远程过程调用的应用程序编程接口。它使得在客户端运行的程序可以调用远程服务器上运行的对象的方法。RMI模型是一种分布式对象应用,允许一个JVM中的对象调用另一个JVM中的对象方法并获取调用结果。这里的另一个JVM可以在同一台计算机也可以是远程计算机。因此,RMI需要有一个Server端和一个Client端。通常,Server端会创建一个对象,使之可以被其他客户端远程访问和调用。
服务器端(Server):
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 定义一个远程接口
interface Hello extends Remote {
String sayHello() throws RemoteException;
}
// 实现远程接口
class HelloImpl extends UnicastRemoteObject implements Hello {
protected HelloImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException {
return "Hello, world!";
}
}
// 服务器端主类
public class Server {
public static void main(String[] args) {
try {
// 创建并导出远程对象
HelloImpl hello = new HelloImpl();
Naming.rebind("Hello", hello);
System.out.println("Server is ready.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端(Client):
import java.rmi.Naming;
import java.rmi.RemoteException;
// 定义一个远程接口的代理类
interface Hello extends Remote {
String sayHello() throws RemoteException;
}
// 客户端主类
public class Client {
public static void main(String[] args) {
try {
// 查找远程对象
Hello hello = (Hello) Naming.lookup("rmi://localhost/Hello");
// 调用远程方法
String result = hello.sayHello();
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
首先运行服务器端的Server
类,然后运行客户端的Client
类。客户端将调用服务器端的sayHello
方法并输出结果。
Socket
Socket是一种用于实现网络通信的编程接口。它允许两个程序之间进行双向数据传输,包括发送和接收数据。在Java中,可以使用java.net包中的Socket类来创建和管理Socket连接。
服务端代码示例:
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) {
try {
// 创建一个ServerSocket对象,监听指定端口号
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器已启动,等待客户端连接...");
// 等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("客户端已连接,IP地址:" + socket.getInetAddress().getHostAddress());
// 获取输入流,用于接收客户端发送的数据
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = in.readLine();
System.out.println("收到客户端消息:" + message);
// 关闭资源
in.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码示例:
import java.io.*;
import java.net.*;
public class Client {
public static void main(String[] args) {
try {
// 创建一个Socket对象,连接到指定的主机和端口号
Socket socket = new Socket("localhost", 8080);
System.out.println("已连接到服务器,IP地址:" + socket.getInetAddress().getHostAddress());
// 获取输出流,用于向服务器发送数据
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello, Server!");
// 关闭资源
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
RMI和Socket都是网络通信的技术,但它们在使用方式和功能上存在显著的区别。首先,RMI是Java语言接口定义的远程对象,它集合了Java序列化和Java远程方法协议 (Java Remote Method Protocol)。这使得原先的程序在同一操作系统的方法调用变成了不同操作系统之间程序的方法调用。然而,需要注意的是,RMI是与语言相绑定的。比如当你使用Java RMI技术的时候,客户端与服务器端都必须使用Java开发。
相反,Socket是一种网络编程技术,允许不同的计算机之间通过网络进行通信。基于socket的网络编程是独立于开发语言的,甚至独立于平台的。这意味着,客户端与服务器端可以使用不同的开发语言和不同的平台进行开发。
在对比两者的优劣时,需要考虑具体的应用需求。一般来说,RMI具有面向对象的特点,更适合处理复杂的业务逻辑和大型项目;而Socket则更加灵活,适用于各种规模的项目和多种平台之间的通信。此外,由于RMI是与语言绑定的,如果需要更换编程语言,可能需要重新设计和实现,增加了开发和维护的难度和复杂性。相比之下,Socket则无此限制,更具通用性和可移植性。
HttpClient
HttpClient是一个Java类库,用于发送HTTP请求和处理HTTP响应。它提供了一种简单而灵活的方式来与Web服务进行通信,可以用于发送GET、POST、PUT、DELETE等不同类型的HTTP请求,并处理返回的HTTP响应。
使用HttpClient可以轻松地实现各种网络编程任务,例如:
- 从Web服务器获取数据
- 向Web服务器发送数据
- 上传文件到Web服务器
- 下载文件从Web服务器
- 与RESTful Web服务交互
- 发送带有参数的HTTP请求
- 处理Cookies和Session等HTTP头信息
- 支持多种认证方式(基本认证、摘要认证、OAuth等)
HttpClient是Apache基金会的一个开源项目,可以在Java开发中使用。
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class HttpClientExample {
public static void main(String[] args) {
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
HttpGet httpGet = new HttpGet("<http://example.com>");
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
String result = EntityUtils.toString(entity);
System.out.println(result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这个示例中,我们首先创建了一个CloseableHttpClient
对象,然后使用HttpGet
方法创建一个GET请求。接着,我们执行请求并获取响应,将响应实体转换为字符串并输出。最后,我们关闭HttpClient对象。
进程间通信
进程间通信主要有以下几种方式:
- 管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
在Java中,可以使用java.lang.ProcessBuilder
类来创建进程,并通过管道进行进程间通信。以下是一个简单的示例:
import java.io.*;
public class PipeExample {
public static void main(String[] args) throws IOException, InterruptedException {
// 创建第一个进程,执行命令行程序
ProcessBuilder processBuilder1 = new ProcessBuilder("ping", "www.baidu.com");
Process process1 = processBuilder1.start();
// 获取第一个进程的输出流
InputStream inputStream1 = process1.getInputStream();
BufferedReader bufferedReader1 = new BufferedReader(new InputStreamReader(inputStream1));
// 创建第二个进程,执行命令行程序
ProcessBuilder processBuilder2 = new ProcessBuilder("grep", "PING");
processBuilder2.redirectErrorStream(true); // 将错误输出流合并到标准输出流
Process process2 = processBuilder2.start();
// 获取第二个进程的标准输出流
OutputStream outputStream2 = process2.getOutputStream();
PrintWriter printWriter2 = new PrintWriter(outputStream2);
// 将第一个进程的输出流传递给第二个进程
String line;
while ((line = bufferedReader1.readLine()) != null) {
printWriter2.println(line);
printWriter2.flush();
}
// 等待两个进程执行完成
process1.waitFor();
process2.waitFor();
// 关闭资源
bufferedReader1.close();
inputStream1.close();
printWriter2.close();
outputStream2.close();
}
}
这个示例中,我们创建了两个进程,分别执行ping www.baidu.com
和grep PING
命令。通过管道,我们将第一个进程的输出流传递给第二个进程,从而实现进程间通信。
- 有名管道(Named Pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
在Java中,可以使用java.nio.channels.Pipe
类来实现有名管道进程间通信。以下是一个简单的示例:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
public class NamedPipeExample {
public static void main(String[] args) throws IOException {
// 创建管道
Pipe pipe1 = Pipe.open();
// 创建一个线程用于写入数据到pipe1
new Thread(() -> {
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
buffer.clear();
String message = "Hello, World!";
buffer.put(message.getBytes());
buffer.flip();
pipe1.sink().write(buffer);
System.out.println("写入数据: " + message);
Thread.sleep(1000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
// 创建一个线程用于从pipe2读取数据
new Thread(() -> {
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
int bytesRead = pipe1.source().read(buffer);
if (bytesRead == -1) {
break;
}
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
String message = new String(data);
System.out.println("读取数据: " + message);
Thread.sleep(1000);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
这个示例中,我们创建了两个管道pipe1
和pipe2
,然后分别启动了两个线程。一个线程向pipe1
写入数据,另一个线程从pipe2
读取数据。这样就实现了有名管道进程间通信。
- 消息队列(Message Queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
消息队列和管道都是进程间通信的方式,但它们具有不同的特点和使用场景。
- 灵活性:消息队列提供了更大的灵活性。它提供有格式字节流,有利于减少开发人员的工作量;同时,消息具有类型,使得在实际应用中更加灵活。
- 存在方式:消息队列独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。
- 使用场景:管道一般用于父子进程之间相互通信,而消息队列可以基于FIFO或基于任务优先级方式排队消息,常被用作单个CPU中的任务之间的通信机制。
- 数据传递方式:管道是一种半双工的通信方式,数据只能单向流动;而消息队列是一种特殊的链表,存放在内核中并由消息队列标识符标识,它可以存储多个消息,支持不同的读写操作。
- 实现方式:管道通过文件描述符进行实现,而消息队列则是通过系统调用来实现。
- 信号量(Semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 共享内存:这是一种最快的进程间通信方式,它允许多个进程访问同一块内存空间,从而实现数据共享。
在Java中,可以使用java.nio.channels.FileChannel
类来实现共享内存进程间通信。以下是一个简单的示例:
首先,创建一个共享内存文件:
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class SharedMemory {
public static void main(String[] args) throws Exception {
// 创建一个共享内存文件
RandomAccessFile sharedMemoryFile = new RandomAccessFile("shared_memory.dat", "rw");
FileChannel fileChannel = sharedMemoryFile.getChannel();
// 映射共享内存文件到内存
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4);
buffer.putInt(12345); // 写入一个整数到共享内存
// 关闭文件通道和共享内存文件
fileChannel.close();
sharedMemoryFile.close();
}
}
然后,在另一个进程中读取共享内存中的整数:
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class Consumer {
public static void main(String[] args) throws Exception {
// 打开共享内存文件
RandomAccessFile sharedMemoryFile = new RandomAccessFile("shared_memory.dat", "r");
FileChannel fileChannel = sharedMemoryFile.getChannel();
// 映射共享内存文件到内存
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, 4);
int value = buffer.getInt(); // 从共享内存中读取一个整数
// 输出读取到的整数
System.out.println("Value from shared memory: " + value);
// 关闭文件通道和共享内存文件
fileChannel.close();
sharedMemoryFile.close();
}
}
这个示例中,我们创建了一个共享内存文件shared_memory.dat
,并在两个进程中分别写入和读取一个整数。注意,这个示例仅适用于在同一台计算机上的两个进程之间进行通信。如果要在不同计算机上的进程之间进行通信,需要使用其他方法,如套接字或消息队列。
- 套接字(Socket):套接字是一种网络通信的方式,支持不同主机上的两个进程IPC。
服务器端代码:
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已启动,等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("客户端已连接,IP地址为:" + socket.getInetAddress().getHostAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String msg;
while ((msg = in.readLine()) != null) {
System.out.println("收到客户端消息:" + msg);
out.println("服务器已收到消息:" + msg);
}
in.close();
out.close();
socket.close();
serverSocket.close();
}
}
客户端代码:
import java.io.*;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8888);
System.out.println("已连接到服务器,IP地址为:" + socket.getInetAddress().getHostAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String msg;
while ((msg = in.readLine()) != null) {
System.out.println("发送给服务器的消息:" + msg);
out.println(msg);
}
in.close();
out.close();
socket.close();
}
}
首先运行服务器端代码,然后运行客户端代码。在客户端输入消息并按回车键,服务器端会收到并回复相同的消息。
CDN
CDN(Content Delivery Network,内容分发网络)是一种通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,以此提高用户访问网站的响应速度的技术。
实现CDN的方法有很多,其中一种常见的方法是使用第三方CDN服务提供商,如阿里云、腾讯云等。这些服务商通常会提供API接口,开发者可以通过调用这些接口来实现内容的上传、下载和分发。
以下是一个简单的Java代码示例,用于使用阿里云的CDN服务:
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
public class AliyunCDNDemo {
public static void main(String[] args) {
// 创建OSSClient实例。
String endpoint = "*** Provide your OSS endpoint ***";
String accessKeyId = "*** Provide your Access Key ID ***";
String accessKeySecret = "*** Provide your Access Key Secret ***";
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 上传文件到OSS。
String bucketName = "*** Provide your bucket name ***";
String objectName = "*** Provide your object name ***";
String localFilePath = "*** Provide your local file path ***";
ossClient.putObject(bucketName, objectName, new File(localFilePath));
// 下载文件从OSS。
ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File("*** Provide your download file path ***"));
// 删除OSS上的文件。
ossClient.deleteObject(bucketName, objectName);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭OSSClient。
ossClient.shutdown();
}
}
}
请注意,这个示例需要引入阿里云的OSS Java SDK,可以通过Maven或Gradle将其添加到项目中。同时,需要替换代码中的占位符,如*** Provide your OSS endpoint ***
、*** Provide your Access Key ID ***
等,以匹配您的阿里云账户设置。
DNS记录类型
DNS记录类型有多种,以下是一些常见的类型:
- A地址记录(Address):它返回域名指向的IP地址。这是将域名解析成IP地址的关键机制,让用户在利用域名访问时,自动将域名转换为对应的IP。
- NS域名服务器记录(Name Server):它返回保存下一级域名信息的服务器地址。这种记录只能设置为域名,不能设置为IP地址。
- MX邮件记录(Mail Exchange):它指定负责处理一个域名的邮件服务的服务器地址。
- CNAME别名记录(Canonical Name):它提供了一种方法,可以将多个名字映射到同一台计算机。通常用于将复杂的域名映射到一个简单的域名。
- TXT文本记录(Text):它允许管理员为域名添加额外的信息。
DNS污染
DNS污染是一种网络攻击手段,它通过篡改DNS解析记录,将域名指往不正确的IP地址,从而将网络用户导引到错误的服务器或服务器的网址。这种攻击方式又称为“网域服务器缓存污染”(DNS cache pollution)或“域名服务器缓存投毒”(DNS cache poisoning)。
一般来说,网络用户访问特定网站时,其DNS解析请求会被发送到可信赖的DNS服务器上,然后服务器会返回正确的IP地址。然而,在DNS污染的情况下,攻击者通过伪装成一个合法的DNS服务器,返回错误的IP地址,从而将用户引导到错误的服务器或网址上。
DNS污染的攻击方式有很多种,其中一种常见的做法是利用UDP协议的漏洞。因为UDP协议没有验证机制,攻击者可以随意发送伪造的DNS数据包。另外,一些网络设备(例如防火墙)可能会干扰DNS解析过程,导致DNS污染的发生。
为了防范DNS污染,可以采取以下几种措施:
- 使用可信赖的DNS解析服务。例如,许多域名注册企业提供免费的DNS解析服务,这些服务通常具有较高的解析速度和多组DNS服务器,可以更好地避免DNS污染。
- 使用第三方DNS解析服务或CDN服务。这些服务会提供他们的DNS服务器解析服务和网络IP地址,以帮助用户避免DNS污染。
- 强制使用TCP协议进行DNS解析。虽然这并不能完全防止DNS污染,但可以在一定程度上降低污染发生的可能性。
- 使用VPN或代理服务器。这些工具可以帮助用户在访问特定网站时,将DNS解析请求发送到可信赖的DNS服务器上。
DNS劫持
DNS劫持是一种互联网攻击方式,也是网络犯罪的一种手段。它通过攻击域名解析服务器(DNS)或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的IP地址,从而使得用户无法访问目标网站,或者蓄意或恶意要求用户访问指定IP地址(网站)。这种攻击方式又称为“网域服务器缓存污染”(DNS cache pollution)或“域名服务器缓存投毒”(DNS cache poisoning)。
DNS劫持的实现方式有很多种,其中一种常见的方法是通过伪装成一个合法的DNS服务器,返回错误的IP地址。此外,一些网络设备(例如防火墙)可能会干扰DNS解析过程,导致DNS劫持的发生。
为了防范DNS劫持,可以采取以下几种措施:
- 使用可信赖的DNS解析服务。例如,许多域名注册企业提供免费的DNS解析服务,这些服务通常具有较高的解析速度和多组DNS服务器,可以更好地避免DNS劫持。
- 使用第三方DNS解析服务或CDN服务。这些服务会提供他们的DNS服务器解析服务和网络IP地址,以帮助用户避免DNS劫持。
- 强制使用TCP协议进行DNS解析。虽然这并不能完全防止DNS劫持,但可以在一定程度上降低其发生的可能性。
- 使用VPN或代理服务器。这些工具可以帮助用户在访问特定网站时,将DNS解析请求发送到可信赖的DNS服务器上。
公共DNS
公共DNS是系统默认的DNS解析服务器。DNS帮助用户在互联网上寻找路径,每个计算机在互联网上都有唯一的地址,称为“IP地址”,由于IP地址不方便记忆,DNS允许用户使用一串常见的字母(即“域名”)取代。不同的运营商和公司会提供不同的公共DNS服务,例如阿里DNS、百度DNS、腾讯DNS、114DNS、谷歌DNS、360DNS等。公共DNS的具体使用方式因不同的服务而异,例如谷歌DNS还可以通过设置浏览器或操作系统来使用。
- 114DNS
114DNS是由中国电信、中国移动和中国铁通联合推出的公共DNS服务,其主推的特色是稳定、安全。在境内,114DNS的服务器地址分别为114.114.114.114和114.114.115.115。这两个地址在大部分地区都能提供稳定且快速的DNS解析服务。
为了确保网络安全,114DNS采用了包括拦截钓鱼病毒木马网站、增强网银证券、购物、游戏、隐私信息安全,以及抗攻击DNS等措施。
- Google DNS
谷歌公共域名解析服务(Google Public DNS)是谷歌公司于2009年发布的一项新的DNS服务,主要为了替代ISP或其他公司提供的DNS服务。它是一个免费、安全且可靠的选择,可以帮助用户在互联网上快速、准确地访问网页内容。谷歌全球通用DNS地址:首选DNS地址:8.8.8.8,备选DNS地址:8.8.4.4。
谷歌DNS使用全球多个服务器节点进行解析,可以为用户提供更快、更稳定的DNS解析服务。同时,它还具备一些先进的功能,如IPv6支持、加密和缓存技术等,以保护用户的隐私和网络安全。
框架
Servlet
Mybatis
- 缓存机制
MyBatis提供了查询缓存,用于减轻数据压力,提高数据库性能。MyBatis提供一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,二级缓存是Mapper级别的缓存 。
在日常工作中,我们大多使用MyBatis的默认缓存配置,但是MyBatis缓存机制也有一些不足之处,一不留神就会出现脏数据,形成一些潜在的隐患,后续排查问题容易浪费时间精力。
Mybatis提供了对缓存的支持,主要分为一级缓存和二级缓存。MyBatis缓存机制的默认策略是一级缓存和二级缓存。
一级缓存是SqlSession级别的缓存,它默认开启,并且在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的,即它只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。
二级缓存则是mapper级别的缓存,基于mapper文件的namespace,与SqlSessionFactory相关联。这种级别的缓存可以跨多个SqlSession共享数据。
然而,Mybatis的缓存机制也存在一些问题。例如,当多个SqlSession操作同一个Mapper但使用不同的参数时,会导致二级缓存失效。此外,由于二级缓存是从MappedStatement中获取,是存在于全局配置,如果被多个CachingExecutor获取到,则可能会出现线程安全问题导致脏读。
为解决这些问题,MyBatis提供了一些解决方案。对于一级缓存问题,可以通过调整本地缓存范围或使用第三方缓存来解决。而对于二级缓存的问题,可以通过事务管理来保证数据的一致性和安全性。
-
{} 和 ${}
在MyBatis中,#{}和${}是用于动态SQL查询的两种占位符。
-
{}:这种占位符会将参数直接替换到SQL语句中,然后执行这个SQL语句。这种方式可以有效防止SQL注入攻击,因为它会对参数进行安全检查。
- ${}:这种占位符不会对参数进行任何处理,它会直接将参数插入到SQL语句中。这种方式可能会导致SQL注入攻击,因此在实际使用中应尽量避免。
- mapper 中传递多个参数
在MyBatis中,可以通过以下两种方式传递多个参数:
- 使用@Param注解指定参数名
可以在Mapper接口的方法中使用@Param注解来指定参数名,然后在XML文件中使用这个名称来引用参数。例如:
public interface UserMapper {
User selectUserByIdAndName(@Param("id") int id, @Param("name") String name);
}
<select id="selectUserByIdAndName" resultType="User">
SELECT * FROM user WHERE id = #{id} AND name = #{name}
</select>
- 使用Map传递多个参数
也可以使用Map来传递多个参数,将多个参数封装到一个Map对象中,然后将该Map对象作为方法的参数传入。例如:
public interface UserMapper {
User selectUserByIdAndName(Map<String, Object> params);
}
xml不需要设置输入参数了。。。
<select id="selectUserByIdAndName" resultType="User">
SELECT * FROM user WHERE id = #{id} AND name = #{name}
</select>
在调用Mapper方法时,可以将多个参数封装到一个Map对象中,然后将其作为参数传入:
Map<String, Object> params = new HashMap<>();
params.put("id", 1);
params.put("name", "Tom");
User user = userMapper.selectUserByIdAndName(params);
- mybatis动态sql
MyBatis动态SQL中的if、choose、when、otherwise、trim、where、set和foreach标签分别用于实现条件判断、多条件选择、循环遍历等功能。
- if:用于根据条件判断是否拼接SQL语句片段。
- choose、when、otherwise:类似于Java中的switch-case语句,根据条件选择不同的SQL语句片段。
- trim、where、set:用于自动处理多余的逗号和AND、OR关键字。
- foreach:用于遍历集合,拼接SQL语句片段。
以下是一个简单的示例:
<select id="findUserByCondition" parameterType="map" resultType="User">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="age != null">
AND age = #{age}
</if>
<choose>
<when test="gender == 'male'">
AND gender = 'male'
</when>
<when test="gender == 'female'">
AND gender = 'female'
</when>
<otherwise>
AND gender IS NULL
</otherwise>
</choose>
</where>
</select>
在这个示例中,我们根据传入的参数动态地拼接了查询条件的SQL语句片段。如果传入的参数中包含username和age,那么就会拼接出相应的查询条件;否则,只会查询所有的用户。同时,我们还使用了choose、when和otherwise标签来实现性别的判断。
MyBatis中的动态SQL主要包括trim、set和foreach三种。
- trim:用于去除字符串两端的空格或指定字符。语法如下:
<trim prefix="" suffix="" prefixOverrides="" suffixOverrides="">
...
</trim>
示例:
<select id="findUserByName" parameterType="String" resultMap="BaseResultMap">
SELECT * FROM user
<trim prefix="WHERE " suffixOverrides="AND">
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</trim>
</select>
- set:用于更新记录,将指定的值设置到指定的字段上。语法如下:
<set>
...
</set>
示例:
<update id="updateUser" parameterType="User">
UPDATE user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="email != null">
email = #{email}
</if>
</set>
WHERE id = #{id}
</update>
- foreach:用于遍历集合,根据集合中的元素生成SQL语句。语法如下:
<foreach collection="list" item="item" open="(" separator="," close=")">
...
</foreach>
示例:
首先,我们需要创建一个实体类,例如User
:
public class User {
private Integer id;
private String name;
private List<Integer> roleIds;
// getter和setter方法
}
接下来,我们创建一个UserMapper
接口:
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
List<User> selectUsersByRoleIds(@Param("roleIds") List<Integer> roleIds);
}
然后,在UserMapper.xml
文件中编写SQL语句:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "<http://mybatis.org/dtd/mybatis-3-mapper.dtd>">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUsersByRoleIds" resultType="com.example.entity.User">
SELECT * FROM user
WHERE id IN
<foreach item="item" index="index" collection="roleIds" open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>
- mybatis延迟加载
MyBatis 是一个流行的 Java 持久层框架,它支持延迟加载是其一个重要的特性。延迟加载是指在执行查询操作时,只加载需要的字段,而不是将所有字段都加载到内存中。这可以大大减少查询操作的内存消耗,提高查询效率。
在 MyBatis 中,可以通过在映射文件中设置 fetchType
属性来实现延迟加载。fetchType
属性有三个值:lazy
、eager
和 default
。
lazy
:表示延迟加载,只有当访问某个字段时才会加载该字段的数据。eager
:表示立即加载,所有字段的数据都会被加载到内存中。default
:表示使用 MyBatis 的默认设置,即使用延迟加载。
例如,在映射文件中,可以使用以下语句设置延迟加载:
xml复制代码
<resultMap id="userResultMap" type="com.example.User"> <id property="id" column="id" /> <result property="name" column="name" fetchType="lazy"/> <result property="age" column="age" fetchType="lazy"/></resultMap>
在上面的示例中,userResultMap
结果映射中设置了两个字段的延迟加载,即 name
和 age
字段。这意味着在执行查询操作时,只会加载 id
字段的数据,而 name
和 age
字段的数据只有在需要时才会被加载。
需要注意的是,延迟加载可能会增加查询操作的次数,因为每个字段都需要单独加载。因此,在使用延迟加载时,应该根据实际情况进行权衡,以确定是否使用延迟加载以及延迟加载哪些字段。
Spring
- Bean生命周期
Bean的生命周期是指从对象的创建到销毁的过程。在Spring中,一个Bean的生命周期可以分为六个阶段:Bean定义、实例化、属性赋值、初始化、生存期和销毁。
- Bean定义:这是生命周期的开始,此时正在定义Bean的名称、类名和其他配置信息。
- 实例化:根据Bean的定义,创建Bean的实例。
- 属性赋值:在实例化后,为Bean的属性赋值。这些属性可以在Bean的配置文件中定义,也可以通过注入的方式提供。
- 初始化:这是Bean生命周期中一个重要的阶段,在这个阶段,Bean会执行所有的初始化操作,例如调用所有的初始化方法。
- 生存期:在初始化之后,Bean进入生存期阶段。在这个阶段,Bean可以被应用程序使用,执行所需的操作。
- 销毁:最后,当Bean不再需要时,它会被销毁,释放占用的资源。
- AOP原理
面向切面编程(AOP)是一种编程范式,它旨在横向扩展程序功能,解决代码重复和耦合性的问题。其实现原理基于动态代理和代码注入的概念,将关注点(例如日志记录、性能监控、事务管理等)封装为一个切面(Aspect),然后通过切面与应用程序的核心业务逻辑进行关联。
在具体实现上,以Spring AOP为例,在使用Spring AOP时,需要定义切面类,并为需要实现AOP的方法添加注解。Spring会自动将切面逻辑插入到这些方法中,从而实现AOP的功能。
AOP的主要作用包括简化代码和增强代码。它可以将方法中固定位置的重复代码抽取出来,使被抽取的方法更专注于自己的核心功能,提高内聚性。同时,也可以把特定的功能封装到切面类中,哪里有需要就套用,从而使被套用了切面逻辑的方法得到增强。
- IOC原理
Spring IOC(控制反转)是一种设计思想,其原理是将对象的创建和对象依赖的管理反转给容器来实现。这要求实现一个容器,并将容器需要创建的对象及对象间的依赖关系描述出来,由这个容器来实现对象的创建及初始化工作。对象的描述和依赖关系可以采用xml,properties文件和注解来完成。IOC容器的主要职责是对象的创建和依赖的管理注入。在Spring中,最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。每个接口都有它使用的场合,主要是为了区分在Spring内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。
- Spring 四种依赖注入方式
Spring的四种依赖注入方式包括:
- Set方法注入:这是最常用的方式。
- 构造器注入。
- 静态工厂的方法注入。
- 实例工厂的方法注入。
此外,按照实现方式,这四种依赖注入方式还可以分为注解(如@Autowired)和配置文件(如xml)两种实现方式。其中,@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean时,才会按类型装配。使用注解注入依赖对象可以不再在代码中写依赖对象的setter方法或者该类的构造方法,并且不用再配置文件中配置大量的依赖对象,使代码更加简洁,清晰,易于维护。