Personal Notes - SpringBoot Integrated Socket

foreword

In my other article, there is a brief introduction to the related concept link of Socket: SpringBoot simply integrates WebSocket

After the initial understanding, this time, we will conduct an in-depth and popular understanding.

As a communication mechanism, Socket is also commonly called "socket".

It is similar to the "phone behavior" between people. We have everyone's phone number as a separate port. Before two people make a call, one of them needs to know the "port" of the other. Then apply to make a dial-up call (request connection) to the other party.

At this time, if the connected party happens to be free and picks up the phone, the two parties have officially reached a connection. Start formal communication. As long as one of the parties hangs up the phone, the Socket connection will be closed.

Two types of Socket

1. Streaming Socket (Stream). A connection-oriented Socket, which is safe but inefficient for connection-oriented TCP service applications. It is a more commonly used method in the industry

2. Datagram Socket (DataGram). A connectionless Socket. Insecure and efficient.

text

Let's start to integrate Socket in SpringBoot

In JDK1.8, the Socket service is officially integrated into the java.net package. So no other dependencies need to be introduced.

1. First configure the port for Socket listening in the configuration file

#Socket配置
socket:
  port: 8082

2. Configure the Socket connection class

@Slf4j
@Component
public class SocketServerConfig {
    
    //注入被开放的端口
    @Value("${socket.port}")
    private Integer port;  

    //Socket服务是否启动的标识
    private boolean socketStart = false;

    //Socket服务
    public static ServerSocket serverSocket = null;
    
    //当前连接用户数
    public static Integer userCount = 1;

    //客户端缓存信息
    public static ConcurrentHashMap<String, ClientSocket> clientsMap = new ConcurrentHashMap<>();
}

3. Configure a custom client class

@Data
@Slf4j
public class ClientSocket implements Runnable {

    private Socket socket;
    private ObjectInputStream inputStream;
    private ObjectOutputStream outputStream;
    private String key;
    private String message;

    private final IDataSocket dataSocket= ApplicationContextProvider.getBean((IDataSocket.class));

    @Override
    public void run() {
        // 另起一个线程在指定时间内连接一次客户端
        while (true) {
            try {
                //设定为10s/次
                TimeUnit.SECONDS.sleep(5);
                if (isSocketClosed(this)) {
                    logout();
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 注册socket到map里
     */
    public static ClientSocket register(Socket socket, int count) {
        ClientSocket client = new ClientSocket();
        try {
            log.info("客户端IP:{},用户:{},正在连接Socket服务...", socket.getInetAddress().getHostAddress(), count);
            client.setSocket(socket);
            client.setInputStream(new ObjectInputStream(socket.getInputStream()));
            client.setOutputStream(new ObjectOutputStream(socket.getOutputStream()));
            client.setKey("user" + count);
            DataApplySocketServer.clientsMap.put(client.getKey(), client);
            DataApplySocketServer.userCount++;
            log.info("客户端IP:{},用户:{},连接Socket服务成功...", socket.getInetAddress().getHostAddress(), count);
            return client;
        } catch (Exception e) {
            client.logout();
            return null;
        }
    }

    /**
     * 登出操作, 关闭各种流
     */
    public void logout() {
        SocketServerConfig.clientsMap.remove("user" + key);
        try {
            log.info("用户:{}执行登出操作", key);
            inputStream.close();
            outputStream.close();
            SocketServerConfig.userCount--;
            log.info("用户:{}执行登出完成", key);
        } catch (Exception e) {
            log.error("关闭Socket输入/输出异常", e);
        } finally {
            try {
                socket.close();
            } catch (Exception e) {
                log.error("关闭socket异常", e);
            }
        }
    }

    /**
     * 发送数据包,判断数据连接状态
     */
    public boolean isSocketClosed(ClientSocket clientSocket) {
        try {
            clientSocket.getSocket().sendUrgentData(1);
            //执行业务处理
            dataSocket.executeBusinessCode(this);
            return false;
        } catch (IOException e) {
            return true;
        }
    }
}

4. Configure the Start method in the Socket connection class

public void start() {
        try {
            //创建Socket服务
            serverSocket = new ServerSocket(port);
            log.info("socket在端口:{}中开启", port);
            socketStart = true;
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
        try {
            while (socketStart) {
                //接受本次连接
                Socket socket = serverSocket.accept();
                //保持监听
                socket.setKeepAlive(true);
                //调用自定义客户端配置类中的注册方法,将本次连接的用户注册进去
                ClientSocket clientSocket = ClientSocket.register(socket, userCount);
                if (clientSocket != null) {
                    //注册成功后,使用ExecutorService的submit方法,让自定义客户端配置类
                    //的run方法进行执行
                    executorService.submit(clientSocket);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

5. In order to prevent the Socket service from being unable to start after being packaged into a war package, the following code needs to be added to the main method of the Application startup class

 public static void main(String[] args) throws UnknownHostException {
        ConfigurableApplicationContext application = SpringApplication.run(SocketApplication.class, args);
        //避免打包为war后,无法启动Socket服务
        //在spring容器启动后,取到已经初始化的SocketServer,启动Socket服务,start中可填写端口号,若不填写,默认按照配置文件中的端口号
        application.getBean(SocketServerConfig.class).start();
    }

6. Define the business interface

public interface IDataSocket{

    /**
     * 从Socket中接受到的代码,并执行
     * @param socket socket
     */
    void executeBusinessCode(ClientSocket socket);
}

7. Define the business implementation class

@Slf4j
@Service
public class DataSocketImpl implements IDataSocket {

    @Override
    @SneakyThrows
    public void executeBusinessCode(ClientSocket socket) {
        ObjectInputStream ois = socket.getInputStream();
        ObjectOutputStream oos = socket.getOutputStream();
        //这里的Object可以是Json对象或普通的String字符
        Object object = ois.readObject();
        if (ObjectUtil.isNotEmpty(object)) {
            String logId = UUID.randomUUID().toString().toUpperCase();
            log.info("监听到客户端信息,监听日志ID为:{}", logId);
            String responseMsg = "";
            try {
                //拿到数据后执行的业务代码
                responseMsg = "success"
            } catch (DataInterfaceException exception) {
                exception.printStackTrace();
                //失败后的消息
                responseMsg = "error"
            }
            // 输出流发送返回参数
            log.info("客户端信息消息处理完毕,日志ID为:{}", logId);
            oos.writeUTF(responseMsg);
            oos.flush();
        }
    }
}

8. Simple test (another SpringBoot service can be started)

 public static void main(String[] args) throws Exception {
        Socket socket = null;
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        try {
            //建立连接
            socket = new Socket("127.0.0.1", 8082);
            // 输出流写数据
            oos = new ObjectOutputStream(socket.getOutputStream());
            // 输入流读数据
            ois = new ObjectInputStream(socket.getInputStream());
            test(oos, ois);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            ois.close();
            oos.close();
            socket.close();
        }
    }

    @SneakyThrows
    public static void test(ObjectOutputStream oos, ObjectInputStream ois) {
        for (int i = 0; i < 5; i++) {
            log.info("测试第" + i + "次发送数据");
            // 输出流给服务端发送数据
            oos.writeObject("测试第" + i + "次发送数据");
            oos.flush();
            // 输入流接收服务端返回的数据
            String message = ois.readUTF();
            System.out.println(message);
            //休息三秒后进入下一次循环
            TimeUnit.SECONDS.sleep(3);
        }
    }

Guess you like

Origin blog.csdn.net/qq_33351639/article/details/129124814