springboot +netty-socket使用详细说明

socket使用说明书

1 socket基本配置

1、本文使用到netty-socketio开源库,,所以首先在build.gradle中添加相应的依赖库

  compile("com.corundumstudio.socketio:netty-socketio:1.7.12")

2、在spring-boot资源配置文件application.yml或application.properties 配置socket的主机名和端口,本例中用的配置文件是application.yml

socket: 
  host: 172.18.8.12
  port: 9999

2 socket服务器创建

1、在springboot的入口类(一般是Application.java)中,利用@Value方式读取配置文件中socket的主机名和端口

 @Value("${socket.host}")
 private String host;
 
 @Value("${socket.port}")
 private int port;

2 在Application.java 注册一个bean,并创建一个socket服务。

    @Bean
    public SocketIOServer socketIOServer()
    {
        Configuration config = new Configuration();
        config.setHostname(host);
        config.setPort(port);
        final SocketIOServer server = new SocketIOServer(config);
        return server;
    }

3、socket自动扫描

   @Bean
   public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
       return new SpringAnnotationScanner(socketServer);
   }

4、存储客户端socket消息

public class SocketClient {

   /**
    * 用户Id
    **/
   private String userId;

   /**
    * 用户状态  0为空闲 1为忙碌
    **/
   private String status;

   /**
    * 用户角色类型 
    * 
    **/
   private String roleId;

   /**
    * 中心编码
    **/
   private String centerCode;

   /**
    * 服务类型
    */
   private String serverType;

   private SocketIOClient socketIOClient;

   ............
}

5、socket服务启动

@Component
public class ServerRunner implements CommandLineRunner {
   private final SocketIOServer server;

   /**
    * Instantiates a new Server runner.
    *
    * @param server the server
    */
   @Autowired
   public ServerRunner(SocketIOServer server) {
       this.server = server;
   }

   /**
    * 服务启动
    */
   @Override
   public void run(String... args) throws Exception {
       server.start();
   }
}

6、添加消息处理类NoticeEventHandler.java,添加初始化监听.

@Component
public class NoticeEventHandler {
   private Logger log = LoggerFactory.getLogger(NoticeEventHandler.class);
   private final SocketIOServer server;
   private SocketManager socketManager;




   /**
    * Instantiates a new Notice event handler.
    *
    * @param server the server
    *
    */
   @Autowired
   public NoticeEventHandler(SocketIOServer server, @Qualifier("socketEventListenerManager") final SocketEventListenerManager socketEventListenerManager,
                             @Qualifier("socketManager") final SocketManager socketManager) {
       this.server = server;
       this.socketManager = socketManager;
       initSocketEventListener(socketEventListenerManager);
   }

   /**
    * 初始化 SocketEventListenerManager
    * 1、先扫描SocketEventListenerManager文件中定义事件
    * 2、遍历 将订阅事件添加客户端监听中
    * 3、eventListener将订阅的消息 转化为消息对象 传到对应的noticeService的receive方法中
    * */
   private void initSocketEventListener(SocketEventListenerManager socketEventListenerManager) {
       List<EventListener> eventListeners = socketEventListenerManager.getEventListener();
       if (CollectionUtils.isEmpty(eventListeners))
           return;
       for (EventListener eventListener : eventListeners) {
           if (eventListener != null) {
               server.addEventListener(eventListener.getEventName(), java.lang.Object.class, (client, data, ackSender) -> {
                   if (eventListener.getNoticeService() != null) {
                       if (eventListener.getNoticeMessageMapperService() != null) {
                           eventListener.getNoticeService().receive(eventListener.getNoticeMessageMapperService().objectToMessage(data));
                       }
                   }
               });
           }
       }
   }
   
}
@Service
@ComponentScan(basePackages = {"com.gsafety.socket.notice.service"})
public class SocketEventListenerManager {

   private Logger logger = LoggerFactory.getLogger(this.getClass());

   @Autowired
   @NoticeEvent(event = "chatEvent")
   private BCANoticeServiceImpl bcaNoticeService;

   @Autowired
   @NoticeEvent(event = "chatEvent")
   private BCANoticeMessageMapperImpl bcaNoticeMessageMapper;

   @Autowired
   @NoticeEvent(event = "checkEvent")
   private CheckNoticeMessageMapperImpl checkNoticeMessageMapperImpl;

