websocket搭建教程

Service搭建教程

1,依赖引入,springweb的起步依赖虽然有websocket的相关依赖,但是并不完整,所以依然需要导入websocket的起步依赖,依赖版本要统一(我使用的是2.5.3)

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

<dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

    <optional>true</optional>

</dependency>

2,注解实现服务端

    2.1,@ServerEndpoint("/xxx/xxx/xxx"),指定服务器连接地址,支持restful风格,"/"不能省。该注解加在自定义的一个WebSocketServer的类,每个连接过来都会创建一个WebSocketServer的对象。

    为什么同一个用户连接过来不会再次创建一个WebSocketServer对象?

这个问题本身就有问题,建立连接和发送消息在websocket协议中是有一个状态码的,用来区分,同时,建立连接后,服务端会存储当前连接对象(推送消息到客户端就用这个对象的session),客户端也会存储当前连接对象(发消息就用这个对象发),这样就能一一对应了,如果同一个用户并发请求10000个过来创建连接,那就可能创建1w个连接对象(未加锁的情况下,可以以客户维度加锁),但是由于后端会用一个唯一识别码(例如客户号),去存储当前连接(使用concurrentHashMap,static修饰,让所有的WebSocketServer对象用的都是同一个Map),所以最终值存储了一个,那么客户端也只能用这个最终的连接对象进行发送消息。

websocketSession和httpSession不是同一个

    2.2,@ServerEndpoint的对象,是多例,但是还是需要被spring管理,需要加上@Componet注解(否则可能无法启动报错,或者启动了也无法接收请求),虽然加上的注解@Componet注解默认是单例,但是spring特殊处理过,所以是多例,但是是被spring管理。

@ServerEndpoint("/jerry/123")

@Component

@Slf4j

public class WebSocketServer {

    private Session session;

    private final static ConcurrentHashMap<String, WebSocketServer> clients = new ConcurrentHashMap<>();

    2.3,@ServerEndpoint的对象,满足以上两点,还是无法接收到请求,这个时候其实还需要注册一个@ServerEndpoint的管理器,使得连接过来的请求处理到端点。

@Configuration

@Slf4j

public class WebSocketServiceConfig {

    @Bean

    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

}

3.@ServerEndpoint注解的对象使用到的注解

    3.1@OnOpen

        当客户端和服务端建立连接成功时,会触发通知到当前注解的方法,方法入参要求Session和url(@pathParam注解restful风格的入参,这个入参可以没有),这个入参Session和HttpSession不同,是有websocket包自己实现的session

@OnOpen

public void openSuccess(Session session) {

    if (session == null) {

        log.info("连接异常(过期),session为空");

        return;

    }

    //获取session

    this.session = session;

    String businessNo = getBusinessNo(session);

    if (StringUtils.isEmpty(businessNo)) {

        log.error("没有对应的参数,不保存连接");

        return;

    }

    //存储当前客户端连接对象

    saveWebClient(businessNo);

}

private String getBusinessNo(Session session) {

    Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();

    System.out.println("requestParameterMap = " + requestParameterMap);

    List<String> businessList = MapUtils.getObject(requestParameterMap, "businessNo");

    if (businessList == null) {

        return null;

    }

    return businessList.get(0);

}

3.2@OnMessage

    当客户端和服务端建立成功后,监听消息,通常在服务器端,用来监听客户端发送过来的信息,进行处理转发(WebSocket有4种消息类型,BinaryMessage,PingMessage,PongMessage,TextMessage)

例如,客户1给客户2发送消息,当两个客户都跟服务器建立了连接,那么客户1,发送的消息,进入后,需要将消息推送给客户2,

TextMessage

消息格式,1客户 to 2客户,则一般时要带一个对方的Id

{ "businessNo":"2134349800","msg":"你好呀!!!", "toBusinessNo":"2134349801" }

那么这里面就得有两个标识符了,客户1和2的标识符,这样才能知道客户2的session(clients.get(getWebClientKey(businessNo).session),才能给客户2推送消息

@OnMessage

public void onMessage(String message) {

    //clients

    if (StringUtils.isBlank(message)) {

        return;

    }

    WebClientMsgDto webClientMsgDto = JSON.parseObject(message, WebClientMsgDto.class);

    if (webClientMsgDto == null) {

        return;

    }

    String businessNo = webClientMsgDto.getBusinessNo();

    if (StringUtils.isBlank(businessNo)) {

        return;

    }

    WebSocketServer webClient = getWebClient(businessNo);

    if (webClient == null) {

        return;

    }

    String toBusinessNo = webClientMsgDto.getToBusinessNo();

    WebSocketServer toWebClient = getWebClient(toBusinessNo);

    if (toWebClient == null){

        //告知当前用户对方离线

        webClient.sendMsg("对方不在线,请稍后再发送");

        return;

    }

    String msg = webClientMsgDto.getMsg();

    if (StringUtils.isEmpty(msg)){

        webClient.sendMsg("消息不能为空");

        return;

    }

    toWebClient.sendMsg(JSON.toJSONString(

            WebClientMsgDto.builder()

                    .msg(msg)

                    .businessNo(toBusinessNo)

                    .toBusinessNo(businessNo)

                    .build()));

}

/**

* 存储当前客户端连接对象

*

* @param businessNo

*/

private void saveWebClient(String businessNo) {

    clients.put(getWebClientKey(businessNo), this);

    log.info("clients1:{}",clients);

}

private String getWebClientKey(Session session) {

    return getBusinessNo(session) + WebClientSenceEnum.CHAT_ROOM.getSenceName();

}

3.3@OnClose 

    客户关闭连接,或者异超时等关闭连接,会触发该注解方法,当触发该方法时,我们主要还是处理些扫尾的工作,比如清除存储的Client(clients.remove(getWebClientKey(session));)

/**

* @param session

*/

@OnClose

public void onClose(Session session) {

    System.out.println("session = " + session);

    //    关闭时,要记得删除存储的链接对象

    rmWebClient(session);

}

private void rmWebClient(Session session) {

    clients.remove(getWebClientKey(session));

}

3.4@OnError

当连接报错时,会触发该注解方法,可以根据需要自己实现,一般有两个参数,1,session,2,Throwable

@OnError

public void onError(Session session, Throwable throwable) {

    System.out.println("session = " + session);

    System.out.println("throwable = " + throwable);

}

  • 58
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值