精华内容
下载资源
问答
  • Websocket通信

    2020-07-21 14:53:53
    Websocket + stomp ssm整合非... 虽然它们不同,但RFC 6455规定:“WebSocket设计为通过80和443端口工作,以及支持HTTP代理和中介”,从而使其与HTTP协议兼容。 为了实现兼容性,WebSocket握手使用HTTP Upgrade头...

    Websocket + stomp ssm整合非springboot

    WebSocket是一种与HTTP不同的协议。两者都位于OSI模型的应用层,并且都依赖于传输层的TCP协议。 虽然它们不同,但RFC 6455规定:“WebSocket设计为通过80和443端口工作,以及支持HTTP代理和中介”,从而使其与HTTP协议兼容。 为了实现兼容性,WebSocket握手使用HTTP Upgrade头[1]从HTTP协议更改为WebSocket协议。
    WebSocket协议支持Web浏览器(或其他客户端应用程序)与Web服务器之间的交互,具有较低的开销,便于实现客户端与服务器的实时数据传输。 服务器可以通过标准化的方式来实现,而无需客户端首先请求内容,并允许消息在保持连接打开的同时来回传递。通过这种方式,可以在客户端和服务器之间进行双向持续对话。 通信通过TCP端口80或443完成,这在防火墙阻止非Web网络连接的环境下是有益的。另外,Comet之类的技术以非标准化的方式实现了类似的双向通信。
    大多数浏览器都支持该协议,包括Google Chrome、Firefox、Safari、Microsoft Edge、Internet Explorer和Opera。
    与HTTP不同,WebSocket提供全双工通信。[2][3]此外,WebSocket还可以在TCP之上启用消息流。TCP单独处理字节流,没有固有的消息概念。 在WebSocket之前,使用Comet可以实现全双工通信。但是Comet存在TCP握手和HTTP头的开销,因此对于小消息来说效率很低。WebSocket协议旨在解决这些问题。
    WebSocket协议规范将ws(WebSocket)和wss(WebSocket Secure)定义为两个新的统一资源标识符(URI)方案[4],分别对应明文和加密连接。除了方案名称和片段ID(不支持#)之外,其余的URI组件都被定义为此URI的通用语法。[5]
    使用浏览器开发人员工具,开发人员可以检查WebSocket握手以及WebSocket框架。

    起因

    写一个小项目准备练手,找到了一个医院门诊系统,那就开搞,这个时候有一个小问题触及到了我的知识盲区,那就是即时通信。经过面向百度编程发现,有两种解决办法,一种是Ajax轮询,一种是websocket通信。

    1. Ajax轮询
      轮询是指客户端定时向服务器发送ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
      上代码
    <head>
        <title>haorooms轮询案例</title>
        <script src="js/jquery.js"></script>
    </head>
    <body>
    <script type="text/javascript">
            var getting = {
            url:'server.action',
            dataType:'json',
            	success:function(res) {
             		console.log(res);
    			}
    		};
    	//Ajax定时访问服务端,不断获取数据 ,这里是1秒请求一次。
    	window.setInterval(function(){$.ajax(getting)},1000);
    	</script>
    </body>
    </html>
    
    

    也就是说这个方式其实就是写一个定时器,每隔一段时间和后端服务器来一个连接拿到回应后就关闭。
    前一次请求完成后,无论有无结果返回,一秒之后下一次请求又会发出。这就叫做Ajax轮询。这样存在性能问题,服务器也增加很多压力。但是这就是轮询的一种实现方式了。

    长轮询

    客户端向服务器发送Ajax请求,服务器接到请求后停住住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。长轮询本身不是一种真正的推送技术,而只是传统轮询技术的一个变种。然而,其能够在真正推送技术无法实现时模拟推送机制。

    看示例

    html>
    <head>
        <title>haorooms长轮询案例</title>
        <script src="js/jquery.js"></script>
    </head>
    <body>
    <script type="text/javascript">
        //前端Ajax持续调用服务端,称为Ajax轮询技术
        var get = {
            url:'server.action',
            dataType:'json',
            success:function(res) {
             console.log(res);
             $.ajax(get); //关键在这里,回调函数内再次请求Ajax
    }        
            //当请求时间过长(默认为60秒),就再次调用ajax长轮询
            error:function(res){
            $.ajax($get);
      【;/*-
        }
    };
    $.ajax(get);
    </script>
    </body>
    </html>
    
    1. websocket
      WebSocket 是独立的、创建在 TCP 上的协议。
      我们一直使用的http协议只能由客户端发起,服务端无法直接进行推送,这就导致了如果服务端有持续的变化客户端想要获知就比较麻烦。WebSocket协议就是为了解决这个问题应运而生。
      WebSocket协议,客户端和服务端都可以主动的推送消息,可以是文本也可以是二进制数据。而且没有同源策略的限制,不存在跨域问题。协议的标识符就是ws。像https一样如果加密的话就是wxs
      来看一个简单的例子
    let ws = new WebSocket("ws://localhost:8181");
    ws.onopen = function() {
      console.log("client:打开连接");
      ws.send("client:hello,服务端");
    };
    ws.onmessage = function(e) {
      console.log("client:接收到服务端的消息 " + e.data);
      setTimeout(() => {
        ws.close();
      }, 5000);
    };
    ws.onclose = function(params) {
      console.log("client:关闭连接");
    };
    

    这就是一个简单的例子

    <!--   项目要依赖的jar包-->
    <dependency>
       <groupId>javax.websocket</groupId>
       <artifactId>javax.websocket-api</artifactId>
       <version>1.1</version>
       <scope>provided</scope>
    </dependency>
    

    编写后台代码

    后台实现websocket有两种方式:使用继承类、使用注解;注解方式比较方便,一下代码中使用注解方式来进行演示。

    声明websocket地址类似Spring MVC中的@controller注解类似,websocket使用@ServerEndpoint来进行声明接口:@ServerEndpoint(value="/websocket/{paraName}") ; 其中 “ { } ”用来表示带参数的连接,如果需要获取{}中的参数在参数列表中增加:@PathParam(“paraName”) Integer userId 。则连接地址形如:ws://localhost:8080/project-name/websocket/8,其中每个连接可以设置不同的paraName的值。

    注解、成员数据介绍:

    1.@OnOpen
    public void onOpen(Session session) throws IOException{ } -------有连接时的触发函数。 我们可以在用户连接时记录用户的连接带的参数,只需在参数列表中增加参数:@PathParam(“paraName”) String paraName。

    2.@OnClose
    public void onClose(){ } ------连接关闭时的调用方法。

    3.@OnMessage
    public void onMessage(String message, Session session) { } -------收到消息时调用的函数,其中Session是每个websocket特有的数据成员,详情见4.

    4.Session ----每个Session代表了两个web socket断点的会话;当websocket握手成功后,websocket就会提供一个打开的Session,可以通过这个Session来对另一个端点发送数据;如果Session关闭后发送数据将会报错。

    5.Session.getBasicRemote().sendText(“message”) -------向该Session连接的用户发送字符串数据。

    6.@OnError
    public void onError(Session session, Throwable error) { } --------发生意外错误时调用的函数。
    dome:

    import java.io.IOException;
    import javax.websocket.OnClose;
    import javax.websocket.OnError;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    /** 
     * @Class: Test
     * @Description: websocket demo
     * @author 老马
     */
    @ServerEndpoint(value="/websocketTest/{userId}")
    public class Test {
        private Logger logger = LoggerFactory.getLogger(Test.class);
        
        private static String userId;
        
        //连接时执行
        @OnOpen
        public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{
            this.userId = userId;
            logger.debug("新连接:{}",userId);
        }
        
        //关闭时执行
        @OnClose
        public void onClose(){
            logger.debug("连接:{} 关闭",this.userId);
        }
        
        //收到消息时执行
        @OnMessage
        public void onMessage(String message, Session session) throws IOException {
            logger.debug("收到用户{}的消息{}",this.userId,message);
            session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回复用户
        }
        
        //连接错误时执行
        @OnError
        public void onError(Session session, Throwable error){
            logger.debug("用户id为:{}的连接发送错误",this.userId);
            error.printStackTrace();
        }
    
    }
    

    从比尔

    展开全文
  • WebSocket协议实时通信技术原理

    千次阅读 2017-11-16 11:24:04
    咳咳咳~毕业设计弄了一个基于PC端和移动端的客服聊天系统,使用的核心技术是H5的WebSocket。还没找到实习,有时间整理整理下哈。HTML5 WebSocket协议实时通讯机制Ajax长轮询过程中,客户端通过频繁地向服务器发送...

    咳咳咳~毕业设计弄了一个基于PC端和移动端的客服聊天系统,使用的核心技术是H5的WebSocket。还没找到实习,有时间整理整理下哈。


    HTML5 WebSocket协议实时通讯机制

    Ajax长轮询过程中,客户端通过频繁地向服务器发送HTTP请求的方式与服务器保持这一种虚拟的连接,此连接的方式属于循环连接而不属于长连接。

    相对于HTTP协议这一非持久连接的特点来说,为避免HTTP轮询的滥用,2011年,由IETF(互联网工程任务组)制定并规范了WebSocket通信协议。

    WebSocket通信协议是HTML5支持浏览器与服务器进行多路复用全双工(Full-Duplex)通信的技术,是一个持久化协议,允许服务器主动发送信息给客户端。客户端通过JavaScript实现相应的API与服务器建立WebSocket连接后,客户端发送的Request请求信息当中不再带有请求头Head的部分信息,与Ajax长轮询通信对比,WebSocket通讯,不仅能降低服务器的压力而且保证了数据的实时传输。

    WebSocket协议实时通信技术原理

    最喜欢拆东西,解剖技术原理了。做事多问个为什么,凭什么要这样实现,这样做的作用是什么。

    WebSocket协议是基于TCP协议并遵从HTTP协议的握手规范的一种通讯协议,其通过发送连接请求,握手,验证握手信息这三个步骤与服务器建立WebSocket连接。

    这里写图片描述


    • 发送连接请求

    客户端通过一个格式为:ws://host:port/的请求地址发起WebSocket连接请求,并由JavaScript实现WebSocket API与服务器建立WebSocket连接,其中host为服务器主机IP地址或域名,port为端口。为了让本客服系统能够在不同浏览器建立WebSocket连接,在发送连接请求前,需要开启SockJS的支持,创建一个全双工的信道。

    WebSocket请求头信息:

    这里写图片描述

    相关字段说明:

    字段名 说明
    Connection:Upgrade 标识该HTTP请求是一个协议升级请求
    Upgrade: WebSocket 协议升级为WebSocket协议
    Sec-WebSocket-Version: 13 客户端支持WebSocket的版本
    Sec-WebSocket-Key:jONIMu4nFOf0iwNnc2cihg== 客户端采用base64编码的24位随机字符序列。
    Sec-WebSocket-Extensions 协议扩展类型

    HTTP协议和WebSocket协议关系图:

    这里写图片描述

    可以看出WebSocket请求是HTTP协议进行升级的,即使请求格式为ws://,其本质也是一个HTTP请求,借用了HTTP的部分设施兼容了客户端的握手规则。


    • 握手

    当服务器收到请求后,会解析请求头信息,根据升级后的协议判断该请求为WebSocket请求,并取出请求信息中的Sec-WebSocket-Key字段的数据按照某种算法重新生成一个新的字符串序列放入响应头Sec-WebSocket-Accept中。

    WebSocket服务器响应头信息:

    这里写图片描述

    相关字段说明:
    Sec-WebSocket-Accept:服务器接受客户端HTTP协议升级的证明。


    • WebSocket建立连接

    客户端接收服务器的响应后,同样会解析请求头信息,取出请求信息中的Sec-WebSocket-Accept字段,并用服务器内部处理Sec-WebSocket-Key字段的算法处理之前发送的Sec-WebSocket-Key,把处理得到的结果与Sec-WebSocket-Accept进行对比,数据相同则表示客户端与服务器成功建立WebSocket连接,反之失败。


    WebSocket通信协议的数据传输

    WebSocket通讯协议的数据传输格式是以帧的形式传输的,其帧格式如图:

    这里写图片描述

    相关字段说明:

    这里写图片描述

    根据数据帧的设计可以看出,WebSocket通讯协议是通过心跳检查PING-PONG帧来实现WebSocket长连接。当WebSocket连接建立后,PING帧和PONG帧都会不携带数据地进行来回传输,当连接发生变化时,相应数据信息会被植入PING帧,PONG帧作为响应帧返回结果。

    建立连接后,可在客户端使用JavaScript实现相关的WebSocket API。相关实现接口如下表:

    实现方式 说明
    New WebSocket(“ws://host:port/”); 发起与服务器建立WebSocket连接的对象
    websocket.onopen()=function(){} 接收连接成功建立的响应函数
    websocket.onerror()=function(){} 接收异常信息的响应函数
    websocket.onmessage()=functionm(event){} 接收服务器返回的消息函数
    websocket.onclose()=function(){} 接收连接关闭的响应函数
    sendMessage(event.data)=function(data){} 发送消息函数
    websocket.close()=function(){} 连接关闭函数

    ..

    从目前各Web技术领域来看,各大浏览器均支持WebSocket协议的实现,但WebSocket协议所支持服务器较少,如:NodeJS、Tomcat7.0等,相信未来会得到普及。


    Spring-WebSocket源码剖析

    上面已经通过解析数据包阐述了WebSocket的基本原理。由于Spring 4.0更新后直接增加了对WebSocket的支持,下文将借此通过Spring 框架提供的源代码进行分析Spring 框架是如何实现在接收到客户端的连接请求后与服务器建立连接的。

    以下代码片段摘自官方下载的源代码:spring-websocket-4.0.6.RELEASE-sources
    下载地址:https://spring.io/blog/2014/07/08/spring-framework-4-0-6-released

    (1)封装来自客户端发送的WebSocket连接请求信息:

    public class WebSocketHttpHeaders extends HttpHeaders {
    
        public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
    
        public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";
    
        public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
    
        public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
    
        public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
    
        private static final long serialVersionUID = -6644521016187828916L;
    
        private final HttpHeaders headers;
    
        public WebSocketHttpHeaders() {
            this(new HttpHeaders(), false);
        }
    
        public WebSocketHttpHeaders(HttpHeaders headers) {
            this(headers, false);
        }
    
        private WebSocketHttpHeaders(HttpHeaders headers, boolean readOnly) {
            this.headers = readOnly ? HttpHeaders.readOnlyHttpHeaders(headers) : headers;
        }
    
        public static WebSocketHttpHeaders readOnlyWebSocketHttpHeaders(WebSocketHttpHeaders headers){
            return new WebSocketHttpHeaders(headers, true);
        }
    
        ***
        **
        *
        *
    }
    

    (2)判断请求的协议类型,获取请求信息进入握手环节: 注意相关注释!!

    public abstract class AbstractWebSocketClient implements WebSocketClient {
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        private static final Set<String> specialHeaders = new HashSet<String>();
    
        static {
            specialHeaders.add("cache-control");
            specialHeaders.add("connection");
            specialHeaders.add("host");
            specialHeaders.add("sec-websocket-extensions");
            specialHeaders.add("sec-websocket-key");
            specialHeaders.add("sec-websocket-protocol");
            specialHeaders.add("sec-websocket-version");
            specialHeaders.add("pragma");
            specialHeaders.add("upgrade");
        }
    
        @Override
        public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,String uriTemplate, Object... uriVars) {..
    
        @Override
        public final ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
                WebSocketHttpHeaders headers, URI uri) {
    
            Assert.notNull(webSocketHandler, "webSocketHandler must not be null");
            Assert.notNull(uri, "uri must not be null");
    
            String scheme = uri.getScheme();
    
            /*判断协议类型*/
            Assert.isTrue(((scheme != null) && ("ws".equals(scheme) || "wss".equals(scheme))), "Invalid scheme: " + scheme);
    
            if (logger.isDebugEnabled()) {
                logger.debug("Connecting to " + uri);
    
            /*封装响应信息*/
            HttpHeaders headersToUse = new HttpHeaders();
            if (headers != null) {
                for (String header : headers.keySet()) {
                    if (!specialHeaders.contains(header.toLowerCase())) {
                        headersToUse.put(header, headers.get(header));
                    }
                }
            }
    
            List<String> subProtocols = ((headers != null) && (headers.getSecWebSocketProtocol() != null)) ?
                    headers.getSecWebSocketProtocol() : Collections.<String>emptyList();
    
            List<WebSocketExtension> extensions = ((headers != null) && (headers.getSecWebSocketExtensions() != null)) ?
                    headers.getSecWebSocketExtensions() : Collections.<WebSocketExtension>emptyList();
    
            /*进入握手环节*/
            return doHandshakeInternal(webSocketHandler, headersToUse, uri, subProtocols, extensions,
                    Collections.<String, Object>emptyMap());
        }
    
        protected abstract ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
                HttpHeaders headers, URI uri, List<String> subProtocols, List<WebSocketExtension> extensions,
                Map<String, Object> attributes);
    
    }

    (3)握手处理: 注意相关注释!!

    public class StandardWebSocketClient extends AbstractWebSocketClient {
    
        private final WebSocketContainer webSocketContainer;
    
        private AsyncListenableTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
    
        public StandardWebSocketClient() {
            this.webSocketContainer = ContainerProvider.getWebSocketContainer();
        }
    
        public StandardWebSocketClient(WebSocketContainer webSocketContainer) {
            Assert.notNull(webSocketContainer, "WebSocketContainer must not be null");
            this.webSocketContainer = webSocketContainer;
        }
    
        public void setTaskExecutor(AsyncListenableTaskExecutor taskExecutor) {..
    
        public AsyncListenableTaskExecutor getTaskExecutor() {..
    
        /*握手处理,生成连接会话句柄Session*/
        @Override
        protected ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
                HttpHeaders headers, final URI uri, List<String> protocols,
                List<WebSocketExtension> extensions, Map<String, Object> attributes) {
    
            int port = getPort(uri);
            InetSocketAddress localAddress = new InetSocketAddress(getLocalHost(), port);
            InetSocketAddress remoteAddress = new InetSocketAddress(uri.getHost(), port);
    
            final StandardWebSocketSession session = new StandardWebSocketSession(headers,
                    attributes, localAddress, remoteAddress);
    
            final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create();
            configBuilder.configurator(new StandardWebSocketClientConfigurator(headers));
            configBuilder.preferredSubprotocols(protocols);
            configBuilder.extensions(adaptExtensions(extensions));
            final Endpoint endpoint = new StandardWebSocketHandlerAdapter(webSocketHandler, session);
    
            Callable<WebSocketSession> connectTask = new Callable<WebSocketSession>() {
                @Override
                public WebSocketSession call() throws Exception {
                    webSocketContainer.connectToServer(endpoint, configBuilder.build(), uri);
                    return session;
                }
            };
    
            if (this.taskExecutor != null) {
                return this.taskExecutor.submitListenable(connectTask);
            }
            else {
                ListenableFutureTask<WebSocketSession> task = new ListenableFutureTask<WebSocketSession>(connectTask);
                task.run();
                return task;
            }
        }
    
        private static List<Extension> adaptExtensions(List<WebSocketExtension> extensions){..
    
        private InetAddress getLocalHost() {..
        private int getPort(URI uri) {..
        private class StandardWebSocketClientConfigurator extends Configurator {
            private final HttpHeaders headers;
            public StandardWebSocketClientConfigurator(HttpHeaders headers) {..
            @Override
            public void beforeRequest(Map<String, List<String>> requestHeaders) {..
            @Override
            public void afterResponse(HandshakeResponse response) {..
    
    }

    (4)建立WebSocket连接,开始通讯: 注意相关注释!!

    public abstract class ConnectionManagerSupport implements SmartLifecycle {
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        private final URI uri;
    
        private boolean autoStartup = false;
    
        private boolean isRunning = false;
    
        private int phase = Integer.MAX_VALUE;
    
        private final Object lifecycleMonitor = new Object();
    
        public ConnectionManagerSupport(String uriTemplate, Object... uriVariables) {
            this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(
                    uriVariables).encode().toUri();
        }
    
        public void setAutoStartup(boolean autoStartup) {..
        @Override
        public boolean isAutoStartup() {..
        public void setPhase(int phase) {..
        @Override
        public int getPhase() {..
        protected URI getUri() {..
        @Override
        public boolean isRunning() {..
    
        @Override
        public final void start() {
            synchronized (this.lifecycleMonitor) {
                if (!isRunning()) {
                    startInternal();
                }
            }
        }
    
        /*打开连接,synchronized处理并发连接*/
        protected void startInternal() {
            synchronized (lifecycleMonitor) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Starting " + this.getClass().getSimpleName());
                }
                this.isRunning = true;
                openConnection();
            }
        }
    
        protected abstract void openConnection();
    
        @Override
        public final void stop() {..
    
        /*关闭连接*/
        protected void stopInternal() throws Exception {
            if (isConnected()) {
                closeConnection();
            }
        }
    
        protected abstract boolean isConnected();
    
        protected abstract void closeConnection() throws Exception;
    
        @Override
        public final void stop(Runnable callback) {
            synchronized (this.lifecycleMonitor) {
                this.stop();
                callback.run();
            }
        }
    
    }

    Spring-WebSocket领域模型:

    这里写图片描述


    今天不卖奶茶了~

    展开全文
  • 服务端使用c++实现websocket协议解析及通信

    万次阅读 热门讨论 2017-01-08 16:30:54
    WebSocket 设计出来的目的就是要使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接...

           WebSocket 设计出来的目的就是要使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势。下面是一个简单 Web 应用分别用轮询方式和 WebSocket 方式来实现,下面是测试结果图:

                                                     

           通过这张图可以清楚的看出,在流量和负载增大的情况下,WebSocket 方案相比传统的 Ajax 轮询方案有极大的性能优势。
           好了不过多介绍 WebSocket 了,更多介绍大家可以点击参考资料引用的链接查看,还是回到解析协议及通信上来。解析协议这种事,就得耐着性子,一个字节一个字节解析,按步骤一点一点写程序。不过读懂了文档,知道了每个字节的属性意义后,解析起来还是挺简单的。按照协议说明,一旦完成数据解码,那么编码就稍微容易一些,差不多就是解码的逆向操作了。服务端使用c++完成 WebSocket 通信,主要需要完成以下三方面编程:
           1. 服务端与h5客户端发起的 WebSocket 连接握手:

    int wsHandshake(string &request, string &response)
    {
        // 解析http请求头信息
        int ret = WS_STATUS_UNCONNECT;
        std::istringstream stream(request.c_str());
        std::string reqType;
        std::getline(stream, reqType);
        if (reqType.substr(0, 4) != "GET ")
        {
            return ret;
        }
    
        std::string header;
        std::string::size_type pos = 0;
        std::string websocketKey;
        while (std::getline(stream, header) && header != "\r")
        {
            header.erase(header.end() - 1);
            pos = header.find(": ", 0);
            if (pos != std::string::npos)
            {
                std::string key = header.substr(0, pos);
                std::string value = header.substr(pos + 2);
                if (key == "Sec-WebSocket-Key")
                {
                    ret = WS_STATUS_CONNECT;
                    websocketKey = value;
                    break;
                }
            }
        }
    
        if (ret != WS_STATUS_CONNECT)
        {
            return ret;
        }
    
        // 填充http响应头信息
        response = "HTTP/1.1 101 Switching Protocols\r\n";
        response += "Upgrade: websocket\r\n";
        response += "Connection: upgrade\r\n";
        response += "Sec-WebSocket-Accept: ";
    
        const std::string magicKey("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
        std::string serverKey = websocketKey + magicKey;
    
        char shaHash[32];
        memset(shaHash, 0, sizeof(shaHash));
        sha1::calc(serverKey.c_str(), serverKey.size(), (unsigned char *) shaHash);
        serverKey = base64::base64_encode(std::string(shaHash)) + "\r\n\r\n";
        string strtmp(serverKey.c_str());
        response += strtmp;
    
        return ret;
    }

           2. 完成握手后连接就建立了。然后就是接收h5客户端通过 WebSocket 发过来的数据帧并解码:

    int wsDecodeFrame(string inFrame, string &outMessage)
    {
        int ret = WS_OPENING_FRAME;
        const char *frameData = inFrame.c_str();
        const int frameLength = inFrame.size();
        if (frameLength < 2)
        {
            ret = WS_ERROR_FRAME;
        }
    
        // 检查扩展位并忽略
        if ((frameData[0] & 0x70) != 0x0)
        {
            ret = WS_ERROR_FRAME;
        }
    
        // fin位: 为1表示已接收完整报文, 为0表示继续监听后续报文
        ret = (frameData[0] & 0x80);
        if ((frameData[0] & 0x80) != 0x80)
        {
            ret = WS_ERROR_FRAME;
        }
    
        // mask位, 为1表示数据被加密
        if ((frameData[1] & 0x80) != 0x80)
        {
            ret = WS_ERROR_FRAME;
        }
    
        // 操作码
        uint16_t payloadLength = 0;
        uint8_t payloadFieldExtraBytes = 0;
        uint8_t opcode = static_cast<uint8_t >(frameData[0] & 0x0f);
        if (opcode == WS_TEXT_FRAME)
        {
            // 处理utf-8编码的文本帧
            payloadLength = static_cast<uint16_t >(frameData[1] & 0x7f);
            if (payloadLength == 0x7e)
            {
                uint16_t payloadLength16b = 0;
                payloadFieldExtraBytes = 2;
                memcpy(&payloadLength16b, &frameData[2], payloadFieldExtraBytes);
                payloadLength = ntohs(payloadLength16b);
            }
            else if (payloadLength == 0x7f)
            {
                // 数据过长,暂不支持
                ret = WS_ERROR_FRAME;
            }
        }
        else if (opcode == WS_BINARY_FRAME || opcode == WS_PING_FRAME || opcode == WS_PONG_FRAME)
        {
            // 二进制/ping/pong帧暂不处理
        }
        else if (opcode == WS_CLOSING_FRAME)
        {
            ret = WS_CLOSING_FRAME;
        }
        else
        {
            ret = WS_ERROR_FRAME;
        }
    
        // 数据解码
        if ((ret != WS_ERROR_FRAME) && (payloadLength > 0))
        {
            // header: 2字节, masking key: 4字节
            const char *maskingKey = &frameData[2 + payloadFieldExtraBytes];
            char *payloadData = new char[payloadLength + 1];
            memset(payloadData, 0, payloadLength + 1);
            memcpy(payloadData, &frameData[2 + payloadFieldExtraBytes + 4], payloadLength);
            for (int i = 0; i < payloadLength; i++)
            {
                payloadData[i] = payloadData[i] ^ maskingKey[i % 4];
            }
    
            outMessage = payloadData;
            delete[] payloadData;
        }
    
        return ret;
    }

           3. 解码完数据帧,服务端做出相应处理后将结果按照 WebSocket 协议编码,然后发给h5客户端:

    int wsEncodeFrame(string inMessage, string &outFrame, enum WS_FrameType frameType)
    {
        int ret = WS_EMPTY_FRAME;
        const uint32_t messageLength = inMessage.size();
        if (messageLength > 32767)
        {
            // 暂不支持这么长的数据
            return WS_ERROR_FRAME;
        }
    
        uint8_t payloadFieldExtraBytes = (messageLength <= 0x7d) ? 0 : 2;
        // header: 2字节, mask位设置为0(不加密), 则后面的masking key无须填写, 省略4字节
        uint8_t frameHeaderSize = 2 + payloadFieldExtraBytes;
        uint8_t *frameHeader = new uint8_t[frameHeaderSize];
        memset(frameHeader, 0, frameHeaderSize);
        // fin位为1, 扩展位为0, 操作位为frameType
        frameHeader[0] = static_cast<uint8_t>(0x80 | frameType);
    
        // 填充数据长度
        if (messageLength <= 0x7d)
        {
            frameHeader[1] = static_cast<uint8_t>(messageLength);
        }
        else
        {
            frameHeader[1] = 0x7e;
            uint16_t len = htons(messageLength);
            memcpy(&frameHeader[2], &len, payloadFieldExtraBytes);
        }
    
        // 填充数据
        uint32_t frameSize = frameHeaderSize + messageLength;
        char *frame = new char[frameSize + 1];
        memcpy(frame, frameHeader, frameHeaderSize);
        memcpy(frame + frameHeaderSize, inMessage.c_str(), messageLength);
        frame[frameSize] = '\0';
        outFrame = frame;
    
        delete[] frame;
        delete[] frameHeader;
        return ret;
    }

           4. 握手只需一次,随后反复执行第2步及第3步,就完成了服务端与h5客户端通信。这个只是c++版本的,可以很容易改成java版本的。下面是上述方法用到的一些枚举:

    enum WS_Status
    {
        WS_STATUS_CONNECT = 0,
        WS_STATUS_UNCONNECT = 1,
    };
    
    enum WS_FrameType
    {
        WS_EMPTY_FRAME = 0xF0,
        WS_ERROR_FRAME = 0xF1,
        WS_TEXT_FRAME   = 0x01,
        WS_BINARY_FRAME = 0x02,
        WS_PING_FRAME = 0x09,
        WS_PONG_FRAME = 0x0A,
        WS_OPENING_FRAME = 0xF3,
        WS_CLOSING_FRAME = 0x08
    };


           参考资料:



    展开全文
  • MQTT vs webSocket协议

    千次阅读 2019-09-10 17:33:55
    modular-2 edge 设计了自己的消息系统base service ,它采纳了websocket协议。为什么选择websocket? 主要是基于如下考虑: 在一个边缘设备中,消息系统需要解决两方面的通信: app 和web 网页之间的...

    边缘服务器采用了容器和微服务架构,其中重要的一个方面就是要选择一个高效率的消息系统,用于微服务之间的消息交换。

    为什么选择websocket 协议

     modular-2 edge 设计了自己的消息系统base service ,它采纳了websocket协议。为什么选择websocket? 主要是基于如下考虑:

    在一个边缘设备中,消息系统需要解决两方面的通信:

    1. app 和web 网页之间的双向通信
    2. app 和base service 之间的双向通信

    websocket是建立在tcp 协议上的,由http 协议通过升级方式建立websocket。所有一般说来,websocket 的传输效率肯定没有tcp 高。

      但是,在边缘设备中,用户界面是通过web 网页实现的,在HTML5的控制面板上实时显示数据为了避免不断地polling ,需要一种双向通信协议,在web 服务器中比较多的是采用socket.io,而socket.io 正是基于websocket的 .

    ,baseservice 统一采用了websocket 作为消息系统的协议.

    为什么不用MQTT?

    同事向我提议”为什么又不采用MQTT协议呢?”,我也知道许多的边缘设备采纳MQTT作为消息总线.而且云端应用中也使用基于pub/sub 的消息系统.毕竟采纳现成的系统会可靠一点。网络上对MQTT的处理能力也大势宣传。

    于是,我花了一段时间测试了基于MQTT 消息系统的微服务通信。

    IO 模块(我们称为microserver) 上使用的paho MQTT embedded C client。MQTT 代理使用mosquitto 。App 采用C++ paho MQTT client 代码。

    测试的结果表明 MQTT 对于短小,低速的IOT 消息交换是可行的,但是如果是高速的大数据流,MQTT 的传输速度不快,我测试的结果是网络传输速率大约2 Mbps 左右.而且mosquitto 占用CPU 的时间也比较大.

    也许MQTT代理 对于巨量客户端的消息交换又不俗的表现,但是对于少量,实时高速数据而言,好像没那么好.

    想想也是,MQTT 可以在websocket 上传输,实际上如果在web 网页中使用MQTT 的话,必需使用MQTT over websocket 方式(9100端口)。这时,MQTT和websocket 的关系就好比苹果和篮子的关系了。

    二进制编码的UDP 效率最高

    在baseservice 开发的早期,IO模块采用了UDP 协议,那是传输效率最高的.后来由于基于二进制编码的UDP 通信编程容易出错.我们放弃了.如果需要的化,还可以回到UDP.

    疑问

    kafka 这样的消息系统在小型边缘设备中的表现会是什么样的呢?

    网络上,有老外说,MQTT 最好,不到万不得已,不要使用websocket ,但是许多事情需要自己实验了,才指定是否合适.当然不同场景,也各有不同.对于低速iot 而言,MQTT 是好的选择.

     

    展开全文
  • WebSocket API是下一代客户端-服务器的异步通信方法。该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序。WebSocket目前由W3C进行标准化。WebSocket已经受到Firefox 4、Chrome 4、...
  • WebSocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信问题而设计的。协议定义ws和wss协议,分别为普通请求和基于SSL的安全传输, ws端口是80,wss的端口为443. WebSocket协议由两部分组成,握手和...
  • 通过WebSocket与HDL模拟通信协议 背景 所有芯片设计不可避免地都有某种输入和输出到被测设备之外的世界。 可以以不同的方式为不同的目的对该I / O进行建模。 对于回归测试,这通常采取预定义刺激的形式发送到设备...
  • websocket全双工协议

    2017-04-18 16:05:00
    websock简介 WebSocket协议是基于TCP的一种新...http协议设计之初,由于考虑到服务端如果能主动地让浏览器端推送广告的情况,所以我们现在所使用的http协议并不能由服务器端主动地向浏览器端发送数据。 发展与需求的...
  • Websocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信的问题而设计的,是完全意义上的Web应用端的双向通信技术,可以取代之前使用半双工HTTP协议而模拟全双工通信,同时克服了带宽和访问速度等的...
  • socket.io 是基于 WebSocket 的 C-S 实时通信库,底层是 engine.io,这个库实现了跨平台的双向通信。 engine.io 使用了 WebSocket 和 XMLHttprequest(或JSONP) 封装了一套自己的 Socket 协议(暂时叫 EIO Socket)...
  • websocket

    2019-10-07 16:26:13
    websocket是一个协议,在单个TCP连接上提供全双工通信. websocket设计并被实现在 web浏览器和 web 服务器上,但是它可以被用于任何c/s 架构的应用程序中。  websocket是基于tcp协议,而它与HTTP唯一的关联就是:...
  • 长轮询,WebSocket和服务器发送事件是客户端和服务器(如Web浏览器和Web服务器)之间流行的通信协议。首先,让我们了解标准HTTP Web请求。以下是常规HTTP请求的一系列事件: 客户端打开连接并从服务器请求数据。 服
  • WebSocket

    2019-03-28 04:02:06
    WebSocket设计用来实现单个TCP连接上全双工通信协议。(通俗一点说,就是建立一个TCP连接后,客户端和服务端都可以主动向对端发送数据)。WebSocket是HTML5中定义的,主要用于网页请求。 WebSocket草案中文版...
  • WebSocket 协议提供了通过一个套接字实现全双工通信的功能。一次连接以后,会建立tcp连接,后续客户端与服务器交互为全双工方式的交互方式,客户端可以发送消息到服务端,服务端也可将消息发送给客户端。 ...
  • websocket介绍

    2019-01-10 17:58:00
    Websocket是一个因为应用场景越来越复杂而提出的,针对浏览器和web服务器之间双向持续通信设计,而且优雅地兼容HTTP的协议WebSocket 实际上指的是一种协议,与我们熟知的Http协议是同等的一个网络协议。用网络...
  • WebSocket 帮助

    2015-07-13 16:00:57
    WebSocket Support原文译:WebSocket 帮助damon2014.9.2921.WebSocket帮助这部分参考文档涉及了spring框架对WebSocket类型在网络通信应用中的支持,其中包含了WebSocket协议的应用层子协议STOMP(流文本定向消息协议...
  • 1 课程设计目的和任务 ... 2 课程设计的主要内容 实现图形界面 实现一个聊天室中多人聊天 实现发送图片和表情的功能 3 相关技术介绍 1.websocket: WebSocket是一种在单个TCP连接上进行全双工通信...WebSocket通信协议...
  • 近来要设计开发一个聊天功能服务端,同时要支持app和和微信小程序,因为微信小程序通信只支持websocket,并且app也能支持websocket协议通信,所以技术方案选择java websocket + activemq。 之前没有接触过通信方面...
  •  WebSocket是基于TCP协议,实现单个连接上的双向通信。  本章内容包括: 异步读写字符串和二进制数据、选择连接丢失策略、何时使用WebSocket。 2. 主要内容  2.1异步读写字符串和二进制数据  * ...
  • Html5中引入了websocket这一全双工通信协议,利用此技术实现的方案能够在减轻服务器负担的同时高效地对服务器消息进行实时推送。使用node.js平台和socket.io库技术设计并实现了一种基于websocket协议的服务器实时...
  • WebSocket备忘

    2019-02-24 00:12:27
    WebSocket备忘 ...HTTP不允许服务端主动向client发送数据,websocket设计之初着重考虑了这个问题,所以websocket支持全双工通信,服务端可以主动给客户端推送消息 传输更有效率 HTTP经常需...
  • WebSocket 学习

    2019-06-10 03:01:12
    项目中之前已经使用过 websocket 进行一些和服务器的实时数据通信,但是对于协议本身并不十分了解,也是借此机会学习一下并分享出来。 OSI 位置? 应用层,和 Http 协议是同级关系 为什么需要 WebSocket ? 既然同 ...
  • 与此同时随着互联网的发展在HTML5中提出了websocket协议,能更好的节省服务器资源和带宽并且服务器和浏览器能够双向实时通讯。为了能让用户体验传统客户端和web带来的即时通信结合的超爽体验,本次毕业设计将完成web...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 157
精华内容 62
关键字:

websocket通信协议设计