轻松搞定WebSocket

版权声明:转载请注明出处 https://blog.csdn.net/cowbin2012/article/details/85220055

实现后台向前端推送信息

pom.xml引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocketConfig

启用WebSocket的支持也是很简单,几句代码搞定

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
​
/**
 * 开启WebSocket支持
 * @author zhengkai
 */
@Configuration  
public class WebSocketConfig {  
    
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
  
}

WebSocketServer

因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

直接@ServerEndpoint("/websocket")@Component启用即可,然后在里面实现@OnOpen,@onClose,@onMessage等方法

/**
 * @Author: ynz
 * @Date: 2018/12/22/022 10:35
 */
@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
​
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet =
            new CopyOnWriteArraySet<WebSocketServer>();
​
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
​
    //接收sid
    private String sid="";
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
        this.sid=sid;
        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            log.error("websocket IO异常");
        }
    }
​
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }
​
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口"+sid+"的信息:"+message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
​
    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
​
​
    /**
     * 群发自定义消息
     * */
    public static void sendInfo(String message,@PathParam("sid") String sid)
            throws IOException {
        log.info("推送消息到窗口"+sid+",推送内容:"+message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if(sid==null) {
                    item.sendMessage(message);
                }else if(item.sid.equals(sid)){
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }
​
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
​
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }
​
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

消息推送

至于推送新信息,可以再自己的Controller写个方法调用WebSocketServer.sendInfo();即可

@Controller
public class CheckCenterController {
​
    //推送数据接口
    @ResponseBody
    @RequestMapping("/socket/push/{cid}")
    public String pushToWeb(@PathVariable String cid,@RequestBody String message) {
        try {
            WebSocketServer.sendInfo(message,cid);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return message;
    }
}

页面发起socket请求

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script src="js/jquery.min.js"></script>
        <script>
            var socket = null;
            function connect(){
                if(typeof(WebSocket) == "undefined") {
                    console.log("您的浏览器不支持WebSocket");
                }else{
                    console.log("您的浏览器支持WebSocket");
                    //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
                    socket = new WebSocket($("#url").val());
                    //打开事件
                    socket.onopen = function() {
                        console.log("Socket 已打开");
                        $("#status").html("已连接...");
                        //socket.send("这是来自客户端的消息" + location.href + new Date());
                    };
                    //获得消息事件
                    socket.onmessage = function(msg) {
                        console.log(msg.data);
                        $("#displayMsg").html( $("#displayMsg").html()+"<br>"+msg.data );
                        //发现消息进入    开始处理前端触发逻辑
                    };
                    //关闭事件
                    socket.onclose = function() {
                        console.log("Socket已关闭");
                        $("#status").html("未连接...");
                        socket = null;
                    };
                    //发生了错误事件
                    socket.onerror = function() {
                        alert("Socket发生了错误");
                        //此时可以尝试刷新页面
                    }
                }
            }
​
            function send() {
                if(socket == null){
                    alert("未连接");
                    return false;
                }
                socket.send($("#sendMsg").val());
            }
​
            function closeConnect(){
                $("#status").html("已断开...");
                socket.close();
            }
​
​
        </script>
    </head>
    <body>
        连接地址:<input type="text" id="url" style="width:400px;" value="ws://127.0.0.1:8080/websocket/22"></input>
        <button type="button" id="connect" onclick="connect()">连接</button>
        <button type="button" id="closeConnect" onclick="closeConnect()">断开</button>
        &nbsp;&nbsp;&nbsp;&nbsp;<div id="status" style="display:inline;">未连接...</div>
        <br><br>
        发送消息:<input type="text" id="sendMsg" style="width:400px;"></input>
        <button type="button"  onclick="send()">发送</button><br><br>
        <div>接收到消息:</div>
        <div id="displayMsg"></div>
    </body>
</html>

访问:http://127.0.0.1:8080/socket.html,可以连接服务,发送消息。

或者:http://127.0.0.1:8080/socket/push/{cid}给相应的服务发消息。

实现SSH WEB客户端

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
​
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.54</version>
</dependency>

WebSocketMessageBrokerConfigurer

配置消息代理,默认情况下使用内置的消息代理。 类上的注解@EnableWebSocketMessageBroker:此注解表示使用STOMP协议来传输基于消息代理的消息,此时可以在@Controller类中使用@MessageMapping

在方法registerStompEndpoints()里addEndpoint方法:添加STOMP协议的端点。这个HTTP URL是供WebSocket或SockJS客户端访问的地址;withSockJS:指定端点使用SockJS协议

在方法configureMessageBroker()里设置简单消息代理,并配置消息的发送的地址符合配置的前缀的消息才发送到这个broker

@Configuration
// 此注解表示使用STOMP协议来传输基于消息代理的消息,此时可以在@Controller类中使用@MessageMapping
@EnableWebSocketMessageBroker
public class SSHSocketConfig  implements WebSocketMessageBrokerConfigurer {
​
    /**
     * setAllowedOrigins方法用来设置来自那些域名的请求可访问,默认为localhost
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket")
                .setAllowedOrigins("*");
         //SockJS客户端访问
        /*registry.addEndpoint("/my-websocket").withSockJS();*/
    }
​
    /**
     * 配置消息代理
     * 启动Broker,消息的发送的地址符合配置的前缀来的消息才发送到这个broker
     */
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /**
         * 配置消息代理
         * 启动简单Broker,消息的发送的地址符合配置的前缀来的消息才发送到这个broker
         */
        registry.enableSimpleBroker("/topic");
        //只接收这前缀发送过来的消息
        registry.setApplicationDestinationPrefixes("/send");//应用请求前缀
    }
}

