资讯专栏INFORMATION COLUMN

在tornado中使用tcpserver和tcpclient实现echo服务器

liukai90 / 1839人阅读

摘要:本文主要介绍了在框架中使用实现简单服务器的过程。在网络通信中,需要发送二进制流数据函数负责数据组包,即将数据按照规定的传输协议组合起来函数负责数据拆包,即按照规定的协议将数据拆分开来。不多说,具体实现代码咱们来看一下。

本文主要介绍了在tornado框架中,使用tcpserver,tcpclient,struct.pack(),struct.unpack实现简单echo服务器的过程。

在网络通信中,需要发送二进制流数据;struct.pack()函数负责数据组包,即将数据按照规定的传输协议组合起来;struct.unpack()函数负责数据拆包,即按照规定的协议将数据拆分开来。

不多说,具体实现代码咱们来看一下。

tcp客户端代码如下:

# coding=utf-8


import struct
import logging

from tornado import ioloop, gen
from tornado.tcpclient import TCPClient


"""
tcpclient-struct.pack()组包
发送数据包格式:消息头+消息体
消息头:消息发送者(4字节)+消息接收者(4字节)+消息类型(1字节)+消息体中数据长度(4字节)
消息体:待发送数据
struct.unpack()拆包
接收数据包格式:消息头+消息体
消息头:消息发送者(4字节)+消息类型(1字节)+消息体中数据长度(4字节)
消息体:待接收数据
"""


logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


class ChatClient(object):
    def __init__(self, host, port):
        self.host = host
        self.port = port

    @gen.coroutine
    def start(self):
        self.stream = yield TCPClient().connect(self.host, self.port)
        while True:
            yield self.send_message()
            yield self.receive_message()

    @gen.coroutine
    def send_message(self):
        # 待发送数据
        msg = input("输入:")
        bytes_msg = bytes(msg.encode("utf-8"))
        # 消息发送者
        chat_id = 10000000
        # 消息接收者
        receive_id = 10000001
        # 消息类型 1-文本 2-图片 3-语音 4-视频 等
        msg_type = 1

        binary_msg = struct.pack("!IIBI"+str(len(msg))+"s", chat_id, receive_id, msg_type, len(msg), bytes_msg)
        # 发送数据
        yield self.stream.write(binary_msg)

    @gen.coroutine
    def receive_message(self):
        """
        接收数据
        :return:
        """
        try:
            logger.debug("receive data ...")
            # 消息发送者 4字节
            sender = yield self.stream.read_bytes(4, partial=True)
            sender = struct.unpack("!I", sender)[0]
            logger.debug("sender:%s", sender)

            # 消息类型 1字节
            msg_type = yield self.stream.read_bytes(1, partial=True)
            msg_type = struct.unpack("!B", msg_type)[0]
            logger.debug("msg_type:%s", msg_type)

            # 消息长度 4字节
            msg_len = yield self.stream.read_bytes(4, partial=True)
            msg_len = struct.unpack("!I", msg_len)[0]
            logger.debug("msg_len:%s", msg_len)

            # 真实数据
            data = yield self.stream.read_bytes(msg_len, partial=True)
            data = struct.unpack("!" + str(msg_len) + "s", data)
            logger.debug("data:%s", data)
        except Exception as e:
            logger.error("tcp client exception:%s", e)


def main():
    c1 = ChatClient("127.0.0.1", 8888)
    c1.start()
    ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()
    
    
    

tcp服务端代码:

# coding=utf-8


import struct
import logging

from tornado.tcpserver import TCPServer
from tornado.netutil import bind_sockets
from tornado.iostream import StreamClosedError
from tornado import gen
from tornado.ioloop import IOLoop


"""
tcpserver-struct.unpack()拆包
接收数据包格式:消息头+消息体
消息头:消息发送者(4字节)+消息接收者(4字节)+消息类型(1字节)+消息体中数据长度(4字节)
消息体:待接收数据
struct.pack()组包
转发数据包格式:消息头+消息体
消息头:消息发送者(4字节)+消息类型(1字节)+消息体中数据长度(4字节)
消息体:待发送数据
"""


logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


