socket常用方法:
服务端套接字函数 s.bind() 绑定(主机,端口号)到套接字 s.listen() 开始TCP监听 s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来 客户端套接字函数 s.connect() 主动初始化TCP服务器连接 s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 公共用途的套接字函数 s.recv() 接收TCP数据 s 相当于 conn s.send() 发送TCP数据 #(拆包在发送) 常用 s.sendall() 发送TCP数据 $(一次性发送) s.recvfrom() 接收UDP数据 s.sendto() 发送UDP数据 s.getpeername() 连接到当前套接字的远端的地址 s = conn s.getsockname() 当前套接字的地址 s = conn s.getsockopt() 返回指定套接字的参数 s.setsockopt() 设置指定套接字的参数 s.close() 关闭套接字 l例 #print(conn.getsockname()) 面向锁的套接字方法 s.setblocking() 设置套接字的阻塞与非阻塞模式 s.settimeout(5) 设置阻塞套接字操作的超时时间 #参数5指等待阻塞5秒,超过5秒报错 s.gettimeout() 得到阻塞套接字操作的超时时间 面向文件的套接字的函数 s.fileno() 套接字的文件描述符 s.makefile() 创建一个与该套接字相关的文件
send 和 sendall 的区别: 对于程序员来说,用起来是没有什么区别的 实际上,在socket底层对于两个方法的封装有却别: send(num) 此方法会尝试先发送n个数据(n<num),接下来再尝试发送num-n sendall(num) 此方法会尝试一次性将num个数据发送出去 (重点): setblocking(True) 阻塞 setblocking(False) 非阻塞 settimeout(int) 针对阻塞状态,设置一个延时等待 gettimeout() 获得延时的时间
服务器可以开多个端口,可以设置阻塞时间:
import socket sk = socket.socket() sk.setblocking(True) # 设置 阻塞非阻塞 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 允许一个端口号重用的解决方法 sk.bind(('127.0.0.1',9090)) sk.listen() while 1: try: conn,addr = sk.accept() print(123) conn.recv(1024) print(123) # while 1: # try: # msg = conn.recv(1024) # print(msg) # break # except BlockingIOError: # pass except BlockingIOError: print(789) # print(conn.getpeername()) # print(sk.getsockname()) # print(sk.getsockopt()) conn.close() sk.close() 服务器: import socket sk = socket.socket() sk.setblocking(True) # 设置 阻塞非阻塞 sk.settimeout(5) #阻塞5秒,超过5秒接收不到会报错 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 允许一个端口号重用的解决方法 sk.bind(('127.0.0.1',9090)) sk.listen() print(123) print(sk.gettimeout()) # 获取阻塞时间 conn,addr = sk.accept() print(456) conn.close() sk.close()
客户端: import socket import time sk = socket.socket() sk.connect_ex(('127.0.0.1',9090)) time.sleep(2) sk.send(b'qweqweqweqweqweqweqw') sk.close()
设置服务器客户端不用写encode,decode方法(调用类)
import socket class MySocket(socket.socket): def __init__(self,encoding = 'utf-8'): self.encoding = encoding super(MySocket, self).__init__(type=socket.SOCK_DGRAM) def my_recv(self,num): msg,addr = self.recvfrom(num) return msg.decode(self.encoding),addr def my_send(self,msg,addr): return self.sendto(msg.encode(self.encoding),addr)
验证客户连接:
方法1:固态加盐:
服务器: import socket import hashlib sk = socket.socket() sk.bind(('127.0.0.1',9090)) sk.listen() conn,addr = sk.accept() key = '天王盖地虎' # 这个是固定盐 ch = '这是一个随机字符串' conn.send(ch.encode('utf-8')) # 把随机字符串发给client md5_obj = hashlib.md5(key.encode('utf-8')) md5_obj.update(ch.encode('utf-8')) re = md5_obj.hexdigest() # 固定的用盐的加密方式 client_re = conn.recv(1024).decode('utf-8') # 接收client端加密后的结果 if re == client_re: print('你好机油!') '''收发数据的逻辑''' else: print('你根本不是老司机') conn.close() sk.close()
客户端: import socket import hashlib sk = socket.socket() sk.connect(('127.0.0.1',9090)) key = '天王盖地虎' ch = sk.recv(1024) md5_obj = hashlib.md5(key.encode('utf-8')) md5_obj.update(ch) re = md5_obj.hexdigest() sk.send(re.encode('utf-8')) sk.close()
方法2;用真实的随机字符串
import socket import hashlib import os sk = socket.socket() sk.bind(('127.0.0.1',9090)) sk.listen() conn,addr = sk.accept() key = '天王盖地虎' # 这个是固定盐 ch = os.urandom(10) conn.send(ch) # 把随机字符串发给client md5_obj = hashlib.md5(key.encode('utf-8')) md5_obj.update(ch) re = md5_obj.hexdigest() # 固定的用盐的加密方式 client_re = conn.recv(1024).decode('utf-8') # 接收client端加密后的结果 if re == client_re: print('你好机油!') '''收发数据的逻辑''' else: print('你根本不是老司机') conn.close() sk.close()
客户端: import socket import hashlib sk = socket.socket() sk.connect(('127.0.0.1',9090)) key = '天王盖地虎' ch = sk.recv(1024) md5_obj = hashlib.md5(key.encode('utf-8')) md5_obj.update(ch) re = md5_obj.hexdigest() sk.send(re.encode('utf-8')) sk.close()
方法3:用hmac模块实现机密:
服务器: import socket import hashlib import os import hmac sk = socket.socket() sk.bind(('127.0.0.1',9090)) sk.listen() conn,addr = sk.accept() key = '天王盖地虎' # 这个是固定盐 ch = os.urandom(10) conn.send(ch) # 把随机字符串发给client obj = hmac.new(key.encode('utf-8'),ch) re = obj.digest() # 固定的用盐的加密方式 client_re = conn.recv(1024) # 接收client端加密后的结果 if re == client_re: print('你好机油!') '''收发数据的逻辑''' else: print('你根本不是老司机') conn.close() sk.close()
客户端: import socket import hashlib import hmac sk = socket.socket() sk.connect(('127.0.0.1',9090)) key = '天王盖地虎' ch = sk.recv(1024) obj = hmac.new(key.encode('utf-8'),ch) re = obj.digest() sk.send(re) sk.close()
hmac模块(算法模块) 定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。 使用hmac和普通hash算法非常类似。hmac输出的长度和原始哈希算法的长度一致。需要注意传入的key和message都是bytes类型,str类型需要首先编码为bytes。 HMAC的应用 hmac主要应用在身份验证中,它的使用方法是这样的: (1) 客户端发出登录请求(假设是浏览器的GET请求) (2) 服务器返回一个随机值,并在会话中记录这个随机值 (3) 客户端将该随机值作为密钥,用户密码进行hmac运算,然后提交给服务器 (4) 服务器读取用户数据库中的用户密码和步骤2中发送的随机值做与客户端一样的hmac运算,然后与用户发送的结果比较,如果结果一致则验证用户合法 在这个过程中,可能遭到安全攻击的是服务器发送的随机值和用户发送的hmac结果,而对于截获了这两个值的黑客而言这两个值是没有意义的,绝无获取用户密码的可能性,随机值的引入使hmac只在当前会话中有效,大大增强了安全性和实用性。大多数的语言都实现了hmac算法,比如php的mhash、python的hmac.py、java的MessageDigest类,在web验证中使用hmac也是可行的,用js进行md5运算的速度也是比较快的。
socketserver模块 (解决tcp)
现在没有学习并发编程,现在解决不了tcp协议中一个服务器同时连接多个客户端
socketserver,看其名字,就知道是一个socket的服务器模块的使用,在这个模块中,主要也就是实现服务器类的相关功能,在其中,也就是将socket模块和select模块进行了封装,从而创建了一些基类供人使用。
SocketServer内部使用 IO多路复用以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。
原理: 1、服务器启动socket监听端口 2、服务器内部利用while循环监视句柄的变化 3、客户端请求 4、服务器为这个请求分配线程或进程(底层调用select)。 SocketServer模块有两个方法ThreadingTCPServer和ForkingTCPServer,分别创建线程或者进程
服务器: import socketserver #sk conn import json import hashlib class Myserver(socketserver.BaseRequestHandler): def handle(self): while 1: dic_str = self.request.recv(1024).decode('utf-8') # 接收到序列化的字典 dic = json.loads(dic_str) # 反序列化字典,字典中有用户名和密码 # 用户名当盐 加上密码去做md5 with open('info',encoding='utf-8') as f: for user_info in f:# 旭哥 | 7d79a61dd0bd94a3df2f765ac12fe492 username,pawd = user_info.split('|') if username.strip() == dic['username']: # 先去对比用户名正确与否 '''如果用户名存在的情况下 加密方式 : 用户名当盐,对密码加密''' md5_obj = hashlib.md5(dic['username'].encode('utf-8')) md5_obj.update(dic['passwd'].encode('utf-8')) re = md5_obj.hexdigest() if re == pawd.strip(): # 拿加密完的密码密文对比文件中密码的密文 self.request.send(b'success!') '''通信的逻辑''' else: '''失败,返回给客户端信息.....''' self.request.send(b'failed!') break else: '''对应for 如果用户名不存在''' print('用户不存在!') server = socketserver.TCPServer(('127.0.0.1',9090),Myserver) # 绑定一个服务 server.serve_forever() # 永久性开启服务
客户端: import socket import time import json sk = socket.socket() sk.connect(('127.0.0.1',9090)) dic = {'username':None,'passwd':None} while 1: username = input('用户名>>>') passwd = input('密码>>>') dic['username'] = username dic['passwd'] = passwd dic_str = json.dumps(dic) sk.send(dic_str.encode('utf-8')) print(sk.recv(1024)) sk.close()