javaWeb(二) && 用手机app控制电脑网页(一) websocket

273次阅读

javaWeb(二) && 用手机app控制电脑网页(一) websocket

最近偶然间看到了一些关于用手柄打游戏的视频,于是冒出了这么个想法,为什么不能用手机app模拟出来一个手柄,然后在电脑端用网页加载一个游戏,通过手机模拟手柄来控制网页游戏人物的移动甚至其他动作呢,说干就干。

系统架构

整个系统采用一台服务器做数据中转,手机app将数据发送到服务器端后,通过数据携带的用户id等验证信息,确定要将数据发送到哪个网页在线id下,网页浏览器端负责建立连接,接收数据。首先要解决的就是如何从服务器源源不断的向浏览器发送数据,传统http协议下,需要浏览器端发送请求,然后服务器才会返回请求的数据,一次通信往往只有一次数据交互,而我们需要的功能需要服务器端持续向浏览器端发送数据。

一种解决方法是轮询,但是这种方式需要重复建立释放连接,很难在有限的硬件条件下做到低延时,因此放弃这种方法,另外一种方式就是使用WebSocket

WebSocket

WebSocket是随着H5标准发布而实现的一种新型通信方式,真正实现了全双工通信,传统HTTP1.1只能实现半双工。

一般支持H5标准的浏览器都提供了WebSocket的实现Api,实现方式较简单。

Spring Boot2整合WebSocket
pom.xml
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.20</version>
</dependency>

引入spring整合的websocket jar包以及处理json数据的fastjson,记录日志的hutool

加入webSocket实现,保证websocket能够正常使用
package xyz.tapuvw.app.socket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
实现WebSocket服务器端

主要是实现

  • OnOpen
  • OnMessage
  • OnError
  • OnClose
package xyz.tapuvw.app.socket;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import xyz.tapuvw.app.MyData;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
// 浏览器访问路径以及参数
@ServerEndpoint("/websocket/{username}")
public class WebSocketServer {
    private static final Log logger = LogFactory.get(WebSocketServer.class);
    // 在线人数
    public static int onlineNumber = 0;
    //以用户的姓名为Key,Websocketserver为对象保存起来
    private static final Map<String, WebSocketServer> clients = new ConcurrentHashMap<String, WebSocketServer>();

    //会话
    private Session session;
    // 用户名称
    private String username;

    // 统一服务器返回json数据格式的Bean类
    private MyData myData = new MyData();

    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) {

        // 把自己的信息加入到存储当前在线用户名单中
        if (clients.containsKey(username)) {
            clients.remove(username);
            clients.put(username, this);
        } else {
        clients.put(username, this);
        }
        onlineNumber++;
        logger.info("当前链接用户的id:" + session.getId() + "用户名:" + username);
        this.username = username;
        this.session = session;
        logger.info("有新用户加入,当前在线人数" + onlineNumber);
        //messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
        //先给所有人发送通知,说我上线了
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("message", 1);
        jsonObject.put("username", username);
        this.myData.setFlag(true);
        this.myData.setData(jsonObject.toJSONString());
        SendMessageToAll(JSON.toJSONString(myData));
    }

    @OnError
    public void onError(Session session, Throwable error) {
        logger.info("服务器发生了错误" + error.getMessage());
    }

    @OnClose
    public void onClose() {
        clients.remove(username);
        onlineNumber = clients.size();
        Map<String, Object> map1 = new HashMap<>();
        logger.info("有用户下线,当前在线人数" + onlineNumber);
    }

    @OnMessage
    public void OnMessage(String message, Session session) {
        logger.info("来自客户端信息:" + message + "客户端id:" + session.getId() + "username" + username);
        // 这里加入处理客户端数据的业务代码
    }

    public void SendMessageToAll(String message) {
        for (WebSocketServer item : clients.values()) {
            item.session.getAsyncRemote().sendText(message);
        }
    }

    public void sendMessageTo(String message, String ToUserName) {
        for (WebSocketServer item : clients.values()) {
            if (item.username.equals(ToUserName)) {
                item.session.getAsyncRemote().sendText(message);
                break;
            }
        }
    }

    public static synchronized int getOnlineNumber() {
        return onlineNumber;
    }
}
统一服务器返回json数据格式的Bean类

