Skip to content

Java网络编程

网络编程是Java语言中非常重要的一个方面,它允许Java应用程序通过网络与其他应用程序进行通信。本文将深入探讨Java网络编程的核心概念、API使用以及高级主题。

网络编程基础

网络模型概述

OSI七层模型

OSI(Open Systems Interconnection)七层模型是一个概念性框架,用于理解网络通信的工作原理:

  1. 物理层(Physical Layer):传输原始比特流
  2. 数据链路层(Data Link Layer):在网络节点间传输数据帧
  3. 网络层(Network Layer):负责IP寻址和路由选择
  4. 传输层(Transport Layer):提供端到端的通信服务
  5. 会话层(Session Layer):管理会话和同步
  6. 表示层(Presentation Layer):数据格式转换和加密
  7. 应用层(Application Layer):直接为应用程序提供服务

TCP/IP四层模型

TCP/IP模型是实际网络中使用最广泛的协议族,它将OSI七层模型简化为四层:

  1. 网络接口层(Network Interface Layer):对应OSI的物理层和数据链路层
  2. 网络层(Internet Layer):对应OSI的网络层,主要协议是IP
  3. 传输层(Transport Layer):对应OSI的传输层,主要协议有TCP和UDP
  4. 应用层(Application Layer):对应OSI的会话层、表示层和应用层

IP地址和端口

IP地址

IP地址是网络上设备的唯一标识:

  • IPv4:32位地址,格式为点分十进制(如192.168.1.1)
  • IPv6:128位地址,格式为冒分十六进制(如2001:0db8:85a3:0000:0000:8a2e:0370:7334)

端口

端口是应用程序在设备上的唯一标识,范围从0到65535:

  • 知名端口(Well-known Ports):0-1023,由IANA分配给特定服务
  • 注册端口(Registered Ports):1024-49151,用于特定应用程序
  • 动态/私有端口(Dynamic/Private Ports):49152-65535,临时使用

TCP和UDP协议

TCP协议

TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议:

  • 面向连接:通信前需要建立连接(三次握手),通信后需要关闭连接(四次挥手)
  • 可靠传输:通过序列号、确认应答、超时重传、流量控制、拥塞控制等机制确保数据可靠传输
  • 字节流:数据以字节流形式传输,没有消息边界
  • 全双工:允许数据在两个方向上同时传输

UDP协议

UDP(User Datagram Protocol)是一种无连接的传输层协议:

  • 无连接:通信前不需要建立连接,通信后不需要关闭连接
  • 不可靠传输:不保证数据的可靠传输,可能丢失、重复或乱序
  • 数据报:数据以数据报形式传输,有消息边界
  • 开销小:头部开销比TCP小,传输效率高
  • 适用于实时应用:如视频流、语音通话、在线游戏等

Java网络编程API

InetAddress类

InetAddress类用于表示IP地址。

