Java网络编程学习(二)

传输层

Socket 套接字

定义
  • Socket(套接字)是网络通信的端点,用于实现不同主机或同一主机上的进程之间的通信。它是应用层与传输层之间的接口,提供了网络通信的编程接口。
  • Socket套接字 本质是编程的API接口,是对TCP/IP的一个封装。
作用
  1. Socket 是网络通信的基础,允许应用程序通过网络发送和接收数据。
  2. 建立连接:通过Socket,应用程序可以创建一个连接,将自己与远程主机上的应用程序关联起来。在客户端-服务端模型中,客户端通过Socket发起连接请求,服务端通过Socket接受连接请求,建立连接后双方可以进行数据的发送和接收。
  3. 数据传输:Socket提供了发送和接收数据的方法。通过Socket,应用程序可以将数据打包发送给远程主机上的应用程序,也可以从远程主机接收数据。对于TCP协议,Socket提供了可靠的、面向连接的数据传输;对于UDP协议,Socket提供了不可靠的、无连接的数据传输。
  4. 网络编程:Socket是进行网络编程的基础接口。通过使用Socket,开发者可以在应用程序中实现与网络相关的功能,如创建服务器、客户端,进行数据交换、文件传输等。Socket提供了一系列函数和方法,使得网络编程更加方便和灵活。
  5. 协议支持:它支持多种协议(如 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表示发送方已经没有数据发送了,即关闭本方数据流。用来释放一个连接。
  • 窗口大小(Window Size)

    • 16位,数据字节数,表示从确认号开始,本报文的源方可以接收的字节数,即源方接收窗口大小。
    • 占2字节,窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口),窗口值会告诉对方:从现在开始,我只要多少的数据,是因为接收方的缓冲区大小是有限制的,窗口字段明确指出了现在允许对方发送的数据里量。
  • 校验和 (Checksum)

    • 16 位。
    • 此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。
  • 紧急指针(Urgent Pointer)

    • 16 位,只有当 URG 标志置1时紧急指针才有效。TCP 的紧急方式是发送端向另-端发送紧急数据的一种方式。
  • 选项(Options)

    • 最常见的可选字段是最长报文大小,又称为 MSS(Maximum Segment Size)。
    • 每个连接方通常都在通信的第一个报文段(为建立连接而设置 SYN 标志的那个段)中指明这个选项它指明本端所能接收的最大长度的报文段。
    • 选项长度不一定是 32 位字的整数倍,所以要加填充位,使得报头长度成为整字数。
  • 数据

    • TCP 报文段中的数据部分是可选的。
特点
  • 面向连接:通信前需建立连接,结束后释放连接。
  • 可靠性:通过确认、重传、校验和等机制保证数据准确送达。
  • 有序性:数据按发送顺序到达。
  • 流量控制:通过滑动窗口机制防止接收方过载。
  • 拥塞控制:通过拥塞窗口和算法避免网络拥塞。
连接与中止
三次握手

通过三次握手建立连接,确保双方都能正常发送和接收数据。
在这里插入图片描述

三次握手流程步骤

  1. 第一次握手(SYN):
  • 客户端向服务器发送一个 SYN(Synchronize)报文,表示请求建立连接。
  • 报文中包含:
    • SYN 标志位:置为 1。
    • 初始序列号(ISN):客户端随机生成的一个序列号(Sequence Number),用于标识数据字节流。
  1. 第二次握手(SYN-ACK):
  • 服务器收到客户端的 SYN 报文后,回复一个 SYN-ACK 报文,表示确认客户端的请求并同意建立连接。
  • 报文中包含:
    • SYN 标志位:置为 1。
    • ACK 标志位:置为 1。
  • 确认号(Acknowledgment Number):客户端的初始序列号 + 1。
  • 服务器的初始序列号(ISN):服务器随机生成的一个序列号。
  1. 第三次握手(ACK):
  • 客户端收到服务器的 SYN-ACK 报文后,发送一个 ACK 报文,表示确认服务器的响应。
  • 报文中包含:
    • ACK 标志位:置为 1。
    • 确认号(Acknowledgment Number):服务器的初始序列号 + 1。

完成三次握手后,连接建立,双方可以开始数据传输。

四次挥手

四次挥手用于在客户端和服务器之间终止 TCP 连接,确保双方都能安全关闭连接。

