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) 

 

posted @ 2018-06-04 16:44  banshaohuan  阅读(164)  评论(0)    收藏  举报