@Controller类 ,消息处理

@Controller
public class SSHController {
    @Resource
    private SimpMessagingTemplate messagingTemplate ;
    static Map<String,SSHData> map = new HashMap<>();
​
    /**
     * 接收消息
     */
    @MessageMapping("/receive/{id}")
   // @SendTo("/topic/test")
    public String receiver(@DestinationVariable("id") String id, String msg)
            throws IOException {
        SSHData sshData = map.get(id);
        if(sshData != null){
            OutputStream outputStream = map.get(id).getOutputStream();
            outputStream.write((msg).getBytes());
            outputStream.flush();
        }else{
            messagingTemplate.convertAndSend("/topic/"+id,"远程服务器未连接。。。\n\r");
        }
        return msg;
    }
​
    /**
     * 建立SSH连接
     */
    @RequestMapping("/connect")
    @ResponseBody
    public String connect(String user,String host,Integer port,String password,String id)
            throws IOException {
        SSHData sshData = map.get(id);
        if(sshData != null){
            sshData.release();
        }
        ChannelShell channelShell = SshUtils.getShellChannel( user, host, port , password, id);
        if(channelShell == null){
            messagingTemplate.convertAndSend("/topic/"+id,
                    "远程服务器连接失败,请检查用户或者密码是正确\n\r");
            return "";
        }
        map.put(id,new SSHData(channelShell,messagingTemplate,id));
        return "";
    }
​
    /**
     * 断开连接
     */
    @RequestMapping("/disconnect")
    @ResponseBody
    public String disConnect(String id) throws IOException {
        SSHData sshData = map.get(id);
        if(sshData != null){
            sshData.release();
            map.remove(id);
        }
        messagingTemplate.convertAndSend("/topic/"+id,"已断开连接。。。\n\r");
        return "";
    }
}
​
  • @MessageMapping:指定要接收消息的地址,类似@RequestMapping

  • @SendTo默认消息将被发送到与传入消息相同的目的地,但是目的地前面附加前缀(默认情况下为“/topic”}

  • @DestinationVariable接收URL的参数,类似@PathVariable

前端stomp、sockjs的配置

Stomp

websocket使用socket实现双工异步通信能力。但是如果直接使用websocket协议开发程序比较繁琐,我们可以使用它的子协议Stomp

SockJS

sockjs是websocket协议的实现,增加了对浏览器不支持websocket的时候的兼容支持 SockJS的支持的传输的协议有3类: WebSocket, HTTP Streaming, and HTTP Long Polling。默认使用websocket,如果浏览器不支持websocket,则使用后两种的方式。

SockJS使用”Get /info”从服务端获取基本信息。然后客户端会决定使用哪种传输方式。如果浏览器使用websocket,则使用websocket。如果不能,则使用Http Streaming,如果还不行,则最后使用 HTTP Long Polling。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="js/jquery.min.js"></script>
    <script src="js/xterm/sockjs.min.js"></script>
    <script src="js/xterm/stomp.min.js"></script>
    <link href="js/xterm/xterm.css" rel="stylesheet"></link>
    <script src="js/xterm/xterm.js"></script>
</head>
<body>
​
    <div id="terminal"></div>
    <div id="terminal2" style="height: 10%">
        <div id="desc"></div><br>
        IP:<input id="ip" type="text" value="47.106.106.**"></input>
        Port:<input id="port" type="text" value="22"></input>
        用户名:<input id="username" type="text" value="root"></input>
        密码:<input id="password" type="text" value="*"></input>
        <button onclick="connect()">登录</button>
        <button onclick="disconnect()">断开</button>
    </div>
​
<script>
    var stompClient ;
    var term = new Terminal({
        cols: 150,
        rows: 35,
        screenKeys: true,
        useStyle: true,
        cursorBlink: true
    });
​
    term.open(document.getElementById('terminal'));
    term.on('data', function($data) {
        //term.write($data);
        stompClient.send("/send/receive/1",{}, $data );
    });
​
    document.onkeydown=function(){
        if (event.keyCode == 13){
            term.write("\n\r");
        }
    }
​
</script>
<script>
    $(document).ready(function() {
        openSocket();
    });
    function openSocket() {
        if(stompClient==null){
            var socketPath ='ws://127.0.0.1:8080/websocket';
            var socket = new WebSocket(socketPath);
            stompClient = Stomp.over(socket);
            var headers={
                "Access-Control-Allow-Origin":"*",
                "Access-Control-Allow-Credentials":"true",
                "token":"kltoen"
            };
            stompClient.connect(headers, function(frame) {
                $("#desc").html("WebSocket已连接");
                stompClient.subscribe('/topic/1', function(event) {
                    term.write(event.body);
                },headers);
            },function (error) {
                $("#desc").html("WebSocket连接失败");
            });
​
            window.setInterval(function(){ //每隔5秒钟发送一次心跳,避免websocket连接超时而自动断开
                stompClient.send(" ");
            },30000);
        }
    }
​
    function connect() {
        $.ajax({
            type: "GET",
            url: "http://127.0.0.1:8080/connect?user="+$("#username").val()+
                       "&host="+$("#ip").val()+ "&port="+$("#port").val()+
                       "&password="+$("#password").val()+"&id=1"
        });
    }
​
    function disconnect() {
        $.ajax({
            type: "GET",
            url: "http://127.0.0.1:8080/disconnect?&id=1"
        });
    }
</script>
</body>
</html>

测试

猜你喜欢

转载自blog.csdn.net/cowbin2012/article/details/85220055