基于WebSocket的网页(JS)与服务器(Python)数据交互

May 5,2018   6170 words   23 min

Tags: Web

在之前的博客中,通过Socket简单实现了Android手机与电脑之间的数据传输,博客在这里。 但某种程度上来说是实现了手机姿态数据到电脑的传输,电脑并没有发送什么实际的数据给手机。 因此在这篇博客中以更广泛的使用场景,也即网页与服务器的数据交互为目标,学习WebSocket的使用。 实现一个完整的数据交互流程,即网页端发送数据到服务器,服务器接收数据并进行处理,并将处理后的结果重新返回给网页。

1.WebSocket介绍与原理

(1)简介

WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。WebSocket协议在2008年诞生,2011年成为国际标准。

它的最大特点就是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。 协议标识符是ws(如果加密则为wss),服务器网址就是URL,如下实例地址。

ws://127.0.0.0:4200
(2)实现原理

WebSocket协议本质上是一个基于TCP的协议。 为了建立一个WebSocket连接,客户端浏览器首先向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加头信息”Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的WebSocket连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务器端就可以通过TCP连接直接交换数据。 当你获取Web Socket连接后,可以通过send()方法来向服务器发送数据,并通过onmessage事件接收服务器返回的数据。

2.代码与实现

(1)客户端

在客户端(这里指网页)实现WebSocket很容易,JavaScript本身就支持,也不需要额外引入新的文件。

在JS中WebSocket对象有如下属性:

  • Socket.readyState:表示连接状态,可以是以下值: 0 - 表示连接尚未建立; 1 - 表示连接已建立,可以进行通信; 2 - 表示连接正在进行关闭; 3 - 表示连接已经关闭或者连接不能打开;
  • Socket.bufferedAmount:已被send()放入正在队列中等待传输,但是还没有发出的UTF-8文本字节数。

WebSocket对象有如下事件:

  • Socket.onopen:连接建立时触发
  • Socket.onmessage:客户端接收服务端数据时触发
  • Socket.onerror:通信发生错误时触发
  • Socket.onclose:连接关闭时触发

WebSocket对象有如下方法:

  • Socket.send():使用连接发送数据
  • Socket.close():关闭连接

完整使用WebSocket的JS代码如下。

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>WebSocket网页端JS示例</title>

    <script type="text/javascript">
        function WebSocketTest() {
            // 新建WebSocket连接
            var ws = new WebSocket("ws://localhost:4200");

            // 连接打开事件,打开连接后发送数据
            ws.onopen = function () {
                // 使用send()方法发送数据
                ws.send("这是测试数据");
                alert("数据发送中:\n这是测试数据");
            };

            // 接收数据事件,event的data就是返回数据
            ws.onmessage = function (evt) {
                var received_msg = evt.data;
                alert("数据已接收:\n" + received_msg);
                // 作为一个好习惯,在接收完数据后就关闭连接,这样可以减少服务器的负担
                ws.close();
            };

            // 关闭连接后要做的事
            ws.onclose = function () {
                alert("连接已关闭...");
            };
        }
    </script>
</head>
<body>
<div id="sse">
    <a href="javascript:WebSocketTest()">运行 WebSocket</a>
</div>
</body>
</html>

上面的代码便是JS使用WebSocket的全部流程了。从打开一个WebSocket连接到发送数据、接收数据最后关闭连接。正如前面说的WebSocket连接默认不会自动关闭的(如果长时间无动作或者客户端被关闭连接会关闭),因此在接收完数据后手动关闭是个好习惯,可以减少服务器的负担。演示效果如下。

(2)服务器端

对于服务器端,有多种实现WebSocket的方式,包括别人博客里推荐的WebSocketd、pywebsocket等等,这里使用基于Python的python-websocket-serverl来实现,Github地址在这里。之所以选择这个库,是在比较了几个之后觉得这个用起来最简单(当然功能可能相对也会少一些,不过满足目前的需求了)。下面会介绍。

Python WebSocket Server安装起来很简单,或者说不用安装,直接拷贝Github项目中的websocket_server文件夹到你需要用的地方,然后在python脚本中import就可以了。

而它的使用也很简单,直接运行Python脚本即可。下面这个脚本演示和说明了Python WebSocket Server的使用。更多函数文档可以看项目的Github主页。

# coding=utf-8
import logging
from websocket_server import WebsocketServer
import sys

# 因为考虑到传入的字符串有非英文字符,
# 所以手动设置编码,否则可能会报编码错误
reload(sys)
sys.setdefaultencoding('utf-8')


def new_client(client, server):
    print("Client(%d) has joined." % client['id'])


def client_left(client, server):
    print("Client(%d) disconnected" % client['id'])


