day 29

作者: 两分与桥 | 来源:发表于2018-04-20 16:54 被阅读5次

    socket 编程
    1.客户端服务器架构
    2.osi 七层
    3.socket 就是封装底层的 TCP 和UDP、IP 等协议的库,我们直接调用 socket 为我们封装的方法就可以利用 TCP 和 UDP 进行通信
    4.套接字,也就是 socket 的中文名,ip address + port,百度百科上面这样写:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口
    5.工作流程,看代码吧,TCP/IP 协议通信

    socket TCP 的 recv 自己这端的缓冲区为空时,阻塞
    socket UDP 的recvfrom 自己这端的缓冲区为空时,就收一个空

    # 服务器端
    import socket
    socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络,tcp/ip通信
    
    socket1.bind(('127.0.0.1',8088)) #绑定到固定端口,等待客户端来访问
    socket1.listen(5)  #最多有5个等待连接
    
    conn,addr = socket1.accept() #没有连接时,会在这句一直等待
    print('conn = ',conn) 
    print('addr = ',addr) #IP地址和端口 ('127.0.0.1', 1941)
    
    data = conn.recv(1024)
    print('data = ', data.decode('utf-8')) 
    
    conn.send('你好啊'.encode('utf-8')) # 发送只能是二进制编码
    
    conn.close()
    socket1.close()
    
    输出结果:
    conn =  <socket.socket fd=492, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 1941)>
    addr =  ('127.0.0.1', 1941)
    data =  天天向上
    

    客户端只需要负责连接就行了

    # 客户端
    import socket
    
    socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    socket1.connect(('127.0.0.1', 8088)) # 发起 TCP 三次握手连接
    
    socket1.send('天天向上'.encode('utf-8'))
    data = socket1.recv(1024)
    print(data.decode('utf-8'))
    socket1.close()
    
    输出结果:
    你好啊
    

    升级版服务器端、客户端,server 可以捕捉异常并继续运行。

    # server
    import socket
    ip_port = ('127.0.0.1', 8088)
    backlog = 5
    buffersize = 1024
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 减少地址复用的时间
    socket_server.bind(ip_port)
    socket_server.listen(backlog)
    while True:
        print('wait to connect')
        conn,addr = socket_server.accept()
        print('conn = ', conn)
        print('addr = ', addr)
        try:
            while True:
                data = conn.recv(buffersize)
                print('>>>', data.decode('utf-8'))
                if data.decode('utf-8') == 'bye':
                    print('prepare to close connect')
                    break
                send = input('>>>').strip()
                conn.send(send.encode('utf-8'))
                if not send or send == 'bye':
                    print('prepare to close connect')
                    conn.send('bye'.encode('utf-8'))
                    break
        except Exception as e:
            print(e)
            conn.close()
            print('close')
    
    socket_server.close()
    
    输出结果:
    wait to connect
    conn =  <socket.socket fd=404, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 6802)>
    addr =  ('127.0.0.1', 6802)
    >>> 
    >>>d
    [WinError 10053] 你的主机中的软件中止了一个已建立的连接。
    close
    wait to connect
    conn =  <socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 6803)>
    addr =  ('127.0.0.1', 6803)
    >>> 413
    
    # client
    import socket
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_client.connect(ip_port)
    while(True):
        send = input('>>>').strip()
        socket_client.send(send.encode('utf-8'))
        if not send or send == 'bye':
            print('prepare to close connect')
            socket_client.send('bye'.encode('utf-8'))
            break
        data = socket_client.recv(buffersize)
        print('>>>', data.decode('utf-8'))
        if data.decode('utf-8') == 'bye':
            print('prepare to close connect')
            break
    socket_client.close()
    print('close')
    
    输出结果:
    >>>413
    Traceback (most recent call last):
      File "C:/Users/libai/PycharmProjects/begin/test.py", line 14, in <module>
        data = socket_client.recv(buffersize)
    KeyboardInterrupt #手动终止客户端,服务端自动断开连接
    

    基于UDP的套接字

    #udp_server
    import socket
    import time
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    udp_server.bind(ip_port)
    print('udp is working')
    while True:
        data,addr = udp_server.recvfrom(buffersize)
        print('addr = ', addr)
        print("data = ",data.decode('utf-8'))
        udp_server.sendto(time.strftime('%Y-%m-%d %X').encode('utf-8'), addr)
    
    输出结果:
    udp is working
    addr =  ('127.0.0.1', 50122)
    data =  123
    addr =  ('127.0.0.1', 50122)
    data =  88
    
    # udp_client
    import socket
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    while True:
        data = input('>>>').strip()
        udp_client.sendto(data.encode('utf-8'),ip_port)
        ntp_time, addr = udp_client.recvfrom(buffersize)
        print('ntp返回的时间:',ntp_time.decode('utf-8'))
        print(addr)
    
    输出结果:
    >>>123
    ntp返回的时间: 2018-04-21 11:04:03
    ('127.0.0.1', 8088)
    >>>88
    ntp返回的时间: 2018-04-21 11:04:08
    ('127.0.0.1', 8088)
    >>>
    

    远程执行系统命令,调用 subprocess 模块,十分有意思

    # server_tcp
    import socket
    import subprocess
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    backlog = 5
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    socket_server.bind(ip_port)
    socket_server.listen(backlog)
    
    while True:
        print('watiing for connect')
        conn, addr = socket_server.accept()
        print('conn = ', conn)
        print('addr = ', addr)
        while True:
            data = conn.recv(buffersize)
            print('输入>>>', data.decode('utf-8'))
            if not data or data.decode('utf-8')=='bye':
                break
            response = subprocess.Popen(data.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)
            error = response.stderr.read()
            if error:
                cmd_res = error
            else:
                cmd_res = response.stdout.read()
            conn.send(cmd_res)
        conn.close()
    
    输出结果:
    watiing for connect
    conn =  <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 27419)>
    addr =  ('127.0.0.1', 27419)
    输入>>> dir
    输入>>> 156
    输入>>> bye
    watiing for connect
    
    # client_tcp
    import socket
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    
    socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_client.connect(ip_port)
    while True:
        data = input('>>>')
        socket_client.send(data.encode('utf-8'))
        if not data: continue
        if data == 'bye':
            break
        response = socket_client.recv(buffersize)
        print(response.decode('gbk'))
    print('connect close')
    
    输出结果:
    
    2018/04/21  15:25    <DIR>          .
    2018/04/21  15:25    <DIR>          ..
    2018/04/21  15:28    <DIR>          .idea
    2018/04/20  17:23    <DIR>          a
    2018/04/18  16:03                25 a.txt
    2018/04/21  15:25             1,028 begin
    2018/04/21  15:00                19 process.py
    2018/04/21  15:23               392 test.py
                   4 个文件          1,464 字节
                   4 个目录 68,050,141,184 可用字节
    
    >>>156
    '156' 不是内部或外部命令,也不是可运行的程序
    或批处理文件。
    
    >>>bye
    connect close
    

    socket TCP 会出现粘包现象,也就是recv命令收取的buffersize没有全部收完,就会等到下次再继续收
    例如:先运行ipconfig 命令,没有收完缓冲区使得打印信息不全,再运行别的命令,下次的命令会打印运行ipconfig命令剩余的信息。
    而UDP不会出现粘包现象

    解决粘包 第一个版本

    # server
    import socket
    import subprocess
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    backlog = 5
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    socket_server.bind(ip_port)
    socket_server.listen(backlog)
    
    while True:
        print('watiing for connect')
        conn, addr = socket_server.accept()
        print('conn = ', conn)
        print('addr = ', addr)
        while True:
            data = conn.recv(buffersize)
            print('输入>>>', data.decode('utf-8'))
            if not data or data.decode('utf-8')=='bye':
                break
            response = subprocess.Popen(data.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)
            error = response.stderr.read()
            if error:
                cmd_res = error
            else:
                cmd_res = response.stdout.read()
            if not cmd_res:
                cmd_res = 'execution succeed'.encode('gbk')
            length = len(cmd_res)
            conn.send(str(length).encode('gbk'))
            ready = conn.recv(buffersize)
            if ready.decode('gbk') == 'ready':
                conn.send(cmd_res)
        conn.close()
    
    
    #client
    import socket
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    
    socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_client.connect(ip_port)
    try:
        while True:
            data = input('>>>')
            socket_client.send(data.encode('utf-8'))
            if not data: continue
            if data == 'bye':
                break
            length = socket_client.recv(buffersize)
            length = int(length.decode('gbk'))
            socket_client.send('ready'.encode("gbk"))
            response = b''
            while len(response) < length:
                response += socket_client.recv(buffersize)
            print(response.decode('gbk'))
    except Exception as e:
        print(e)
        print('connect close')
        socket_client.close()
    

    解决粘包 第二个版本

    # server
    import socket
    import subprocess
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    backlog = 5
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    socket_server.bind(ip_port)
    socket_server.listen(backlog)
    
    while True:
        print('watiing for connect')
        conn, addr = socket_server.accept()
        print('conn = ', conn)
        print('addr = ', addr)
        while True:
            data = conn.recv(buffersize)
            print('输入>>>', data.decode('utf-8'))
            if not data or data.decode('utf-8')=='bye':
                break
            response = subprocess.Popen(data.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)
            error = response.stderr.read()
            if error:
                #response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
                cmd_res = error.decode('gbk')
            else:
                cmd_res = response.stdout.read().decode('gbk')
            if not cmd_res:
                cmd_res = 'execution succeed'
            length = len(cmd_res)
            send_data = str(length)+'!o!'+cmd_res
            # 字符长度,'!o!'符号作为分隔符,再加上返回的数据一起发送
            conn.send(send_data.encode('gbk'))
        conn.close()
    
    输出结果:
    watiing for connect
    conn =  <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 28581)>
    addr =  ('127.0.0.1', 28581)
    输入>>> ipconfig
    输入>>> bye
    watiing for connect
    
    import socket
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    
    socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_client.connect(ip_port)
    try:
        while True:
            data = input('>>>')
            socket_client.send(data.encode('utf-8'))
            if not data: continue
            if data == 'bye':
                break
            send_data = socket_client.recv(buffersize)
            # 不知道发送的字符长度,从收取的第一个data中截取出来,没读完再继续读取
            send_data = send_data.decode('gbk')
            key_line = send_data.find('!o!')# 找到分隔符,返回的是第一个!的位置
            length = int(send_data[0:key_line]) # 切割出返回字符长度
            response = send_data[key_line+3:].encode('gbk') # 切割出返回字符,再次编码以便和未发送完的字符串拼接
            while len(response) < length:
                response += socket_client.recv(buffersize)
            print(response.decode('gbk'))
            print('返回%s个字节' %length)
    except Exception as e:
        print(e)
        print('connect close')
        socket_client.close()
    
    输出结果:#只复制了一部分
    以太网适配器 VMware Network Adapter VMnet8:
    
       连接特定的 DNS 后缀 . . . . . . . :
       本地链接 IPv6 地址. . . . . . . . : fe80::8dae:48ae:e19c:207d%9
       IPv4 地址 . . . . . . . . . . . . : 192.168.1.1
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . :
    
    无线局域网适配器 本地连接* 12:
    
       连接特定的 DNS 后缀 . . . . . . . :
       本地链接 IPv6 地址. . . . . . . . : fe80::7ca0:36c4:83dd:d3ac%22
       IPv4 地址 . . . . . . . . . . . . : 192.168.23.1
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       IPv4 地址 . . . . . . . . . . . . : 192.168.137.1
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . :
    
    返回1631个字节
    >>>bye
    

    解决粘包 第三版

    #server
    import socket
    import subprocess
    import struct
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    backlog = 5
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    socket_server.bind(ip_port)
    socket_server.listen(backlog)
    
    while True:
        print('waiting for connect')
        conn, addr = socket_server.accept()
        print('conn = ', conn)
        print('addr = ', addr)
        while True:
            data = conn.recv(buffersize)
            print('输入>>>', data.decode('utf-8'))
            if not data or data.decode('utf-8')=='bye':
                break
            response = subprocess.Popen(data.decode('utf-8'),shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE)
            error = response.stderr.read()
            if error:
                #response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
                cmd_res = error.decode('gbk')
            else:
                cmd_res = response.stdout.read().decode('gbk')
            if not cmd_res:
                cmd_res = 'execution succeed'
            length = len(cmd_res)
            length_pack = struct.pack('i', length) #struct.pack 编码出来的length_pack 为四个字节
            conn.send(length_pack)
            conn.send(cmd_res.encode('gbk'))
        conn.close()
    
    
    #client
    import socket
    import struct
    
    ip_port = ('127.0.0.1', 8088)
    buffersize = 1024
    
    socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_client.connect(ip_port)
    try:
        while True:
            data = input('>>>')
            socket_client.send(data.encode('utf-8'))
            if not data: continue
            if data == 'bye':
                break
            length_pack = socket_client.recv(4) #取出四字节的编码字符数据长度
            length = int(struct.unpack('i',length_pack)[0])
            data = b''
            while len(data) < length:
                data += socket_client.recv(buffersize)
            print(data.decode('gbk'))
            print('返回%s个字节' %length)
    except Exception as e:
        print(e)
        print('connect close')
        socket_client.close()
    

    day 29 结束

    相关文章

      网友评论

          本文标题:day 29

          本文链接:https://www.haomeiwen.com/subject/ytrzkftx.html