zoukankan      html  css  js  c++  java
  • 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

    代码执行:

  • 相关阅读:
    函数
    python操作文件
    POJ-2689-Prime Distance(素数区间筛法)
    POJ-2891-Strange Way to Express Integers(线性同余方程组)
    POJ-2142-The Balance
    POJ-1061-青蛙的约会(扩展欧几里得)
    Educational Codeforces Round 75 (Rated for Div. 2) D. Salary Changing
    Educational Codeforces Round 75 (Rated for Div. 2) C. Minimize The Integer
    Educational Codeforces Round 75 (Rated for Div. 2) B. Binary Palindromes
    Educational Codeforces Round 75 (Rated for Div. 2) A. Broken Keyboard
  • 原文地址:https://www.cnblogs.com/wxzhe/p/9019820.html
Copyright © 2011-2022 走看看