def message_back(client, server, message):
    # 这里的message参数就是客户端传进来的内容
    print("Client(%d) said: %s" % (client['id'], message))
    # 这里可以对message进行各种处理
    result = "服务器已经收到消息了..." + message
    # 将处理后的数据再返回给客户端
    server.send_message(client, result)


# 新建一个WebsocketServer对象,第一个参数是端口号,第二个参数是host
# 如果host为空,则默认为本机IP
server = WebsocketServer(4200, host='', loglevel=logging.INFO)
# 设置当有新客户端接入时的动作
server.set_fn_new_client(new_client)
# 设置当有客户端断开时的动作
server.set_fn_client_left(client_left)
# 设置当接收到某个客户端发送的消息后的操作
server.set_fn_message_received(message_back)
# 设置服务一直运行
server.run_forever()

一直运行这个脚本则WebSocket服务就会一直运行。下面是利用刚刚上面的JS代码进行通信的服务器端展示。 这样便完成了数据交互的整个回路(数据发送-数据处理-数据返回)。

3.模拟登陆实例

下面通过一个稍微复杂点的实例来演示WebSocket。主要内容是在网页上有用户名和密码两个输入框,用户输入信息,点击按钮登陆。网页将数据发送到服务器,服务器接收到数据后,判断是否和已有的信息相同(这里只用字符串比较,不用数据库)。如果相同返回登陆成功,否则返回登陆失败。

(1)客户端
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>模拟登陆实例</title>

    <script type="text/javascript">
        function sendLoginInfo() {

            var input_username = document.getElementById("input_username");
            var input_password = document.getElementById("input_password");

            var username = input_username.value;
            var password = input_password.value;

            var loginInfo = username + "*****" + password;

            // 打开连接
            var ws = new WebSocket("ws://localhost:4200");

            // 发送数据
            ws.onopen = function () {
                // 使用send()方法发送数据
                ws.send(loginInfo);
            };

            // 接收数据
            ws.onmessage = function (evt) {
                var received_msg = evt.data;
                alert(received_msg);
                // 作为一个好习惯,在接收完数据后就关闭连接,这样可以减少服务器的负担
                ws.close();
            };

            // 关闭连接后的事件
            ws.onclose = function () {
            };
        }
    </script>
</head>
<body>
<div id="sse">
    <table>
        <tr>
            <td>用户名</td>
            <td><input type="text" id="input_username" style="width:200px"></td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" id="input_password" style="width:200px"></td>
        </tr>
    </table>
    <input id="btn_login" type="button" value="登陆" onclick="sendLoginInfo()" style="width:270px;">
</div>
</body>
</html>
(2)服务器端
# coding=utf-8
import logging
from websocket_server import WebsocketServer
import sys

# 因为考虑到传入的字符串有非英文字符,
# 所以手动设置编码,否则可能会报编码错误
reload(sys)
sys.setdefaultencoding('utf-8')


def new_client(client, server):
    print("Client(%d) has joined." % client['id'])


def client_left(client, server):
    print("Client(%d) disconnected" % client['id'])


def message_back(client, server, message):
    # 这里的message参数就是客户端传进来的内容
    print("Client(%d) said: %s" % (client['id'], message))
    # 这里可以对message进行各种处理
    result = handle_login(message)
    # 将处理后的数据再返回给客户端
    server.send_message(client, result)


def handle_login(text):
    # 这里使用5个星号作为数据间的分隔符
    # 当然在JS中也可以使用更高级的JSON字符串
    # 但这里为了简单就没有拼接
    username = text.split('*****')[0]
    password = text.split('*****')[1]
    if username == 'XUHUI' and password == '123456':
        login_res = "Login success!\nWelcome my master " + username + ". ๑乛◡乛๑"
    else:
        login_res = "Login fail!\nYou are not my master.(..•˘_˘•..)"
    return login_res


server = WebsocketServer(4200, host='', loglevel=logging.INFO)
server.set_fn_new_client(new_client)
server.set_fn_client_left(client_left)
server.set_fn_message_received(message_back)
server.run_forever()
(3)测试运行

运行效果如下。正确的用户名和密码是XUHUI,123456。从客户端看的结果如下。当输入的信息和服务器里的不匹配时,提示登录失败,否则提示登录成功。 从服务器端看的结果如下。服务器成功接收了两次连接传过来的数据,并进行了处理。

4.参考资料

  • https://www.runoob.com/html/html5-websocket.html
  • https://www.ruanyifeng.com/blog/2017/05/websocket.html
  • https://github.com/Pithikos/python-websocket-server
  • https://blog.csdn.net/hanglinux/article/details/44961819

本文作者原创,未经许可不得转载,谢谢配合

返回顶部