Springboot之整合Socket连接

SpringBoot 专栏收录该内容
31 篇文章 0 订阅

Socket连接与硬件通信

一、如何让socket随着springboot项目一起启动

SpringBoot中CommandLineRunner的作用:平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中

具体实现

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 易水●墨龙吟
 * @Description
 * @create 2019-04-14 23:40
 */

@Component
public class TestRunner implements CommandLineRunner {

    @Autowired
    private SocketProperties properties;

    @Override
    public void run(String... args) throws Exception {
        ServerSocket server = null;
        Socket socket = null;
        server = new ServerSocket(properties.getPort());
        System.out.println("设备服务器已经开启, 监听端口:" + properties.getPort());
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                properties.getPoolCore(),
                properties.getPoolMax(),
                properties.getPoolKeep(),
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(properties.getPoolQueueInit()),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
        while (true) {
            socket = server.accept();
            pool.execute(new ServerConfig(socket));
        }
    }
}

此处使用了自定义的线程池,提高对于socket的客户端处理能力。

二、自定义配置并使用

此处将socket的端口和线程池的一些配置放到 application.yml中使用,方便使用和修改

# Socket配置
socket:
  # 监听端口 2323
  port: 2323
  # 线程池 - 保持线程数 20
  pool-keep: 20
  # 线程池 - 核心线程数 10
  pool-core: 10
  # 线程池 - 最大线程数 20
  pool-max: 30
  # 线程队列容量 10
  pool-queue-init: 10
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @author 易水●墨龙吟
 * @Description
 * @create 2019-04-18 22:35
 */
@Setter
@Getter
@ToString
@Component
@Configuration
@PropertySource("classpath:application.yml")
@ConfigurationProperties(prefix = "socket")
public class SocketProperties {
    private Integer port;
    private Integer poolKeep;
    private Integer poolCore;
    private Integer poolMax;
    private Integer poolQueueInit;
}

三、Socket对于客户端发来的信息的处理和重发机制

当客户端端连接之后发送信息,如果超时未发送,将会关闭,发送数据有异常将会返回给客户端一个error,让客户端在发送一次数据。

import com.farm.config.socket.resolve.MessageChain;
import com.farm.service.EnvironmentService;
import com.farm.service.impl.EnvironmentServiceImpl;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Map;

/**
 * @author 易水●墨龙吟
 * @Description
 * @create 2019-04-14 23:21
 */
public class ServerConfig extends Thread {

    private Socket socket;

    public ServerConfig(Socket socket) {
        this.socket = socket;
    }
	// 获取spring容器管理的类,可以获取到sevrice的类
    private EnvironmentService service = SpringUtil.getBean(EnvironmentServiceImpl.class);

    private String handle(InputStream inputStream) throws IOException, DataFormException {
        byte[] bytes = new byte[1024];
        int len = inputStream.read(bytes);
        if (len != -1) {
            StringBuffer request = new StringBuffer();
            request.append(new String(bytes, 0, len, "UTF-8"));
            System.out.println("接受的数据: " + request);
            System.out.println("from client ... " + request + "当前线程" + Thread.currentThread().getName());
            Map<String, String> map = MessageChain.out(request.toString());
            System.out.println("处理的数据" + map);
            Integer res = service.addEnvironment(map);
            if (res == 1) {
                return "ok";
            } else {
                throw new DataFormException("数据处理异常");
            }
        } else {
            throw new DataFormException("数据处理异常");
        }
    }

    @Override
    public void run() {
        BufferedWriter writer = null;
        try {
            // 设置连接超时9秒
            socket.setSoTimeout(9000);
            System.out.println("客户 - " + socket.getRemoteSocketAddress() + " -> 机连接成功");
            InputStream inputStream = socket.getInputStream();
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            String result = null;
            try {
                result = handle(inputStream);
                writer.write(result);
                writer.newLine();
                writer.flush();
            } catch (IOException | DataFormException | IllegalArgumentException e) {
                writer.write("error");
                writer.newLine();
                writer.flush();
                System.out.println("发生异常");
                try {
                    System.out.println("再次接受!");
                    result = handle(inputStream);
                    writer.write(result);
                    writer.newLine();
                    writer.flush();
                } catch (DataFormException | SocketTimeoutException ex) {
                    System.out.println("再次接受, 发生异常,连接关闭");
                }
            }
        } catch (SocketException socketException) {
            socketException.printStackTrace();
            try {
                writer.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在此处有一个坑,如果客户端是用C/C++编写的,必须使用如下方法:

byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);

如果使用readLine或者 DataInputStream dataInputStream =new DataInputStream(socket.getInputStream())这样会出现使用TCP连接助手,客户端发送数据收不到。

四、如何在普通类中使用Spring注入类

这里需要使用一个工具类。

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author 易水●墨龙吟
 * @Description
 * @create 2019-04-15 0:01
 */
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    /**
     * 获取applicationContext
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean.
     * @param name
     * @return
     */
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}
相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值