精华内容
下载资源
问答
  • 最近的项目有个功能点: 商户端需要扫描用户端的二维码, 扫描结果要展示给商户和用户端. ...2.用户端使用webSocket与服务器进行长连接,有返回数据时再进行提示. 两种方式都有实现,简单说明一下1方式的...

    最近的项目有个功能点: 商户端需要扫描用户端的二维码, 扫描结果要展示给商户和用户端.

    商户端的提示比较好处理, 根据接口返回数据进行展示就可以, 稍微麻烦的是用户被扫的提示.

    解决方案有两种:

    1.用户端进行循环查询,每2秒进行一次接口查询,接口有数据时,根据数据展示;

    2.用户端使用webSocket与服务器进行长连接,有返回数据时再进行提示.

    两种方式都有实现,简单说明一下1方式的实现, 本篇着重介绍2方式的实现

    • 轮询实现方式
    1. 商户扫码,判断二维码信息的合法性, 合法进行扣款;
    2. 扣款的同时在redis里存储一条记录, key是用户的标识, value是需要返回给用户的消息;
    3. 用户端轮询redis中的数据, 返回数据时提示用户扣款结果.

         这种方式对服务器的资源占用较大(仅做说明)

    • webSocket实现方式
    1. 用户进入二维码扫码界面,与服务器建立webSocket连接, 服务端用map存储, key: 用户标识  value: session;
    2. 商户扫码后, 在map中查找用户的session, 向用户发送消息;
    3. 前端接收到webSocket的信息后, 进行相应的展示.

    nginx配置

    小程序的webSocket是https协议, 我们项目使用的是nginx转发, 需要配置nginx, 贴一下nginx配置,

    如果不配置nginx的upgrade协议, wss请求会报错:Error during WebSocket handshake: Unexpected response code: 400

    location /webSocket/ {}  中的webSocket根据自己websocket的注解进行更改

        map $http_upgrade $connection_upgrade {
            default upgrade;
            ''      close;
        }
    
        server {
        listen 443 ssl;
        server_name 你的域名;
        ssl on;
        ssl_certificate   cert/你的ssl pem.pem;
        ssl_certificate_key  cert/你的ssl key.key;
        ssl_session_timeout 5m;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        location / {
            proxy_pass   http://127.0.0.1:8089;
        }
        location /webSocket/ {
                proxy_pass http://127.0.0.1:8089;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
            }
    
    }
    

    maven依赖

            <dependency>
                <groupId>javax</groupId>
                <artifactId>javaee-api</artifactId>
                <version>7.0</version>
                <scope>provided</scope>
            </dependency>

    webSocket 文件

    webSocket的路径中包含了用户标识{userId}和{carwashId}, 这个方法也是借鉴大神的,没有找到出处了, 就没注明啦

    import com.carwash.util.ValidatorUtil;
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
     * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
     */
    @ServerEndpoint("/webSocket/{userId}/{carwashId}")
    public class WebSocketTest {
        //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
        private static int onlineCount = 0;
    
        //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
        private static ConcurrentHashMap<String, WebSocketTest> webSocketSet = new ConcurrentHashMap<String, WebSocketTest>();
    
        //与某个客户端的连接会话,需要通过它来给客户端发送数据
        private Session session;
        private String userIdCarwashId;
    
        /**
         * 连接建立成功调用的方法
         * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("userId") Integer userId, @PathParam("carwashId") Integer carwashId){
            this.session = session;
            this.userIdCarwashId = userId + "_" + carwashId;
            webSocketSet.put(userIdCarwashId, this);     //加入set中
            addOnlineCount();           //在线数加1
            System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose(){
            webSocketSet.remove(this.userIdCarwashId);  //从set中删除
            subOnlineCount();           //在线数减1
            System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
        }
    
        /**
         * 收到客户端消息后调用的方法
         * @param message 客户端发送过来的消息
         * @param session 可选的参数
         */
        @OnMessage
        public void onMessage(String message, Session session) {
    //        System.out.println("来自客户端的消息:" + message);
    //        //群发消息
    //        for(WebSocketTest item: webSocketSet){
    //            try {
    //                item.sendMessage(message);
    //            } catch (IOException e) {
    //                e.printStackTrace();
    //                continue;
    //            }
    //        }
        }
    
        /**
         * 发生错误时调用
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error){
            System.out.println("发生错误");
            error.printStackTrace();
        }
    
        /**
         * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
         * @param message
         * @throws IOException
         */
        public static void sendMessage(String userIdCarwashId, String message) throws IOException{
            System.out.println("当前在线用户数量: " + webSocketSet.size());
            WebSocketTest userSocket = webSocketSet.get(userIdCarwashId);
            if(ValidatorUtil.isNotNull(userSocket)) {
                userSocket.session.getBasicRemote().sendText(message);
            }
            //this.session.getAsyncRemote().sendText(message);
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void addOnlineCount() {
            WebSocketTest.onlineCount++;
        }
    
        public static synchronized void subOnlineCount() {
            WebSocketTest.onlineCount--;
        }
    }
    

    还需要在springMvc.xml中增加包扫描

    <context:component-scan base-package="com.carwash.websocket" />

    调用发送消息的方法为

    WebSocketTest.sendMessage(QRUserId + "_" + QRCarwashId, "0_扣费成功_剩余次数: " + restNumber);

    小程序调试

    在这里贴一下小程序的调试代码

    websocket.wxml

    <!--pages/websocket/websocket.wxml-->
    <view class="page">
      <view class="page__hd">
      </view>
      <view class="page__bd">
    
      <button bindtap="connectWebsocket" type="primary">连接websocket</button>
    
      </view>
    </view>

    websocket.js

    // pages/websocket/websocket.js
    Page({
    
      /**
       * 页面的初始数据
       */
      data: {
    
      },
      connectWebsocket: function () {
        wx.connectSocket({
          url: 'wss://你的域名/webSocket/17/2',
          data: {
            x: '1',
            y: '22'
          },
          header: {
            'content-type': 'application/json'
          },
          method: "GET"
        })
        wx.onSocketOpen(function (res) {
          console.log('WebSocket连接已打开!')
        })
        wx.onSocketError(function (res) {
          console.log(res)
          console.log('WebSocket连接打开失败,请检查!')
        })
        wx.onSocketMessage(function (res) {
          console.log('收到服务器内容:' + res.data)
        })
      }
    })

    商户扫码后的结果

    到这里就大功告成了, 如有疑问可以留言或者私信我.

    展开全文
  • 当时想的是像http请求一样,通过url直接传参然后后台解析不通过关闭通道,但是发现url上一增加参数 就无法正常连接,当时时间紧未进行处理,当时处理逻辑如下: 1、先设置读写超时120秒 2、连接成功后增加当前...

    以前写netty的聊天工具时遇到个问题,就是websocket连接时验证当前用户是否已经拥有登陆权限,

    当时想的是像http请求一样,通过url直接传参然后后台解析不通过关闭通道,但是发现url上一增加参数

    就无法正常连接,当时时间紧未进行处理,当时处理逻辑如下:

    1、先设置读写超时120秒

    2、连接成功后增加当前通道验证标识

    2、前端监听websocket的onopen方法,一但连接立马发送验证消息,后台验证通过后修改验证标识

    3、前端每过50秒向后台发送一个心跳包,后端收到心跳包判断是否已通过验证,通过后回复心跳包,未通过不回复,不回复的话120秒后该通道会被自动关闭

    贴下相关代码:

    //连接后
    		      socket.onopen = function(event) {
    		    	  IMSocket.bindUserHandle('${userId}','${sessionId}','online');//绑定通道为当前用户
    		    	  openHeartBeat();//开启心跳
    		    	  flushNoReadFrendMes();//展示未读消息
    		    	  if(layui.layim.cache().mine.status=="online"){
    		 	 	     IMSocket.statusnotice('${userId}',3);//通知我上线了	    	 
    		 	      }
    		    	  if(showclosewebsocketlayer){
    		    		  showclosewebsocketlayer=false;
    		    		  layer.close(showclosewebsocketindex);
    		    	  }
    		      };
        /**
         * 消息处理
         *
         * @param hander
         * @param wrapper
         */
        private void receiveMessages(ChannelHandlerContext hander, IMBaseMessage baseMessage) {
        	if(baseMessage.getCmdType()==Constants.CmdType.BIND){//绑定登陆人
        		imConnertor.bindChannel(hander, baseMessage);
    		}else if(baseMessage.getCmdType()==Constants.CmdType.MESSAGE){//发送消息
    			iLayImMessageService.flushSessionTime(hander);
    			baseMessage = iLayImMessageService.saveMessageToDB(baseMessage);
    			imConnertor.sendFrendMessage(baseMessage);
    		}else if(baseMessage.getCmdType()==Constants.CmdType.READMES){//消息已读
    			iLayImMessageService.flushSessionTime(hander);
    			iLayImMessageService.readMes(baseMessage);
    		}else if(baseMessage.getCmdType()==Constants.CmdType.ONLINE||baseMessage.getCmdType()==Constants.CmdType.OFFLINE){//上线 下线
    			List<MyMember> myMembers = iLayImMessageService.userNoticeSatus(baseMessage);
    			imConnertor.userNoticeSatus(baseMessage, myMembers);
    		}else if(baseMessage.getCmdType()==Constants.CmdType.HEARTBEAT){//心跳
    			imConnertor.heartbeat(hander);
    		}
        }

     

    现在补充另外一种方式基于url参数的验证:

     跟踪源码发现在类WebSocketServerProtocolHandshakeHandler的channelRead方法有判断请求的地址是否与我们开启监听服务的配置的地址是否一样,如果不一样就停止该通道。如图:

    class WebSocketServerProtocolHandshakeHandler extends ChannelInboundHandlerAdapter {
    
        private final String websocketPath;
        private final String subprotocols;
        private final boolean allowExtensions;
        private final int maxFramePayloadSize;
        private final boolean allowMaskMismatch;
    
        WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
                boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) {
            this.websocketPath = websocketPath;
            this.subprotocols = subprotocols;
            this.allowExtensions = allowExtensions;
            maxFramePayloadSize = maxFrameSize;
            this.allowMaskMismatch = allowMaskMismatch;
        }
    
        @Override
        public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
            final FullHttpRequest req = (FullHttpRequest) msg;
            if (!websocketPath.equals(req.uri())) {
                ctx.fireChannelRead(msg);
                return;
            }
    
            try {
                if (req.method() != GET) {
                    sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
                    return;
                }

     发现问题就好处理了,处理如下,将WebSocketServerProtocolHandshakeHandler放在TextWebSocketFrameHandler之后,然后TextWebSocketFrameHandler重写channelRead方法,将url上的参数取出来使用后,将地址设置回去就行了。如果是验证登陆权限的话,验证不能过调用ChannelHandlerContext.close()直接关闭通道,避免资源浪费。

                .childHandler(new ChannelInitializer<Channel>(){//同上
    				@Override
    				protected void initChannel(Channel ch) throws Exception {
    					ChannelPipeline pipeline = ch.pipeline();
    			        pipeline.addLast(new IdleStateHandler(READ_IDEL_TIME_OUT,
    			                WRITE_IDEL_TIME_OUT, ALL_IDEL_TIME_OUT, TimeUnit.SECONDS));
    			        pipeline.addLast(new HeartbeatServerHandler()); // 2 集成心跳处理
    			        pipeline.addLast(new HttpServerCodec());
    					pipeline.addLast(new HttpObjectAggregator(64 * 1024));
    					pipeline.addLast(new ChunkedWriteHandler());
    					pipeline.addLast(new TextWebSocketFrameHandler(iLayImMessageService,imConnertor));
    					pipeline.addLast(new WebSocketServerProtocolHandler(WebsocketChatServer.WEB_SOCKET_LINKURL, null, true, 10485760));
    					pipeline.addLast(new HttpRequestHandler(WebsocketChatServer.HTTP_SOCKET_LINKURL));
    				}})
    
    public class TextWebSocketFrameHandler extends
    		SimpleChannelInboundHandler<TextWebSocketFrame> {
    
    	.....
    
    	@Override
    	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    		if (ObjectHelper.isNotEmpty(msg) && msg instanceof FullHttpRequest) {
    			FullHttpRequest request = (FullHttpRequest) msg;
    			String uri = request.uri();
    			String origin = request.headers().get("Origin");
    			if (null == origin) {
    				ctx.close();
    			} else {
    				if (null != uri && uri.contains("?")) {
    					String[] uriArray = uri.split("\\?");
    					if (null != uriArray && uriArray.length > 1) {
    						String[] paramsArray = uriArray[1].split("=");
    						if (null != paramsArray && paramsArray.length > 1) {
    							//TODO 验证连接权限,不通过关闭
    						}
    					}
    					request.setUri(WebsocketChatServer.WEB_SOCKET_LINKURL);
    				}
    			}
    		}
    		super.channelRead(ctx, msg);
    	}
    
        .....

     

    展开全文
  • 前端参数和后端接参及调用可以看这位博主的——Moshow郑锴 没怎么写过博客怕超链接没法用把地址也粘贴过来了————...depth_1-utm_source=distribute.pc_relevant.none-task-bl

    前端参数和后端接参及调用可以看这位博主的——Moshow郑锴

    没怎么写过博客怕超链接没法用把地址也粘贴过来了————https://blog.csdn.net/moshowgame/article/details/80275084?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.nonecase

    其中需要注意的地方是就我使用的jwt而言。不需要验证!事实就是这样,我的jwt和拦截器压根没拦截ws协议。我原本还先想着把token放到消息头进行验证一下子然后因为这个整了半天也没有弄出个所以然。

    还有一点需要注意。

    后端接参数要使用@PathParam。不能用@PathVariable或者@RequestParam。

    因为会报错:

    11:44:11.649 [main] ERROR o.s.boot.SpringApplication - Application run failed
    java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.ysd.config.WebSocketServer

    Caused by: javax.websocket.DeploymentException: A parameter of type [class java.lang.Integer] was found on method[onOpen] of class [java.lang.reflect.Method] that did not have a @PathParam annotation

    这是我个人的理解。因为网上的websocket比较少且单一,基本上没有怎么整合。在这里我提出自己的一点点建议,欢迎斧正。
     

    展开全文
  • websocket

    2020-12-19 19:33:09
    websocket一、websocket简介二、flask-websocket的安装及初始化操作三、创建socket连接四、基于事件接受信息1、基于未定义事件进行通信2、基于自定义事件进行通信五、服务端响应信息六、基于房间管理分发信息七、...

    一、websocket简介

    一直以来,HTTP是无状态、单向通信的网络协议,即客户端请求一次,服务器回复一次,默认情况下,只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。如果想让服务器消息及时下发到客户端,需要采用类似于轮询的机制,大部分情况就是客户端通过定时器使用ajax频繁地向服务器发出请求。这样的做法效率很低,而且HTTP数据包头本身的字节量较大,浪费了大量带宽和服务器资源。

    为了提高效率,HTML5推出了WebSocket技术。

    WebScoket是一种让客户端和服务器之间能进行全双工通信(full-duplex)的技术。它是HTML最新标准HTML5的一个协议规范,本质上是个基于TCP的协议,它通过HTTP/HTTPS协议发送一条特殊的请求进行握手后创建了一个TCP连接,此后浏览器/客户端和服务器之间便可随时随地以通过此连接来进行双向实时通信,且交换的数据包头信息量很小。

    同时为了方便使用,HTML5提供了非常简单的操作就可以让前端开发者直接实现socket通讯,开发者只需要在支持WebSocket的浏览器中,创建Socket之后,通过onopen、onmessage、onclose、onerror四个事件的实现即可处理Socket的响应。

    注意:websocket是HTML5技术的一部分,但是websocket并非只能在浏览器或者HTML文档中才能使用,事实上在python或者C++等语言中只要能实现websocket协议报文,均可使用。

    客户端报文:

    GET /mofang/websocket HTTP/1.1
    Host: 127.0.0.1
    Origin: http://127.0.0.1:5000
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==      # Sec-WebSocket-Key 是随机生成的
    Sec-WebSocket-Version: 13
    

    服务端报文:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= # 结合客户端提供的Sec-WebSocket-Key基于固定算法计算出来的
    Sec-WebSocket-Protocol: chat
    

    在本文中,因为我们使用的是flask框架,所以我们使用flask-websocket

    • 注意:websocket存在版本对应关系,关系表如下:

    关系表

    二、flask-websocket的安装及初始化操作

    安装

    pip install flask-socketio
    pip install gevent-websocket
    

    初始化:

    在application/__init__文件中进行初始化操作-socketio(部分模块是项目其他模块,请忽略):

    from flask import Flask
    from flask_script import Manager
    from flask_sqlalchemy import SQLAlchemy
    from flask_redis import FlaskRedis
    from flask_session import Session
    from flask_migrate import Migrate,MigrateCommand
    from application.utils.logger import Log
    from flask_jsonrpc import JSONRPC
    from flask_marshmallow import Marshmallow
    from flask_jwt_extended import JWTManager
    from flask_admin import Admin
    from flask_babelex import Babel
    from faker import Faker
    from flask_pymongo import PyMongo
    from flask_qrcode import QRcode
    from flask_socketio import SocketIO
    import os,sys
    
    from application.utils.config import load_config
    from application.utils.session import init_session
    from application.utils.commands import load_command
    from application.utils import init_blueprint
    
    
    manager=Manager()
    
    db=SQLAlchemy()
    
    redis=FlaskRedis()
    
    session_store = Session()
    
    migrate=Migrate()
    
    log=Log()
    
    jwt=JWTManager()
    
    admin = Admin()
    babel = Babel()
    
    mongo=PyMongo()
    
    QRcode=QRcode()
    # 初始化jsonrpc模块
    jsonrpc = JSONRPC(service_url="/api")
    
    # 数据转换器的数据创建
    ma = Marshmallow()
    
    # socketio
    socketio = SocketIO()
    
    def init_app(config_path):
    	# 创建app应用对象
    	app = Flask(__name__)
    
    	# 项目根目录
    	app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    
    	# 加载配置
    	Config=load_config(config_path)
    	app.config.from_object(Config)
    
    	# session存储初始化
    	init_session(app)
    	session_store.init_app(app)
    
    	# 数据转换器初始化
    	ma.init_app(app)
    
    	# 数据库初始化
    	db.init_app(app)
    	redis.init_app(app)
    	app.db = db
    	mongo.init_app(app)
    
    	# 数据迁移初始化
    	migrate.init_app(app,db)
    	# 添加数据迁移命令到终端脚本工具中
    	manager.add_command("db",MigrateCommand)
    
    	# 日志初始化
    	app.log=log.init_app(app)
    
    	# 蓝图注册
    	init_blueprint(app)
    
    	#  初始化jsonrpc
    	# jsonrpc.service_url="/api"
    	jsonrpc.init_app(app)
    
    	# jwt初始化
    	jwt.init_app(app)
    
    	#初始化终端脚本工具
    	manager.app=app
    
    
    	# admin站点
    	admin.init_app(app)
    
    	# 项目语言
    	babel.init_app(app)
    
    	# qrcode初始化配置
    	QRcode.init_app(app)
    
    	# 数据种子生成器
    	app.faker = Faker(app.config.get('LANGUAGE'))
    
    	# socketio
    	socketio.init_app(app,cors_allowed_origins=app.config["CORS_ALLOWED_ORIGINS"],async_mode=app.config["ASYNC_MODE"],debug=app.config["DEBUG"])
    
    	# 改写runserver
    	if sys.argv[1] == "runserver":
    		manager.add_command("run",socketio.run(app,host=app.config["HOST"],port=app.config["PORT"]))
    
    	# 注册自定义命令
    	load_command(manager)
    
    	return manager
    
    

    如代码中所示,sockio的配置参数需要在在dev文件中取:

    	# socketio配置信息
    	DEBUG = True
    	CORS_ALLOWED_ORIGINS = "*"
    	ASYNC_MODE = None
    	HOST = "0.0.0.0"
    	PORT = 5000
    

    在加载蓝图的过程中,自动加载socket的api:

    from flask import Blueprint
    from importlib import import_module
    
    def init_blueprint(app):
    	# 自动注册蓝图
    	blueprint_path_list = app.config.get("INSTALLED_APPS")
    	print(blueprint_path_list)
    
    	# 加载admin站点总配置文件
    	try:
    		import_module(app.config.get("ADMIN_PATH"))
    	except:
    		pass
    
    	for blueprint_path in blueprint_path_list:
    		blueprint_name = blueprint_path.split(".")[-1]
    		# 自动创建蓝图对象
    		blueprint = Blueprint(blueprint_name,blueprint_path)
    		# 蓝图自动注册和绑定视图的子路由
    		url_module=import_module(blueprint_path+".urls") # 加载蓝图下子路由文件
    		for url in url_module.urlpatterns: #遍历子路由的所有路由关系
    			blueprint.add_url_rule(**url) #注册到蓝图下
    
    		# 读取路由总文件
    		url_path=app.config.get("URL_PATH")
    		urlpatterns = import_module(url_path).urlpatterns
    		url_prefix = ""
    		for urlpattern in urlpatterns:
    			if urlpattern["blueprint_path"] == blueprint_name + ".urls":
    				url_prefix = urlpattern["url_prefix"]
    				break
    
    		# 注册模型
    		import_module(blueprint_path + ".models")
    		try:
    			import_module(blueprint_path + ".admin")
    		except:
    			pass
    
    		# 加载蓝图内部的socket接口
    		try:
    			import_module(blueprint_path+".socket")
    		except:
    			pass
    
    		# 注册蓝图对象到app应用对象中
    		app.register_blueprint(blueprint,url_prefix=url_prefix)
    def path(rule,func_view):
    	# 把蓝图下视图和路由之间的映射关系处理成字典结构,方便后面注册蓝图的时候,直接传参
    	return {"rule":rule,"view_func":func_view}
    
    def include(url_prefix, blueprint_path):
    	return {"url_prefix":url_prefix,"blueprint_path":blueprint_path}
    
    

    生成蓝图目录:

    cd application/apps/
    python ../../manage.py blue -n=orchard
    

    dev文件中注册蓝图:

    # 注册蓝图
        INSTALLED_APPS = [
            "application.apps.home",
            "application.apps.users",
            "application.apps.marsh",
            "application.apps.orchard",
        ]
    

    前端我们使用vue,新建一个orchard.html文件作为测试html文件,因为我们是基于python-socketio模块提供的服务端,所以此文件中(客户端)必须引入socketIO.js才可通过js与服务端通信:

    <!DOCTYPE html>
    <html>
    <head>
    	<title>用户中心</title>
    	<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    	<meta charset="utf-8">
    	<link rel="stylesheet" href="../static/css/main.css">
    	<script src="../static/js/vue.js"></script>
    	<script src="../static/js/axios.js"></script>
    	<script src="../static/js/main.js"></script>
    	<script src="../static/js/uuid.js"></script>
    	<script src="../static/js/settings.js"></script>
    	<script src="../static/js/socket.io.js"></script>
    </head>
    <body>
    	<div class="app orchard" id="app">
        <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
        <div class="orchard-bg">
    			<img src="../static/images/bg2.png">
    			<img class="board_bg2" src="../static/images/board_bg2.png">
    		</div>
        <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
    
    	</div>
    	<script>
    	apiready = function(){
    		init();
    		new Vue({
    			el:"#app",
    			data(){
    				return {
              music_play:true,
              namespace: '/mofang_orchard',
              token:"",
              socket: null,
              timeout: 0,
    					prev:{name:"",url:"",params:{}},
    					current:{name:"orchard",url:"orchard.html",params:{}},
    				}
    			},
          created(){
            this.checkout();
    
          },
    		methods:{
            go_index(){
              this.game.outWin("orchard");
            },
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    

    css样式:

    .app .orchard-bg{
    	margin: 0 auto;
    	width: 100%;
    	max-width: 100rem;
    	position: absolute;;
    	z-index: -1;
      top: -6rem;
    }
    .app .orchard-bg .board_bg2{
      position: absolute;
      top: 1rem;
    }
    .orchard .back{
    	position: absolute;
    	width: 3.83rem;
    	height: 3.89rem;
      z-index: 1;
      top: 2rem;
      left: 2rem;
    }
    .orchard .music{
      right: 2rem;
    }
    .orchard .header{
      position: absolute;
      top: 0rem;
      left: 0;
      right: 0;
      margin: auto;
      width: 32rem;
      height: 19.28rem;
    }
    
    .orchard .info{
      position: absolute;
      z-index: 1;
      top: 0rem;
      left: 4.4rem;
      width: 8rem;
      height: 9.17rem;
    }
    .orchard .info .avata{
      width: 8rem;
      height: 8rem;
      position: relative;
    }
    .orchard .info .avatar_bf{
      position: absolute;
      z-index: 1;
      margin: auto;
      width: 6rem;
      height: 6rem;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
    }
    .orchard .info .user_avatar{
      position: absolute;
      z-index: 1;
      width: 6rem;
      height: 6rem;
      margin: auto;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      border-radius: 1rem;
    }
    .orchard .info .avatar_border{
      position: absolute;
      z-index: 1;
      margin: auto;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      width: 7.2rem;
      height: 7.2rem;
    }
    .orchard .info .user_name{
      position: absolute;
      left: 8rem;
      top: 1rem;
      width: 11rem;
      height: 3rem;
      line-height: 3rem;
      font-size: 1.5rem;
      text-shadow: 1px 1px 1px #aaa;
      border-radius: 3rem;
      background: #ff9900;
      text-align: center;
    }
    
    .orchard .wallet{
      position: absolute;
      top: 3.4rem;
      right: 4rem;
      width: 16rem;
      height: 10rem;
    }
    .orchard .wallet .balance{
      margin-top: 1.4rem;
      float: left;
      margin-right: 1rem;
    }
    .orchard .wallet .title{
      color: #fff;
      font-size: 1.2rem;
      width: 6.4rem;
      text-align: center;
    }
    .orchard .wallet .title img{
      width: 1.4rem;
      margin-right: 0.2rem;
      vertical-align: sub;
      height: 1.4rem;
    }
    .orchard .wallet .num{
      background: url("../images/btn3.png") no-repeat 0 0;
      background-size: 100%;
      width: 6.4rem;
      font-size: 0.8rem;
      color: #fff;
      height: 2rem;
      line-height: 1.8rem;
      text-indent: 1rem;
    }
    .orchard .header .menu-list{
      position: absolute;
      top: 9rem;
      left: 2rem;
    }
    .orchard .header .menu-list .menu{
      color: #fff;
      font-size: 1rem;
      float: left;
      width: 4rem;
      height: 4rem;
      text-align: center;
      margin-right: 2rem;
    }
    .orchard .header .menu-list .menu img{
      width: 3.33rem;
      height: 3.61rem;
      display: block;
      margin: auto;
      margin-bottom: 0.4rem;
    }
    .orchard .footer{
      position: absolute;
      width: 100%;
      height: 6rem;
      bottom: -2rem;
      background: url("../images/board_bg3.png") no-repeat -1rem 0;
      background-size: 110%;
    }
    .orchard .footer .menu-list{
      width: 100%;
      height: 4rem;
      display: flex;
      position: absolute;
      top: -1rem;
    }
    .orchard .footer .menu-list .menu,
    .orchard .footer .menu-list .menu-center{
      float: left;
      width: 4.44rem;
      height: 5.2rem;
      font-size: 1.5rem;
      color: #fff;
      line-height: 4.44rem;
      text-align: center;
      background: url("../images/btn5.png") no-repeat 0 0;
      background-size: 100%;
      flex: 1;
      margin-left: 4px;
      margin-right: 4px;
    }
    .orchard .footer .menu-list .menu-center{
      background: url("../images/btn6.png") no-repeat 0 0;
      background-size: 100%;
      flex: 2;
    }
    

    三、创建socket连接

    先在后端蓝图下面创建socket.py文件,并提供连接接口, orchard/socket.py:

    from application import socketio
    from flask import request
    from application.apps.users.models import User
    from flask_socketio import join_room, leave_room
    
    @socketio.on("connect",namespace="/mofang")
    def user_connect():
    	# request.sid socketIO基于客户端生成唯一会话id
    	print("用户%s连接过来了" % request.sid)
    
    @socketio.on("disconnect",namespace="/mofang")
    def user_disconnect():
    	print("用户%s离开了" % request.sid)
    

    前端vue连接后端socketio,我们只截取相关函数:

    checkout(){
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this,token,(new_access_token)=>{
                        this.connect();
                    })
                },
    connect(){
                    // socket连接
                    this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace,{transports:["websocket"]});
                    this.socket.on("connect",()=>{  
                            this.game.print("开始连接服务器");
                    })
                },
    

    注意:checkout函数为token检测机制,checkout函数需要使用created方法挂载才能使页面加载时自动执行相关函数,这里不再展示

    效果展示:
    app登陆后进入相应界面:

    服务端:
    进入

    离开相应界面:

    服务端:
    离开
    如图,连接是成功的。

    四、基于事件接受信息

    1、基于未定义事件进行通信

    客户端:

    js可通过通过send方法可以直接发送数据给后端,数据格式为json格式

    checkout(){
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this,token,(new_access_token)=>{
                        this.connect();
                    })
                },
    connect(){
                    // socket连接
                    this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace,{transports:["websocket"]});
                    this.socket.on("connect",()=>{  
                            this.game.print("开始连接服务器");
                            this.login()
                    })
                },
                login(){
                    var id = this.game.get("id")
                    this.socket.send({"uid":id})
                },
    

    服务端:

    后端通过形参来接收数据

    from application import socketio
    from flask import request
    from application.apps.users.models import User
    from flask_socketio import join_room, leave_room
    
    
    @socketio.on("message",namespace="/mofang")
    def user_message(data):
    	id = data.get("uid")
    	print("用户id为%s" % id)
    

    后端输出结果:
    接收

    2、基于自定义事件进行通信

    客户端:

    前端通过emit方法可触发自定义事件进行通信

    checkout(){
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this,token,(new_access_token)=>{
                        this.connect();
                    })
                },
    connect(){
                    // socket连接
                    this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace,{transports:["websocket"]});
                    this.socket.on("connect",()=>{  
                            this.game.print("开始连接服务器");
                            this.login()
                    })
                },
                login(){
                    var id = this.game.get("id")
                    this.socket.emit("login",{"uid":id})
                },
    

    服务端:
    服务端装饰器第一个参数需和emit方法定义的事件名一致

    from application import socketio
    from flask import request
    from application.apps.users.models import User
    from flask_socketio import join_room, leave_room
    
    
    @socketio.on("login",namespace="/mofang")
    def user_message(data):
    	id = data.get("uid")
    	print("自定义通信用户id为%s" % id)
    

    五、服务端响应信息

    服务端:
    服务端使用emit方法向客户端响应数据:

    from application import socketio
    from flask import request
    from application.apps.users.models import User
    from flask_socketio import join_room, leave_room
    
    
    @socketio.on("login",namespace="/mofang")
    def user_message(data):
    	id = data.get("uid")
    	print("自定义通信用户id为%s" % id)
    	# 主动响应数据给客户端(例:从数据库中统计所有用户数量返回给客户端)
    	count = User.query.count()
    	print(count)
    	socketio.emit("server_response",{"count":count},namespace="/mofang")
    

    客户端:
    客户端通过对应事件名来接收服务端传过来的数据

    checkout(){
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this,token,(new_access_token)=>{
                        this.connect();
                    })
                },
    connect(){
                    // socket连接
                    this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace,{transports:["websocket"]});
                    this.socket.on("connect",()=>{  
                            this.game.print("开始连接服务器");
                            this.login();
                            this.get_count()
                    })
                },
       login(){
                    var id = this.game.get("id")
                    this.socket.emit("login",{"uid":id})
                },
    get_count(){
                    this.socket.on("server_response",(res)=>{
                        this.game.print(res.count);
                        alert(`共有${res.count}人在种植园忙碌着~`)
                    });
                },
    

    app展示效果:

    app

    六、基于房间管理分发信息

    使用room方法,可通过一定条件,如用户id,将不同的响应信息私有化分发给对应用户,不会进行广播操作

    服务端:

    from application import socketio
    from flask import request
    from application.apps.users.models import User
    from flask_socketio import join_room, leave_room
    
    
    @socketio.on("login",namespace="/mofang")
    def user_message(data):
    	id = data.get("uid")
    	print("自定义通信用户id为%s" % id)
    	# 基于用户id分配不同房间
    	join_room(id)
    	socketio.emit("server_response",{"login":"登陆成功"},namespace="/mofang",room=id)
    

    客户端:

    checkout(){
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this,token,(new_access_token)=>{
                        this.connect();
                    })
                },
     connect(){
                    // socket连接
                    this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace,{transports:["websocket"]});
                    this.socket.on("connect",()=>{  
                            this.game.print("开始连接服务器");
                            this.login();
                            this.get_count()
                    })
                },
        login(){
                    var id = this.game.get("id")
                    this.socket.emit("login",{"uid":id})
                },
    get_count(){
                    this.socket.on("server_response",(res)=>{
                        this.game.print(res.login);
                        alert(res.login)
                    });
                },
    

    七、服务端定时推送数据

    我们需要开启一个线程来让数据定时变化,使用random模块每隔一秒页面打印随机数

    服务端:

    from application import socketio
    from flask import request
    from application.apps.users.models import User
    from flask_socketio import join_room, leave_room
    from threading import Lock
    import random
    
    
    thread = None
    thread_lock=Lock()
    
    @socketio.on("login",namespace="/mofang")
    def user_message(data):
    	global thread
    	with thread_lock:
    		if thread is None:
    			thread = socketio.start_background_task(target=background_thread)
    def background_thread():
    	while True:
    		# 定时1秒
    		socketio.sleep(1)
    		count=random.randint(1,100)
    		socketio.emit("server_response",{"count":count},namespace="/mofang")
    

    客户端:

    因为需要vue数据驱动视图,故将前端代码截全

    <!DOCTYPE html>
    <html>
    <head>
    	<title>用户中心</title>
    	<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    	<meta charset="utf-8">
    	<link rel="stylesheet" href="../static/css/main.css">
    	<script src="../static/js/vue.js"></script>
    	<script src="../static/js/axios.js"></script>
    	<script src="../static/js/main.js"></script>
    	<script src="../static/js/uuid.js"></script>
    	<script src="../static/js/settings.js"></script>
    	<script src="../static/js/socket.io.js"></script>
    </head>
    <body>
    	<div class="app orchard" id="app">
        <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
        <div class="orchard-bg">
    			<img src="../static/images/bg2.png">
    			<img class="board_bg2" src="../static/images/board_bg2.png">
    		</div>
        <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
        <h1 style="position:absolute;top:20rem;">{{num}}</h1>
    
    	</div>
    	<script>
    	apiready = function(){
    		init();
    		new Vue({
    			el:"#app",
    			data(){
    				return {
              music_play:true,
              token:"",
              num:"",
              socket: null,
              timeout: 0,
    					prev:{name:"",url:"",params:{}},
    					current:{name:"orchard",url:"orchard.html",params:{}},
    				}
                },
            created(){
                this.checkout()
            },
    		methods:{
                checkout(){
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this,token,(new_access_token)=>{
                        this.connect();
                    })
                },
                connect(){
                    // socket连接
                    this.socket = io.connect(this.settings.socket_server + this.settings.socket_namespace,{transports:["websocket"]});
                    this.socket.on("connect",()=>{  
                            this.game.print("开始连接服务器");
                            this.login();
                            this.get_count()
                    })
                },
                login(){
                    var id = this.game.get("id")
                    this.socket.emit("login",{"uid":id})
                },
                get_count(){
                    this.socket.on("server_response",(res)=>{
                        
                        this.num = res.count
                    });
                },
                go_index(){
                    this.game.outWin("orchard")
                }
    			}
    		});
    	}
    	</script>
    </body>
    </html>
    

    app展示效果:

    每隔1秒h1标签中的数字会发生变化,此处我们截不同时刻的两张图

    app1
    app2

    展开全文
  • webSocket

    2019-08-13 21:57:01
    什么是webSocket 是一种在单个TCP连接上进行全双工通信的协议。即浏览器和服务器之间建立一个不受限的双向通信的通道 优点 较少的控制开销,更强的实时性,保持连接状态,更好的二进制支持,可以支持扩展,更好的...
  • WebSocket实现鉴权方案

    2021-06-18 14:39:45
    前端连接二、websocket鉴权方案2.1.url传参方案2.2.websocket头字段Sec-WebSocket-Protocol传参 WebSocket是一种在单个TCP连接上进行全双工通信的协议。它使得客户端和服务器之间的数据交换变得更加简单,允许...
  • websocket.zip

    2019-12-05 17:39:07
    springboot2.x整合websocket的例子,很详细,也带有传参(请求路径),大多数开发都能满足
  • WebSocket实现

    千次阅读 2017-03-18 14:44:21
    概念客户端websocket允许通过JavaScript建立与远程服务器的连接,从而实现客户端与服务器间双向的通信。在websocket中有两个方法:   1、send() 向远程服务器发送数据  2、close() 关闭该websocket链接  ...
  • 基于netty-websocket-spring-boot-starter轻松实现高性能websocket
  • websocket 里面添加 Token

    千次阅读 2019-07-13 22:40:00
    websocket协议在握手阶段借用了HTTP的协议,我们有以下方法,添加参数: ... this.webSocket = new WebSocket(url) this.webSocket.onopen = this.webSocketOnOpen }, // 建立连接成功后的状态 webSocketOnOpe...
  • NodeJS结合express使用websocket

    千次阅读 2020-01-18 21:27:09
    NodeJS结合express使用websocket 如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 文章目录NodeJS结合express使用websocket@[toc]前言1.基本使用2.模块化开发3.如果结合 http 模块使用...
  • melody websocket 使用

    千次阅读 2017-10-07 17:15:51
    melody websocket
  • 使用webSocket

    2021-03-28 18:19:39
    建立连接 const socket = io('地址',{额外传参}) 3.监听连接状态 socket.on('connect', () => { .... }) 4.发消息 socket.emit('自定义消息名', '内容') 5.收消息:socket.on('自定义消息名', function(msg){} 6....
  • 本文需要先自行了解的两个知识点: nginx 配置文件 map 的使用方法 这个百度搜索关键字 “nginx map” 就有很多答案和示例 ...Connection: Upgrade ,这个头部是给 proxy 看的...如果只有 Upgrade: websocket,而没有
  • 近期有一个新的需求,在后台管理系统中加入售后服务人员与客户的在线聊天功能,使用的技术是vue与websocket。我第一次接触,包括从页面的创建到真正实现聊天功能,对我而言都是全新的。我想把在这一过程中,遇到的...
  • websocket探究

    2019-08-06 22:14:00
    最近需要实现个类似于饿了吗的多人分享订单的功能,前几天看了 socket.io,后端说用 websocket,百度了一下发现两者是包含的关系。 参照网上的例子:https://www.cnblogs.com/fps2tao/p/7875618.html 很快能够知道 ...
  • websocket 学习1

    2021-06-24 18:03:28
    websocket 个人总结 例如 springboot 整合 websocket 就和rocketmq 有点像,分广播、点对点 等推送方式 也分订阅、推送 一个主题 有一个订阅接口 例如 群聊功能 (1)一个发送消息到这个订阅地址的网站入口:群成员...
  • 我是基于springboot+websocket,首先,简历websocket服务端,然后在页面进行调用,通过传参告诉服务端你要在哪个聊天室聊天,等等 ,具体的 看我的实现代码。这还是最基础的,中间可以加一些消息件
  • websocket学习记录

    2021-04-20 16:10:31
    个人理解使服务器能主动向客服端发起消息 而不是一味的等待客户端的请求 用于服务器与客户端...通过new构造函数来创建websocket实例 const ws = new Websocket(url) 生命周期状态readyState 有四个取值:connec...
  • 在spring boot框架下基于jdk的原生注解实现websocket
  • uniapp使用websocket通信

    2021-03-25 12:37:43
    简要记录uni-app中使用websocket进行通信 1. 初始化 定义全局变量webSocketTask: {} onLoad()中使用uni.connectSocket建立websocket长链接 this.webSocketTask = uni.connectSocket({ url: common.wsUrl() ...
  • websocket之参数传递和依赖注入

    万次阅读 2017-03-14 10:39:00
    我在通过websocket实现这个功能的过程中遇到了2个问题:websocket建立连接时需要传递参数(例如服务器ip,项目名称,日志文件位置等)进去;需要注入service 层的类,以便在onOpen()方法中进行数据查询和业务处理...
  • python websocket 参数

    2020-09-15 10:55:00
    websocket中就有建立连接connect、发送消息send等函数可供使用,但是websocket.WebSocketApp将这些都封装好了,只用在实例化的时候传入自定义函数即可,更方便。因此这里选择使用websocket.WebSocketApp来模拟客户...
  • Swoole WebSocket初探

    2019-06-13 18:28:11
    一、什么是WebSocket WebSocket是HTML5 开始提供的一种在单个TCP连接上进行全双工通讯的协议。 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。 在WebSocket Api中,...
  • Fastapi Websocket 简明使用 1.基于Fastapi 开发websocket 服务器端 2.基于Fastapi 开发websocket 客户端 环境 github python3.7 win10 依赖 pip install fastapi[all] websocket-client -i ...
  • FastAPI WebSocket

    2020-10-20 17:30:42
    首先安装 websockets , 别...from fastapi import FastAPI, WebSocket, WebSocketDisconnect fastAPI 中的WebSocket也是基于websockets运行的 不安装websockets 前端报错:failed: Error during WebSocket handsha

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,189
精华内容 875
关键字:

websocket传参