zoukankan      html  css  js  c++  java
  • day8--socket网络编程进阶

        socket:socket就是实现服务器和客户端数据的交换,服务器端接收并发送数据,客户端发送并接收数据,并且需要注意的是,在python3中,socket值接收字节。因为客户端在发送连接给服务器的时候,要转换为字节码;服务器端在返回给客户端的时候,也要转换为字节码。

        如下所示:

    服务器端:

    import socket,os
    server = socket.socket()
    server.bind(("localhost",9999))
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("new conn:",addr)
        while True:
            data = conn.recv(1024)
            if not data:
                print("客户端已断开!")
                break
            print("执行指令:",data)
            cmd_res = os.popen(data.decode()).read()
            print("before send",len(cmd_res))
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output....."
            conn.send(cmd_res.encode('utf-8'))
            print("send done")
    
    server.close()

        上面是服务器端,使用os.popen()实现数据的处理,不过只能处理字符串,因此需要decode()成字符串格式,然后发送的时候要转换为字节码,encode()。

    客户端:

    import socket
    client = socket.socket()
    client.connect(("localhost",9999))
    
    while True:
        cmd = input(">>:").strip()
        if len(cmd) == 0:
            continue
        client.send(cmd.encode('utf-8'))
        '''服务器端发送为空,客户端是卡主的'''
        cmd_res = client.recv(1024)
    
        print(cmd_res.decode())
    
    client.close()

        首先启动服务器端,然后启动客户端,如下所示:

        服务器端发送数据:

    >>:1
    cmd has no output.....
    >>:dir
    build_server.py  get_ip.py       socket_server.py  客户端创建过程.py
    class_method.py  lib           s_server.py         类的方法.py
    class的方法.py     property属性.py   static_method.py  人类.py
    error_handle.py  s_client.py       动态导入.py         上节内容
    get_attr.py     socket_client.py  反射.py
    
    >>:ls
    build_server.py
    class_method.py
    class的方法.py
    error_handle.py
    get_attr.py
    get_ip.py
    lib
    property属性.py
    s_client.py
    socket_client.py
    socket_server.py
    s_server.py
    static_method.py
    动态导入.py
    反射.py
    客户端创建过程.py
    类的方法.py
    人类.py
    上节内容

        客户端接收数据:

    new conn: ('127.0.0.1', 55324)
    执行指令: b'1'
    before send 0
    send done
    /bin/sh: 1: 1: not found
    执行指令: b'dir'
    before send 249
    send done
    执行指令: b'ls'
    before send 219
    send done

        当客户端卡顿的时候,说明服务器端是没有数据发送过来的,因为客户端不能接收空的数据服务器端也不能接收空的数据。这样就会造成卡顿的情况。

        在接收的时候,会有一个缓存,缓存不满的时候,是不会发送数据的,客户端就接收不到数据。

        由于缓存的存在,客户端接收数据有时候接收不完整,如何避免呢?要求服务器告诉客户端数据的大小,客户端根据数据的大小来接收数据。

        收发一样大,告诉客户端接收数据的大小,如下:

    服务器端:

    import socket,os,time
    server = socket.socket()
    server.bind(("localhost",9998))
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("new conn:",addr)
        while True:
            data = conn.recv(1024)
            if not data:
                print("客户端已断开!")
                break
            print("执行指令:",data)
            cmd_res = os.popen(data.decode()).read()
            print("before send",len(cmd_res))
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output....."
            conn.send(str(len(cmd_res)).encode('utf-8'))     #先发送数据的大小给客户端 第一个send()发送数据
            time.sleep(1)    #连续发送数据会造成粘包现象,因此要有区分,不然容易粘包,这里让程序休眠一秒,先另外一个接收执行
            conn.send(cmd_res.encode('utf-8'))     #第二个send()发送数据
            print("send done")
    
    server.close()

        在服务器端上,我们计算了发送给客户端的数据大小,先把数据的大小告知客户端,接收多大的数据,然后在发送真正的数据给客户端。在数据发送的时候,要防止粘包,因为send()两次同时发送,造成粘包的情况,因此让程序休眠一秒,time.sleep(1),让客户端先接收数据,过了一秒重新接收数据,这样就不会粘包。

        粘包情况如下;

    >>:1
    命令结果大小: b'22cmd has no output.....'
    Traceback (most recent call last):
      File "/home/zhuzhu/第七天/s_client.py", line 15, in <module>
        while received_size < int(cmd_res_size.decode()):
    ValueError: invalid literal for int() with base 10: '22cmd has no output.....'

        客户端发送数据的时候,服务器返回的时候,由于两个send()同时发送数据造成粘包的情况,会出现错误。两条数据发送的时候连接到一起,造成粘包。两次数据发送链接到一起,为什么造成粘包,是因为程序执行的太快,客户端接收数据很快,速度赶在下一次发送之前。

    客户端:

    import socket
    client = socket.socket()
    client.connect(("localhost",9998))
    
    while True:
        cmd = input(">>:").strip()
        if len(cmd) == 0:
            continue
        client.send(cmd.encode('utf-8'))
        '''服务器端发送为空,客户端是卡主的'''
        cmd_res_size = client.recv(1024)     #接收命令结果的长度(服务器发送的)
        print("命令结果大小:",cmd_res_size)
    
        received_size = 0
        while received_size < int(cmd_res_size.decode()):
            data = client.recv(1024)
            received_size += len(data)    #每次收到的有可能小于1024,所以必须用len()判断
            print(data.decode())
            print(received_size)
        else:
            print("cmd res receive done......",received_size)
    
        # cmd_res = client.recv(1024)
        #
        # print(cmd_res.decode())
    
    client.close()

        在客户端中,我们先接收服务器发来的数据的大小,然后开始接收数据,当数据长度小于接收长度时,继续接收,一直等到没有数据接收为止。

        在客户端中,接收的数据不一定等于规定的长度。并且要统一格式,我们知道,汉字和英文的长度是不一致的,汉字是由3个字节组成,而因为是由两个字节组成的。

        首先启动客户端,然后启动服务器端,如下:

    客户端发送数据:
    >>:1
    命令结果大小: b'22'
    cmd has no output.....
    22
    cmd res receive done...... 22
    >>:ls
    命令结果大小: b'275'
    build_server.py
    class_method.py
    class的方法.py
    error_handle.py
    get_attr.py
    get_ip.py
    lib
    property属性.py
    s_client.py
    socket_client.py
    socket_server.py
    s_server.py
    static_method.py
    动态导入.py
    反射.py
    客户端创建过程.py
    类的方法.py
    人类.py
    上节内容
    
    275
    cmd res receive done...... 275
    >>:dir
    命令结果大小: b'305'
    build_server.py  get_ip.py       socket_server.py  客户端创建过程.py
    class_method.py  lib           s_server.py         类的方法.py
    class的方法.py     property属性.py   static_method.py  人类.py
    error_handle.py  s_client.py       动态导入.py         上节内容
    get_attr.py     socket_client.py  反射.py
    
    305
    cmd res receive done...... 305

        从接收数据可以看出,如果有中文的话,接收数据的长度是不一致的,就是由于中文的字节是3个,因此都要转换为统一格式,现在调整服务器端,让服务器发送数据的长度的时候也是按照字节的形式发送长度,如下:

    import socket,os,time
    server = socket.socket()
    server.bind(("localhost",9998))
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("new conn:",addr)
        while True:
            data = conn.recv(1024)
            if not data:
                print("客户端已断开!")
                break
            print("执行指令:",data)
            cmd_res = os.popen(data.decode()).read()
            print("before send",len(cmd_res))
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output....."
            conn.send(str(len(cmd_res.encode('utf-8'))).encode('utf-8'))     #先发送数据的大小给客户端
            time.sleep(1)    #连续发送数据会造成粘包现象,因此要有区分,不然容易粘包,这里让程序休眠一秒,先另外一个接收执行
            conn.send(cmd_res.encode('utf-8'))
            print("send done")
    
    server.close()

        重新启动服务器,启动客户端发送数据,如下:

    客户端输入指令:
    >>:1
    命令结果大小: b'22'
    cmd has no output.....
    22
    cmd res receive done...... 22
    >>:ls
    命令结果大小: b'275'
    build_server.py
    class_method.py
    class的方法.py
    error_handle.py
    get_attr.py
    get_ip.py
    lib
    property属性.py
    s_client.py
    socket_client.py
    socket_server.py
    s_server.py
    static_method.py
    动态导入.py
    反射.py
    客户端创建过程.py
    类的方法.py
    人类.py
    上节内容
    
    275
    cmd res receive done...... 275
    >>:dir
    命令结果大小: b'305'
    build_server.py  get_ip.py       socket_server.py  客户端创建过程.py
    class_method.py  lib           s_server.py         类的方法.py
    class的方法.py     property属性.py   static_method.py  人类.py
    error_handle.py  s_client.py       动态导入.py         上节内容
    get_attr.py     socket_client.py  反射.py
    
    305
    cmd res receive done...... 305

        从上面可以看出,当调整服务器端发送长度的方式之后,接收数据的长度和服务器告知客户端的长度是一致的。因此,在接收和发送数据的时候要以字节码方式统一计算长度,格式统一很重要,在socket中,计算长度统一格式为:字节码,发送和接收数据都是以字节码的形式操作。

        socket网络编程,其实就是收发数据,只能收发字节的形式,要统一字节格式,要进行及时传唤,要告知客户端数据的大小。然后接收的时候,按照大小来接收。接收完成之后在继续进行执行程序。

        粘包:两次发送数据粘到一起。

        如何解决粘包:

      (1)sleep(),停留一秒,time.sleep(0.5);

    服务器端:

    import socket,os,time
    server = socket.socket()
    server.bind(("localhost",9999))
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("new conn:",addr)
        while True:
            data = conn.recv(1024)
            if not data:
                print("客户端已断开!")
                break
            print("执行指令:",data)
            cmd_res = os.popen(data.decode()).read()
            print("before send",len(cmd_res))
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output....."
            conn.send(str(len(cmd_res.encode('utf-8'))).encode('utf-8'))     #先发送数据的大小给客户端
            time.sleep(1)    #连续发送数据会造成粘包现象,因此要有区分,不然容易粘包,这里让程序休眠一秒,先另外一个接收执行
            conn.send(cmd_res.encode('utf-8'))
            print("send done")
    
    server.close()

        服务器两次发送数据的时间有间隔,这样就能避免数据发送粘包的情况。不过使用sleep()太low了。

        (2)第一次发送之后,接收数据,接收客户端发来的第一次数据发送接收成功的消息,进行第二次发送,这样就能隔断数据的发送

    服务器端:

    import socket,os,time
    server = socket.socket()
    server.bind(("localhost",9998))
    server.listen()
    
    while True:
        conn,addr = server.accept()
        print("new conn:",addr)
        while True:
            data = conn.recv(1024)
            if not data:
                print("客户端已断开!")
                break
            print("执行指令:",data)
            cmd_res = os.popen(data.decode()).read()
            print("before send",len(cmd_res))
            if len(cmd_res) == 0:
                cmd_res = "cmd has no output....."
            conn.send(str(len(cmd_res.encode('utf-8'))).encode('utf-8'))     #先发送数据的大小给客户端
            # time.sleep(1)    #连续发送数据会造成粘包现象,因此要有区分,不然容易粘包,这里让程序休眠一秒,先另外一个接收执行
            clicent_ack = conn.recv(1024)     #等待接收数据,让下面的发送send()暂时不执行,要收到客户端的数据大小进行响应。wait to confirm
            print("ack from client:",clicent_ack)
            conn.send(cmd_res.encode('utf-8'))
            print("send done")
    
    server.close()

        服务器端,要想防止粘包,则在两次发送数据直接增加一个第一次发送数据成功的确认,即接收成功发送数据的指令,如上面所示,conn.recv()其实也没有什么功能,就是隔断第二次发送的执行时间,让第二次发送数据在第一次执行完成之后再进行执行;

    客户端:

    import socket
    client = socket.socket()
    client.connect(("localhost",9998))
    
    while True:
        cmd = input(">>:").strip()
        if len(cmd) == 0:
            continue
        client.send(cmd.encode('utf-8'))
        '''服务器端发送为空,客户端是卡主的'''
        cmd_res_size = client.recv(1024)     #接收命令结果的长度(服务器发送的)
        print("命令结果大小:",cmd_res_size)
        client.send("准备好接收了,loser,可以发了".encode('utf-8'))
    
        received_size = 0
        while received_size < int(cmd_res_size.decode()):
            data = client.recv(1024)
            received_size += len(data)    #每次收到的有可能小于1024,所以必须用len()判断
            print(data.decode())
            print(received_size)
        else:
            print("cmd res receive done......",received_size)
    
        # cmd_res = client.recv(1024)
        #
        # print(cmd_res.decode())
    
    client.close()

        客户端上,数据接收完成之后,发送一条指令,即让服务器接着发送第二条指令。第一条指令是发送数据的大小,第二条指令是发送数据;

        上面就解决了粘包的情况。

        其实,socket在发送和就是数据的时候都是同步的,要想隔断,只能进行成功验证,先隔断不让后面send()执行,只有第一次发送成功之后,并且通过验证,再来进行第二次执行。

  • 相关阅读:
    当简单的计算遇上了大数,其实大数运算也很简单
    揭开源码的神秘面纱,让源码从此无处藏身
    JAVA对象和XML文档、原来他们之间还有这一出
    JAVA反射其实就是那么一回事
    Metatable让我从心认知了Lua(相知篇)
    Github
    常见问题汇总
    文章目录
    前后端分离下使用SignalR
    IdentityServer_0_参考资料
  • 原文地址:https://www.cnblogs.com/gengcx/p/7401737.html
Copyright © 2011-2022 走看看