   @Autowired
   @NoticeEvent(event = "checkEvent")
   private CheckNoticeMessageImpl checkNoticeMessageImpl;


   /**
    * 扫描当前所有的监听事件
    * 1、获取当前类的所有含有@NoticeEvent注释的字段
    * 2、map遍历 创建 一个EventListener对象 设置对象的名称
    * 2.1获取申明时的类型对象(class对象 BCANoticeServiceImpl)并引用
    * 2.2判断当前NoticeService 和 NoticeMessageMapperService 类型 是否包含interfaces的类型名称
    * 3.3 若包含将fied的字段的值转化为NoticeService或NoticeMessageMapperService类型 set到eventListener中
    * @return the event listener
    */
   public List<EventListener> getEventListener() {
       List<EventListener> eventListeners = new ArrayList<>();
       try {
           Map<String, List<Field>> filterClass = getNoticeField(this.getClass());
           for (String event : filterClass.keySet()) {
               EventListener eventListener = new EventListener();
               eventListener.setEventName(event);
               for (Field field : filterClass.get(event)) {
                   Class<?>[] interfaces = field.getType().getInterfaces();
                   if (!ArrayUtils.isEmpty(interfaces)) {
                       List<Class<?>> filterInterfaces = Arrays.stream(interfaces).filter(f -> f.getTypeName() == NoticeService.class.getTypeName()).collect(Collectors.toList());
                       if (!CollectionUtils.isEmpty(filterInterfaces)) {
                           field.setAccessible(true);
                           eventListener.setNoticeService((NoticeService) field.get(this));
                       }
                       filterInterfaces = Arrays.stream(interfaces).filter(f -> f.getTypeName() == NoticeMessageMapperService.class.getTypeName()).collect(Collectors.toList());
                       if (!CollectionUtils.isEmpty(filterInterfaces)) {
                           field.setAccessible(true);
                           eventListener.setNoticeMessageMapperService((NoticeMessageMapperService) field.get(this));
                       }
                   }
               }
               eventListeners.add(eventListener);
           }
           return eventListeners;
       } catch (Exception e) {
           logger.error("error", e.getMessage());
           return eventListeners;
       }
   }

   /** 获取当前类的所有含有@NoticeEvent注解的字释
    * 1、获取所有声明的字段
    * 2、判断这些申明中是否有 @NoticeEvent这样的注释
    * 3、找到后将 @NoticeEvent 中event的值和 当前的字段以键值对的方式存储到Map中
    * */
   private Map<String, List<Field>> getNoticeField(Class currentClass) {
       Field[] fields = currentClass.getDeclaredFields();
       Map<String, List<Field>> filterClasses = new HashMap<>();
       for (Field field : fields) {
           if (field.getAnnotation(NoticeEvent.class) != null) {
               String event = field.getAnnotation(NoticeEvent.class).event();
               if (filterClasses.containsKey(event)) {
                   if (!filterClasses.get(event).contains(field)) {
                       filterClasses.get(event).add(field);
                   }
               } else {
                   List<Field> eventClass = new ArrayList<>();
                   eventClass.add(field);
                   filterClasses.put(event, eventClass);
               }
           }
       }
       return filterClasses;
   }

}

4 建立连接和失去连接

1、客户端发送连接

  var socket =  io.connect('http://172.18.8.12:9999?userId=yaly&status=0&role=2&serverType=crime');

2、服务端在消息处理类NoticeEventHandler.java 中接收连接。

 @OnConnect
    public void onConnect(SocketIOClient client) {
        //连接的时候可以把用户信息传递到服务端
        SocketClient socketClient = new SocketClient();
        socketClient.setUserId(client.getHandshakeData().getSingleUrlParam("userId"));
        socketClient.setStatus(StringUtils.isNotEmpty(client.getHandshakeData().getSingleUrlParam("status")) ? client.getHandshakeData().getSingleUrlParam("status") : "0");
        socketClient.setSocketIOClient(client);
        socketClient.setServerType(client.getHandshakeData().getSingleUrlParam("serverType"));
        socketClient.setUerCode(client.getHandshakeData().getSingleUrlParam("userCode"));
        socketClient.setRole(client.getHandshakeData().getSingleUrlParam("role"));
        socketManager.register(socketClient);
    }