四次挥手流程步骤

  1. 第一次挥手(FIN):
  • 客户端向服务器发送一个 FIN(Finish)报文,表示客户端没有数据要发送了,请求关闭连接。
  • 报文中包含:
    • FIN 标志位:置为 1。
  1. 第二次挥手(ACK):
  • 服务器收到客户端的 FIN 报文后,回复一个 ACK 报文,表示确认客户端的关闭请求。
  • 报文中包含:
    • ACK 标志位:置为 1。
    • 确认号(Acknowledgment Number):客户端的序列号 + 1。

此时,服务器可能还有数据需要发送,因此连接处于半关闭状态(客户端不再发送数据,但服务器仍可以发送数据)。

  1. 第三次挥手(FIN):
  • 当服务器完成数据发送后,向客户端发送一个 FIN 报文,表示服务器也没有数据要发送了,请求关闭连接。
  • 报文中包含:
    • FIN 标志位:置为 1。
  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()判断是否为组播地址
使用示例
  1. 获取本地主机的 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();
        }
    }
}
  1. 根据主机名获取 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();
        }
    }
}
  1. 获取主机的所有 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();
        }
    }
}
  1. 测试 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();
        }
    }
}
  1. 判断是否为回环地址
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通信编程
服务器端
  1. 创建 ServerSocket 对象,指定监听的端口号。
  2. 调用 accept() 方法等待客户端连接,返回一个 Socket 对象。
  3. 获取 Socket 的输入流和输出流。
  4. 通过输入流读取客户端发送的数据。
  5. 通过输出流向客户端发送数据。
  6. 关闭 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();
        }
    }
}
客户端
  1. 创建 Socket 对象,指定服务器的 IP 地址和端口号。
  2. 获取 Socket 的输入流和输出流。
  3. 通过输出流向服务器发送数据。
  4. 通过输入流读取服务器返回的数据。
  5. 关闭 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的一些用法
多线程处理

在服务器端,通常需要同时处理多个客户端的连接请求。如果使用单线程处理,服务器只能依次处理每个客户端的请求,效率较低。通过多线程,服务器可以为每个客户端创建一个独立的线程,从而实现并发处理。

实现步骤

  1. 服务器主线程负责监听客户端连接。
  2. 每当有客户端连接时,创建一个新的线程来处理该客户端的请求。
  3. 每个线程独立处理客户端的输入输出。
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 的事件(如连接、读、写)。

实现步骤

  1. 创建 ServerSocketChannel 并绑定端口。
  2. 将 ServerSocketChannel 注册到 Selector,监听连接事件。
  3. 使用 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 编程的核心类
  1. DatagramSocket
  • 用于发送和接收数据报包。
  • 常用方法:
    • void send(DatagramPacket p):发送数据报包。
    • void receive(DatagramPacket p):接收数据报包。
    • void close():关闭套接字。
  1. DatagramPacket
  • 用于封装数据报包。
  • 常用构造函数:
    • DatagramPacket(byte[] buf, int length):用于接收数据。
    • DatagramPacket(byte[] buf, int length, InetAddress address, int port):用于发送数据。
基本流程
发送端(客户端)
  1. 创建 DatagramSocket 对象。
  2. 准备要发送的数据,并将其封装到 DatagramPacket 中。
  3. 使用 DatagramSocket 发送数据报包。
  4. 关闭 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();
        }
    }
}
接收端(服务器端)
  1. 创建 DatagramSocket 对象,并绑定到指定端口。
  2. 创建 DatagramPacket 对象,用于接收数据。
  3. 使用 DatagramSocket 接收数据报包。
  4. 处理接收到的数据。
  5. 关闭 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();
        }
    }
}
注意事项
  1. 数据包大小

    • UDP 数据包的最大长度为 65507 字节(IPv4)。
    • 如果数据包过大,可能会导致丢包或分片。
  2. 数据丢失

    • UDP 不保证数据的可靠传输,可能会丢失数据包。
    • 如果需要可靠传输,可以在应用层实现重传机制。
  3. 数据顺序

    • UDP 不保证数据包的顺序,可能会乱序到达。
    • 如果需要保证顺序,可以在应用层添加序号。
  4. 多线程处理

    • 如果需要同时处理多个客户端,可以使用多线程或 NIO。
广播和组播
  1. 广播(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();
        }
    }
}
  1. 组播(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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值