• python网络编程之一


    套接字的详细介绍会在另一篇博文指出,此片博文主要是python套接字的简单客户端编写。

    两种套接字介绍:

    • 面向连接的套接字:面向连接的套接字提供序列化,可靠的和不重复的数据交付。面向连接是可靠的传输,数据能够完成无误的传输到对方。传输数据时需要先建立连接(TCP的三次握手),然后传输数据。在编写套接字时使用参数 socket.SOCK_STREAM 来指定建立的TCP套接字对象
    • 无连接的套接字:通信开始前不需要建立连接。数据传输过程中无法保持它的顺序性,可靠性和重复性。数据传输前,不用先建立连接。在编写套接字时使用参数 socket.SOCK_DGRAM 来指定建立UDP套接字对象。

    TCP  C/S编写:

      服务端编写:(伪代码)

      •   导入socket模块
      •        建立对应的TCP 套接字对象
      •        绑定要监听的主机和端口
      •        开启监听,调用listen函数
      •        开启和客户端的交互

          客户端编写: (伪代码)

      •   导入socket模块
      •        建立对应的TCP 套接字对象
      •        调用connect方法建立到server端的连接
      •        进行和服务端的交互

    通过实际例子来编写c/s端程序:

    # *-* coding:utf-8 *-*
    # Auth: wangxz
    
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("localhost", 5969))
    s.listen(5)
    
    while True: # 这个循环保证客户端退出后,可以有另一个客户端接入
        tcp_server, addr = s.accept() 
        # accept函数返回的是一个元组,一个是与客户端建立的新的tcp连接,另一个客户端连接的ip和端口
        print("The client is %s at %s port " % addr ) #
        while True: # 这个循环保证每个客户端与服务持续交互
            print("The communication system".center(50, "-"))
            data = tcp_server.recv(1024)
            if not data: break # 判断如果接收的数据为空,则跳出循环,
            # print(data.decode(encoding='ascii'))
            print(data)
    s.close()
    server
    # *-* coding:utf-8 *-*
    # Auth: wangxz
    
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost", 5969))
    while True: # 循环是客户端可以持续性输入
        msg = raw_input("Please input your msg >>>: ")
        # msg = msg.encode()
        s.send(msg)  #调用send方法发送数据
    客户端数据

    执行这段代码,可以多开启几个客户单程序,会发现只有第一个客户端可以与服务器通信,其余的客户端都是阻塞状态。当第一个断开连接之后,其余的客户端程序中的一个才会与服务端交互。

    模仿写一个ssh命令的请求。

    第1.0版本:

    # *-* coding:utf-8 *-*
    # Auth: wangxz
    import socket
    import os
    import time
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("localhost", 12345))
    s.listen(5)
    while True:
        tcp_server, addr = s.accept()
        print("The connectios is from %s at %s port" % addr)
        while True:
            data = tcp_server.recv(1024)  # 接收客户端的命令代码,然后执行,如果是空则退出循环
            if not data: break
            tmp = os.popen(data, "r").read()  # 调用popen()函数,执行shell命令,得到返回结果。
            if not tmp:
                tcp_server.send("33[32;1m The command has esec succeddfully,  but no return!33[0m")
                continue
            else:
                size = str(len(tmp))
                tcp_server.send(size) # 首先发给客户端的是,此次执行cmd命令,返回结果数据的大小
            tcp_server.sendall(tmp)   # 注意这里
    s.close()
    # *-* coding:utf-8 *-*
    # Auth: wangxz
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost",12345))
    while True:
        cmd = raw_input("Please input your command >>>: ")
        s.send(cmd)
        get_size = s.recv(1024) # 得到返回的数据大小,注意这里只接收返回的数据大小,并没有接收命令执行的结果
        print("The msg is %s byte" % get_size)

    执行结果如下:

    Please input your command >>>: cd /etc # 没有返回结果
    The msg is  The command has esec succeddfully,  but no return! byte
    Please input your command >>>: w  # 本应只得到234字节大小的,但是却返回了命令执行的结果
    The msg is 234 22:49:03 up  5:15,  2 users,  load average: 0.00, 0.01, 0.05
    USER     TTY        LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0     17:34    7.00s  0.10s  0.01s w
    root     pts/1     22:30    7.00s  0.04s  0.02s python ssh_client.py
     byte
    Please input your command >>>: df -h
    The msg is 382Filesystem               Size  Used Avail Use% Mounted on
    /dev/mapper/centos-root   18G  1.3G   17G   8% /
    devtmpfs                 488M     0  488M   0% /dev
    tmpfs                    494M     0  494M   0% /dev/shm
    tmpfs                    494M  6.7M  487M   2% /run
    tmpfs                    494M     0  494M   0% /sys/fs/cgroup
    /dev/sda1                497M   96M  401M  20% /boot
     byte
    Please input your command >>>:

    下面这段解释为引用:

    哈哈,这里就引入了一个重要的概念,“粘包”, 即服务器端你调用时send 2次,但你send调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的socket发送缓冲区里,等缓冲区满了、或者数据等待超时了,数据才会被send到客户端,这样就把好几次的小数据拼成一个大数据,统一发送到客户端了,这么做的目地是为了提高io利用效率,一次性发送总比连发好几次效率高嘛。 但也带来一个问题,就是“粘包”,即2次或多次的数据粘在了一起统一发送了。就是我们上面看到的情况 。 

    我们在这里必须要想办法把粘包分开, 因为不分开,你就没办法取出来服务器端返回的命令执行结果的大小呀。so ,那怎么分开呢?首先你是没办法让缓冲区强制刷新把数据发给客户端的。 你能做的,只有一个。就是,让缓冲区超时,超时了,系统就不会等缓冲区满了,会直接把数据发走,因为不能一个劲的等后面的数据呀,等太久,会造成数据延迟了,那可是极不好的。so如果让缓冲区超时呢?

    解决这个问题,改进之后的服务端代码如下:

    • 第一种:使用time.sleep延迟处理(强烈不推荐)
    • 第二种:服务端调用一个recv方法。由于recv在接收不到数据时是阻塞的,这样就会造成,服务器端接收不到客户端的响应,就不会执行后面的conn.sendall(命令结果)的指令,收到客户端响应后,再发送命令结果时,缓冲区就已经被清空了,因为上一次的数据已经被强制发到客户端了
    # *-* coding:utf-8 *-*
    # Auth: wangxz
    import socket
    import os
    import time
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("localhost", 12345))
    s.listen(5)
    while True:
        tcp_server, addr = s.accept()
        print("The connectios is from %s at %s port" % addr)
        while True:
            data = tcp_server.recv(1024)  # 接收客户端的命令代码,然后执行,如果是空则退出循环
            if not data: break
            tmp = os.popen(data, "r").read()  # 调用popen()函数,执行shell命令,得到返回结果。
            if not tmp:
                tcp_server.send("33[32;1m The command has esec succeddfully,  but no return!33[0m")
                continue
            else:
                size = str(len(tmp))
                tcp_server.send(size) # 首先发给客户端的是,此次执行cmd命令,返回结果数据的大小
            # time.sleep(0.5)  # 不推荐使用
            tcp_server.recv(1024)  # 接收客户端要求传送开始的请求
    tcp_server.sendall(tmp) # 注意这里 s.close()

    客户端代码如下:

    # *-* coding:utf-8 *-*
    # Auth: wangxz
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost",12345))
    while True:
        cmd = raw_input("Please input your command >>>: ")
        s.send(cmd)
        get_size = s.recv(1024)
        print("The msg is %s byte" % get_size)
        s.send("Please start transfer the data!") # 请求开始传送数据
        data = s.recv(1024)
        print(data)

    执行结果如下:

    [root@mgto7 cs]# python ssh_client.py 
    Please input your command >>>: w
    The msg is 234 byte
     23:02:20 up  5:29,  2 users,  load average: 0.00, 0.01, 0.05
    USER     TTY        LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0     17:34    4.00s  0.09s  0.00s w
    root     pts/1     22:30    4.00s  0.04s  0.02s python ssh_client.py
    
    Please input your command >>>: df -h
    The msg is 382 byte
    Filesystem               Size  Used Avail Use% Mounted on
    /dev/mapper/centos-root   18G  1.3G   17G   8% /
    devtmpfs                 488M     0  488M   0% /dev
    tmpfs                    494M     0  494M   0% /dev/shm
    tmpfs                    494M  6.7M  487M   2% /run
    tmpfs                    494M     0  494M   0% /sys/fs/cgroup
    /dev/sda1                497M   96M  401M  20% /boot
    
    Please input your command >>>: top -bn 1

    一切完美,但是执行top -bn 1命令时,因为传送的数据过于大,因此返回的结果不会一次全部传送完毕,而是会暂存在socket缓冲中,下次执行w(或其余的命令)时,会发现仍然返回的是top -bn 1 命令的结果,一直到缓冲区发送完毕,然后才会有新的结果赶回。

    上面的客户端执行没有返回结果的命令是,会返回一个句话,可以自己执行查看!

    上面的脚步命令不能执行类似于top这种实时刷新的命令。

    先改进数据全部传输的问题:

    思路:客户端接受数据时,通过判断接收数据的大小与原来传送的数据大小进行比较,若是两者相等,则表示已经接收完毕,否则继续接收。

    服务端代码不变,客户端代码如下:

    # *-* coding:utf-8 *-*
    # Auth: wangxz
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost",12345))
    while True:
        cmd = raw_input("Please input your command >>>: ")
        s.send(cmd)
        get_size = s.recv(1024)
        print("The msg is %s byte" % get_size)
        s.send("Please start transfer the data!")
        received_size = 0
        cmd_msg = " "
        while received_size < int(get_size):  #加入了一个循环
            data = s.recv(1024)
            received_size += int(len(data))
            cmd_msg += data
        else:
            print(cmd_msg)
    ssh_client

    这样至少可以处理类似于top -bn 1这样的有很多返回结果的数据。

    需要注意:

        在发送命令返回的数据时,我们使用了sendall而不是send,因为发送到也是有数据限制的,这样就相当于多次调用了send。可以先这样理解。

    UDP C/S编写:

    因为udp是无连接的,因此编写相对简单:

    server端:

      •   导入socket模块
      •        建立udp套接字对象
      •        绑定端口
      •        进行交互

    client端:

      •        导入socket模块
      •        建立udp套接字对象
      •        进行交互

    一个简单的udp 实例如下:

    # *-* coding:utf-8 *-*
    # Auth: wangxz
    
    import socket
    import time
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(("localhost", 12465))
    
    while True:
        print("Waiting for  the  connect...")
        data, addr = s.recvfrom(1024) # 返回两个数据,一个是接收到的数据,另一个是客户端的ip和端口组成的元组
        print("33[35;1m The client is %s at %s port 33[0m" % addr)
        s.sendto("[%s] 
     ---- %s" % (time.strftime("%y-%m-%d %H:%M:%S",  (time.localtime(time.time()))
    ) , data), addr)  # udp使用seldto发送时候,需要指定发送到的地址
    server端
    # *-* coding:utf-8 *-*
    # Auth: wangxz
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ADDR = ("localhost", 12465)
    while True: # 客户端不用建立连接,直接使用sendto发送即可
        data = raw_input("Please input your msg >>> : ")
        s.sendto(data, ADDR)
        msg, addr = s.recvfrom(1024)
        print(msg)
    udp client

    代码执行:

  • 相关阅读:
    2019-2020-1学期 20192413 《网络空间安全专业导论》第九周学习总结
    2019-2020-1学期 20192413 《网络空间安全专业导论》第八周学习总结
    175210《网络对抗技术》Exp9 Web安全基础
    175210闵天 《网络对抗技术》Exp8 Web基础
    175210《网络对抗技术》Exp7 网络欺诈防范
    175210课设个人报告
    175210 Exp6 MSF基础应用
    175210课设第三次报告
    175210 《网络对抗技术》 Exp5 信息搜集与漏洞扫描
    175210课设第二次报告
  • 原文地址:https://www.cnblogs.com/wxzhe/p/9019820.html
Copyright © 2020-2023  润新知