zoukankan      html  css  js  c++  java
  • python 实现 websocket

    一、websocket概要:

      websocket是基于TCP传输层协议实现的一种标准协议(关于网络协议,可以看看文末的图片),用于在客户端和服务端双向传输数据

      传统的客户端想要知道服务端处理进度有两个途径:

      1)通过ajax不断轮询,由于http的无状态性,每次轮询服务器都需要去解析http协议,对服务器压力也很大

      2)采用long poll的方式,服务端不给客户端反馈,客户端就一直等待,服务就一直被挂起,此阶段一直是阻塞状态

      而当服务器完成升级(http-->websocket)后,上面两个问题就得到解决了:

        1)被动性,升级后,服务端可以主动推送消息给客户端,解决了轮询造成的同步延迟问题

        2)升级后,websocket只需要一次http握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析http协议,减少了资源的开销

    二、websocket通信过程:

      websocket目前基本主流浏览器都已经支持,IE10以下不支持。

      1、建立连接

        在客户端,new WebSocket实例化一个新的WebSocket客户端对象,连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL, WebSocket 客户端对象会自动解析并识别为 WebSocket 请求,从而连接服务端接口,执行双方握手过程,客户端发送数据格式类似:

        1)客户端请求报文:

    GET / HTTP/1.1
    
    Upgrade:websocket  #line1
    
    Connection:Upgrade  #line2 :与http请求报文比,多了line1和line2这两行,它告诉服务器此次发起的是websocket协议,而不是http协议了,记得要升级哦
    
    Host:example.com
    
    Origin:http://example.com
    
    Sec-WebSocket-Key:sN9cRrP/n9NdMgdcy2VJFQ==  # line3:这个是浏览器随机生成的一个base64加密值,提供基本的防护,告诉服务器,我有提供的密码的,我会做验证的,防止恶意或无意的连接
      
    Sec-WebSocket-Version:13  #line4 :告诉服务器使用的websocket版本,如果服务器不支持该版本,会返回一个Sec-WebSocket-Versionheader,里面包含服务器支持的版本号

        客户端创建websocket连接

          var ws = new websocket("ws:127.0.0.1:8000")

        完整客户端代码如下:

    <script type="text/javascript">
        var ws;
        var box = document.getElementById("box");
        
        function startWS(){
            ws = new websocket("ws:127.0.0.1:8000");
            ws.onopen = function(msg){
                console.log("websocket opened!");
            }
            ws.onomessage = function(message){
                console.log("receive message:"+message.data);
                box.insertAdjacentHTML("beforeend", "<p>"+message.data+"</p>");
            }
            ws.onerror = function(err){
                console.log("error:"+err.name+err.number);
            }
            ws.onclose = function(){
                console.log("websocket closed!")
            }
        }
    
        function sendMsg(){
            console.log("sending a message...");
            var text = document.getElementById("text");
            ws.send(text.value);
        }
    
        window.onbeforeunload = function(){
            ws.onclose = function(){}
            ws.close()
        }
    </script>
    

        2)服务端响应报文:

    HTTP/1.1 101 Switching Protocols  # 101表示服务端已经理解了客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求
    
    Upgrade:websocket
    
    Connection:Upgrade  # 这里两行是告诉浏览器,我已经成功切换协议了,协议是websocket
    
    Sec-WebSocket-Accept:HSmrc0sM1YUkAGmm50PpG2HaGwK=  #经过服务器确认并加密后的Sec-WebSocket-Key
    
    Sec-WebSocket-Protocol:chat  # 表示最终使用的协议,至此http就已经完成全部的工作,接下来就是完全按照websocket协议进行了。

         上文的Sec-WebSocket-Accept加密算法:

            a)将Sec-WebSocket-Key和258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接

            b)通过SHA1计算出摘要,并转成Base64字符串

              如token = base64.b64encode(hashlib.sha1(key+magic_str).encode("utf8").degist())

              这里在做加密之前,key一定要记得看前后有没有空白,有的话要记得去空白,不然加密的结果会一直报错不匹配,这个坑被坑了很久

            注意:此处的Sec-WebSocket-Key/Sec-WebSocket-Accept的换算,只能带来基本保障,但连接是否安全,数据是否安全,客户端 服务端是否是合法的ws客户端 ws服务端,并没有实际保证

          Sec-WebSocket-Protocol:表示最终使用的协议

    完整的服务端代码:

      1)创建websocket服务端

    import socket
    import threading
    
    global clients
    clients = {}
    
    
    class Websocket_Server(threading.Thread):
        def __init__(self, port):
            self.port = port
            super(Websocket_Server, self).__init__()
    
        def run(self):
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(("127.0.0.1", self.port))
            sock.listen(5)
            
            while(True):
           # 等待客户端连接 conn, addr
    = sock.accept() print("客户端{}连接成功:".format(addr)) conn.send(("welcome...").encode("utf8")) while(True): try: info = conn.recv(1024) connId = "ID:"+str(addr[1]) clients[connId] = conn print("{0}:{1}".format(connId, info.decode("utf8"))) except Exception as e: print(e) msg = input() conn.send(msg.encode("utf8")) if info == b"bye": print("客户端退出") conn.close() break

    上面创建了websocket服务端,通过socket.socket()创建了TCP服务对象

        接收两个参数:family和type

          family:有三种:

            AF_INET :即IPV4(默认)

            AF_INET6:即IPV6

            AF_UNIX:只能够用于单一的Unix系统进程间通信

          type:套接字类型:

            流套接字(SOCK_STREAM)(默认):只读取TCP协议的数据,用于提供面向连接,可靠的数据传输服务。该服务可以保证数据可以实现无差错无重复发送,并按序接收。之所以能够实现可靠的数据传输,原因在于使用了传输控制协议(TCP)

            数据报套接字(SOCK_DGRAM):只读取UDP协议的数据。提供了一种无连接服务,该服务并不能保证数据的可靠性。有可能在数据传输过程中出现数据丢失,错乱重复等。由于数据包套接字不能保证数据的可靠性,对于有可能出现数据丢失的情况,在程序中要做相应的处理。

            原始套接字(SOCK_RAW):原始套接字和标准套接字(上面两种)的区别是:原始套接字可以读取内核没有处理的IP数据包。而流套接字只能读取TCP协议的数据;数据包套接字只能读取UDP协议的数据

            可靠UDP形式:(SOCK_RDM),会对数据进行校验,一般不会使用

            可靠的连续数据包:(SOCK_SEQPACKET)一般也不会使用

      2)创建websocket客户端

    import socket
    import threading
    
    class Websocket_Client(threading.Thread):
        def __init__(self):
            self.host ="localhost"
            self.port = 8000
            self.address = (host, port)
            self.buffer = 1024
        
        def run():
            #创建TCP客户端程序
            tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 连接服务端
            tcp_client.connect(self.address)
            while True:
                info = tcp_client.recv(self.buffer)
                print("{}".format(str(info, encoding="utf8")))
                
                msg = input()
                tcp_client.send(msg.encode("utf8"))
                if info.lower().decode("utf8")=="bye":
                    tcp_client.close()
                    break

    完成上述代码,分别打开两个终端,运行WebSocket_Server和WebSocket_Client代码,控制台交互结果如下,可以看到已经成功实现了客户端和服务端的通信:

      3)如何监听浏览发送过来的socket连接请求?修改上面的WebSocket_Client如下:

    class WebSocket_Client():
      def __init__(self, conn, connId):
        self.connection = conn
        self.connId= connId
        super(WebSocket_Client, self).__init__()
      
      def run():
        print("new socket joined")
        # 前端传递过来的请求连接数据
        data = self.connection.recv(1024)
        try:
          headers = self.parse_header(data)
          token = self.generate_token(headers["Sec-WebSocket-Key"]).decode("utf8")
          response ="HTTP/1.1 101 Switching Protocols "
                "Connection:Upgrade "
                "Upgrade:websocket "
                "Sec-WebSocket-Accept:{0} "
                "WebSocket-Protocol:chat ".format(token)
          # 通知前端已经成功建立连接
          self.connection.send(response.encode("utf8"))
        except socket.error as e:
          print("unexpect error:", e)
          clients.pop(self.connId)
        while True:
          
    # 启动 Socket 并监听连接,使用socket中的同名方法,创建TCP服务对象 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # socket.AF_INET是指使用哪种IP地址,socket.AF_INET即使用IPv4,如果是socket.AF_INET6的话,则使用IPv6;socket.SOCKET_STREAM是指使用的套接字类型,在这里我们使用流式套接字。 try: sock.bind(("127.0.0.1",8000)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.listen(5) except Exception as e: logging.error(e) return else: logging.info(Server running...)   # 等待访问 while True: conn, addr = sock.accept() #此时会进入waiting状态 data = str(conn.recv(1024)) logging.debug(data) header_dict = {} header, _ = data.split(r" ", 1) for line in header.split(r" ")[1:]: key, val = line.split(":",1) header_dict[key] = val if "Sec-WebSocket-Key" not in header_dict: logging.error(this socket is not websocket, connection closed...) conn.close() return magic_key = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" sec_key = header_dic["Sec-WebSocket-Key"]+magic_key key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding="utf-8")).digest()) key_str = str(key)[2:30] logging.debug(key_str) response = "HTTP/1.1 101 Switching Protocols " "Connection:Upgrade " "Upgrade:websocket " "Sec-WebSocket-Accept:{0} " "WebSocket-Protocol:chat ".format(key_str) conn.send(bytes(response),encoding="utf-8") logging.debug("send the handshake data") WebsocketThread(conn).start()

        3)进行通信:

          Sever端接收到client发来的报文进行解析

          

    下面是软件通信的七层结构:

      下三层结构偏向于数据通信,上三层更偏向于数据处理,中间的传输层是上三层与下三层之间的连接桥梁,每一层做不同的工作,上层协议依赖于下层协议,上层协议需要调用下层协议的接口,下层协议使用上层协议的参数,分工合作。

      socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。属于传输控制层协议,websocket是一个典型的应用层协议。

     

    参考资料:https://www.cnblogs.com/JetpropelledSnake/p/9033064.html

            https://www.cnblogs.com/lichmama/p/3931212.html

  • 相关阅读:
    C++11学习笔记
    孙鑫MFC学习笔记20:Hook编程
    孙鑫MFC学习笔记19:动态链接库
    孙鑫MFC学习笔记18:ActiveX
    孙鑫MFC学习笔记17:进程间通信
    孙鑫MFC学习笔记16:异步套接字
    孙鑫MFC学习笔记:15多线程
    ionic2 使用slides制作滑动效果的类型选择栏
    JavaScript简明教程之Node.js
    ionic2实战-使用Chart.js
  • 原文地址:https://www.cnblogs.com/fiona-zhong/p/9556429.html
Copyright © 2011-2022 走看看