• 8_7 网络编程之tcp协议


    一。socket模块

      socket模块就是用来网络搭建的模块,socket也叫套接字。

      创建网络连接,需要使用两个模块进行模拟,一个作为server服务器端,一个作为client客户端。

      在服务器端,需要先申明一个socket,再使用bind等待接入,需要传入IP地址和端口号,这里注意,这两个需要放在一个元组里。

      再调用server.listen输入接入的连接池大小,也就是最大可以接受多少客户端的等待。

      服务器端:

    import socket
    
    server = socket.socket()  
    server.bind(('127.0.0.1',9000)) #127.0.0.1是本机回还地址
    server.listen(5) #连接池
    
    conn,addr = server.accept()
    print(conn)
    res = conn.recv(1024)
    print(res)
    conn.send(b'hello client')
    
    conn.close()
    server.close()

      客户端

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',9000))
    
    client.send(b'hello')
    
    res = client.recv(1024)
    print(res )
    client.close()

      其中服务器端的accept是服务器等待接听的过程,返回 一个socket对象,随后可以调用该对象进行接受数据与发送数据的操作。

      注意,客户端,与服务器之间需要send和recv操作一一对应,不能出现同时发送和接受的命令,否则回出现双方都在等待对方发送的数据或者收到数据的反馈。

    二。循环通信。

      上面只是一个简单的客户端与服务器之间的通信,要想实现循环发送数据给服务器,可以使用while循环:

      服务器:

    import socket
    
    server = socket.socket()
    server.bind(('127.0.0.1',9000))
    server.listen(5)
    
    conn,addr = server.accept()
    while True:
        res = conn.recv(1024)
        conn.send(res.upper())

      客户端;

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',9000))
    
    while True:
        cmd = input('>>>').encode()
        client.send(cmd)
        res = client.recv(1024)
        print(res)

      使用循环把send和recv操作放入就可以实现单客户端反复传入数据给服务器并返回对应的值。

      其中,当客户端输入回车时,send会将b‘’传给服务器,服务器没有收到数据就会一直等待,导致该次循环不能结束,所以需要添加判断条件。

      针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b‘’,所以为了兼容性,也需要添加判断条件。

      当一个客户端无故的退出了连接后,服务器会报出错误:

    ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

      想要避免这个错误,要使用try  except 对其进行异常处理:

      服务器

    import socket
    
    server = socket.socket()
    server.bind(('127.0.0.1',9000))
    server.listen(5)
    
    conn,addr = server.accept()
    while True:
        try:
            res = conn.recv(1024)
            if not res: break
            conn.send(res.upper())
        except ConnectionResetError:
            print('一个连接已断开')
            break

      客户端:

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',9000))
    
    while True:
        cmd = input('>>>').encode()
        if not cmd: continue
        client.send(cmd)
        res = client.recv(1024)
        print(res)

    三。循环连接:

      虽然处理了异常,但是当一个客户端连接断开后,服务器依然不能准备好连接下一个客户端,需要对连接进行循环:

      服务器

    import socket
    
    """
    服务端
        固定的ip和port
        24小时不间断提供服务
    """
    server = socket.socket()  # 生成一个对象
    server.bind(('127.0.0.1',8080))  # 绑定ip和port
    server.listen(5)  # 半连接池
    
    while True:
        conn, addr = server.accept()  # 等到别人来  conn就类似于是双向通道
        print(addr)  # ('127.0.0.1', 51323) 客户端的地址
        while True:
            try:
                data = conn.recv(1024)
                print(data)  # b''  针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b''
                if len(data) == 0:break
                conn.send(data.upper())
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()

      客户端

    import socket
    
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:continue
        client.send(msg)
        data = client.recv(1024)
        print(data)

    四。粘包问题:

      TCP特点 会将数据量比较小的并且时间间隔比较短的数据 一次性打包发送给对方

      所以,必须每次控制好recv的数据大小。

      这里使用了struct,struct就是可以将一串很长的数据压缩成4个字节,使用‘i'模式,这样就可以提前知道一个数据多大。

      所以先使用struct将所传数据的长度发给客户端,客户端准备一个4字节的接受,接受到数据后根据设置的recv大小循环取值,直到数据结束。

      服务器:

    import json
    import socket
    import struct
    
    server = socket.socket()
    server.bind(('127.0.0.1',8010))
    server.listen(5)
    
    while True:
        conn,addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0:break
                import subprocess
                obj = subprocess.Popen(cmd.decode('utf-8'), shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                res = obj.stdout.read()+obj.stderr.read()
                header_dict = {'name':'dicr','len':len(res),'something':'lalala'}
                json_h=json.dumps(header_dict).encode('utf-8')
                json_h_len=struct.pack('i',len(json_h))
                conn.send(json_h_len)
                conn.send(json_h)
                conn.send(res)
            except ConnectionResetError:
                print('有一个连接断开')
                break

      客户端:

    import socket
    import struct
    import json
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8010))
    
    while True:
        cmd = input('>>>').encode()
        if not cmd: continue
        client.send(cmd)
        header_dict = client.recv(4)
        dict_size = struct.unpack('i', header_dict)[0]
        dict = json.loads(client.recv(dict_size).decode('utf-8'))
        print(dict)
        file_size = dict['len']
        real_recv = b''
        file_size1 = 0
        while file_size1 < file_size:
            date = client.recv(1024)
            real_recv += date
            file_size1 += len(date)
        print(real_recv.decode('gbk'))

      但是,当所传数据大于struct所能传输的数据时,就会解压不下,所以。

      需要先准备一个字典,将长度传给字典保存。  

      再将字典传给客户端,

      客户端收到字典后解析出文件长度,根据长度循环接收数据。

      具体版如下:

      服务器:

    import socket
    import subprocess
    import struct
    import json
    
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    
    while True:
        conn, addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0:break
                cmd = cmd.decode('utf-8')
                obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()
                d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
                json_d = json.dumps(d)
                # 1.先制作一个字典的报头
                header = struct.pack('i',len(json_d))
                # 2.发送字典报头
                conn.send(header)
                # 3.发送字典
                conn.send(json_d.encode('utf-8'))
                # 4.再发真实数据
                conn.send(res)
                # conn.send(obj.stdout.read())
                # conn.send(obj.stderr.read())
            except ConnectionResetError:
                break
        conn.close()

      客户端:

    import socket
    import struct
    import json
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:continue
        client.send(msg)
        # 1.先接受字典报头
        header_dict = client.recv(4)
        # 2.解析报头 获取字典的长度
        dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
        # 3.接收字典数据
        dict_bytes = client.recv(dict_size)
        dict_json = json.loads(dict_bytes.decode('utf-8'))
        # 4.从字典中获取信息
        print(dict_json)
        recv_size = 0
        real_data = b''
        while recv_size < dict_json.get('file_size'):  # real_size = 102400
            data = client.recv(1024)
            real_data += data
            recv_size += len(data)
        print(real_data.decode('gbk'))

    总结:

      服务器:服务端

         1.先制作一个发送给客户端的字典

        2.制作字典的报头

        3.发送字典的报头

        4.发送字典

        5.再发真实数据

      客户端:

        1.先接受字典的报头
        2.解析拿到字典的数据长度
        3.接受字典
        4.从字典中获取真实数据的长度
        5.接受真实数据

    异常处理

      当在运行服务器或客户端时,有可能出现这样的错误,即使重启了服务器还是会出现这样的错误,是端口号占用的问题,解决方法如下:

    #加入一条socket配置,重用ip和端口
    import socket
    from socket import SOL_SOCKET,SO_REUSEADDR
    sk = socket.socket()
    sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)   #接收客户端信息
    print(ret)              #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)

     

  • 相关阅读:
    九九乘法表
    计算器界面
    3.2封装的日期类
    杨辉三角
    100以内的素数
    九九 乘法表
    七、logging模块
    六、MySQLdb 模块
    四、浏览器运行模式
    五、configparser模块
  • 原文地址:https://www.cnblogs.com/LZXlzmmddtm/p/11318029.html
Copyright © 2020-2023  润新知