3、当服务端检测客户端断开连接时,服务端关闭当前的连接。

  /**
     * 添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息
     *
     * @param client the client
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        //失去连接的时候移除用户信息
        socketManager.remove(client.getSessionId());
    }

5 消息发送

1、创建消息体

/**
 * 通知消息
 * Created by MyGirl on 2017/8/21.
 */
public class NoticeMessage {
    /**
     * id
     */
    private String id;
    /**
     * 消息体
     */
    private Object content;
    /**
     * 创建的时间
     */
    private Date time;
    /**
     * 发送人
     */
    private String senderId;
    /**
     * 接收人
     */
    private String receiverId;
    /**
     * 订阅的事件名称
     */
    private String event;

    /**
     * Gets event.
     *
     * @return the event
     */
    public String getEvent() {
        return event;
    }

    /**
     * Sets event.
     *
     * @param event the event
     */
    public void setEvent(String event) {
        this.event = event;
    }

    /**
     * Gets sender id.
     *
     * @return the sender id
     */
    public String getSenderId() {
        return senderId;
    }

    /**
     * Sets sender id.
     *
     * @param senderId the sender id
     */
    public void setSenderId(String senderId) {
        this.senderId = senderId;
    }

    /**
     * Gets receiver id.
     *
     * @return the receiver id
     */
    public String getReceiverId() {
        return receiverId;
    }

    /**
     * Sets receiver id.
     *
     * @param receiverId the receiver id
     */
    public void setReceiverId(String receiverId) {
        this.receiverId = receiverId;
    }

    /**
     * Gets id.
     *
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * Sets id.
     *
     * @param id the id
     */
    public void setId(String id) {
        this.id = id;
    }

    public Object getContent() {
        return content;
    }

    public void setContent(Object content) {
        this.content = content;
    }

    /**
     * Gets time.
     *
     * @return the time
     */
    public Date getTime() {
        return time;
    }

    /**
     * Sets time.
     *
     * @param time the time
     */
    public void setTime(Date time) {
        this.time = time;
    }
}

2、通过客户端订阅发送消息

socket.emit('chatEvent', jsonObject);

3、服务端接收并发送消息

  • 接收消息
 server.addEventListener(eventListener.getEventName(), java.lang.Object.class, (client, data, ackSender) -> {
                    if (eventListener.getNoticeService() != null) {
                        if (eventListener.getNoticeMessageMapperService() != null) {
                            eventListener.getNoticeService().receive(eventListener.getNoticeMessageMapperService().objectToMessage(data));
                        }
                    }
                });
  • 消息体转化
/**
     * 将Object转化为 NoticeMessage对象类型
     * 判断接收着是否登陆
     * 发送通知 审核席位给操作员发送消息
     */
    @Override
    public void receive(Object message) {
        String json = JSON.toJSONString(message);
        SocketClient socketClient = socketManager.get(msg.getReceiverId());
        msg.setEvent("checkEvent");
        if (socketClient != null) {
            socketMessageManager.sendMessage(json);
        }

    }
  • 消息发送
  public void sendMessage(String  message) { 
  NoticeMessage noticeMessage = JsonUtil.fromJson(message, NoticeMessage.class);
  
        if (noticeMessage != null) {
            SocketClient socketClient = socketManager.get(noticeMessage.getReceiverId());
            if (socketClient != null) {
 
                socketIOClient.sendEvent(noticeMessage.getEvent(), noticeMessage);
              
            }
        }

    }

5通过api接口发送消息

