zoukankan      html  css  js  c++  java
  • socket编程进阶

    1、   动态导入模块

    第一种方法(python解释器自己内部用的):

    上图是我程序的目录结构

    下面代码是动态导入模块3.py的源码:

    1 #AUTHOR:FAN
    2 lib_dir = __import__('lib.aa')
    3 print(lib)
    4 obj = lib.aa.C()
    5 print(obj.name)

    lib包目录下aa.py的源码如下:

    1 #AUTHOR:FAN
    2 
    3 class C:
    4     def __init__(self):
    5         self.name = "dean"

    这个时候运行动态导入模块.py程序,运行结果如下:

    1 D:python35python.exe D:/python培训/s14/day8/动态导入模块3.py
    2 <module 'lib' from 'D:\python培训\s14\day8\lib\__init__.py'>
    3 dean
    4 Process finished with exit code 0

    对上述动态导入模块3.py代码进行分析:

    lib_dir = __import__('lib.aa')

    print(lib)

    打印出的内容是:

    <module 'lib' from 'D:\python培训\s14\day8\lib\__init__.py'>

    从打印结果可以看出__import__('lib.aa')这个得到的就是lib目录

    所以这个时候想要调用lib目录下aa中类中的数据直接:

    obj = lib.aa.C()

    print(obj.name)

    这样就取得了aa.py程序中C类中初始化参数的name的值

    第二种方法(官方建议):

    程序目录如下:

    动态导入模块4.py的代码如下:

    1 #AUTHOR:FAN
    2 import importlib
    3 aa = importlib.import_module('lib.aa')
    4 print(aa)
    5 obj = aa.C()
    6 print(obj.name)

    lib目录下的aa.py不变化还是上述相同

    这个时候运行动态导入模块4.py,运行结果如下:

    1 D:python35python.exe D:/python培训/s14/day8/动态导入模块4.py
    2 <module 'lib.aa' from 'D:\python培训\s14\day8\lib\aa.py'>
    3 dean
    4 
    5 Process finished with exit code 0

    对上述代码进行分析:

    aa = importlib.import_module('lib.aa')

    print(aa)

    打印出的内容如下:

    <module 'lib.aa' from 'D:\python培训\s14\day8\lib\aa.py'>

    可以看出打印出的是lib.aa,所以这个时候可以直接实例化aa.C,并取得类中初始化参数中的name的值:

    obj = aa.C()

    print(obj.name)

    通过上述两种方法也可以得出,两者虽然最终结果是相同的,但是过程中还是有区别的:

    第一种方法:lib_dir = __import__('lib.aa'),这种方法得到的module是lib

    第二种方法:

    aa = importlib.import_module('lib.aa')这种方法得到的module是lib.aa

    2、断言assert

    先看如下代码:

    1 #AUTHOR:FAN
    2 name = "dean"
    3 assert type(name) is str
    4 print(name)

    运行结果如下:

    1 D:python35python.exe D:/python培训/s14/day8/断言.py
    2 dean
    3 
    4 Process finished with exit code 0

    断言其实就是对值进行判断,看是否满足条件,如果满足就向下执行,如果不满足就报错,我们将代码进行更改,再次运行:

    1 #AUTHOR:FAN
    2 name = 123
    3 assert type(name) is str
    4 print(name)

    运行结果如下:

    1 D:python35python.exe D:/python培训/s14/day8/断言.py
    2 Traceback (most recent call last):
    3   File "D:/python培训/s14/day8/断言.py", line 5, in <module>
    4     assert type(name) is str
    5 AssertionError
    6 
    7 Process finished with exit code 1

    3、Socket

    socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过“套接字”向网络发出请求或者应答网络请求

    下面是一些功能:

    我们再通常使用socket的时候都需要先导入socket模块

    即import socket,然后实例化socket,例如:

    sk = socket(socket.AF_INET,socket.SOCK_STREAM,0)

    参数一:地址簇

    在这个参数中包含以下几个参数:

    socket.AF_INET  表示IPV4(默认)

    socket.AF_INET6 表示IPV6

    socket.AF_UNIX   只能用于单一的Unix系统进程间的通信

    参数二:类型

    socket.SOCK_STREAM  流式socket for TCP(默认)

    socket.SOCK_DGRAM   数据格式socket,for UDP

    socket.SOCK_RAW     原始套接字,普通的套接字无法处理ICMP,IGMP等网络报文,可以通过IP_HDRINCL套接字选项由用户构造IP头

    socket.SOCK_RDM      是一种可靠的UDP形式,即保证交付数据报但不保证顺序,SOCK_RDM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文,SOCK_RAM通常仅限于高级用户或管理员运行的程序使用

    socket.SOCK_SEQPACKET  可靠的连续数据包服务

    参数三:协议

    默认与特定地址家族相关的协议,如果是0 则系统就会根据地址格式和套接类别,自动选择一个合适的协议

    sk.bind((ip地址,port端口))

    这种是在默认的AF_INET下这样以元组的形式存在即(ip,port)

    sk.listen(backlog)

    开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量

    backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5

    这个值不能无限大,因为需要在内核中维护连接队列

    sk.setblocking(bool)

    是否阻塞(默认为True),如果设置False,那么accept和recv时一旦无数据,就会报错

    sk.accept()

    接收连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据,address是连接客户端的地址

    接收TCP客户端的连接(阻塞)等待连接的到来

    sk.connect(address)

    连接到address处的套接字,一般,address的格式为元组(hostname,port),如果连接出错,返回socket,error错误

    sk.connect_ex(address)

    同上的sk.connect只不过会有返回值,连接成功时返回0,连接失败时返回编码

    sk.close()

    关闭套接字

    sk.recv(bufsize[,flag])

    接收套接字的数据,数据以字符串形式返回,bufsize指定最多可以接收的数量,flag提供有关消息的其他信息,通常可以忽略

    sk.recvfrom(bufsize[.flag])

    与recv()类似,但返回值是(data,address)其中data是包含接收数据的字符串,address是发送数据的套接字地址

    sock.send(string[,flag])

    将string中的数据发送到连接的套接字,返回值是要发送的字节数量,该数量可能小于string的字节大小,即:可能未将指定内容全部发送

    sk.sendall(string[,flag])

    将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有的数据,成功返回None,失败则抛出异常

    内部通过递归调用send,将所有内容发送出去

    sk.sendto(string[,flag],address)

    将数据发送到套接字,address是形式为(ip地址,port)的元组,指定远程地址,返回值时发送的字节数,该函数主要用于UDP协议

    sk.settimeout(timeout)

    设置套接字操作的超时期,timeout是一个浮点数,但是为秒

    值为None表示没有超时期,一般超时期应该在刚创建套接字时设置,因为他们可能用于连接的操作

    sk.getpeername()

    返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

    sk.fileno()

    套接字的文件描述符

    用socket写一个简单的类似ssh的工具:

    服务端:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 server = socket.socket()
     5 server.bind(('127.0.0.1',9999))
     6 
     7 server.listen()
     8 
     9 while True:
    10     conn,addr = server.accept()
    11     print("一个新的连接:",addr)
    12     while True:
    13         print("等待新指令")
    14         data = conn.recv(1024)
    15         if not data:
    16             print("客户端已经断开")
    17             break
    18         print("执行指令:",data)
    19         cmd_res = os.popen(data.decode()).read()
    20         print("send before")
    21         if len(cmd_res) == 0:
    22             cmd_res = "cmd has no output......"
    23         conn.send(cmd_res.encode())
    24         print("send done")
    25 server.close()

    客户端:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 client = socket.socket()
     5 
     6 client.connect(('127.0.0.1',9999))
     7 
     8 while True:
     9         cmd = input(">>:").strip()
    10         if len(cmd) == 0:continue
    11         client.send(cmd.encode("utf-8"))
    12         cmd_res = client.recv(1024)
    13         print(cmd_res.decode())
    14 client.close()

    先启动服务端,在启动客户端,并在客户端执行ipconfig命令(先在windows上测试),运行结果如下:

    客户端运行后结果显示:

     1 D:python35python.exe D:/python培训/s14/day8/sock_ssh_client.py
     2 >>:ipconfig
     3 
     4 Windows IP 配置
     5 
     6 
     7 以太网适配器 Bluetooth 网络连接:
     8 
     9    媒体状态  . . . . . . . . . . . . : 媒体已断开
    10    连接特定的 DNS 后缀 . . . . . . . : 
    11 
    12 以太网适配器 本地连接:
    13 
    14    媒体状态  . . . . . . . . . . . . : 媒体已断开
    15    连接特定的 DNS 后缀 . . . . . . . : 
    16 
    17 无线局域网适配器 无线网络连接:
    18 
    19    连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
    20    本地链接 IPv6 地址. . . . . . . . : fe80::85b7:6c65:f032:d29%12
    21    IPv4 地址 . . . . . . . . . . . . : 192.168.1.102
    22    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    23    默认网关. . . . . . . . . . . . . : 192.168.1.1
    24 
    25 以太网适配器 VMware Network Adapter VMnet1:
    26 
    27    连接特定的 DNS 后缀 . . . . . . . : 
    28    本地链接 IPv6 地址. . . . . . . . : fe80::9c7d:99a2:b09b:fa49%15
    29    IPv4 地址 . . . . . . . . . . . . : 10.0.10.22
    30    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    31    默认网关. . . . . . . . . . . . . : 
    32 
    33 以太网适配器 VMware Network Adapter 
    34 >>:dir
    35 VMnet8:
    36 
    37    连接特定的 DNS 后缀 . . . . . . . : 
    38    本地链接 IPv6 地址. . . . . . . . : fe80::4d7:4817:776d:6386%16
    39    IPv4 地址 . . . . . . . . . . . . : 172.16.1.200
    40    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    41    默认网关. . . . . . . . . . . . . : 
    42 
    43 隧道适配器 isatap.DHCP HOST:
    44 
    45    媒体状态  . . . . . . . . . . . . : 媒体已断开
    46    连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
    47 
    48 隧道适配器 Teredo Tunneling Pseudo-Interface:
    49 
    50    媒体状态  . . . . . . . . . . . . : 媒体已断开
    51    连接特定的 DNS 后缀 . . . . . . . : 
    52 
    53 隧道适配器 isatap.{AEC4AF99-6E8C-4696-B0CE-1044479986E4}:
    54 
    55    媒体状态  . . . . . . . . . . . . : 媒体已断开
    56    连接特定的 DNS 后缀 . . . . . . . : 
    57 
    58 隧道适配器 isatap.{E8300FF5-2E5A-4E63-8F58-33282553726B}:
    59 
    60    媒体状态  . . . . . . . . . . . . : 媒体已断开
    61    连接特定的 DNS 后缀 . . . . . . . : 
    62 
    63 >>:

    服务端显示:

     1 D:python35python.exe D:/python培训/s14/day8/sock_ssh_server.py
     2 一个新的连接: ('127.0.0.1', 62969)
     3 等待新指令
     4 执行指令: b'ipconfig'
     5 send before
     6 send done
     7 等待新指令
     8 执行指令: b'dir'
     9 send before
    10 send done
    11 等待新指令

    从上面我们可以看出,当我们执行ipconfig命令的时候,显示的内容并不全,当再次执行dir命令的时候刚才没有显示的部分,从这里也可以看出,ipconfig第一次没有显示的内容被放在了缓冲区里,当dir执行的时候,先将缓冲区的内容显示,而这个时候dir命令显示的内容又被存在了缓冲区中………

    所以需要解决上述问题:

    解决的思路就是在发送数据之前需要将要发送文件的大小先发送过去,这样客户端根据收到数据的大小和这个文件的大小比较,直到收完为止(切记一个问题,整数不能直接encode()必须转换成字符串)

    服务端代码如下:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 server = socket.socket()
     5 server.bind(('127.0.0.1',9999))
     6 server.listen()
     7 while True:
     8         conn,addr = server.accept()
     9         print("一个新的连接:",addr)
    10         while True:
    11             print("等待新指令")
    12             data = conn.recv(1024)
    13             if not data:
    14                 print("客户端已经断开")
    15                 break
    16             print("执行指令:",data)
    17             cmd_res = os.popen(data.decode()).read()
    18             print("send before")
    19             if len(cmd_res) == 0:
    20                 cmd_res = "cmd has no output......"
    21             conn.send(str(len(cmd_res)).encode())
    22             conn.send(cmd_res.encode())
    23             print("send done")
    24 server.close()

    客户端代码如下:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 client = socket.socket()
     5 
     6 client.connect(('127.0.0.1',9999))
     7 
     8 while True:
     9         cmd = input(">>:").strip()
    10         if len(cmd) == 0:continue
    11         client.send(cmd.encode("utf-8"))
    12         cmd_res_size = client.recv(1024)
    13         print("命令结果大小:",cmd_res_size)
    14     #received_data = b''
    15     received_data_size = 0
    16     while received_data_size < int(cmd_res_size.decode()):
    17         data = client.recv(1024)
    18         received_data_size+= len(data)
    19         #print(data.decode())
    20         #cmd_res = client.recv(1024)
    21         print(received_data_size)
    22         # print(cmd_res.decode())
    23     else:
    24         print("cmd res receive done")
    25 client.close()

    这个时候现将收到命令结果给注释,先打印收到服务端发送命令数据的大小,已经自己收到的数据累加的大小

    同样先启动服务端再启动客户端

    运行结果如下:(下面是客户端的测试结果)

     1 D:python35python.exe D:/python培训/s14/day8/sock_ssh_client.py
     2 >>:ipconfig
     3 命令结果大小: b'1494'
     4 1024
     5 1960
     6 cmd res receive done
     7 >>:dir
     8 命令结果大小: b'734'
     9 852
    10 cmd res receive done
    11 >>:ipconfig /all
    12 命令结果大小: b'4660'
    13 1024
    14 2048
    15 3072
    16 4096
    17 5120
    18 cmd res receive done
    19 >>

    从户可以看出,服务端发送给客户端命令结果的大小和客户端实际通过多次收到数据的总和不匹配,出现了客户端收到的数据大小大于服务器端发送过来的大小。

    将服务端代码放到linux系统上执行,在windows运行客户端,运行结果如下:

     1 D:python35python.exe D:/python培训/s14/day8/sock_ssh_client.py
     2 >>:pwd
     3 命令结果大小: b'11'
     4 11
     5 cmd res receive done
     6 >>:ifconfig
     7 命令结果大小: b'902'
     8 902
     9 cmd res receive done
    10 >>:

    从这里可以看出是相同的了,并且判断出是因为在windows下有中文,同时在服务端发送的时候,有点问题,服务端代码的问题是下面部分:

    conn.send(str(len(cmd_res)).encode())

    这个发送的时候应该先改为:

    conn.send(str(len(cmd_res.encode())).encode())

    这样就解决了汉字发送后大小不匹配的问题

    这样重新在windows上测试服务端程序和客户端程序:

    更改之后客户端的运行结果如下:

    1 D:python35python.exe D:/python培训/s14/day8/sock_ssh_client.py
    2 >>:ipconfig
    3 命令结果大小: b'1960'
    4 1024
    5 1960
    6 cmd res receive done
    7 >>:

    这样就完美的解决了之前的问题

    这样重新将服务端整理好的代码如下:

    服务端代码如下:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 server = socket.socket()
     5 server.bind(('127.0.0.1',9999))
     6 
     7 server.listen()
     8 
     9 while True:
    10         conn,addr = server.accept()
    11         print("一个新的连接:",addr)
    12         while True:
    13             print("等待新指令")
    14             data = conn.recv(1024)
    15             if not data:
    16                 print("客户端已经断开")
    17                 break
    18             print("执行指令:",data)
    19             cmd_res = os.popen(data.decode()).read()
    20             print("send before")
    21             if len(cmd_res) == 0:
    22                 cmd_res = "cmd has no output......"
    23             conn.send(str(len(cmd_res.encode())).encode())           
             conn.send(cmd_res.encode())
    24 print("send done") 25 server.close()

    客户端代码如下:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 client = socket.socket()
     5 client.connect(('127.0.0.1',9999))
     6 
     7 while True:
     8         cmd = input(">>:").strip()
     9         if len(cmd) == 0:continue
    10         client.send(cmd.encode("utf-8"))
    11         cmd_res_size = client.recv(1024)
    12         print("命令结果大小:",cmd_res_size)
    13         received_data = b''
    14         received_data_size = 0
    15         while received_data_size < int(cmd_res_size.decode()):
    16             data = client.recv(1024)
    17             received_data_size+= len(data)这里用len判断长度是因为服务器每次发来的不一定是1024,这个问题一定要注意
    18             print(received_data_size)
    19             received_data+=data
    20         else:
    21             print("cmd res receive done")
    22             print(received_data.decode())
    23 client.close()

    客户端运行结果如下:

     1 D:python35python.exe D:/python培训/s14/day8/sock_ssh_client.py
     2 >>:dir
     3 命令结果大小: b'852'
     4 852
     5 cmd res receive done
     6  驱动器 D 中的卷是 新加卷
     7  卷的序列号是 7095-8443
     8 
     9  D:python培训s14day8 的目录
    10 
    11 2016/09/12  23:25    <DIR>          .
    12 2016/09/12  23:25    <DIR>          ..
    13 2016/09/11  16:54    <DIR>          cc
    14 2016/05/12  17:25       181,238,643 jdk8.tar.gz
    15 2016/09/10  10:00    <DIR>          lib
    16 2016/09/12  23:25               681 sock_ssh_client.py
    17 2016/09/12  23:22               608 sock_ssh_server.py
    18 2016/09/10  09:34                11 __init__.py
    19 2016/09/10  10:26               292 动态导入模块.py
    20 2016/09/10  10:06                88 动态导入模块2.py
    21 2016/09/11  16:12               102 动态导入模块3.py
    22 2016/09/11  16:23               113 动态导入模块4.py
    23 2016/09/11  16:45                66 断言.py
    24                9 个文件    181,240,604 字节
    25                4 个目录 181,201,088,512 可用字节
    26 
    27 >>:ipconfig
    28 命令结果大小: b'1960'
    29 1024
    30 1960
    31 cmd res receive done
    32 
    33 Windows IP 配置
    34 
    35 
    36 以太网适配器 Bluetooth 网络连接:
    37 
    38    媒体状态  . . . . . . . . . . . . : 媒体已断开
    39    连接特定的 DNS 后缀 . . . . . . . : 
    40 
    41 以太网适配器 本地连接:
    42 
    43    媒体状态  . . . . . . . . . . . . : 媒体已断开
    44    连接特定的 DNS 后缀 . . . . . . . : 
    45 
    46 无线局域网适配器 无线网络连接:
    47 
    48    连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
    49    本地链接 IPv6 地址. . . . . . . . : fe80::85b7:6c65:f032:d29%12
    50    IPv4 地址 . . . . . . . . . . . . : 192.168.1.103
    51    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    52    默认网关. . . . . . . . . . . . . : 192.168.1.1
    53 
    54 以太网适配器 VMware Network Adapter VMnet1:
    55 
    56    连接特定的 DNS 后缀 . . . . . . . : 
    57    本地链接 IPv6 地址. . . . . . . . : fe80::9c7d:99a2:b09b:fa49%15
    58    IPv4 地址 . . . . . . . . . . . . : 10.0.10.22
    59    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    60    默认网关. . . . . . . . . . . . . : 
    61 
    62 以太网适配器 VMware Network Adapter VMnet8:
    63 
    64    连接特定的 DNS 后缀 . . . . . . . : 
    65    本地链接 IPv6 地址. . . . . . . . : fe80::4d7:4817:776d:6386%16
    66    IPv4 地址 . . . . . . . . . . . . : 172.16.1.200
    67    子网掩码  . . . . . . . . . . . . : 255.255.255.0
    68    默认网关. . . . . . . . . . . . . : 
    69 
    70 隧道适配器 isatap.DHCP HOST:
    71 
    72    媒体状态  . . . . . . . . . . . . : 媒体已断开
    73    连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
    74 
    75 隧道适配器 Teredo Tunneling Pseudo-Interface:
    76 
    77    媒体状态  . . . . . . . . . . . . : 媒体已断开
    78    连接特定的 DNS 后缀 . . . . . . . : 
    79 
    80 隧道适配器 isatap.{AEC4AF99-6E8C-4696-B0CE-1044479986E4}:
    81 
    82    媒体状态  . . . . . . . . . . . . : 媒体已断开
    83    连接特定的 DNS 后缀 . . . . . . . : 
    84 
    85 隧道适配器 isatap.{E8300FF5-2E5A-4E63-8F58-33282553726B}:
    86 
    87    媒体状态  . . . . . . . . . . . . : 媒体已断开
    88    连接特定的 DNS 后缀 . . . . . . . : 
    89 
    90 >>:

    这个时候命令结果显示正常,并且结果大小也显示正常

    但是当把程序放到linux上执行时发现出现问题了,客户端提示错误如下,并且提示错误后程序退出,服务端提示客户端断开连接:

    1 >>:ifconfig
    2 命令结果大小: b'906ens33     Link encap:Ethernet  HWaddr 00:0c:29:96:2f:bc  
              inet addr:192.168.1.105  Bcast:192.168.1.255  Mask:255.255.255.0
              inet6 addr: fe80::b011:2300:ecc8:c70d/64 Scope:Link
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:814292 errors:0 dropped:0 overruns:0 frame:0
              TX packets:65371 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:923448604 (923.4 MB)  TX bytes:4865728 (4.8 MB)
    
    lo        Link encap:Local Loopback  
              inet addr:127.0.0.1  Mask:255.0.0.0
              inet6 addr: ::1/128 Scope:Host
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:1444 errors:0 dropped:0 overruns:0 frame:0
              TX packets:1444 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1 
              RX bytes:115764 (115.7 KB)  TX bytes:115764 (115.7 KB)
    
    '
    3 Traceback (most recent call last):
    4   File "socket_ssh_client.py", line 16, in <module>
    5     while received_data_size < int(cmd_res_size.decode()):
    6 ValueError: invalid literal for int() with base 10: '906ens33     Link encap:Ethernet  HWaddr 00:0c:29:96:2f:bc  
              inet addr:192.168.1.105  Bcast:192.168.1.255  Mask:255.255.255.0
              inet6 addr: fe80::b011:2300:ecc8:c70d/64 Scope:Link
    7 root@python:~/0912#

    出现上述错误的原因是因为在服务端的两行代码:

    conn.send(str(len(cmd_res.encode())).encode())

    conn.send(cmd_res.encode())

    这两句代码连着发导致了粘包:

    两次连着send缓冲区会将两次的数据合并为一条发送给客户端从而导致数据粘在一起

    解决方法:

    服务端代码:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 server = socket.socket()
     5 server.bind(('127.0.0.1',9999))
     6 
     7 server.listen()
     8 
     9 while True:
    10         conn,addr = server.accept()
    11         print("一个新的连接:",addr)
    12         while True:
    13             print("等待新指令")
    14             data = conn.recv(1024)
    15             if not data:
    16                 print("客户端已经断开")
    17                 break
    18             print("执行指令:",data)
    19             cmd_res = os.popen(data.decode()).read()
    20             print("send before")
    21             if len(cmd_res) == 0:
    22                 cmd_res = "cmd has no output......"
    23             conn.send(str(len(cmd_res.encode())).encode())
    24             client_ack = conn.recv(1024)
    25             print("来自客户端的确认:",client_ack.decode())
    26             conn.send(cmd_res.encode())
    27             print("send done")
    28 server.close()

    客户端:

     1 #AUTHOR:FAN
     2 import socket,os
     3 
     4 client = socket.socket()
     5 
     6 client.connect(('127.0.0.1',9999))
     7 #client.connect(('192.168.1.105',9999))
     8 
     9 while True:
    10         cmd = input(">>:").strip()
    11         if len(cmd) == 0:continue
    12         client.send(cmd.encode("utf-8"))
    13         cmd_res_size = client.recv(1024)
    14         client.send("准备好接收数据了".encode())
    15         print("命令结果大小:",cmd_res_size)
    16         received_data = b''
    17         received_data_size = 0
    18         while received_data_size < int(cmd_res_size.decode()):
    19             data = client.recv(1024)
    20             received_data_size+= len(data)
    21             print(received_data_size)
    22             received_data+=data
    23         else:
    24             print("cmd res receive done")
    25             print(received_data.decode())
    26 client.close()

    这样就完美的解决了粘包的问题

    4、 SocketServer模块

    SocketServer内部使用IO多路复用以及“多线程”和“多进程”,从而实现并发处理多个客户端请求的Socket服务端,即:每个客户端请求连接到服务器时,Socket服务端都会在服务器上创建一个线程或进程专门负责处理当前客户端的所有请求

    关于SocketServer的使用:

    a. 必须自己创建一个请求处理类,并且这个类要继承BaseRequestHandler并且还要重写父类里的handle()

    b. 必须实例化TCPServer,并且传递server ip和上面创建的请求处理类给这个TCPServer

    c. server.handle_request()只处理一个请求

    server.server_forever()处理多个请求,永远执行

    d. 最后关闭socket

    下面是一个socketserver的一个例子:

    服务端:

     1 #AUTHOR:FAN
     2 
     3 import socketserver
     4 #对应上面所说的自己创建一个请求处理类并继承BaseRequestHandler
     5 class MyTCPHandler(socketserver.BaseRequestHandler):
     6 
     7 #对应上面的重写父类里的handle(),切记所有的交互都在handle()里面切记
     8 def handle(self):  
     9         while True:
    10             try:
    11                 self.data = self.request.recv(1024).strip()
    12                 print("{} wrote:".format(self.client_address[0]))
    13                 print(self.data)
    14                 self.request.sendall(self.data.upper())
    15             except ConnectionResetError as e:
    16                 print(e)
    17                 break
    18 if __name__ == "__main__":
    19         HOST,PORT = '127.0.0.1',9999
    20 #这里对应上面的实例化TCPServer,并传递server ip和上面创建的请求处理类,也就是MyTCPHandler
    21 server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)
    22 server.serve_forever()
  • 相关阅读:
    网上邻居无法打开的问题 客户端无法连接打印机问题
    今天一天下午到晚上都在研究如何刷手机,要是被领导知道我帮同学在刷手机系统,非开除我不可。还是贴出来,以后少走弯路吧
    “屏幕保护程序”没有出现“在恢复时使用密码保护”的解决方法
    NOD 32客户端安装时出现的问题
    [模板]二叉树的前、中、后序遍历
    [算法]浅谈求n范围以内的质数(素数)
    [模板]二叉搜索树
    [OI]Noip 2018 题解&总结(普及)
    Centos7.2安装git(源码安装)
    Centos7.2安装maven
  • 原文地址:https://www.cnblogs.com/zhaof/p/5875920.html
Copyright © 2011-2022 走看看