• socket编程


    socket编程

    林老师讲socket
    netstat -an |grep 8080 监听端口

    一 基于TCP的套接字

    tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

    ss = socket() #创建服务器套接字
    ss.bind()      #把地址绑定到套接字
    ss.listen()      #监听链接
    inf_loop:      #服务器无限循环
        cs = ss.accept() #接受客户端链接
        comm_loop:         #通讯循环
            cs.recv()/cs.send() #对话(接收与发送)
        cs.close()    #关闭客户端套接字
    ss.close()        #关闭服务器套接字(可选)
    
    cs = socket()    # 创建客户套接字
    cs.connect()    # 尝试连接服务器
    comm_loop:        # 通讯循环
        cs.send()/cs.recv()    # 对话(发送/接收)
    cs.close()            # 关闭客户套接字
    

    二 基于UDP的套接字

    udp是无连接的,先启动哪一端都不会报错

    1 ss = socket()   #创建一个服务器的套接字
    2 ss.bind()       #绑定服务器套接字
    3 inf_loop:       #服务器无限循环
    4     cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    5 ss.close()                         # 关闭服务器套接字
    
    cs = socket()   # 创建客户套接字
    comm_loop:      # 通讯循环
        cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
    cs.close()   
    

    三 基于tcp制作一个远程执行命令程序

    注意:

    res=subprocess.Popen(cmd.decode('utf-8'),
                        shell=True,
                        stderr=subprocess.PIPE,
                        stdout=subprocess.PIPE)
    

    的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

    且只能从管道里读一次结果

    注意:命令ls -l ; lllllll ; pwd的结果是既有正确stdout结果,又有错误stderr结果
    服务端

    1. from socket import
    2. import subprocess 
    3. ip_port = ('127.0.0.1', 8080
    4. back_log = 5 
    5. buffer_size = 1024 
    6.  
    7. tcp_server = socket(AF_INET, SOCK_STREAM) #=> 选择TCP模式 
    8. tcp_server.bind(ip_port) #=> 绑定服务端地址和端口 
    9. tcp_server.listen(back_log) #=> 开始监听 
    10.  
    11. while True
    12. conn, addr = tcp_server.accept() 
    13. print('新的客户端链接', addr) 
    14. while True
    15. # 收 
    16. try
    17. cmd = conn.recv(buffer_size) 
    18. if not cmd:break 
    19. print('收到客户端的命令', cmd) 
    20.  
    21. # 执行命令,得到命令的运行结果cmd_res 
    22. #=> 传过来的是字节,需要用decode解码,五个参数 
    23. res = subprocess.Popen(cmd.decode('utf-8'),shell=True
    24. stderr=subprocess.PIPE, 
    25. stdout=subprocess.PIPE, 
    26. stdin=subprocess.PIPE) 
    27. err = res.stderr.read() #=> stderr保存的是错误信息 
    28. #=> 如果出错将报错信息赋值给cmd_res,否则将输出内容赋值 
    29. if err: 
    30. cmd_res = err 
    31. else
    32. cmd_res = res.stdout.read() #=> stdout保存的是输出信息 
    33.  
    34. # 发 
    35. if not cmd_res: #=> 如果cmd_res不为空,发送给客户端 
    36. cmd_res = '执行成功'.encode('gbk') #=> 发送时要encode,Windows下中文编码为gbk 
    37. conn.send(cmd_res) 
    38. except Exception as e: 
    39. print(e) 
    40. break 

    客户端

    1. from socket import
    2. ip_port = ('127.0.0.1', 8080
    3. back_log = 5 
    4. buffer_size = 1024 
    5.  
    6. tcp_client = socket(AF_INET, SOCK_STREAM) 
    7. tcp_client.connect(ip_port) #=> 服务端是绑定ip_port,而客户端是连接,connect 
    8.  
    9. #=> 用到while True的时候考虑好break和continue的条件 
    10. while True
    11. cmd = input('>>:').strip() 
    12. if not cmd:continue 
    13. if cmd == 'quit':break 
    14.  
    15. tcp_client.send(cmd.encode('utf-8')) #=> 发送时同样要encode 
    16. cmd_res = tcp_client.recv(buffer_size) #=> 客户端、服务端接收时只需要传入buffer_size 
    17. print('命令执行的结果是', cmd_res.decode('gbk')) #=> 收到的数据必须decode,而且要与encode时的编码一致 
    18.  
    19. tcp_client.close()#=>最后要关闭连接 

    四 粘包现象

    客户端提走的数据,后一次提走的数据可能并不是这次请求所获的数据,而是前一次请求得到数据所剩下的。
    只有TCP有粘包现象,UDP永远不会粘包
    发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
    所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
    此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
    1.TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    2.UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
    3.tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

    五 解决粘包的lowB方法

    问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

    解决粘包-服务端

    1. from socket import
    2. import subprocess 
    3. ip_port = ('127.0.0.1', 8080
    4. back_log = 5 
    5. buffer_size = 1024 
    6.  
    7. tcp_server = socket(AF_INET, SOCK_STREAM) #=> 选择TCP模式 
    8. tcp_server.bind(ip_port) #=> 绑定服务端地址和端口 
    9. tcp_server.listen(back_log) #=> 开始监听 
    10.  
    11. while True
    12. conn, addr = tcp_server.accept() 
    13. print('新的客户端链接', addr) 
    14. while True
    15. # 收 
    16. try
    17. cmd = conn.recv(buffer_size) 
    18. if not cmd:break 
    19. print('收到客户端的命令', cmd) 
    20.  
    21. # 执行命令,得到命令的运行结果cmd_res 
    22. #=> 传过来的是字节,需要用decode解码,五个参数 
    23. res = subprocess.Popen(cmd.decode('utf-8'),shell=True
    24. stderr=subprocess.PIPE, 
    25. stdout=subprocess.PIPE, 
    26. stdin=subprocess.PIPE) 
    27. err = res.stderr.read() #=> stderr保存的是错误信息 
    28. #=> 如果出错将报错信息赋值给cmd_res,否则将输出内容赋值 
    29. if err: 
    30. cmd_res = err 
    31. else
    32. cmd_res = res.stdout.read() #=> stdout保存的是输出信息 
    33.  
    34. # 发 
    35. if not cmd_res: #=> 如果cmd_res不为空,发送给客户端 
    36. cmd_res = '执行成功'.encode('gbk') #=> 发送时要encode,Windows下中文编码为gbk 
    37. length = len(cmd_res)#=>执行命令后要返回内容的长度 
    38. conn.send(str(length).encode('utf-8'))#=>将长度发送给客户端 
    39. client_ready = conn.recv(buffer_size)#=>用来接收客户端准备好接收的指令 
    40. if client_ready == b'ready':#=>确定客户端准备接收后发送内容 
    41. conn.send(cmd_res) 
    42. except Exception as e: 
    43. print(e) 
    44. break 

    解决粘包-客户端

    1. from socket import
    2. ip_port = ('127.0.0.1', 8080
    3. back_log = 5 
    4. buffer_size = 1024 
    5.  
    6. tcp_client = socket(AF_INET, SOCK_STREAM) 
    7. tcp_client.connect(ip_port) #=> 服务端是绑定ip_port,而客户端是连接,connect 
    8.  
    9. #=> 用到while True的时候考虑好break和continue的条件 
    10. while True
    11. cmd = input('>>:').strip() 
    12. if not cmd:continue 
    13. if cmd == 'quit':break 
    14.  
    15. tcp_client.send(cmd.encode('utf-8')) #=> 发送时同样要encode 
    16.  
    17. # 解决粘包 
    18. length = tcp_client.recv(buffer_size) #=>从服务端接收到返回内容的长度 
    19. print('========>', length) 
    20. tcp_client.send(b'ready')#=>提示服务端客户端已做好接收返回内容的准备 
    21. length = int(length.decode('utf-8'))#=>接收的length是字节,转成int 
    22. recv_size = 0#=>表示已接收的内容长度 
    23. recv_msg = b'' 
    24. #=>不断接收内容直到接收完毕 
    25. while recv_size < length: 
    26. recv_msg += tcp_client.recv(buffer_size)#=>将接收到的内容接在后面 
    27. recv_size = len(recv_msg)#=>更新已经接收到的长度 
    28. # cmd_res = tcp_client.recv(buffer_size) #=> 客户端、服务端接收时只需要传入buffer_size 
    29. print('命令执行的结果是', recv_msg.decode('gbk')) #=> 收到的数据必须decode,而且要与encode时的编码一致 
    30. tcp_client.close()#=>最后要关闭连接 

    为何low:
    程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

    六 解决粘包的NB方法

    1 方法整体说明
    将整体分成两部分,为字节流加上自定义固定长度报头,报头中只包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头(接收固定值的字节流),然后再取真实数据

    我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

    发送时:
    先发报头长度
    再编码报头内容然后发送
    最后发真实内容

    接收时:
    先手报头长度,用struct取出来
    根据取出的长度收取报头内容,然后解码,反序列化
    从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

    2 用到的知识点

    struct模块

    该模块可以把一个类型,如数字,转成固定长度的bytes
    利用里面的函数struct.pack(fmt, values),第一个参数设置为'i'代表转成int长度的值

    struct.pack('i',1111111111111)

     

    详细对应关系

    详细对应关系

    iter()的高级用法

     

    iter(object, sentinel)
    第一个参数是要迭代的对象,第二个参数是当输出值为这个参数时停止迭代。

    functools中的partial

    partial函数用于固定函数执行时的第一个参数
    第一个参数为要执行的函数,第二个参数为要传给执行函数的第一个固定参数。

    服务端

    1. from socket import
    2. import subprocess 
    3. import struct 
    4. ip_port = ('127.0.0.1', 8080
    5. back_log = 5 
    6. buffer_size = 1024 
    7.  
    8. tcp_server = socket(AF_INET, SOCK_STREAM) #=> 选择TCP模式 
    9. tcp_server.bind(ip_port) #=> 绑定服务端地址和端口 
    10. tcp_server.listen(back_log) #=> 开始监听 
    11.  
    12. while True
    13. conn, addr = tcp_server.accept() 
    14. print('新的客户端链接', addr) 
    15. while True
    16. # 收 
    17. try
    18. cmd = conn.recv(buffer_size) 
    19. if not cmd:break 
    20. print('收到客户端的命令', cmd) 
    21.  
    22. # 执行命令,得到命令的运行结果cmd_res 
    23. #=> 传过来的是字节,需要用decode解码,五个参数 
    24. res = subprocess.Popen(cmd.decode('utf-8'),shell=True
    25. stderr=subprocess.PIPE, 
    26. stdout=subprocess.PIPE, 
    27. stdin=subprocess.PIPE) 
    28. err = res.stderr.read() #=> stderr保存的是错误信息 
    29. #=> 如果出错将报错信息赋值给cmd_res,否则将输出内容赋值 
    30. if err: 
    31. cmd_res = err 
    32. else
    33. cmd_res = res.stdout.read() #=> stdout保存的是输出信息 
    34.  
    35. # 发 
    36. if not cmd_res: #=> 如果cmd_res不为空,发送给客户端 
    37. cmd_res = '执行成功'.encode('gbk') #=> 发送时要encode,Windows下中文编码为gbk 
    38. length = len(cmd_res)#=>执行命令后要返回内容的长度 
    39. # conn.send(str(length).encode('utf-8'))#=>将长度发送给客户端 
    40. # client_ready = conn.recv(buffer_size)#=>用来接收客户端准备好接收的指令 
    41. # if client_ready == b'ready':#=>确定客户端准备接收后发送内容 
    42. data_length = struct.pack('i', length)#=>将返回内容的长度打包成int的长度,即4bytes 
    43. conn.send(data_length) 
    44. conn.send(cmd_res) 
    45. except Exception as e: 
    46. print(e) 
    47. break 
    48.  

    客户端

    1. from socket import
    2. import struct 
    3. from functools import partial 
    4. ip_port = ('127.0.0.1', 8080
    5. back_log = 5 
    6. buffer_size = 1024 
    7.  
    8. tcp_client = socket(AF_INET, SOCK_STREAM) 
    9. tcp_client.connect(ip_port) #=> 服务端是绑定ip_port,而客户端是连接,connect 
    10.  
    11. #=> 用到while True的时候考虑好break和continue的条件 
    12. while True
    13. cmd = input('>>:').strip() 
    14. if not cmd:continue 
    15. if cmd == 'quit':break 
    16.  
    17. tcp_client.send(cmd.encode('utf-8')) #=> 发送时同样要encode 
    18.  
    19. # 解决粘包 
    20. # length = tcp_client.recv(buffer_size) #=>从服务端接收到返回内容的长度 
    21. # tcp_client.send(b'ready')#=>提示服务端客户端已做好接收返回内容的准备 
    22. # length = int(length.decode('utf-8'))#=>接收的length是字节,转成int 
    23. # recv_size = 0#=>表示已接收的内容长度 
    24. # recv_msg = b'' 
    25. # #=>不断接收内容直到接收完毕 
    26. # while recv_size < length: 
    27. # recv_msg += tcp_client.recv(buffer_size)#=>将接收到的内容接在后面 
    28. # recv_size = len(recv_msg)#=>更新已经接收到的长度 
    29. # cmd_res = tcp_client.recv(buffer_size) #=> 客户端、服务端接收时只需要传入buffer_size 
    30. #新解决粘包方法 
    31. length_data = tcp_client.recv(4)#=>先开始接收4个字节,这4个字节肯定是内容长度信息 
    32. length = struct.unpack('i', length_data)[0]#=>解压之后是个tuple,第一项是值 
    33. # =>jion函数可以作用于迭代器,将多次接收的信息连接到一起,相当于for循环 
    34. recv_msg = ''.join(iter(partial(tcp_client.recv, buffer_size), b'')) 
    35. print('命令执行的结果是', recv_msg.decode('gbk')) #=> 收到的数据必须decode,而且要与encode时的编码一致 
    36.  
    37. tcp_client.close()#=>最后要关闭连接 

    七 认证客户端链接的合法性

    利用hmac+加盐的方式来实现

    客户端

    1. #!/usr/bin/env python  
    2. #-*- coding:utf-8 -*-  
    3. """  
    4. @author:BanShaohuan 
    5. @file: server_合法.py  
    6. @time: 2018/06/04 
    7. @contact: banshaohuan@163.com 
    8. @software: PyCharm  
    9. """  
    10.  
    11. from socket import
    12. import hmac,os 
    13.  
    14. secret_key=b'banshaohuan' 
    15. def conn_auth(conn): 
    16. ''' 
    17. 认证客户端链接 
    18. :param conn: 
    19. :return: 
    20. ''' 
    21. print('开始验证新链接的合法性'
    22. msg=os.urandom(32
    23. conn.sendall(msg) 
    24. h=hmac.new(secret_key,msg) 
    25. digest=h.digest() 
    26. respone=conn.recv(len(digest)) 
    27. return hmac.compare_digest(respone,digest) 
    28.  
    29. def data_handler(conn,bufsize=1024): 
    30. if not conn_auth(conn): 
    31. print('该链接不合法,关闭'
    32. conn.close() 
    33. return 
    34. print('链接合法,开始通信'
    35. while True
    36. data=conn.recv(bufsize) 
    37. if not data:break 
    38. conn.sendall(data.upper()) 
    39.  
    40. def server_handler(ip_port,bufsize,backlog=5): 
    41. ''' 
    42. 只处理链接 
    43. :param ip_port: 
    44. :return: 
    45. ''' 
    46. tcp_socket_server=socket(AF_INET,SOCK_STREAM) 
    47. tcp_socket_server.bind(ip_port) 
    48. tcp_socket_server.listen(backlog) 
    49. while True
    50. conn,addr=tcp_socket_server.accept() 
    51. print('新连接[%s:%s]' %(addr[0],addr[1])) 
    52. data_handler(conn,bufsize) 
    53.  
    54. if __name__ == '__main__'
    55. ip_port=('127.0.0.1',9999
    56. bufsize=1024 
    57. server_handler(ip_port,bufsize) 
    58.  

    合法客户端

    1. #!/usr/bin/env python  
    2. #-*- coding:utf-8 -*-  
    3. """  
    4. @author:BanShaohuan 
    5. @file: client_合法.py  
    6. @time: 2018/06/04 
    7. @contact: banshaohuan@163.com 
    8. @software: PyCharm  
    9. """  
    10. #_*_coding:utf-8_*_ 
    11. from socket import
    12. import hmac,os 
    13.  
    14. secret_key=b'banshaohuan' 
    15. def conn_auth(conn): 
    16. ''' 
    17. 验证客户端到服务器的链接 
    18. :param conn: 
    19. :return: 
    20. ''' 
    21. msg=conn.recv(32
    22. h=hmac.new(secret_key,msg) 
    23. digest=h.digest() 
    24. conn.sendall(digest) 
    25.  
    26. def client_handler(ip_port,bufsize=1024): 
    27. tcp_socket_client=socket(AF_INET,SOCK_STREAM) 
    28. tcp_socket_client.connect(ip_port) 
    29.  
    30. conn_auth(tcp_socket_client) 
    31.  
    32. while True
    33. data=input('>>: ').strip() 
    34. if not data:continue 
    35. if data == 'quit':break 
    36.  
    37. tcp_socket_client.sendall(data.encode('utf-8')) 
    38. respone=tcp_socket_client.recv(bufsize) 
    39. print(respone.decode('utf-8')) 
    40. tcp_socket_client.close() 
    41.  
    42. if __name__ == '__main__'
    43. ip_port=('127.0.0.1',9999
    44. bufsize=1024 
    45. client_handler(ip_port,bufsize) 

    客户端非法:不知道加密方式

    1. #!/usr/bin/env python  
    2. #-*- coding:utf-8 -*-  
    3. """  
    4. @author:BanShaohuan 
    5. @file: client_非法.py  
    6. @time: 2018/06/04 
    7. @contact: banshaohuan@163.com 
    8. @software: PyCharm  
    9. """  
    10. #_*_coding:utf-8_*_ 
    11. from socket import
    12.  
    13. def client_handler(ip_port,bufsize=1024): 
    14. tcp_socket_client=socket(AF_INET,SOCK_STREAM) 
    15. tcp_socket_client.connect(ip_port) 
    16.  
    17. while True
    18. data=input('>>: ').strip() 
    19. if not data:continue 
    20. if data == 'quit':break 
    21.  
    22. tcp_socket_client.sendall(data.encode('utf-8')) 
    23. respone=tcp_socket_client.recv(bufsize) 
    24. print(respone.decode('utf-8')) 
    25. tcp_socket_client.close() 
    26.  
    27. if __name__ == '__main__'
    28. ip_port=('127.0.0.1',9999
    29. bufsize=1024 
    30. client_handler(ip_port,bufsize) 

    客户端非法:不知道secret_key

    1. #!/usr/bin/env python  
    2. #-*- coding:utf-8 -*-  
    3. """  
    4. @author:BanShaohuan 
    5. @file: client非法_2.py  
    6. @time: 2018/06/04 
    7. @contact: banshaohuan@163.com 
    8. @software: PyCharm  
    9. """  
    10.  
    11. #_*_coding:utf-8_*_ 
    12. from socket import
    13. import hmac,os 
    14.  
    15. secret_key=b'banshaohuan111' 
    16. def conn_auth(conn): 
    17. ''' 
    18. 验证客户端到服务器的链接 
    19. :param conn: 
    20. :return: 
    21. ''' 
    22. msg=conn.recv(32
    23. h=hmac.new(secret_key,msg) 
    24. digest=h.digest() 
    25. conn.sendall(digest) 
    26.  
    27. def client_handler(ip_port,bufsize=1024): 
    28. tcp_socket_client=socket(AF_INET,SOCK_STREAM) 
    29. tcp_socket_client.connect(ip_port) 
    30.  
    31. conn_auth(tcp_socket_client) 
    32.  
    33. while True
    34. data=input('>>: ').strip() 
    35. if not data:continue 
    36. if data == 'quit':break 
    37.  
    38. tcp_socket_client.sendall(data.encode('utf-8')) 
    39. respone=tcp_socket_client.recv(bufsize) 
    40. print(respone.decode('utf-8')) 
    41. tcp_socket_client.close() 
    42.  
    43. if __name__ == '__main__'
    44. ip_port=('127.0.0.1',9999
    45. bufsize=1024 
    46. client_handler(ip_port,bufsize) 
  • 相关阅读:
    Canvas简单验证码识别
    原生JS操作class 极致版
    JS实现——体彩足球预测分析
    JS实现——俄罗斯方块
    JS实现——用3L和5L量出4L的水
    pygame躲敌人的游戏
    ubuntu14.04 在自带python2.7上安装python3.3.5 可以用但是有问题
    ubuntu14.04 pygame安装 python2.7
    各类书籍汇总
    python调用java jython
  • 原文地址:https://www.cnblogs.com/banshaohuan/p/9134144.html
Copyright © 2020-2023  润新知