一、什么是 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>

运行效果:

最新文章

  1. js性能优化-事件委托
  2. Bootstrap&lt;基础十五&gt; 输入框组
  3. word中那些重要但是被人忽略的快捷键和长word文档的跳转
  4. C#.NET如何不序列化字段、属性
  5. 公网IP、私网IP
  6. bzoj4042
  7. 写漂亮C#代码的小技巧
  8. UVA 10041 (13.08.25)
  9. KingPaper初探 wamp下本地虚拟主机的搭建
  10. MySql - JdbcType - Oracle类型映射
  11. LeetCode 448. Find All Numbers Disappeared in an Array (在数组中找到没有出现的数字)
  12. VxWorks 引导程序
  13. Herriot
  14. 盘点 Python 中的那些冷知识(二)
  15. 按下回车默认提交form表单问题
  16. Introduction to CELP Coding
  17. ZOJ 3987 Numbers(Java枚举)
  18. git回滚到某个版本操作
  19. eclipse中tomcat启动设置参数
  20. java 获取浏览器类型

热门文章

  1. 【Dubbo3终极特性】「流量治理体系」一文教你如何通过Dubbo-Admin实现动态进行流量隔离机制
  2. 算法竞赛向 C++ Standard Library 使用速查
  3. SpringBoot项目动态定时任务之 ScheduledTaskRegistrar(解决方案一)
  4. 内存概述-java虚拟机的内存划分
  5. (Newtonsoft)Json增删改查
  6. Jetpack Compose学习(10)——使用Compose物料清单BOM,更好管理依赖版本
  7. Archiver Appliance bug report(问题已解决,见文末)
  8. 来了!来了!国内使用chatGPT的方式总结
  9. TNF拮抗剂的结构、功能与结核感染_Wallis2008
  10. CCRD_TOC_2007年11月_总第12期