1、发送消息包含发送单条消息和多条消息

  • 在mesageController中添加发送单条消息和多条消息的接口

 @ApiOperation(value = "message And Message controller", notes = "给单个用户发送消息")
    //ApiResponses swagger相应注解
    @ApiResponses(value = {
            @ApiResponse(code = 500, message = "Internal Server Error", response = HttpError.class),
            @ApiResponse(code = 406, message = "Not Acceptable", response = HttpError.class)})
    @RequestMapping(value = "/message/send", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Boolean> sendMessage(@RequestBody MessageInfo messageInfo) {
        if (StringUtils.isEmpty(messageInfo.getReceiverId()) || StringUtils.isEmpty(messageInfo.getEvent())) {
            return new ResponseEntity<>(false, HttpStatus.OK);
        }
        boolean result = messageService.sendMessageOne(messageInfo);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }
     public boolean sendMessageOne(MessageInfo messageInfo) {
        SocketClient socketClient = socketManager.findByUserIdAndRole(messageInfo.getReceiverId(), messageInfo.getRole());
        if (socketClient == null) {
            return false;
        } else {
            socketClient.setUserId(messageInfo.getReceiverId());
            socketClient.setStatus(messageInfo.getStatus() != null ? messageInfo.getStatus() : "0");
            socketManager.register(socketClient);
            //发送消息
            this.sendMessageToClient(messageInfo);
        }
        return true;
    }
     //ApiOperation swagger操作注解
    @ApiOperation(value = "message And Message controller", notes = "给多个用户发送消息")
    //ApiResponses swagger相应注解
    @ApiResponses(value = {
            @ApiResponse(code = 500, message = "Internal Server Error", response = HttpError.class),
            @ApiResponse(code = 406, message = "Not Acceptable", response = HttpError.class)})
    @RequestMapping(value = "/message/sendAll", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Boolean> sendMessageAll(@RequestBody List<MessageInfo> messageInfoList) {
        if (messageInfoList.size() == 0) {
            return new ResponseEntity<>(false, HttpStatus.OK);
        }
        boolean result = messageService.sendMessageALl(messageInfoList);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }
  • 在MessageServiceImpl类中添加方法
/**
 *(单个) 发送消息
 * */
    public boolean sendMessageOne(MessageInfo messageInfo) {
        SocketClient socketClient = socketManager.findByUserIdAndRole(messageInfo.getReceiverId(), messageInfo.getRole());
        if (socketClient == null) {
            return false;
        } else {
            socketClient.setUserId(messageInfo.getReceiverId());
            socketClient.setStatus(messageInfo.getStatus() != null ? messageInfo.getStatus() : "0");
            socketManager.register(socketClient);
            //发送消息
            this.sendMessageToClient(messageInfo);
        }
        return true;
    }
    /**
     * 给(多个用户)发送消息
     * */
    public boolean sendMessageALl(List<MessageInfo> messageInfo) {
        messageInfo.forEach(message -> {
            SocketClient socketClient = socketManager.findByUserIdAndRole(message.getReceiverId(), message.getRole());
            if (socketClient != null) {
                //更改当前的忙碌状态
                socketClient.setUserId(message.getReceiverId());
                socketClient.setStatus(message.getStatus() != null ? message.getStatus() : "0");
                socketManager.register(socketClient);
                //给接收人发送通知
                this.sendMessageToClient(message);
            }
          }
        );

        return true;
    }
    /**
     * 给客户端用户发送消息
     * */
    private void  sendMessageToClient(MessageInfo message){
        NoticeMessage noticeMessage = new NoticeMessage();
        noticeMessage.setEvent(message.getEvent());
        noticeMessage.setReceiverId(message.getReceiverId());
        noticeMessage.setContent(message.getContent());
        noticeMessage.setTime(new Date());
        socketMessageManager.sendMessage(noticeMessage);
    }

6 获取空闲用户列表

  • 通过调用api文档来获取指定状态 socket列表

1、创建搜索体

public class SearchInfo {
    /**
     * 服务类型
     * */
    private ServerType serverType;

    public  enum ServerType{
      crime,
      bdms
    }
    /**
     * 角色
     * */
    public  String   roleId;

    /**
     * 角色列表
     * */
    public List<String> roleList;

    /**
     * 状态
     * */
    public String status;

2、在SocketController中添加搜索的api接口

  /*
    *  获取空闲的用户列表(通过角色和服务类型)
    * @params searchInfo (serverType 服务类型  role角色 roleList 角色列表)
    **/
    //ApiOperation swagger操作注解
    @ApiOperation(value = "socket And Socket controller", notes = "获取空闲的用户列表")
    //ApiResponses swagger相应注解
    @ApiResponses(value = {
            @ApiResponse(code = 500, message = "Internal Server Error", response = HttpError.class),
            @ApiResponse(code = 406, message = "Not Acceptable", response = HttpError.class)})
    @RequestMapping(value = "/socket/getAll//{search}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<User>> getUserList(@PathVariable String  search) {
        List<User> result=new ArrayList<>();
        if(StringUtils.isEmpty(search)){
            return new ResponseEntity<>(result, HttpStatus.OK);
        }
        SearchInfo searchInfo = JSON.parseObject(search, SearchInfo.class);
        result = socketService.getAllUserList(searchInfo);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }

3、在SocketServiceImpl中添加搜索方法

/**
    * 根据搜索条件获取指定类型的socket
    * 分三种
    * 第一种 服务类型查询
    * 第二中 服务类型+角色
    * 第三中 服务类型+角色列表
    * */
    public List<User> getAllUserList(SearchInfo searchInfo) {
        List<SocketClient> socketList=new ArrayList<>();
        if(StringUtils.isEmpty(searchInfo.getRole()) && CollectionUtils.isEmpty(searchInfo.getRoleList())){
            socketList = socketManager.getSocketClientList(searchInfo.getServerType().toString());
        }else if(StringUtils.isNotEmpty(searchInfo.getRole())){
            socketList = socketManager.getSocketClientListByRole(searchInfo.getServerType().toString(),searchInfo.getRole());
        }else if(searchInfo.getRoleList().size()>0){
            socketList = socketManager.getSocketClientListByRoleList(searchInfo.getServerType().toString(),searchInfo.getRoleList());
        }
        List<User> userList = new ArrayList<>();
        socketList.forEach(s -> {
            User u = new User();
            u.setUserId(s.getUserId());
            u.setStatus(s.getStatus());
            u.setRole(s.getRole());
            userList.add(u);
        });
        return userList;
    }
Spring Boot是一个非常流行的Java开发框架,而Netty-socketio是一个基于Netty框架的WebSocket实现,提供了方便的实时通信解决方案。将它们结合起来,可以实现高效的WebSocket通信服务。 下面是整合的步骤: 1. 添加依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>1.7.16</version> </dependency> ``` 2. 编写Netty-socketio服务 创建一个类,继承自SpringBoot的ApplicationListener接口,用于启动Netty-socketio服务。 ``` import com.corundumstudio.socketio.Configuration; import com.corundumstudio.socketio.SocketIOServer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Component public class NettySocketIoServer implements ApplicationListener<ApplicationReadyEvent> { @Value("${socketio.host}") private String host; @Value("${socketio.port}") private Integer port; private SocketIOServer server; @Override public void onApplicationEvent(ApplicationReadyEvent event) { Configuration config = new Configuration(); config.setHostname(host); config.setPort(port); server = new SocketIOServer(config); server.start(); } } ``` 其中,@Value注解用于从配置文件中读取host和port的值,SocketIOServer是Netty-socketio提供的服务类,用于启动和管理WebSocket服务。 3. 配置WebSocket处理器 创建一个类,继承自Spring BootWebSocketHandler接口,用于处理WebSocket连接和消息。 ``` import com.corundumstudio.socketio.SocketIOClient; import com.corundumstudio.socketio.SocketIOServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; @Component public class SocketIoHandler extends TextWebSocketHandler { @Autowired private SocketIOServer server; @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); SocketIOClient client = server.getClient(session.getId()); if (client == null) { client = server.addClient(session); } } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { super.handleTextMessage(session, message); SocketIOClient client = server.getClient(session.getId()); if (client != null) { client.sendEvent("message", message.getPayload()); } } } ``` 其中,@Autowired注解用于从Spring容器中获取SocketIOServer实例,afterConnectionEstablished方法用于处理WebSocket连接建立时的逻辑,handleTextMessage方法用于处理WebSocket消息。 4. 配置WebSocket处理器映射 创建一个WebSocketHandlerRegistry类,用于配置WebSocket处理器的映射关系。 ``` import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private SocketIoHandler socketIoHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(socketIoHandler, "/ws").setAllowedOrigins("*"); } } ``` 其中,@EnableWebSocket注解用于开启WebSocket支持,registerWebSocketHandlers方法用于配置WebSocket处理器映射关系。 5. 配置application.yml 在application.yml文件中添加以下配置: ``` socketio: host: localhost port: 8080 ``` 其中,host和port的值应与Netty-socketio服务的配置一致。 6. 运行程序 现在,可以运行程序,并访问http://localhost:8080/ws,即可建立WebSocket连接。发送消息时,可以使用socket.emit()方法,接收消息时,可以使用socket.on()方法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值