class ChatServer(TCPServer):

    PORT = 8888
    clients = dict()

    @gen.coroutine
    def handle_stream(self, stream, address):
        """
        数据拆包并解析
        :param stream:
        :param address:
        :return:
        """
        logger.debug("%s已上线", address)
        ChatServer.clients[address] = stream
        while True:
            try:
                # !表示使用大端方式解析数据
                # 消息发送者 4字节
                sender = yield stream.read_bytes(4, partial=True)
                sender = struct.unpack("!I", sender)[0]
                logger.debug("sender:%s", sender)

                # 消息接收者 4字节
                receiver = yield stream.read_bytes(4, partial=True)
                receiver = struct.unpack("!I", receiver)[0]
                logger.debug("receiver:%s", receiver)

                # 消息类型 1字节
                msg_type = yield stream.read_bytes(1, partial=True)
                msg_type = struct.unpack("!B", msg_type)[0]
                logger.debug("msg_type:%s", msg_type)

                # 消息长度 4字节
                msg_len = yield stream.read_bytes(4, partial=True)
                msg_len = struct.unpack("!I", msg_len)[0]
                logger.debug("msg_len:%s", msg_len)

                if msg_type == 1:
                    # 文本信息处理
                    logger.debug("text message ...")
                    self.handle_text_stream(stream, sender, msg_len)
                elif msg_type == 2:
                    logger.debug("picture message ...")
                    self.handle_pic_stream(stream, sender, msg_len)

            except StreamClosedError:
                logger.debug("%s已下线", address)
                del ChatServer.clients[address]
                break

    @gen.coroutine
    def handle_text_stream(self, stream, sender, msg_len):
        """
        处理文本数据
        :param stream:
        :param send_to:
        :param msg_len:
        :return:
        """
        data = yield stream.read_bytes(msg_len, partial=True)
        data = struct.unpack("!"+str(msg_len)+"s", data)
        logger.debug("data:%s", data)
        try:
            # 打包数据,数据格式:数据发送者+数据类型+数据长度+数据体
            binary_msg = struct.pack("!IBI" + str(msg_len) + "s", sender, 1, msg_len, data[0])
            # 发送数据
            yield stream.write(binary_msg)
            logger.debug("="*25)
        except KeyError:
            # 将离线消息保存到数据库
            pass

    @gen.coroutine
    def handle_pic_stream(self, stream, sender, msg_len):
        pass


if __name__ == "__main__":
    sockets = bind_sockets(ChatServer.PORT)
    server = ChatServer()
    server.add_sockets(sockets)
    IOLoop.current().start()
    

以上就是具体的代码实现,如有错误,欢迎大家与我交流指正,谢谢!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44741.html

相关文章

  • django开发-djangotornado的不同

    摘要:中常用的几个框架有等,今天来总结一下和的不同。本文使用的环境是。文件可以加载路由信息和项目配置信息,文件负责启动项目。以上就简单的比较了和几个方面的不同,它们各有优缺点,实际工作中可以根据不同的需求选择不同的框架进行开发。 python中常用的几个web框架有django, tornado, flask等,今天来总结一下django和tornado的不同。工作中django和torna...

    Reducto 评论0 收藏0
  • #网络编程

    摘要:网络编程网络编程有常见的链接面向连接的,就像打电话必须要一来一往的做出回应是不面向链接的,不需要做出回应这是一个简单的代码例子输入你的信息编程这是客户端发送的信息 网络编程 2017-07-12 18:51:50 bloggithub网络编程有常见的tcp,udp 链接 tcp 面向连接的,就像打电话必须要一来一往的做出回应 udp 是不面向链接的, 不需要做出回应 这是一个简单的tc...

    winterdawn 评论0 收藏0
  • tornado 源码分析 之 异步io的实现方式

    摘要:前言本文将尝试详细的带大家一步步走完一个异步操作从而了解是如何实现异步的其实本文是对上一篇文的实践和复习主旨在于关注异步的实现所以会忽略掉代码中的一些异常处理文字较多凑合下吧接下来只会贴出部分源码帮助理解希望有耐心的同学打开源码一起跟踪一遍 前言 本文将尝试详细的带大家一步步走完一个异步操作,从而了解tornado是如何实现异步io的. 其实本文是对[上一篇文][1]的实践和复习 主...

    xiangzhihong 评论0 收藏0
  • 1、网络三要素及传输协议 2、实现UDP协议的发送端接收端 3、实现TCP协议的客户端务器 4

    摘要:应用层主要负责应用程序的协议,例如协议协议等。在计算机中,不同的应用程序是通过端口号区分的。区别在于,中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。 01网络模型 *A:网络模型 TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。 链路层:链路层是用于定义物理传输通道,通常是对...

    CastlePeaK 评论0 收藏0
  • Tornado 4.3文档翻译: HTTP 服务客户端-非阻塞 HTTP server

    摘要:译者说于年月日发布,该版本正式支持的关键字,并且用旧版本编译同样可以使用这两个关键字,这无疑是一种进步。原谅我没排好版非阻塞非阻塞,单线程。其旧名称仍作为一个别名。非阻塞,单线程。要使可以服务于加密的流量,需要把参数设置为一个对象。 译者说 Tornado 4.3于2015年11月6日发布,该版本正式支持Python3.5的async/await关键字,并且用旧版本CPython编译T...

    tianyu 评论0 收藏0

发表评论

0条评论

liukai90

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<