12.30 java网络编程之socket编程(NIO多路复用版本) socket编程大作业答案

发布于:2025-02-11 ⋅ 阅读:(87) ⋅ 点赞:(0)

在本次项目中,我们将实现一个简单的客户端-服务器(Client-Server)通信模型。通过这个项目,你将学习到如何使用Java的SocketCh和ServerSocket类来创建网络连接,进行数据的发送和接收。该项目不仅涵盖了Socket编程的基础知识,还将帮助你理解网络通信中的重要概念,如TCP/IP协议、阻塞与非阻塞I/O等。本socket教程基于NIO的SocketChannal等完成

同时我们会将他部署到自己的云服务器中实现远程消息收发

传统套接字:基于流(Stream),默认为阻塞I/O,在传统的Java Socket编程中,通信是基于流(Stream)的模型。流是一种单向的数据传输机制,分为输入流(InputStream)和输出流(OutputStream)。
缓冲区(Buffer):

NIO的数据读写是基于缓冲区(Buffer)的。缓冲区是一个固定大小的内存块,用于临时存储数据。

缓冲区提供了对数据的结构化访问,例如可以通过position、limit和capacity等属性来管理数据的读写位置。

常见的缓冲区类型包括ByteBuffer、CharBuffer、IntBuffer等

代码流程

服务器

  1. 首先是创建selctor(NIO核心组件),
  2. 创建serverSocketChannal
    这个serverSocketChannal就理解为以前的ServerSocket就行了,都是监听端口是否有信息的
    之前的ServerSocket是只要有一个消息,我们就会返回一个socket
  3. 绑定serverSocketChannal到selctor
  4. 然后就死循环等待消息到来(有两种连接消息/读消息)
    这里会/获取发生事件的 SelectionKey 集合(我们知道NIO多路复用底层用的是epoll,也就是不会遍历所有监听的客户端,而是直接获取)
    然后再对遍历集合中的key我们就可以从里面后获取Socketchannel
    然后对Socketchannel 就和socket一样,可以从里面获取消息以及发送消息

客户端

  1. 创建一个socketchannel,然后连接服务器
  2. 向socketchannel中写东西
  3. 同时可以接受服务器的东西
  4. 关闭socketchannel

服务器代码

package coml.zy;

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 Main {
    public static void main(String[] args) throws IOException {
        // 创建 Selector
        Selector selector = Selector.open();

        // 创建 ServerSocketChannel 并绑定端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //此处应该写0.0.0.0而非localhost
        serverSocketChannel.bind(new InetSocketAddress("0.0.0.0", 12345));
        serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式

        // 将 ServerSocketChannel 注册到 Selector,监听 ACCEPT 事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器已启动,等待客户端连接...");

        while (true) {
            // 阻塞等待事件发生
            selector.select();

            // 获取发生事件的 SelectionKey 集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove(); // 处理完后移除

                if (key.isAcceptable()) {
                    // 处理客户端连接
                    handleAccept(key, selector);
                } else if (key.isReadable()) {
                    // 处理客户端数据读取
                    handleRead(key);
                }
            }
        }
    }

    // 处理客户端连接
    private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept(); // 接受客户端连接
        clientChannel.configureBlocking(false); // 设置为非阻塞模式

        // 将客户端通道注册到 Selector,监听 READ 事件
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("客户端已连接:" + clientChannel.getRemoteAddress());
    }

    // 处理客户端数据读取
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        int bytesRead = clientChannel.read(buffer); // 读取客户端数据
        if (bytesRead == -1) {
            // 客户端断开连接
            System.out.println("客户端已断开:" + clientChannel.getRemoteAddress());
            clientChannel.close(); // 关闭通道
            key.cancel(); // 移除相关的 SelectionKey
            return;
        }

        if (bytesRead > 0) {
            buffer.flip(); // 切换为读模式
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String message = new String(data);
            System.out.println("收到客户端消息:" + message);

            // 向客户端发送响应
            String response = "服务器已收到你的消息:" + message;
            ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
            clientChannel.write(responseBuffer);
        }
    }
}

然后是一个简单的客户端

package coml.zy;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NioClient2 {
	//这里可以换成远程服务器的版本,假如是本地就localhost
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 12345;

    public static void main(String[] args) {
        try {
            startClient();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void startClient() throws IOException {
        // 创建 SocketChannel 并连接服务器
        SocketChannel socketChannel = SocketChannel.open();
        //此处应该写0.0.0.0而非localhost
        socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
        System.out.println("已连接到服务器,可以开始发送消息。输入 'exit' 退出。");

        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 读取用户输入
            System.out.print("请输入消息:");
            String message = scanner.nextLine();

            // 如果用户输入 'exit',则退出循环
            if ("exit".equalsIgnoreCase(message)) {
                break;
            }

            // 发送消息给服务器
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
            socketChannel.write(buffer);
            System.out.println("已发送消息:" + message);

            // 接收服务器的响应
            ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
            int bytesRead = socketChannel.read(responseBuffer);
            if (bytesRead > 0) {
                responseBuffer.flip();
                byte[] data = new byte[responseBuffer.remaining()];
                responseBuffer.get(data);
                String response = new String(data);
                System.out.println("收到服务器响应:" + response);
            }
        }

        // 关闭连接
        socketChannel.close();
        System.out.println("客户端已断开连接。");
    }
}

运行方式
在本地
是将这个服务器打包成JAR包,然后双击,就算是运行了

远程部署
用docker部署,打包成jar包之后
Docker flie

# 使用 OpenJDK 的基础镜像
FROM openjdk:11.0-jre-buster

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone


COPY  socketDemo-1.0-SNAPSHOT.jar /app.jar


ENTRYPOINT ["java", "-jar", "/app.jar"]

先build再这个run
运行效果
在这里插入图片描述
在这里插入图片描述
该示例代码由deepSeek大模型辅助生成,deepSeek自我感觉确实比其他的国产大模型靠谱一点,另外比gpt要方便(网络/生成快),gemini最快最傻比国产还傻


网站公告

今日签到

点亮在社区的每一天
去签到