• socket编程(python)


    交互原理:

    服务端和客户端通过底层socket接口编程通信,交互的信息都是通过byte字节形式传递,网络传输中不能保证信息完整传输有可能是分片传输,所以可能从缓冲区获取的信息需要分段拼接或拆分组合成一段段完整的信息读取;现在为了避免信息不完整,一般是通过给信息加上一个头部信息(一般存储了对信息长度和规范的描述)进行判断

    题目:实现一个简单的客服聊天系统

    客服中心是tcp服务端程序,客户使用tcp客户端程序。 
    连接成功后, 客户端发送给服务端第一个消息必须告诉客服中心用户的名字
    一个客户连接后,别的客户不能连接, 等到前面的客户断开连接后,才能连上。
    客户端和服务端都是手动在终端输入信息,发送消息后,必须等待接受到对方的消息才能发送下一个消息。
    我们定义消息的格式如下:
    0008|1|nickname,用竖线隔开3部分的字段,分别表示 消息长度、 消息类型 、消息内容
    
    前面字段是4个字节的字符串,比如'0008',其内容是数字,表示消息的长度, 不足4个字节前面补零。
    后面用竖线隔开的字段,是1个字节的字符串,是消息类型,其内容是数字,1表示客户昵称, 2表示普通消息
    
    前面两个字段合起来 0008|1|,可以看成是一个消息的头部,nickname 是消息体
    再后面用竖线隔开的字段是消息内容,其长度等于前面消息长度字段指明的长度减去消息头部长度(也就是7个字节

    解答:

    服务端Server:

    # coding=utf8
    import sys
    from socket import socket,AF_INET,SOCK_STREAM
    
    HOST = ''
    PORT = 21567
    BUFSIZ = 1024
    ADDR = (HOST, PORT)
    
    class CloseSocketError(Exception):
        pass
    
    # 一个 ConnectionHandler 处理和一个客户端的连接
    class ConnectionHandler:
        # 0008|1|nickname
        LEN_MSG_LEN_FIELD = 4
        LEN_MSG_LEN_TYPE_FIELD = 7
    
        def __init__(self,sock):
            # 消息缓存区
            self._readbuffer = b''
            self.sock = sock
            self.customername = ''
    
        # msgBody 是 unicode
        @staticmethod
        def encode(msgType,msgBody):
            rawMsgBody = msgBody.encode('utf8')
    
            msgLenth = '{:04}' 
            .format(len(rawMsgBody)+ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD) 
            .encode()
    
            msgType = f'{msgType}'.encode()
            return b'|'.join([msgLenth,msgType,rawMsgBody])
    
        @staticmethod
        def decode(rawmsg):
            msgType = int(rawmsg[5:6])  # 这样写rawmsg[5] 返回的是字节对应的数字
            msgbody = rawmsg[ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD:].decode('utf8')
            return [msgType,msgbody]
    
        def readMsg(self):
            bytes = self.sock.recv(BUFSIZ)
    
            # ** 用不同的返回值表示不同的含义
    
            # 当对方关闭连接的时候,抛出异常
            if not bytes:
                self.sock.close()
                raise CloseSocketError()
    
            # 应用程序的读取缓冲,和前面讲的系统的读取缓冲是两个不同的缓冲
            self._readbuffer += bytes
    
            buffLen = len(self._readbuffer)
    
            # 如果已经获取了消息头部 (包括 消息长度,消息类型)
            if buffLen >= self.LEN_MSG_LEN_TYPE_FIELD:
                msgLen = int(self._readbuffer[:self.LEN_MSG_LEN_FIELD])
                # 缓存区消息 已经包含了一个整体的消息(包括 消息长度,消息类型,消息体)
                if buffLen >= msgLen:
                    # 从缓存区,截取整个消息
                    msg = self._readbuffer[0:msgLen]
                    # 缓存区变成剩余的消息部分
                    self._readbuffer = self._readbuffer[msgLen:]
    
                    return self.decode(msg)
    
            # 如果已经获取的消息还不包括一个完整的消息头部, 不做处理等待下面继续接受消息
            else:
                return None
    
            print('get:%s' % bytes)
    
        # msgBody 是 unicode
        def sendMsg(self,msgType,msgBody):
            self.sock.sendall(self.encode(msgType,msgBody))
    
        def handleMsg(self,msgType,msgBody):
            # 客户名称
            if msgType == 1:
                self.customername = msgBody
                print('客户名称设置:%s' % self.customername)
    
            # 普通消息
            elif msgType == 2:
                print(msgBody)
                print('---------------')
                # 客服输入消息内容
                msgSend = input('>>')
                self.sendMsg(2,msgSend)
    
        # 主循环,不断的接受消息发送消息
        def mainloop(self):
            while True:
                try:
                    msg = self.readMsg()
                    # msg 里面包含了 type 和body
                    if msg:
                        msgType,msgBody= msg
                        self.handleMsg(msgType,msgBody)
                except CloseSocketError:
                    print('对方断开了连接,等待下一个客户')
                    break
                except IOError:
                    print('对方断开了连接,等待下一个客户')
                    break
    
    #创建socket,指明协议
    tcpSerSock = socket(AF_INET, SOCK_STREAM)
    
    #绑定地址和端口
    tcpSerSock.bind(ADDR)
    
    tcpSerSock.listen(5)
    
    print('等待客户端连接...')
    while True:
        #阻塞式等待连接请求
        tcpCliSock, addr = tcpSerSock.accept()
        print('有客户连接上来', addr)
    
        handler = ConnectionHandler(tcpCliSock)
        handler.mainloop()
    
    tcpSerSock.close()

    客户端Client:

    # coding=utf-8
    from socket import *
    import traceback,sys
    
    HOST = 'localhost'
    PORT = 21567
    BUFSIZ = 1024
    ADDR = (HOST, PORT)
    
    class CloseSocketError(Exception):
        pass
    
    class ConnectionHandler:
        # 0008|1|nickname
        LEN_MSG_LEN_FIELD = 4
        LEN_MSG_LEN_TYPE_FIELD = 7
    
        def __init__(self,sock):
            # 消息缓存区
            self._readbuffer = b''
            self.sock = sock
            self.customername = ''
    
        # msgBody 是 unicode
        @staticmethod
        def encode(msgType,msgBody):
            rawMsgBody = msgBody.encode('utf8')
    
            msgLenth = '{:04}' 
            .format(len(rawMsgBody)+ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD) 
            .encode()
    
            msgType = f'{msgType}'.encode()
            return b'|'.join([msgLenth,msgType,rawMsgBody])
    
        @staticmethod
        def decode(rawmsg):
            msgType = int(rawmsg[5:6])
            msgbody = rawmsg[ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD:].decode('utf8')
            return [msgType,msgbody]
    
        def readMsg(self):
            bytes = self.sock.recv(BUFSIZ)
    
            # ** 用不同的返回值表示不同的含义
    
            # 当对方关闭连接的时候,抛出异常
            if not bytes:
                self.sock.close()
                raise CloseSocketError()
    
            self._readbuffer += bytes
    
            buffLen = len(self._readbuffer)
    
            # 如果已经获取了消息头部 (包括 消息长度,消息类型)
            if buffLen >= self.LEN_MSG_LEN_TYPE_FIELD:
                msgLen = int(self._readbuffer[:self.LEN_MSG_LEN_FIELD])
                # 缓存区消息 已经包含了一个整体的消息(包括 消息长度,消息类型,消息体)
                if buffLen >= msgLen:
                    # 从缓存区,截取整个消息
                    msg = self._readbuffer[0:msgLen]
                    # 缓存区变成剩余的消息部分
                    self._readbuffer = self._readbuffer[msgLen:]
    
                    return self.decode(msg)
    
            # 如果已经获取的消息还不包括一个完整的消息头部, 不做处理等待下面继续接受消息
            else:
                return None
    
            print('--> %s' % bytes)
    
        # msgBody 是 unicode
        def sendMsg(self,msgType,msgBody):
            self.sock.sendall(self.encode(msgType,msgBody))
    
        def handleMsg(self,msgType,msgBody):
            # 客户名称
            if msgType == 2:
                print(msgBody)
                print('---------------')
    
        def userinputAndSend(self):
            msgSend = input('>>')
            self.sendMsg(2, msgSend)
    
        # 主循环,不断的接受消息发送消息
        def mainloop(self):
            # 先发送客户名称
            userName = '***'
            if len(sys.argv) > 1:
                userName = sys.argv[1].decode(sys.stdin.encoding)
            self.sendMsg(1,userName)
            while True:
                try:
                    self.userinputAndSend()
                    # print('reading...')
                    msg = self.readMsg()
                    if msg:
                        msgType,msgBody= msg
                        self.handleMsg(msgType,msgBody)
                except CloseSocketError:
                    print('对方断开了连接,程序退出')
                    return
                except IOError:
                    print('对方断开了连接,程序退出')
                    return
    
    #创建socket,指明协议
    tcpCliSock = socket(AF_INET, SOCK_STREAM)
    
    #连接远程地址和端口
    tcpCliSock.connect(ADDR)
    
    handler = ConnectionHandler(tcpCliSock)
    handler.mainloop()
  • 相关阅读:
    什么时候应该使用C#的属性
    Unicode和字符集小结
    C#编译器怎么检查代码是否会执行
    C#中如何操作2个list
    用Windbg来看看CLR的JIT是什么时候发生的
    bzoj-1579: [Usaco2009 Feb]Revamping Trails 道路升级
    次小生成树
    bzoj-3687: 简单题
    bzoj-3669: [Noi2014]魔法森林
    uva 11732 (trie树)
  • 原文地址:https://www.cnblogs.com/blogofzxf/p/10526830.html
Copyright © 2020-2023  润新知