这个按照需求可加可不加

package xyz.tapuvw.app;

import org.springframework.stereotype.Component;

@Component
public class MyData {
    private Boolean flag;
    private Object data;

    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
定时推送
package xyz.tapuvw.app.controller;

import com.alibaba.fastjson.JSON;
import netscape.security.UserTarget;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import xyz.tapuvw.app.MyData;
import xyz.tapuvw.app.socket.WebSocketServer;
import xyz.tapuvw.app.utils.RandomPosition;

@Component
@EnableScheduling
public class webSocketController {
    @Autowired
    private WebSocketServer webSocketServer = null;

    @Autowired
    private MyData myData;

    @Autowired
    private RandomPosition randomPosition;

    private static int count = 0;
    static int posX=0;
    static int posY=0;

    //每50ms推送一次数据
    @Scheduled(fixedDelay = 50)
    public void sendMsg() {
        if(posX==1000){
            posX=0;
        }else{
            posX++;
        }
        posY= (int) (200*Math.cos(posX));
        count++;
        randomPosition.setPosX(posX);
        randomPosition.setPosY(posY);
        myData.setFlag(true);
        myData.setData(randomPosition);
        System.out.println("---------------定时发送数据" + count + "---------------------");
        webSocketServer.SendMessageToAll(JSON.toJSONString(myData));
    }
}
封装位置数据的RandomPosition
package xyz.tapuvw.app.utils;

import org.springframework.stereotype.Component;

@Component
public class RandomPosition {
    private int posX;
    private int posY;

    public int getPosX() {
        return posX;
    }

    public void setPosX(int posX) {
        this.posX = posX;
    }

    public int getPosY() {
        return posY;
    }

    public void setPosY(int posY) {
        this.posY = posY;
    }
}
前端演示代码
<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My WebSocket</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link rel="stylesheet" href="../css/index.css"/>
</head>

<body>
Welcome<br/>
<input id="text" type="text" />
<input id="username" type="text">
<button onclick="openSocket()">connect</button>
<button onclick="send()">send</button>
<button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>

<div>
    <div id="posX">当前X:</div>
    <div id="posY">当前Y:</div>
</div>
<div id="plane">
    <i class="fa fa-send" aria-hidden="true"></i>
</div>
</body>

<script type="text/javascript">
    var websocket = null;
    let plane = document.getElementById("plane");

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
        websocket.close();
    }

    //关闭连接
    function closeWebSocket(){
        websocket.close();
    }
    function openSocket() {
        // 判断当前使用的浏览器是否支持websocket
        if('WebSocket' in window){
            console.log(document.getElementById("username").value);
            websocket = new WebSocket("ws://localhost:8080/websocket/"+document.getElementById("username").value);
            console.log("您的浏览器支持WebSocket");

            //打开事件
            websocket.onopen = function() {
                console.log("websocket已打开");
                websocket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            websocket.onmessage = function(msg) {

                //解析服务器的json数据,改变小飞机的位置
                var t= JSON.parse(msg.data);
                console.log(t);
                var pox=t.data.posX;
                var poy=t.data.posY;
                console.log(t.data.posX);
                console.log(t.data.posY);

                plane.style.top = poy + 'px';
                plane.style.left =pox + 'px';
                document.getElementById("posX").innerText=pox+ 'px';
                document.getElementById("posY").innerText=poy+ 'px';
            };
            //关闭事件
            websocket.onclose = function() {
                console.log("websocket已关闭");
            };
            //发生了错误事件
            websocket.onerror = function() {
                console.log("websocket发生了错误");
            }
        }
        else{
            alert('Not support websocket')
        }
    }

    //发送消息
    function send(){
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>
演示

开启服务器,然后打开页面,在第二个输入栏中输入用户名,点击connect,小飞机就可以开始运动了,这里小飞机的运动轨迹是正弦轨迹,不妨动手改变一下试试

总结

总的来说,websocket的实现不难,只需要熟悉websocket通信的方式,就可以写出你自己想要的效果。

后续将更新app与服务器的通信,敬请期待

liubobo
版权声明:本站原创文章,由 liubobo 2022-02-16发表,共计7515字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。