Python中Websocket的实现及基本原理

一、什么是 WebSocket ?

 

WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输。但它跟 HTTP 没什么关系,它是基于 TCP 的一种独立实现。

以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较大。另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,就一直不返回 Response 给客户端,连接阶段一直是阻塞的。

而 WebSocket 解决了 HTTP 的这几个难题。当服务器完成协议升级后( HTTP -> WebSocket ),服务端可以主动推送信息给客户端,解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。

 

 

 

 

主要使用场景:

 

没有其他能像 WebSocket 一样实现全双工传输的技术了,迄今为止,大部分开发者还是使用 Ajax 轮询来实现,但这是个不太优雅的解决办法,WebSocket 虽然用的人不多,可能是因为协议刚出来的时候有安全性的问题以及兼容的浏览器比较少,但现在都有解决。如果你有这些需求可以考虑使用 WebSocket:

 

  1. 多个用户之间进行交互;

  2. 需要频繁地向服务端请求更新数据。

 

比如弹幕、消息订阅、多玩家游戏、协同编辑、股票基金实时报价、视频会议、在线教育等需要高实时的场景。

 

**主要还是:消息推送

 

 

 

实现一个简单的聊天室程序,代码如下:

#-*- coding:utf8 -*-

import threading
import hashlib
import socket
import base64

global clients
clients = {}

#通知客户端
def notify(message):
  for connection in clients.values():
      connection.send('%c%c%s' % (0x81, len(message), message))

#客户端处理线程
class websocket_thread(threading.Thread):
  def __init__(self, connection, username):
      super(websocket_thread, self).__init__()
      self.connection = connection
      self.username = username
   
  def run(self):
      print 'new websocket client joined!'
      data = self.connection.recv(1024)
      headers = self.parse_headers(data)
      token = self.generate_token(headers['Sec-WebSocket-Key'])
      self.connection.send('\
HTTP/1.1 101 WebSocket Protocol Hybi-10\r\n\
Upgrade: WebSocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: %s\r\n\r\n' % token)
      while True:
          try:
              data = self.connection.recv(1024)
          except socket.error, e:
              print "unexpected error: ", e
              clients.pop(self.username)
              break
          data = self.parse_data(data)
          if len(data) == 0:
              continue
          message = self.username + ": " + data
          notify(message)
           
  def parse_data(self, msg):
      v = ord(msg[1]) & 0x7f
      if v == 0x7e:
          p = 4
      elif v == 0x7f:
          p = 10
      else:
          p = 2
      mask = msg[p:p+4]
      data = msg[p+4:]
      return ''.join([chr(ord(v) ^ ord(mask[k%4])) for k, v in enumerate(data)])
       
  def parse_headers(self, msg):
      headers = {}
      header, data = msg.split('\r\n\r\n', 1)
      for line in header.split('\r\n')[1:]:
          key, value = line.split(': ', 1)
          headers[key] = value
      headers['data'] = data
      return headers

  def generate_token(self, msg):
      key = msg + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
      ser_key = hashlib.sha1(key).digest()
      return base64.b64encode(ser_key)

#服务端
class websocket_server(threading.Thread):
  def __init__(self, port):
      super(websocket_server, self).__init__()
      self.port = port

  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)
      print 'websocket server started!'
      while True:
          connection, address = sock.accept()
          try:
              username = "ID" + str(address[1])
              thread = websocket_thread(connection, username)
              thread.start()
              clients[username] = connection
          except socket.timeout:
              print 'websocket connection timeout!'

if __name__ == '__main__':
  server = websocket_server(9000)
  server.start()

 

测试页面:

<!--
@http://www.cnblogs.com/zhuweisky/p/3930780.html
-->
<!DOCTYPE html>
</html>
   <head>
       <meta charset="utf-8">
   </head>
   <body>
       <h3>WebSocketTest</h3>
       <div id="login">
           <div>
               <input id="serverIP" type="text" placeholder="服务器IP" value="127.0.0.1" autofocus="autofocus" />
               <input id="serverPort" type="text" placeholder="服务器端口" value="9000" />
               <input id="btnConnect" type="button" value="连接" onclick="connect()" />
           </div>
           <div>
               <input id="sendText" type="text" placeholder="发送文本" value="I'm WebSocket Client!" />
               <input id="btnSend" type="button" value="发送" onclick="send()" />
           </div>
           <div>
               <div>
                  来自服务端的消息
               </div>
               <textarea id="txtContent" cols="50" rows="10" readonly="readonly"></textarea>
           </div>
       </div>
   </body>
   <script>
       var socket;

       function connect() {
           var host = "ws://" + $("serverIP").value + ":" + $("serverPort").value + "/"
           socket = new WebSocket(host);
           try {

               socket.onopen = function (msg) {
                   $("btnConnect").disabled = true;
                   alert("连接成功!");
              };

               socket.onmessage = function (msg) {
                   if (typeof msg.data == "string") {
                       displayContent(msg.data);
                  }
                   else {
                       alert("非文本消息");
                  }
              };

               socket.onclose = function (msg) { alert("socket closed!") };
          }
           catch (ex) {
               log(ex);
          }
      }

       function send() {
           var msg = $("sendText").value
           socket.send(msg);
      }

       window.onbeforeunload = function () {
           try {
               socket.close();
               socket = null;
          }
           catch (ex) {
          }
      };

       function $(id) { return document.getElementById(id); }

       Date.prototype.Format = function (fmt) { //author: meizz
           var o = {
               "M+": this.getMonth() + 1, //月份
               "d+": this.getDate(), //日
               "h+": this.getHours(), //小时
               "m+": this.getMinutes(), //分
               "s+": this.getSeconds(), //秒
               "q+": Math.floor((this.getMonth() + 3) / 3), //季度
               "S": this.getMilliseconds() //毫秒
          };
           if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
           for (var k in o)
               if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
           return fmt;
      }

       function displayContent(msg) {
           $("txtContent").value += "\r\n" +new Date().Format("yyyy/MM/dd hh:mm:ss")+ ": " + msg;
      }
       function onkey(event) { if (event.keyCode == 13) { send(); } }
   </script>
</html>

 

 

运行效果:

 

posted @ 2018-11-20 19:13  Legendary1  阅读(679)  评论(0编辑  收藏  举报