传输层
Socket 套接字
定义
- Socket(套接字)是网络通信的端点,用于实现不同主机或同一主机上的进程之间的通信。它是应用层与传输层之间的接口,提供了网络通信的编程接口。
- Socket套接字 本质是编程的API接口,是对TCP/IP的一个封装。
作用
- Socket 是网络通信的基础,允许应用程序通过网络发送和接收数据。
- 建立连接:通过Socket,应用程序可以创建一个连接,将自己与远程主机上的应用程序关联起来。在客户端-服务端模型中,客户端通过Socket发起连接请求,服务端通过Socket接受连接请求,建立连接后双方可以进行数据的发送和接收。
- 数据传输:Socket提供了发送和接收数据的方法。通过Socket,应用程序可以将数据打包发送给远程主机上的应用程序,也可以从远程主机接收数据。对于TCP协议,Socket提供了可靠的、面向连接的数据传输;对于UDP协议,Socket提供了不可靠的、无连接的数据传输。
- 网络编程:Socket是进行网络编程的基础接口。通过使用Socket,开发者可以在应用程序中实现与网络相关的功能,如创建服务器、客户端,进行数据交换、文件传输等。Socket提供了一系列函数和方法,使得网络编程更加方便和灵活。
- 协议支持:它支持多种协议(如 TCP、UDP)和通信模式(如点对点、广播)。
类型
根据协议和通信方式的不同,Socket 主要分为以下两种类型:
-
流式 Socket(Stream Socket):
基于 TCP 协议,提供可靠的、面向连接的通信。
特点:- 数据按字节流传输,保证数据的顺序和完整性。
- 适合需要可靠传输的场景(如文件传输、网页浏览)。
-
数据报 Socket(Datagram Socket):
基于 UDP 协议,提供无连接的通信。
特点:- 数据以数据报的形式传输,不保证可靠性、顺序或完整性。
- 适合实时性要求高的场景(如视频流、在线游戏)。
表示方法
- 套接字Socket =(IP地址:端口号),套接字的表示方法是点分十进制的lP地址后面写上端口号,中间用冒号或逗号隔开。
- 每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。
TCP协议
定义
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它确保数据在网络中准确、有序地传输。
格式
-
源端口号(Source Port)
- 16位。
- 标识源主机的一个应用进程。
-
目的端口号 (Destination Port)
- 16位。
- 标识目的主机的一个应用进程。
- 源端口号+ 目的端口号+IP 报头中的源主机 IP 地址和目的主机 IP 地址唯一确定一个 TCP 连接。
-
序列号 (Sequence Number)
- 32位,简称seq。
- 用来标识从 TCP 源端向 TCP 目的端发送的数据字节流,它表示在这个报文段中的第一个数据字节的顺序号。
- 每一个TCP连接中传送的字节流的每一个字节都是按顺序编号,整个要传送的字节流的起始序号必须在建立时设置,通过SYN包传给接收方,解决网络包乱序(去重)的问题。
-
确认号 (Acknowledgment Number):
- 32位,简称ack。
- 是期望收到对方下一个报文段的第一个数据字节的序号,确认序号应当是上次已成功收到数据字节顺序号加1,主要解决不丢包的问题。只有 ACK 标志为1时确认序号字段才有效。
- TCP 为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持每个方向上的传输数据顺序号。
-
报头长度(Data Offset)
- 4 位,表示该TCP头部有多少个32位bit(有多少个4字节),它实际上指明数据从哪里开始,TCP头部最大长度是15 * 4 = 60。
-
保留位(Reserved)
- 6位,保留给将来使用,目前置为 0。
-
控制位( Control Flags)
6 位,报头中有 6 个标志比特,它们中的多个可同时被设置为 1。- URG
- 为1表示紧急指针有效,为0则忽略紧急指针值。
- 当URG = 1时,表示当前报文段中存在优先处理的数据,也叫带外数据(OOB:out of band),不要按原来的排队顺序发送,会把数据紧急插入到本报文段的最前面,这时就和后面的的16位紧急指针配合使用,可以理解为一种数据的插队机制。
- ACK
- 仅当ACK = 1时,确认号字段才有效,ACK = 0时,确认号无效。
- PSH
- 提示接收端应用程序立刻从TCP缓冲区把数据读走,比如:A和B正在通信,A端的一个进程希望立刻获得B端的回应,这时A端就把PSH置为1,立即创建一个报文段发送出去,B端收到后,尽快交付给上层的进程,不需要等待缓冲区填满再向上交付。
- RST
- 用于复位由于主机崩溃或其他原因而出现错误的连接。RST = 1时,说明TCP连接出现了问题,必须释放连接,然后再重新建立连接,RST还可以用来拒绝一个非法的报文段或者拒绝打开一个连接,RST也可以叫做重置位。
- SYN
- 同步序号,在连接建立时用来同步序号,当SYN = 1,ACK = 0时,说明这是一个连接请求报文段,如果对方同意,在响应报文段中SYN = 1,ACK = 1。
- FIN :
- 用于释放连接,为1表示发送方已经没有数据发送了,即关闭本方数据流。用来释放一个连接。
- URG
-
窗口大小(Window Size)
- 16位,数据字节数,表示从确认号开始,本报文的源方可以接收的字节数,即源方接收窗口大小。
- 占2字节,窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口),窗口值会告诉对方:从现在开始,我只要多少的数据,是因为接收方的缓冲区大小是有限制的,窗口字段明确指出了现在允许对方发送的数据里量。
-
校验和 (Checksum)
- 16 位。
- 此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。
-
紧急指针(Urgent Pointer)
- 16 位,只有当 URG 标志置1时紧急指针才有效。TCP 的紧急方式是发送端向另-端发送紧急数据的一种方式。
-
选项(Options)
- 最常见的可选字段是最长报文大小,又称为 MSS(Maximum Segment Size)。
- 每个连接方通常都在通信的第一个报文段(为建立连接而设置 SYN 标志的那个段)中指明这个选项它指明本端所能接收的最大长度的报文段。
- 选项长度不一定是 32 位字的整数倍,所以要加填充位,使得报头长度成为整字数。
-
数据
- TCP 报文段中的数据部分是可选的。
特点
- 面向连接:通信前需建立连接,结束后释放连接。
- 可靠性:通过确认、重传、校验和等机制保证数据准确送达。
- 有序性:数据按发送顺序到达。
- 流量控制:通过滑动窗口机制防止接收方过载。
- 拥塞控制:通过拥塞窗口和算法避免网络拥塞。
连接与中止
三次握手
通过三次握手建立连接,确保双方都能正常发送和接收数据。
三次握手流程步骤:
- 第一次握手(SYN):
- 客户端向服务器发送一个 SYN(Synchronize)报文,表示请求建立连接。
- 报文中包含:
- SYN 标志位:置为 1。
- 初始序列号(ISN):客户端随机生成的一个序列号(Sequence Number),用于标识数据字节流。
- 第二次握手(SYN-ACK):
- 服务器收到客户端的 SYN 报文后,回复一个 SYN-ACK 报文,表示确认客户端的请求并同意建立连接。
- 报文中包含:
- SYN 标志位:置为 1。
- ACK 标志位:置为 1。
- 确认号(Acknowledgment Number):客户端的初始序列号 + 1。
- 服务器的初始序列号(ISN):服务器随机生成的一个序列号。
- 第三次握手(ACK):
- 客户端收到服务器的 SYN-ACK 报文后,发送一个 ACK 报文,表示确认服务器的响应。
- 报文中包含:
- ACK 标志位:置为 1。
- 确认号(Acknowledgment Number):服务器的初始序列号 + 1。
完成三次握手后,连接建立,双方可以开始数据传输。
四次挥手
四次挥手用于在客户端和服务器之间终止 TCP 连接,确保双方都能安全关闭连接。
四次挥手流程步骤:
- 第一次挥手(FIN):
- 客户端向服务器发送一个 FIN(Finish)报文,表示客户端没有数据要发送了,请求关闭连接。
- 报文中包含:
- FIN 标志位:置为 1。
- 第二次挥手(ACK):
- 服务器收到客户端的 FIN 报文后,回复一个 ACK 报文,表示确认客户端的关闭请求。
- 报文中包含:
- ACK 标志位:置为 1。
- 确认号(Acknowledgment Number):客户端的序列号 + 1。
此时,服务器可能还有数据需要发送,因此连接处于半关闭状态(客户端不再发送数据,但服务器仍可以发送数据)。
- 第三次挥手(FIN):
- 当服务器完成数据发送后,向客户端发送一个 FIN 报文,表示服务器也没有数据要发送了,请求关闭连接。
- 报文中包含:
- FIN 标志位:置为 1。
- 第四次挥手(ACK):
- 客户端收到服务器的 FIN 报文后,回复一个 ACK 报文,表示确认服务器的关闭请求。
- 报文中包含:
- ACK 标志位:置为 1。
- 确认号(Acknowledgment Number):服务器的序列号 + 1。
完成四次挥手后,连接完全关闭。
UDP协议
定义
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的、不可靠的传输层协议。它提供简单的数据传输服务,不保证数据的可靠性、有序性或完整性。
UDP通常用于对实时性要求较高的场景,如语音通信,视频通话,直播流媒体,实时多人游戏等,这些场景中,丢失一些数据包对整体效果影响不大,但是要求传输延迟较低。
格式
- 源端口号(Source Port)
- 16位。
- 标识源主机的一个应用进程。
- 目的端口号 (Destination Port)
- 16位。
- 标识目的主机的一个应用进程。
- 包长度(Length)
- 保存了UDP的首部长度和数据长度的和。
- 校验和(Checksum)
- 16位。
- 提供可靠的UDP首部和数据,检测数据报在传输中是否有错,有错就丢弃。
特点
- 无连接:通信前无需建立连接,直接发送数据。
- 不可靠:不保证数据能否到达目的地,也不保证数据的顺序。
- 轻量级:头部开销小,传输效率高。
- 支持广播和多播:可以向多个目标发送数据。
- 实时性高:适合对实时性要求高的应用(如视频流、在线游戏)。
Java网络编程
InetAddress类
说明
- InetAddress 是 Java 中用于表示 IP 地址的类,它既可以表示 IPv4 地址,也可以表示 IPv6 地址。
- InetAddress 类提供了许多静态方法用于获取和操作 IP 地址。
作用
- 表示一个 IP 地址(IPv4 或 IPv6)。
- 提供方法用于解析主机名和 IP 地址。
- 支持获取本地主机和任意主机的 IP 地址。
特点
- 不可变类:InetAddress 对象一旦创建,其值不可更改。
- 无公共构造函数:不能直接通过 new 创建 InetAddress 对象,必须通过静态工厂方法获取实例。
- 支持 IPv4 和 IPv6:可以处理两种 IP 地址格式。
- 实现了Serializable接口,其对象可序列化。
常用方法
方法 | 描述 |
---|---|
static InetAddress getByName(String host) | 根据主机名或 IP 地址字符串获取 InetAddress 对象 |
static InetAddress[] getAllByName(String host) | 根据主机名获取所有关联的 InetAddress 对象(用于多 IP 主机) |
static InetAddress getLocalHost() | 获取本地主机的 InetAddress 对象 |
String getHostName() | 获取主机名 |
String getHostAddress() | 获取 IP 地址字符串 |
boolean isReachable(int timeout) | 测试 IP 地址是否可达(ping 测试) |
boolean isLoopbackAddress() | 判断是否为回环地址(如 127.0.0.1 或 ::1) |
boolean isMulticastAddress() | 判断是否为组播地址 |
使用示例
- 获取本地主机的 IP 地址和主机名
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
// 获取本地主机的 InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
// 获取主机名和 IP 地址
String hostName = localHost.getHostName();
String hostAddress = localHost.getHostAddress();
System.out.println("Host Name: " + hostName);
System.out.println("Host Address: " + hostAddress);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
- 根据主机名获取 IP 地址
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
// 根据主机名获取 InetAddress 对象
InetAddress address = InetAddress.getByName("www.google.com");
// 获取主机名和 IP 地址
String hostName = address.getHostName();
String hostAddress = address.getHostAddress();
System.out.println("Host Name: " + hostName);
System.out.println("Host Address: " + hostAddress);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
- 获取主机的所有 IP 地址
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
// 获取主机的所有 InetAddress 对象
InetAddress[] addresses = InetAddress.getAllByName("www.google.com");
// 遍历并打印所有 IP 地址
for (InetAddress address : addresses) {
System.out.println("Host Name: " + address.getHostName());
System.out.println("Host Address: " + address.getHostAddress());
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
- 测试 IP 地址是否可达
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
// 获取 InetAddress 对象
InetAddress address = InetAddress.getByName("www.google.com");
// 测试 IP 地址是否可达(超时时间 5000 毫秒)
boolean isReachable = address.isReachable(5000);
System.out.println("Is Reachable: " + isReachable);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 判断是否为回环地址
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
// 获取本地主机的 InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
// 判断是否为回环地址
boolean isLoopback = localHost.isLoopbackAddress();
System.out.println("Is Loopback Address: " + isLoopback);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
Socket类
定义
Socket 类是 Java 中用于实现网络通信的核心类之一,它位于 java.net 包中。Socket 类提供了客户端与服务器之间进行双向通信的机制。通过 Socket,客户端可以连接到服务器,并与之进行数据交换。
- Socket:Socket 是网络通信的端点,它封装了 IP 地址和端口号。通过 Socket,客户端和服务器可以建立连接并进行数据交换。
- 客户端 Socket:客户端使用 Socket 类来连接到服务器。
- 服务器 Socket:服务器使用 ServerSocket 类来监听客户端的连接请求,并为每个连接创建一个新的 Socket 对象。
常用构造函数和方法
- 构造函数
Socket(String host, int port) | 创建一个流套接字并将其连接到指定主机上的指定端口号 |
---|---|
Socket(InetAddress address, int port) | 创建一个流套接字并将其连接到指定 IP 地址的指定端口号 |
Socket(String host, int port, InetAddress localAddr, int localPort) | 创建一个套接字并将其连接到指定的远程主机和端口,同时绑定到指定的本地地址和端口 |
- 常用方法
方法 | 说明 |
---|---|
InputStream getInputStream() | 返回此套接字的输入流。用于从套接字读取数据。 |
OutputStream getOutputStream() | 返回此套接字的输出流。用于向套接字写入数据。 |
void close() | 关闭此套接字。 |
void shutdownInput() | 关闭输入流。调用此方法后,套接字的输入流将不再接收数据。 |
void shutdownOutput() | 关闭输出流。调用此方法后,套接字的输出流将不再发送数据。 |
boolean isConnected() | 返回套接字的连接状态。 |
boolean isClosed() | 返回套接字是否已关闭。 |
InetAddress getInetAddress() | 返回套接字连接的远程 IP 地址。 |
int getPort() | 返回套接字连接的远程端口号。 |
InetAddress getLocalAddress() | 返回套接字绑定的本地地址。 |
int getLocalPort() | 返回套接字绑定的本地端口号。 |
TCP通信编程
服务器端
- 创建 ServerSocket 对象,指定监听的端口号。
- 调用 accept() 方法等待客户端连接,返回一个 Socket 对象。
- 获取 Socket 的输入流和输出流。
- 通过输入流读取客户端发送的数据。
- 通过输出流向客户端发送数据。
- 关闭 Socket。
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) {
try {
// 1. 创建ServerSocket对象,监听指定端口
ServerSocket serverSocket = new ServerSocket(12345);
// 2. 等待客户端连接
System.out.println("Waiting for client...");
Socket socket = serverSocket.accept();
System.out.println("Client connected!");
// 3. 获取输入输出流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// 4. 读取客户端发送的数据
byte[] buffer = new byte[1024];
int length = in.read(buffer);
String request = new String(buffer, 0, length);
System.out.println("Client request: " + request);
// 5. 向客户端发送数据
String response = "Hello, Client!";
out.write(response.getBytes());
// 6. 关闭Socket
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
- 创建 Socket 对象,指定服务器的 IP 地址和端口号。
- 获取 Socket 的输入流和输出流。
- 通过输出流向服务器发送数据。
- 通过输入流读取服务器返回的数据。
- 关闭 Socket。
import java.io.*;
import java.net.*;
public class Client {
public static void main(String[] args) {
try {
// 1. 创建Socket对象,连接服务器
Socket socket = new Socket("localhost", 12345);
// 2. 获取输入输出流
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
// 3. 发送数据到服务器
String message = "Hello, Server!";
out.write(message.getBytes());
// 4. 读取服务器返回的数据
byte[] buffer = new byte[1024];
int length = in.read(buffer);
String response = new String(buffer, 0, length);
System.out.println("Server response: " + response);
// 5. 关闭Socket
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Socket 的异常处理
在使用 Socket 进行网络通信时,可能会遇到各种异常情况,例如网络中断、连接超时等。因此,在实际开发中,通常需要对 Socket 操作进行异常处理。
常见的异常包括:
- IOException:输入输出异常,通常发生在网络通信过程中。
- UnknownHostException:无法解析主机名或 IP 地址时抛出。
- SocketTimeoutException:在设置超时时间后,操作超时时抛出。
Socket的一些用法
多线程处理
在服务器端,通常需要同时处理多个客户端的连接请求。如果使用单线程处理,服务器只能依次处理每个客户端的请求,效率较低。通过多线程,服务器可以为每个客户端创建一个独立的线程,从而实现并发处理。
实现步骤
- 服务器主线程负责监听客户端连接。
- 每当有客户端连接时,创建一个新的线程来处理该客户端的请求。
- 每个线程独立处理客户端的输入输出。
import java.io.*;
import java.net.*;
public class MultiThreadedServer {
public static void main(String[] args) {
try {
// 1. 创建ServerSocket,监听端口
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("Server started, waiting for clients...");
while (true) {
// 2. 等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
// 3. 为每个客户端创建一个新线程
ClientHandler clientHandler = new ClientHandler(clientSocket);
new Thread(clientHandler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientHandler implements Runnable {
private Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try {
// 1. 获取输入输出流
InputStream in = clientSocket.getInputStream();
OutputStream out = clientSocket.getOutputStream();
// 2. 读取客户端数据
byte[] buffer = new byte[1024];
int length = in.read(buffer);
String request = new String(buffer, 0, length);
System.out.println("Received from client: " + request);
// 3. 向客户端发送响应
String response = "Hello from server!";
out.write(response.getBytes());
// 4. 关闭连接
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO(非阻塞 I/O)
Java NIO(New I/O)提供了非阻塞的 I/O 操作,适用于高并发的网络应用。NIO 的核心组件包括:
- Channel:类似于流,但可以同时读写数据。
- Buffer:数据容器,用于与 Channel 交互。
- Selector:用于监听多个 Channel 的事件(如连接、读、写)。
实现步骤
- 创建 ServerSocketChannel 并绑定端口。
- 将 ServerSocketChannel 注册到 Selector,监听连接事件。
- 使用 Selector 轮询事件,处理客户端的连接、读、写操作。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NioServer {
public static void main(String[] args) throws IOException {
// 1. 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(12345));
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
// 2. 创建Selector
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 监听连接事件
System.out.println("Server started, waiting for clients...");
while (true) {
selector.select(); // 阻塞,直到有事件发生
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
// 处理连接事件
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ); // 监听读事件
System.out.println("Client connected: " + clientChannel.getRemoteAddress());
}
if (key.isReadable()) {
// 处理读事件
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
String request = new String(buffer.array(), 0, bytesRead);
System.out.println("Received from client: " + request);
// 向客户端发送响应
String response = "Hello from server!";
ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
clientChannel.write(responseBuffer);
} else if (bytesRead == -1) {
// 客户端关闭连接
clientChannel.close();
}
}
iterator.remove();
}
}
}
}
SSL/TLS 加密通信
SSL/TLS 是一种加密协议,用于在网络通信中保护数据的安全。Java 提供了 SSLSocket 和 SSLServerSocket 类来实现加密通信。
实现步骤
- 生成密钥库和信任库(可以使用 keytool 工具)。
- 配置服务器和客户端的 SSL 上下文。
- 使用 SSLSocket 和 SSLServerSocket 进行加密通信。
import javax.net.ssl.*;
import java.io.*;
import java.security.KeyStore;
public class SslServer {
public static void main(String[] args) throws Exception {
// 1. 加载密钥库
char[] password = "password".toCharArray();
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("server.keystore"), password);
// 2. 初始化KeyManagerFactory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, password);
// 3. 初始化SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// 4. 创建SSLServerSocket
SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(12345);
System.out.println("SSL Server started, waiting for clients...");
while (true) {
// 5. 接受客户端连接
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
System.out.println("Client connected: " + clientSocket.getInetAddress());
// 6. 处理客户端请求
InputStream in = clientSocket.getInputStream();
OutputStream out = clientSocket.getOutputStream();
byte[] buffer = new byte[1024];
int length = in.read(buffer);
String request = new String(buffer, 0, length);
System.out.println("Received from client: " + request);
String response = "Hello from SSL server!";
out.write(response.getBytes());
clientSocket.close();
}
}
}
import javax.net.ssl.*;
import java.io.*;
public class SslClient {
public static void main(String[] args) throws Exception {
// 1. 加载信任库
char[] password = "password".toCharArray();
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream("client.truststore"), password);
// 2. 初始化TrustManagerFactory
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(trustStore);
// 3. 初始化SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
// 4. 创建SSLSocket
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket) socketFactory.createSocket("localhost", 12345);
// 5. 发送数据到服务器
OutputStream out = socket.getOutputStream();
String message = "Hello, SSL Server!";
out.write(message.getBytes());
// 6. 读取服务器响应
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = in.read(buffer);
String response = new String(buffer, 0, length);
System.out.println("Server response: " + response);
socket.close();
}
}
UDP通信编程
Java 提供了 DatagramSocket 和 DatagramPacket 类来实现 UDP 编程。
UDP 编程的核心类
- DatagramSocket
- 用于发送和接收数据报包。
- 常用方法:
- void send(DatagramPacket p):发送数据报包。
- void receive(DatagramPacket p):接收数据报包。
- void close():关闭套接字。
- DatagramPacket
- 用于封装数据报包。
- 常用构造函数:
- DatagramPacket(byte[] buf, int length):用于接收数据。
- DatagramPacket(byte[] buf, int length, InetAddress address, int port):用于发送数据。
基本流程
发送端(客户端)
- 创建 DatagramSocket 对象。
- 准备要发送的数据,并将其封装到 DatagramPacket 中。
- 使用 DatagramSocket 发送数据报包。
- 关闭 DatagramSocket。
import java.net.*;
public class UdpClient {
public static void main(String[] args) {
try {
// 1. 创建DatagramSocket
DatagramSocket socket = new DatagramSocket();
// 2. 准备要发送的数据
String message = "Hello, UDP Server!";
byte[] sendData = message.getBytes();
// 3. 创建DatagramPacket,指定服务器地址和端口
InetAddress serverAddress = InetAddress.getByName("localhost");
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, 12345);
// 4. 发送数据
socket.send(sendPacket);
System.out.println("Message sent to server: " + message);
// 5. 关闭Socket
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
接收端(服务器端)
- 创建 DatagramSocket 对象,并绑定到指定端口。
- 创建 DatagramPacket 对象,用于接收数据。
- 使用 DatagramSocket 接收数据报包。
- 处理接收到的数据。
- 关闭 DatagramSocket。
import java.net.*;
public class UdpServer {
public static void main(String[] args) {
try {
// 1. 创建DatagramSocket,绑定到指定端口
DatagramSocket socket = new DatagramSocket(12345);
System.out.println("UDP Server started, waiting for data...");
// 2. 创建DatagramPacket,用于接收数据
byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 3. 接收数据
socket.receive(receivePacket);
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("Received from client: " + message);
// 4. 关闭Socket
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意事项
-
数据包大小
- UDP 数据包的最大长度为 65507 字节(IPv4)。
- 如果数据包过大,可能会导致丢包或分片。
-
数据丢失
- UDP 不保证数据的可靠传输,可能会丢失数据包。
- 如果需要可靠传输,可以在应用层实现重传机制。
-
数据顺序
- UDP 不保证数据包的顺序,可能会乱序到达。
- 如果需要保证顺序,可以在应用层添加序号。
-
多线程处理
- 如果需要同时处理多个客户端,可以使用多线程或 NIO。
广播和组播
- 广播(Broadcast)
- 广播是将数据包发送到同一网络中的所有主机。
- 广播地址为 255.255.255.255 或子网广播地址(如 192.168.1.255)。
import java.net.*;
public class UdpBroadcastSender {
public static void main(String[] args) {
try {
// 1. 创建DatagramSocket
DatagramSocket socket = new DatagramSocket();
socket.setBroadcast(true); // 启用广播
// 2. 准备要发送的数据
String message = "Hello, Broadcast!";
byte[] sendData = message.getBytes();
// 3. 创建DatagramPacket,指定广播地址和端口
InetAddress broadcastAddress = InetAddress.getByName("255.255.255.255");
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, broadcastAddress, 12345);
// 4. 发送数据
socket.send(sendPacket);
System.out.println("Broadcast message sent: " + message);
// 5. 关闭Socket
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
-
组播(Multicast)
- 组播是将数据包发送到一组特定的主机。
- 组播地址范围为 224.0.0.0 到 239.255.255.255。
import java.net.*;
public class UdpMulticastReceiver {
public static void main(String[] args) {
try {
// 1. 创建MulticastSocket,绑定到指定端口
MulticastSocket socket = new MulticastSocket(12345);
// 2. 加入组播组
InetAddress group = InetAddress.getByName("230.0.0.1");
socket.joinGroup(group);
// 3. 创建DatagramPacket,用于接收数据
byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// 4. 接收数据
socket.receive(receivePacket);
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("Received from multicast group: " + message);
// 5. 离开组播组并关闭Socket
socket.leaveGroup(group);
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}