个人笔记篇-SpringBoot集成Socket

前言

在我的另一篇文章中,有简单介绍过Socket的相关概念链接:SpringBoot简单集成WebSocket

初步了解后,本次再进行一个深入通俗的理解。

Socket作为一种通信机制,通常也被称为"套接字"。

它类似于人们之间的"打电话行为"。我们将每个人的电话号作为独立端口。两个人打电话之前则首先需要其中一方知晓另一方的"端口"。然后申请向对方进行拨号呼叫(请求连接)。

此时被连接方如果正好空闲,接起电话,则双方正式达成连接。开始正式通讯.只要有其中一方挂断电话,则关闭Socket连接。

Socket的两种类型

1.流式Socket(Stream).一种面向连接的Socket,针对面向连接的TCP服务应用,安全但效率低。属于业内较为常用的一种方式

2.数据报式Socket(DataGram).一种无连接的Socket.不安全,效率高.

正文

下面开始在SpringBoot中实战整合Socket

在JDK1.8中.官方整合了Socket服务至java.net包中。因此不需要引入其它依赖。

1.先在配置文件中配置Socket监听的端口

#Socket配置
socket:
  port: 8082

2.配置Socket连接类

@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.配置自定义客户端类

@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.在Socket连接类中配置Start启动方法

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.为了避免打成war包后无法启动Socket服务,需要在Application启动类的main方法中添加如下代码

 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.定义业务接口

public interface IDataSocket{

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

7.定义业务实现类

@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.简单测试(可另起一个SpringBoot服务)

 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);
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_33351639/article/details/129124814