java
// InetAddress类的使用示例
import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
    public static void main(String[] args) {
        try {
            // 获取本地主机的InetAddress对象
            InetAddress localHost = InetAddress.getLocalHost();
            System.out.println("本地主机名: " + localHost.getHostName());
            System.out.println("本地IP地址: " + localHost.getHostAddress());
            
            // 根据主机名获取InetAddress对象
            InetAddress[] addresses = InetAddress.getAllByName("www.baidu.com");
            System.out.println("百度的IP地址:");
            for (InetAddress address : addresses) {
                System.out.println("  - " + address.getHostAddress());
            }
            
            // 根据IP地址获取InetAddress对象
            InetAddress address = InetAddress.getByName("114.114.114.114");
            System.out.println("114.114.114.114的主机名: " + address.getHostName());
            
            // 检查主机是否可达
            boolean reachable = address.isReachable(5000); // 超时5秒
            System.out.println("主机是否可达: " + reachable);
            
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

URL和URLConnection类

URL类用于表示统一资源定位符,URLConnection类用于建立与URL指定资源的连接。

java
// URL和URLConnection类的使用示例
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class URLDemo {
    public static void main(String[] args) {
        try {
            // 创建URL对象
            URL url = new URL("https://www.example.com/");
            
            // 获取URL的各个部分
            System.out.println("协议: " + url.getProtocol());
            System.out.println("主机名: " + url.getHost());
            System.out.println("端口: " + url.getPort());
            System.out.println("路径: " + url.getPath());
            System.out.println("查询参数: " + url.getQuery());
            
            // 打开URL连接
            URLConnection connection = url.openConnection();
            
            // 设置连接属性
            connection.setRequestProperty("User-Agent", "Mozilla/5.0");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            
            // 获取响应信息
            System.out.println("内容类型: " + connection.getContentType());
            System.out.println("内容长度: " + connection.getContentLength());
            System.out.println("最后修改时间: " + connection.getLastModified());
            
            // 读取响应内容
            try (InputStream inputStream = connection.getInputStream();
                 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
                String line;
                StringBuilder content = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                System.out.println("响应内容:");
                System.out.println(content.substring(0, Math.min(content.length(), 200)) + "...");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Socket编程

TCP Socket编程

TCP Socket编程使用Socket类(客户端)和ServerSocket类(服务端)。

TCP服务端示例

java
// TCP服务端示例
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 TCPServer {
    public static void main(String[] args) {
        int port = 8080;
        
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("TCP服务端已启动,监听端口: " + port);
            
            // 循环接受客户端连接
            while (true) {
                // 接受客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
                
                // 为每个客户端创建一个新线程处理
                new Thread(() -> {
                    try (Socket socket = clientSocket;
                         BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                         PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {
                        
                        // 读取客户端发送的消息
                        String message;
                        while ((message = reader.readLine()) != null) {
                            System.out.println("收到客户端消息: " + message);
                            
                            // 向客户端发送响应
                            writer.println("服务端已收到: " + message);
                            
                            // 如果收到exit消息,关闭连接
                            if ("exit".equalsIgnoreCase(message)) {
                                break;
                            }
                        }
                        
                        System.out.println("客户端已断开连接: " + socket.getInetAddress().getHostAddress());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

TCP客户端示例

java
// TCP客户端示例
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 8080;
        
        try (Socket socket = new Socket(host, port);
             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
            
            System.out.println("已连接到服务端: " + host + ":" + port);
            System.out.println("请输入消息(输入exit退出):");
            
            // 创建一个线程用于读取服务端响应
            Thread responseThread = new Thread(() -> {
                try {
                    String response;
                    while ((response = reader.readLine()) != null) {
                        System.out.println("服务端响应: " + response);
                    }
                } catch (IOException e) {
                    // 连接关闭时会抛出异常,这里可以忽略
                }
            });
            responseThread.start();
            
            // 从控制台读取用户输入并发送给服务端
            String message;
            while ((message = consoleReader.readLine()) != null) {
                writer.println(message);
                
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
            }
            
            System.out.println("已断开与服务端的连接");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

UDP Socket编程

UDP Socket编程使用DatagramSocket类和DatagramPacket类。

UDP服务端示例

java
// UDP服务端示例
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPServer {
    public static void main(String[] args) {
        int port = 8080;
        byte[] buffer = new byte[1024];
        
        try (DatagramSocket socket = new DatagramSocket(port)) {
            System.out.println("UDP服务端已启动,监听端口: " + port);
            
            while (true) {
                // 创建数据包用于接收客户端消息
                DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
                
                // 接收客户端消息
                socket.receive(receivePacket);
                
                // 获取客户端地址和端口
                InetAddress clientAddress = receivePacket.getAddress();
                int clientPort = receivePacket.getPort();
                
                // 解析收到的消息
                String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
                System.out.println("收到客户端[" + clientAddress.getHostAddress() + ":" + clientPort + "]的消息: " + message);
                
                // 准备响应消息
                String response = "服务端已收到: " + message;
                byte[] responseData = response.getBytes();
                
                // 创建响应数据包
                DatagramPacket sendPacket = new DatagramPacket(responseData, responseData.length, clientAddress, clientPort);
                
                // 发送响应
                socket.send(sendPacket);
                System.out.println("已向客户端发送响应");
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

UDP客户端示例

java
// UDP客户端示例
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 8080;
        byte[] receiveBuffer = new byte[1024];
        
        try (DatagramSocket socket = new DatagramSocket();
             BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
            
            InetAddress serverAddress = InetAddress.getByName(host);
            System.out.println("UDP客户端已启动,请输入消息(输入exit退出):");
            
            while (true) {
                // 读取用户输入
                String message = consoleReader.readLine();
                
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                
                // 准备发送数据
                byte[] sendData = message.getBytes();
                
                // 创建数据包
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, port);
                
                // 发送数据包
                socket.send(sendPacket);
                System.out.println("已发送消息: " + message);
                
                // 准备接收响应
                DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
                
                // 设置超时时间为3秒
                socket.setSoTimeout(3000);
                
                try {
                    // 接收响应
                    socket.receive(receivePacket);
                    
                    // 解析响应消息
                    String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
                    System.out.println("收到服务端响应: " + response);
                } catch (IOException e) {
                    System.out.println("接收超时,未收到服务端响应");
                }
            }
            
            System.out.println("UDP客户端已关闭");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NIO(New I/O)

NIO是Java SE 1.4引入的新I/O API,提供了更高效的I/O操作方式。NIO主要包括三个核心组件:Channel(通道)、Buffer(缓冲区)和Selector(选择器)。

Buffer(缓冲区)

Buffer是一个对象,它包含一些要写入或读取的数据。在NIO中,数据是通过Buffer读写的。

java
// Buffer的使用示例
import java.nio.ByteBuffer;

public class BufferDemo {
    public static void main(String[] args) {
        // 创建一个容量为1024字节的ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        // 输出初始状态
        System.out.println("初始状态:");
        System.out.println("position: " + buffer.position());
        System.out.println("limit: " + buffer.limit());
        System.out.println("capacity: " + buffer.capacity());
        
        // 写入数据到buffer
        String message = "Hello, NIO!";
        buffer.put(message.getBytes());
        
        // 输出写入后的状态
        System.out.println("\n写入数据后:");
        System.out.println("position: " + buffer.position());
        System.out.println("limit: " + buffer.limit());
        
        // 切换到读模式
        buffer.flip();
        
        // 输出切换到读模式后的状态
        System.out.println("\n切换到读模式后:");
        System.out.println("position: " + buffer.position());
        System.out.println("limit: " + buffer.limit());
        
        // 读取数据
        byte[] data = new byte[buffer.limit()];
        buffer.get(data);
        String readMessage = new String(data);
        System.out.println("\n读取的数据: " + readMessage);
        
        // 输出读取后的状态
        System.out.println("\n读取数据后:");
        System.out.println("position: " + buffer.position());
        System.out.println("limit: " + buffer.limit());
        
        // 清空buffer,准备再次写入
        buffer.clear();
        
        // 输出清空后的状态
        System.out.println("\n清空buffer后:");
        System.out.println("position: " + buffer.position());
        System.out.println("limit: " + buffer.limit());
    }
}

Channel(通道)

Channel是一个可以读写数据的通道,与传统的流不同,Channel是双向的。

java
// Channel的使用示例(文件Channel)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ChannelDemo {
    public static void main(String[] args) {
        String sourceFile = "source.txt";
        String destFile = "dest.txt";
        
        try (FileInputStream fis = new FileInputStream(sourceFile);
             FileOutputStream fos = new FileOutputStream(destFile);
             FileChannel sourceChannel = fis.getChannel();
             FileChannel destChannel = fos.getChannel()) {
            
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 从源文件读取数据并写入目标文件
            int bytesRead;
            while ((bytesRead = sourceChannel.read(buffer)) != -1) {
                // 切换到读模式
                buffer.flip();
                
                // 写入目标通道
                while (buffer.hasRemaining()) {
                    destChannel.write(buffer);
                }
                
                // 清空缓冲区,准备下一次读取
                buffer.clear();
            }
            
            System.out.println("文件复制完成");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Selector(选择器)

Selector是一个多路复用器,可以监控多个Channel的事件。使用Selector可以实现单线程管理多个连接。

java
// NIO服务端示例(使用Selector)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) {
        try {
            // 创建Selector
            Selector selector = Selector.open();
            
            // 创建ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            
            // 将ServerSocketChannel注册到Selector,关注OP_ACCEPT事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            
            System.out.println("NIO服务端已启动,监听端口: 8080");
            
            while (true) {
                // 阻塞等待事件发生
                int readyChannels = selector.select();
                
                if (readyChannels == 0) {
                    continue;
                }
                
                // 获取所有就绪的SelectionKey
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    
                    // 处理接受连接事件
                    if (key.isAcceptable()) {
                        handleAccept(key, selector);
                    }
                    
                    // 处理读取事件
                    if (key.isReadable()) {
                        handleRead(key);
                    }
                    
                    // 移除已处理的SelectionKey
                    keyIterator.remove();
                }
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        
        // 将SocketChannel注册到Selector,关注OP_READ事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        
        System.out.println("客户端已连接: " + socketChannel.getRemoteAddress());
    }
    
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        int bytesRead = socketChannel.read(buffer);
        
        if (bytesRead == -1) {
            // 客户端关闭连接
            socketChannel.close();
            System.out.println("客户端已断开连接");
            return;
        }
        
        // 切换到读模式
        buffer.flip();
        
        // 读取数据
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        String message = new String(data);
        
        System.out.println("收到客户端消息: " + message);
        
        // 回显消息给客户端
        ByteBuffer responseBuffer = ByteBuffer.wrap("服务端已收到: ".getBytes());
        socketChannel.write(responseBuffer);
    }
}
java
// NIO客户端示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) {
        try {
            // 创建SocketChannel
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            socketChannel.configureBlocking(false);
            
            System.out.println("NIO客户端已连接到服务端");
            
            Scanner scanner = new Scanner(System.in);
            
            while (true) {
                System.out.print("请输入消息(输入exit退出): ");
                String message = scanner.nextLine();
                
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                
                // 发送消息给服务端
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                socketChannel.write(buffer);
                
                // 读取服务端响应
                buffer.clear();
                int bytesRead = socketChannel.read(buffer);
                
                if (bytesRead > 0) {
                    buffer.flip();
                    byte[] responseData = new byte[buffer.remaining()];
                    buffer.get(responseData);
                    String response = new String(responseData);
                    System.out.println("服务端响应: " + response);
                }
                
                // 短暂休眠,避免CPU占用过高
                Thread.sleep(100);
            }
            
            // 关闭连接
            socketChannel.close();
            scanner.close();
            System.out.println("NIO客户端已关闭");
            
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java NIO.2(AIO)

NIO.2(Asynchronous I/O)是Java SE 7引入的异步I/O API,提供了真正的异步非阻塞I/O操作。

AsynchronousSocketChannel和AsynchronousServerSocketChannel

java
// AIO服务端示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AIOServer {
    public static void main(String[] args) {
        try {
            // 创建AsynchronousServerSocketChannel
            AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            
            System.out.println("AIO服务端已启动,监听端口: 8080");
            
            // 接受连接
            serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                @Override
                public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                    // 继续接受其他连接
                    serverSocketChannel.accept(null, this);
                    
                    try {
                        System.out.println("客户端已连接: " + socketChannel.getRemoteAddress());
                        
                        // 准备读取数据
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        
                        // 读取数据
                        socketChannel.read(buffer, buffer, new ReadHandler(socketChannel));
                        
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                
                @Override
                public void failed(Throwable exc, Object attachment) {
                    System.out.println("接受连接失败: " + exc.getMessage());
                }
            });
            
            // 保持主线程运行
            Thread.sleep(Integer.MAX_VALUE);
            
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    // 读取处理器
    private static class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
        private final AsynchronousSocketChannel socketChannel;
        
        public ReadHandler(AsynchronousSocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }
        
        @Override
        public void completed(Integer result, ByteBuffer buffer) {
            if (result > 0) {
                // 切换到读模式
                buffer.flip();
                
                // 读取数据
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
                String message = new String(data);
                
                System.out.println("收到客户端消息: " + message);
                
                // 回显消息给客户端
                ByteBuffer responseBuffer = ByteBuffer.wrap("服务端已收到: ".getBytes());
                socketChannel.write(responseBuffer, null, new CompletionHandler<Integer, Object>() {
                    @Override
                    public void completed(Integer result, Object attachment) {
                        // 继续读取数据
                        buffer.clear();
                        socketChannel.read(buffer, buffer, ReadHandler.this);
                    }
                    
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("写入数据失败: " + exc.getMessage());
                        try {
                            socketChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                
            } else if (result == -1) {
                // 客户端关闭连接
                try {
                    System.out.println("客户端已断开连接");
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        @Override
        public void failed(Throwable exc, ByteBuffer buffer) {
            System.out.println("读取数据失败: " + exc.getMessage());
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
java
// AIO客户端示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;

public class AIOClient {
    public static void main(String[] args) {
        try {
            // 创建AsynchronousSocketChannel
            AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
            CountDownLatch latch = new CountDownLatch(1);
            
            // 连接服务端
            socketChannel.connect(new InetSocketAddress("localhost", 8080), latch, new CompletionHandler<Void, CountDownLatch>() {
                @Override
                public void completed(Void result, CountDownLatch attachment) {
                    System.out.println("已连接到服务端");
                    attachment.countDown();
                }
                
                @Override
                public void failed(Throwable exc, CountDownLatch attachment) {
                    System.out.println("连接服务端失败: " + exc.getMessage());
                    attachment.countDown();
                }
            });
            
            // 等待连接完成
            latch.await();
            
            Scanner scanner = new Scanner(System.in);
            
            while (true) {
                System.out.print("请输入消息(输入exit退出): ");
                String message = scanner.nextLine();
                
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                
                // 发送消息给服务端
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                CountDownLatch writeLatch = new CountDownLatch(1);
                
                socketChannel.write(buffer, writeLatch, new CompletionHandler<Integer, CountDownLatch>() {
                    @Override
                    public void completed(Integer result, CountDownLatch attachment) {
                        // 读取服务端响应
                        ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
                        socketChannel.read(responseBuffer, responseBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                            @Override
                            public void completed(Integer result, ByteBuffer buffer) {
                                if (result > 0) {
                                    buffer.flip();
                                    byte[] responseData = new byte[buffer.remaining()];
                                    buffer.get(responseData);
                                    String response = new String(responseData);
                                    System.out.println("服务端响应: " + response);
                                }
                                attachment.countDown();
                            }
                            
                            @Override
                            public void failed(Throwable exc, ByteBuffer buffer) {
                                System.out.println("读取响应失败: " + exc.getMessage());
                                attachment.countDown();
                            }
                        });
                    }
                    
                    @Override
                    public void failed(Throwable exc, CountDownLatch attachment) {
                        System.out.println("发送消息失败: " + exc.getMessage());
                        attachment.countDown();
                    }
                });
                
                // 等待写入和读取完成
                writeLatch.await();
            }
            
            // 关闭连接
            socketChannel.close();
            scanner.close();
            System.out.println("AIO客户端已关闭");
            
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

高级主题

线程池在网络编程中的应用

在高并发的网络应用中,使用线程池可以有效地管理线程资源,提高系统性能。

java
// 使用线程池的TCP服务端示例
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTCPServer {
    private static final int PORT = 8080;
    private static final int THREAD_POOL_SIZE = 10;
    
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("线程池TCP服务端已启动,监听端口: " + PORT);
            System.out.println("线程池大小: " + THREAD_POOL_SIZE);
            
            while (true) {
                // 接受客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
                
                // 提交任务给线程池
                executorService.submit(() -> handleClient(clientSocket));
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            executorService.shutdown();
        }
    }
    
    private static void handleClient(Socket socket) {
        try (Socket clientSocket = socket;
             BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true)) {
            
            // 读取客户端发送的消息
            String message;
            while ((message = reader.readLine()) != null) {
                System.out.println("收到客户端消息: " + message);
                
                // 向客户端发送响应
                writer.println("服务端已收到: " + message);
                
                // 如果收到exit消息,关闭连接
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
            }
            
            System.out.println("客户端已断开连接: " + socket.getInetAddress().getHostAddress());
            
        } catch (IOException e) {
            System.out.println("处理客户端连接时发生错误: " + e.getMessage());
        }
    }
}

安全的网络通信(SSL/TLS)

SSL(Secure Sockets Layer)和TLS(Transport Layer Security)是用于在网络上提供安全通信的协议。Java提供了JSSE(Java Secure Socket Extension)来支持SSL/TLS。

java
// SSL/TLS服务端示例
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.KeyStore;

public class SSLServer {
    private static final int PORT = 8443;
    private static final String KEYSTORE_PATH = "server.keystore";
    private static final String KEYSTORE_PASSWORD = "password";
    private static final String KEY_PASSWORD = "password";
    
    public static void main(String[] args) {
        try {
            // 初始化SSL上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");
            KeyStore keyStore = KeyStore.getInstance("JKS");
            
            // 加载密钥库
            try (FileInputStream fis = new FileInputStream(KEYSTORE_PATH)) {
                keyStore.load(fis, KEYSTORE_PASSWORD.toCharArray());
            }
            
            // 初始化密钥管理器
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(keyStore, KEY_PASSWORD.toCharArray());
            
            // 初始化SSL上下文
            sslContext.init(kmf.getKeyManagers(), null, null);
            
            // 创建SSL服务器套接字工厂
            SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
            
            // 创建SSL服务器套接字
            try (SSLServerSocket serverSocket = (SSLServerSocket) socketFactory.createServerSocket(PORT)) {
                // 设置需要客户端认证(可选)
                // serverSocket.setNeedClientAuth(true);
                
                System.out.println("SSL服务端已启动,监听端口: " + PORT);
                
                while (true) {
                    // 接受客户端连接
                    try (SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                         BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                         PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true)) {
                        
                        System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());
                        
                        // 读取客户端发送的消息
                        String message;
                        while ((message = reader.readLine()) != null) {
                            System.out.println("收到客户端消息: " + message);
                            
                            // 向客户端发送响应
                            writer.println("服务端已收到: " + message);
                            
                            // 如果收到exit消息,关闭连接
                            if ("exit".equalsIgnoreCase(message)) {
                                break;
                            }
                        }
                        
                        System.out.println("客户端已断开连接: " + clientSocket.getInetAddress().getHostAddress());
                    }
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
java
// SSL/TLS客户端示例
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.KeyStore;

public class SSLClient {
    private static final String HOST = "localhost";
    private static final int PORT = 8443;
    private static final String TRUSTSTORE_PATH = "client.truststore";
    private static final String TRUSTSTORE_PASSWORD = "password";
    
    public static void main(String[] args) {
        try {
            // 初始化SSL上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");
            KeyStore trustStore = KeyStore.getInstance("JKS");
            
            // 加载信任库
            try (FileInputStream fis = new FileInputStream(TRUSTSTORE_PATH)) {
                trustStore.load(fis, TRUSTSTORE_PASSWORD.toCharArray());
            }
            
            // 初始化信任管理器
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustStore);
            
            // 初始化SSL上下文
            sslContext.init(null, tmf.getTrustManagers(), null);
            
            // 创建SSL套接字工厂
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            
            // 创建SSL套接字
            try (SSLSocket socket = (SSLSocket) socketFactory.createSocket(HOST, PORT);
                 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                 PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                 BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
                
                System.out.println("已连接到SSL服务端: " + HOST + ":" + PORT);
                System.out.println("请输入消息(输入exit退出):");
                
                // 创建一个线程用于读取服务端响应
                Thread responseThread = new Thread(() -> {
                    try {
                        String response;
                        while ((response = reader.readLine()) != null) {
                            System.out.println("服务端响应: " + response);
                        }
                    } catch (Exception e) {
                        // 连接关闭时会抛出异常,这里可以忽略
                    }
                });
                responseThread.start();
                
                // 从控制台读取用户输入并发送给服务端
                String message;
                while ((message = consoleReader.readLine()) != null) {
                    writer.println(message);
                    
                    if ("exit".equalsIgnoreCase(message)) {
                        break;
                    }
                }
                
                System.out.println("已断开与SSL服务端的连接");
                
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

HTTP客户端编程

Java提供了多种HTTP客户端API,包括传统的HttpURLConnection、Apache HttpClient和Java 11引入的HttpClient。

使用Java 11 HttpClient

java
// Java 11 HttpClient示例
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;

public class HttpClientDemo {
    public static void main(String[] args) {
        // 创建HttpClient
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        
        // 创建HttpRequest
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.github.com/users/octocat"))
                .header("Accept", "application/json")
                .GET()
                .build();
        
        try {
            // 同步发送请求
            System.out.println("发送同步请求...");
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            
            // 处理响应
            System.out.println("响应状态码: " + response.statusCode());
            System.out.println("响应头:");
            response.headers().map().forEach((key, values) -> {
                System.out.println("  " + key + ": " + String.join(", ", values));
            });
            System.out.println("响应体(前200字符):");
            System.out.println(response.body().substring(0, Math.min(response.body().length(), 200)) + "...");
            
            // 异步发送请求
            System.out.println("\n发送异步请求...");
            CompletableFuture<HttpResponse<String>> futureResponse = 
                    client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
            
            // 处理异步响应
            futureResponse.thenAccept(resp -> {
                System.out.println("异步响应状态码: " + resp.statusCode());
                System.out.println("异步响应体长度: " + resp.body().length() + " 字符");
            }).join(); // 等待异步操作完成
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // 发送POST请求示例
        sendPostRequest(client);
    }
    
    private static void sendPostRequest(HttpClient client) {
        try {
            String jsonBody = "{\"name\": \"test\", \"description\": \"test repository\"}";
            
            HttpRequest postRequest = HttpRequest.newBuilder()
                    .uri(URI.create("https://httpbin.org/post"))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                    .build();
            
            System.out.println("\n发送POST请求...");
            HttpResponse<String> response = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
            
            System.out.println("POST响应状态码: " + response.statusCode());
            System.out.println("POST响应体(前200字符):");
            System.out.println(response.body().substring(0, Math.min(response.body().length(), 200)) + "...");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

WebSocket编程

WebSocket提供了全双工通信通道,允许服务器主动向客户端推送数据。Java提供了对WebSocket的支持。

java
// WebSocket客户端示例
import javax.websocket.ClientEndpoint;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import java.net.URI;
import java.util.Scanner;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;

@ClientEndpoint
public class WebSocketClient {
    private Session session;
    
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        System.out.println("WebSocket连接已建立");
    }
    
    @OnMessage
    public void onMessage(String message) {
        System.out.println("收到消息: " + message);
    }
    
    public void sendMessage(String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            String uri = "ws://echo.websocket.org";
            
            System.out.println("连接到WebSocket服务端: " + uri);
            WebSocketClient client = new WebSocketClient();
            container.connectToServer(client, URI.create(uri));
            
            Scanner scanner = new Scanner(System.in);
            System.out.println("WebSocket客户端已启动,请输入消息(输入exit退出):");
            
            String message;
            while ((message = scanner.nextLine()) != null) {
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                
                client.sendMessage(message);
            }
            
            scanner.close();
            System.out.println("WebSocket客户端已关闭");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

网络编程最佳实践

异常处理

在网络编程中,异常处理非常重要,需要妥善处理各种可能的异常情况。

java
// 异常处理示例
public class ExceptionHandlingDemo {
    public static void connectToServer(String host, int port) {
        Socket socket = null;
        
        try {
            socket = new Socket(host, port);
            System.out.println("已连接到服务器: " + host + ":" + port);
            
            // 执行网络操作...
            
        } catch (java.net.UnknownHostException e) {
            System.err.println("未知主机: " + host);
        } catch (java.net.ConnectException e) {
            System.err.println("连接被拒绝,服务器可能未运行或端口不正确: " + host + ":" + port);
        } catch (java.net.SocketTimeoutException e) {
            System.err.println("连接超时: " + host + ":" + port);
        } catch (java.io.IOException e) {
            System.err.println("IO异常: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("发生未知异常: " + e.getMessage());
        } finally {
            // 确保关闭Socket
            if (socket != null && !socket.isClosed()) {
                try {
                    socket.close();
                    System.out.println("Socket已关闭");
                } catch (IOException e) {
                    System.err.println("关闭Socket时发生错误: " + e.getMessage());
                }
            }
        }
    }
    
    public static void main(String[] args) {
        connectToServer("localhost", 8080);
    }
}

资源管理

在网络编程中,需要确保正确关闭所有网络资源,避免资源泄漏。

java
// 使用try-with-resources进行资源管理
public class ResourceManagementDemo {
    public static void readFromServer(String host, int port) {
        try (Socket socket = new Socket(host, port);
             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {
            
            // 发送请求
            writer.println("GET / HTTP/1.1");
            writer.println("Host: " + host);
            writer.println();
            
            // 读取响应
            String line;
            StringBuilder response = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                response.append(line).append("\n");
            }
            
            System.out.println("服务器响应:");
            System.out.println(response.substring(0, Math.min(response.length(), 500)) + "...");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 所有资源会自动关闭,不需要手动在finally块中关闭
    }
    
    public static void main(String[] args) {
        readFromServer("www.example.com", 80);
    }
}

超时设置

为网络操作设置超时时间可以防止线程长时间阻塞。

java
// 设置网络超时示例
public class TimeoutDemo {
    public static void connectWithTimeout(String host, int port, int connectTimeout, int readTimeout) {
        try (Socket socket = new Socket()) {
            // 设置连接超时
            socket.connect(new InetSocketAddress(host, port), connectTimeout);
            
            // 设置读取超时
            socket.setSoTimeout(readTimeout);
            
            System.out.println("已连接到服务器: " + host + ":" + port);
            
            // 执行网络操作...
            
        } catch (SocketTimeoutException e) {
            System.err.println("操作超时: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        // 连接超时5秒,读取超时10秒
        connectWithTimeout("www.example.com", 80, 5000, 10000);
    }
}

性能优化

网络编程中的性能优化涉及多个方面:

  1. 使用缓冲区:减少实际的网络传输次数
  2. 选择合适的协议:根据需求选择TCP或UDP
  3. 使用NIO:对于高并发场景,使用NIO可以提高性能
  4. 线程池管理:合理使用线程池处理并发连接
  5. 连接复用:尽量复用连接,避免频繁创建和关闭连接
java
// 使用缓冲区优化网络传输
public class BufferOptimizationDemo {
    public static void sendLargeData(Socket socket, byte[] data) throws IOException {
        try (OutputStream out = socket.getOutputStream()) {
            // 创建缓冲区
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            // 使用缓冲区发送数据
            for (int i = 0; i < data.length; i += buffer.length) {
                int length = Math.min(buffer.length, data.length - i);
                System.arraycopy(data, i, buffer, 0, length);
                out.write(buffer, 0, length);
                out.flush();
            }
        }
    }
}

总结

Java网络编程提供了丰富的API和功能,从基础的Socket编程到高级的NIO和AIO,从同步阻塞到异步非阻塞,Java为开发者提供了多种选择来实现网络通信。在实际开发中,需要根据应用的需求和场景选择合适的技术和API,并遵循最佳实践,如异常处理、资源管理、超时设置和性能优化等,以确保网络应用的稳定性、可靠性和高性能。