• Python之路--Python基础9--Socket编程


    一、socket介绍

      Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

    二、套接字工作流程

       一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。

      先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

     

    三、Socket模块函数的用法

    服务端套接字函数:

      s.bind()绑定(主机,端口号)到套接字
      s.listen()开始TCP监听
      s.accept()被动接受TCP客户的连接,(阻塞式)等待连接的到来

    客户端套接字函数
      s.connect()主动初始化TCP服务器连接
      s.connect_ex()connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

    公共用途的套接字函数
      s.recv() 接收TCP数据
      s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
      s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
      s.recvfrom() 接收UDP数据
      s.sendto() 发送UDP数据
      s.getpeername() 连接到当前套接字的远端的地址
      s.getsockname() 当前套接字的地址
      s.getsockopt() 返回指定套接字的参数
      s.setsockopt() 设置指定套接字的参数
      s.close() 关闭套接字

    面向锁的套接字方法
      s.setblocking() 设置套接字的阻塞与非阻塞模式
      s.settimeout() 设置阻塞套接字操作的超时时间
      s.gettimeout() 得到阻塞套接字操作的超时时间

    面向文件的套接字的函数
      s.fileno() 套接字的文件描述符
      s.makefile() 创建一个与该套接字相关的文件

    四、基于TCP的套接字

    tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端。

    #TCP服务端
    
    ss = socket()  #创建服务器套接字
    ss.bind()      #把地址绑定到套接字
    ss.listen()    #监听链接
    inf_loop:      #服务器无限循环
        cs = ss.accept()   #接受客户端链接
        comm_loop:         #通讯循环
            cs.recv()/cs.send() #对话(接收与发送)
        cs.close()    #关闭客户端套接字
    ss.close()        #关闭服务器套接字(可选)
    #TCP客户端
    
    cs = socket()     # 创建客户套接字
    cs.connect()      # 尝试连接服务器
    comm_loop:        # 通讯循环
        cs.send()/cs.recv()    # 对话(发送/接收)
    cs.close()                 # 关闭客户套接字

    栗子:socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信:

    #服务端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    
    import socket
    ip_port
    =('127.0.0.1',9000) #电话卡 BUFSIZE=1024 #收发消息的尺寸
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 s.bind(ip_port) #手机插卡 s.listen(5) #手机待机 conn,addr=s.accept() #手机接电话 # print(conn) # print(addr) print('接到来自%s的电话' %addr[0]) msg=conn.recv(BUFSIZE) #听消息,听话 print(msg,type(msg)) conn.send(msg.upper()) #发消息,说话 conn.close() #挂电话 s.close() #手机关机
    #客户端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    
    import socket
    ip_port=('127.0.0.1',9000)
    BUFSIZE=1024
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    
    s.connect_ex(ip_port)                           #拨电话
    
    s.send('LHF nb'.encode('utf-8'))                #发消息,说话(只能发送字节类型)
    
    feedback=s.recv(BUFSIZE)                        #收消息,听话
    print(feedback.decode('utf-8'))
    
    s.close()                                       #挂电话

    加上链接循环与通信循环(持续通话)

    #服务端
    import socket
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   #买手机
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #就是它,在bind前加
    phone.bind(("127.0.0.1", 8080))                             #插入手机卡
    
    phone.listen(5)                                             #开机
    
    while True:      #链接循环
        conn, addr = phone.accept()                             #接电话
        print("client :", addr)
    
        while True:  #通讯循环
            try:
                data = conn.recv(1024)                          #收消息
                if not data:
                    break             #针对linux,客户端断开链接的异常处理
                print("from client msg: %s" % data)
                conn.send(data.upper())                         #发消息
            except Exception:
                break
        conn.close()                                            #挂电话
    phone.close()                                               #关机
    #客户端
    
    import socket
    
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    client.connect(("127.0.0.1", 8080)) #拨通电话
    
    while True:
        msg = input(">>>: ")
        client.send(msg.encode("utf-8"))
    
        data = client.recv(1024)
        print(data)
    
    client.close()

    问题:有的同学在重启服务端时可能会遇到以下错误

    解决:

    phone=socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    phone.bind(('127.0.0.1',8080))

    五、基于UDP的套接字

    udp是无链接的,先启动哪一端都不会报错。

    #UDP服务端
    
    ss = socket()   #创建一个服务器的套接字
    ss.bind()       #绑定服务器套接字
    inf_loop:       #服务器无限循环
        cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    ss.close()                         # 关闭服务器套接字
    #UDP客户端
    
    cs = socket()   # 创建客户套接字
    comm_loop:      # 通讯循环
        cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
    cs.close()                      # 关闭客户套接字

    栗子:udp套接字简单示例

    #UDP服务端
    #
    _*_coding:utf-8_*_
    import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_client.bind(ip_port) while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) print(msg,addr) udp_server_client.sendto(msg.upper(),addr)
    #UDP客户端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    
    import socket
    ip_port=('127.0.0.1',9000)
    BUFSIZE=1024
    udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    
    while True:
        msg=input('>>: ').strip()
        if not msg:continue
    
        udp_server_client.sendto(msg.encode('utf-8'),ip_port)
    
        back_msg,addr=udp_server_client.recvfrom(BUFSIZE)
        print(back_msg.decode('utf-8'),addr)

    栗子2:qq聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)

    bug:消息都是服务端接收到,并且服务端进行回复的,相当于服务端一个人与众客户端在聊天

    #QQ服务端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    import socket
    ip_port=('127.0.0.1',8081)
    udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #买手机
    udp_server_sock.bind(ip_port)
    
    while True:
        qq_msg,addr=udp_server_sock.recvfrom(1024)
        print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
        back_msg=input('回复消息: ').strip()
    
        udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
    #QQ客户端1,2,3
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    import socket
    BUFSIZE=1024
    udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    
    qq_name_dic={
        'alex':('127.0.0.1',8081),
        '二':('127.0.0.1',8081),
        '一棵树':('127.0.0.1',8081),
        '武大郎':('127.0.0.1',8081),
    }
    
    while True:
        qq_name=input('请选择聊天对象: ').strip()
        while True:
            msg=input('请输入消息,回车发送: ').strip()
            if msg == 'quit':break
            if not msg or not qq_name or qq_name not in qq_name_dic:continue
            udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
    
            back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
            print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
    
    udp_client_socket.close()

    栗子3:时间服务器

    #ntp服务端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    
    from socket import *
    from time import strftime
    
    ip_port=('127.0.0.1',9000)
    bufsize=1024
    
    tcp_server=socket(AF_INET,SOCK_DGRAM)
    tcp_server.bind(ip_port)
    
    while True:
        msg,addr=tcp_server.recvfrom(bufsize)
        print('===>',msg)
        
        if not msg:
            time_fmt='%Y-%m-%d %X'
        else:
            time_fmt=msg.decode('utf-8')
        back_msg=strftime(time_fmt)
    
        tcp_server.sendto(back_msg.encode('utf-8'),addr)
    
    tcp_server.close()
    #ntp客户端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    from socket import *
    ip_port=('127.0.0.1',9000)
    bufsize=1024
    
    tcp_client=socket(AF_INET,SOCK_DGRAM)
    
    while True:
        msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
        tcp_client.sendto(msg.encode('utf-8'),ip_port)
    
        data=tcp_client.recv(bufsize)
    
        print(data.decode('utf-8'))
    
    tcp_client.close()

     输出:

     

    六、粘包

    1、粘包是啥

      只有TCP有粘包现象,UDP永远不会粘包

      发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

      而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

      例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束。

      所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。(玩过单片机串口通信的都深有感触)

      此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

      

      TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

      UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

      tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

      udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

      tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

    2、两种情况下会发生粘包

    a、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据块很小,会合到一起,产生粘包)

    #服务端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    from socket import *
    ip_port=('127.0.0.1',8080)
    
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    
    conn,addr=tcp_socket_server.accept()
    
    data1=conn.recv(10)
    data2=conn.recv(10)
    
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    
    conn.close()
    #客户端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(ip_port)
    
    s.send('hello'.encode('utf-8'))
    s.send('feng'.encode('utf-8'))

    输出:

     

    b、接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

    #_*_coding:utf-8_*_
    __author__ = 'YL'
    from socket import *
    ip_port=('127.0.0.1',8080)
    
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    
    conn,addr=tcp_socket_server.accept()
    
    data1=conn.recv(2) #一次没有收完整
    data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
    
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    
    conn.close()
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(ip_port)
    
    s.send('hello feng'.encode('utf-8'))

    输出:

    3、解决办法

      为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

      struct模块:该模块可以把一个类型,如数字,转成固定长度的bytes

      我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

      发送时:

      先发报头长度

      再编码报头内容然后发送

      最后发真实内容

     

      接收时:

      先收报头长度,用struct取出来

      根据取出的长度收取报头内容,然后解码,反序列化

      从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

    #服务端
    
    import socket
    import subprocess
    import struct
    import json
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    # 买手机
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加
    phone.bind(("127.0.0.1", 8080))                              # 插入手机卡
    
    phone.listen(5)  # 开机
    
    while True:      # 链接循环
        conn, addr = phone.accept()    # 接电话
        print("client :", addr)
    
        while True:                    # 通讯循环
            try:
                cmd = conn.recv(1024)  # 收消息
                if not cmd:
                    break              # 针对linux,客户端断开链接的异常处理
                print("from client msg: %s" % cmd)
    
                res = subprocess.Popen(cmd.decode("utf-8"),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
                err = res.stderr.read()
                if err:
                    back_msg = err
                else:
                    back_msg = res.stdout.read()
    
                # 第一阶段:制作报头
                head_dic = {
                    "data_size": len(back_msg)
                }
                head_json = json.dumps(head_dic)
                head_bytes = head_json.encode("utf-8")
    
                # 第二阶段:发送包头长度
                conn.send(struct.pack("i", len(head_bytes)))
    
                # 第三阶段:发报头
                conn.send(head_bytes)
    
                # 第四阶段:发送真实数据
                conn.send(back_msg)
    
            except Exception:
                break
        conn.close()  # 挂电话
    phone.close()     # 关机
    #客户端
    
    import socket
    import struct
    import json
    
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    client.connect(("127.0.0.1", 8080))  # 拨通电话
    
    while True:
        cmd = input(">>>: ").strip()
        if not cmd:
            continue
    
        client.send(cmd.encode("utf-8"))
    
        # 收报头的长度
        head = client.recv(4)
        head_size = struct.unpack("i", head)[0]
    
        # 根据报头长度接收报头
        head_bytes = client.recv(head_size)
        head_json = head_bytes.decode("GBK")
        head_dic = json.loads(head_json)
        data_size = head_dic["data_size"]  # 取出真实数据长度
    
        # 接收真实的数据
        recv_size = 0
        recv_bytes = b""
        while recv_size < data_size:
            res = client.recv(1024)
            recv_bytes += res
            recv_size += len(res)
    
        print(res.decode("GBK"))

    注意注意注意:

    res=subprocess.Popen(cmd.decode('utf-8'),
    shell=True,
    stderr=subprocess.PIPE,
    stdout=subprocess.PIPE)

    的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

    且只能从管道里读一次结果

    七、FTP上传下载

    #服务端
    
    import socket
    import struct
    import json
    import subprocess
    import os
    
    class MYTCPServer:
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_STREAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        request_queue_size = 5
    
        server_dir='file_upload'
    
        def __init__(self, server_address, bind_and_activate=True):
            """Constructor.  May be extended, do not override."""
            self.server_address=server_address
            self.socket = socket.socket(self.address_family,
                                        self.socket_type)
            if bind_and_activate:
                try:
                    self.server_bind()
                    self.server_activate()
                except:
                    self.server_close()
                    raise
    
        def server_bind(self):
            """Called by constructor to bind the socket.
            """
            if self.allow_reuse_address:
                self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind(self.server_address)
            self.server_address = self.socket.getsockname()
    
        def server_activate(self):
            """Called by constructor to activate the server.
            """
            self.socket.listen(self.request_queue_size)
    
        def server_close(self):
            """Called to clean-up the server.
            """
            self.socket.close()
    
        def get_request(self):
            """Get the request and client address from the socket.
            """
            return self.socket.accept()
    
        def close_request(self, request):
            """Called to clean up an individual request."""
            request.close()
    
        def run(self):
            while True:
                self.conn,self.client_addr=self.get_request()
                print('from client ',self.client_addr)
                while True:
                    try:
                        head_struct = self.conn.recv(4)
                        if not head_struct:break
    
                        head_len = struct.unpack('i', head_struct)[0]
                        head_json = self.conn.recv(head_len).decode(self.coding)
                        head_dic = json.loads(head_json)
    
                        print(head_dic)
                        #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                        cmd=head_dic['cmd']
                        if hasattr(self,cmd):
                            func=getattr(self,cmd)
                            func(head_dic)
                    except Exception:
                        break
    
        def put(self,args):
            file_path=os.path.normpath(os.path.join(
                self.server_dir,
                args['filename']
            ))
    
            filesize=args['filesize']
            recv_size=0
            print('----->',file_path)
            with open(file_path,'wb') as f:
                while recv_size < filesize:
                    recv_data=self.conn.recv(self.max_packet_size)
                    f.write(recv_data)
                    recv_size+=len(recv_data)
                    print('recvsize:%s filesize:%s' %(recv_size,filesize))
    
    tcpserver1=MYTCPServer(('127.0.0.1',8080))
    
    tcpserver1.run()
    #客户端

    import
    socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run()

    八、认证客户端的链接的合法性

      如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现

    #服务端
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    from socket import *
    import hmac,os
    
    secret_key=b'linhaifeng bang bang bang'
    def conn_auth(conn):
        '''
        认证客户端链接
        :param conn:
        :return:
        '''
        print('开始验证新链接的合法性')
        msg=os.urandom(32)
        conn.sendall(msg)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        respone=conn.recv(len(digest))
        return hmac.compare_digest(respone,digest)
    
    def data_handler(conn,bufsize=1024):
        if not conn_auth(conn):
            print('该链接不合法,关闭')
            conn.close()
            return
        print('链接合法,开始通信')
        while True:
            data=conn.recv(bufsize)
            if not data:break
            conn.sendall(data.upper())
    
    def server_handler(ip_port,bufsize,backlog=5):
        '''
        只处理链接
        :param ip_port:
        :return:
        '''
        tcp_socket_server=socket(AF_INET,SOCK_STREAM)
        tcp_socket_server.bind(ip_port)
        tcp_socket_server.listen(backlog)
        while True:
            conn,addr=tcp_socket_server.accept()
            print('新连接[%s:%s]' %(addr[0],addr[1]))
            data_handler(conn,bufsize)
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        server_handler(ip_port,bufsize)
    #合法客户端
    
    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    from socket import *
    import hmac,os
    
    secret_key=b'linhaifeng bang bang bang'
    def conn_auth(conn):
        '''
        验证客户端到服务器的链接
        :param conn:
        :return:
        '''
        msg=conn.recv(32)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        conn.sendall(digest)
    
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        conn_auth(tcp_socket_client)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)

    服务端输出:

    #非法客户端,不知道加密方式
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    from socket import *
    
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)

    服务端输出:

    不合法客户端输出:

    #非法客户端,不知道secret_key
    
    #_*_coding:utf-8_*_
    __author__ = 'YL'
    from socket import *
    import hmac,os
    
    secret_key=b'linhaifeng bang bang bang1111'
    def conn_auth(conn):
        '''
        验证客户端到服务器的链接
        :param conn:
        :return:
        '''
        msg=conn.recv(32)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        conn.sendall(digest)
    
    def client_handler(ip_port,bufsize=1024):
        tcp_socket_client=socket(AF_INET,SOCK_STREAM)
        tcp_socket_client.connect(ip_port)
    
        conn_auth(tcp_socket_client)
    
        while True:
            data=input('>>: ').strip()
            if not data:continue
            if data == 'quit':break
    
            tcp_socket_client.sendall(data.encode('utf-8'))
            respone=tcp_socket_client.recv(bufsize)
            print(respone.decode('utf-8'))
        tcp_socket_client.close()
    
    if __name__ == '__main__':
        ip_port=('127.0.0.1',9999)
        bufsize=1024
        client_handler(ip_port,bufsize)

    服务端输出:

    不合法客户端输出:

    九、socketserver实现并发

      SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

    栗子:

    #服务端
    
    import socketserver
    
    class FtpServer(socketserver.BaseRequestHandler):
        def handle(self):
            print(self.request)     #conn
            print(self.client_address)
    
            while True:
                data = self.request.recv(1024)
                self.request.send(data.upper())
    
    
    if __name__ == "__main__":
        s = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), FtpServer)
        s.serve_forever()   #链接循环有了
    #客户端1/2/3
    
    from socket import *
    
    client = socket(AF_INET, SOCK_STREAM)
    client.connect(("127.0.0.1", 8080))
    
    while True:
        msg = input(">>>: ")
        client.send(msg.encode("utf-8"))
    
        data = client.recv(1024)
        print(data)
  • 相关阅读:
    极具创意的专辑封面
    【Linux必知必会】五种开源协议的比较(BSD,Apache,GPL,LGPL,MIT)
    【Ubuntu技巧】Ubuntu下gedit 打开txt文件乱码的处理方法
    【Linux原理】Linux中硬链接和软链接的区别和联系
    【短语学习】out of the box的含义和翻译
    【Ubuntu技巧】在全新安装的Ubuntu上快速重装软件包
    【论文阅读心得】图像识别中一个常用词的中英文释义——artifact
    【短语学习】狮子那一份the lions share
    【OpenCV学习】摄像头显示、录像、拍照程序
    【Perl学习】学习笔记(持续更新中)
  • 原文地址:https://www.cnblogs.com/yl-code/p/9076206.html
Copyright © 2020-2023  润新知