第一次接触到这个问题是由于tornado。在tornado.netutil中发现将socket包装成ssl socket的函数,原来可以这样写。恩, 学识浅薄。
Socket
socket对TCP/IP协议的封装和应用, 是程序员层面的。关于协议, 推荐豆瓣阅读上的《协议森林》,很不错, 适合了解一下。
python socket:
__init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
family是协议域, 又称协议族。 协议族决定socket的地址类型,在通信中必须采用对应地址
常用的有:
AF_INET 决定要用ipv4的地址32位, 与端口号(16位的组合)
AF_INET6
AF_LOCAL 或称AF_UNIX UNIX域socket AF_UNIX决定要用一个绝对路径作为地址
AF_ROUTE
type是socket的类型
常用的有:
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
SOCK_PACKET
SOCK_SEQPACKET
proto指定协议
IPPROTO_TCP tcp协议
IPPROTO_UDP udp协议
IPPROTO_SCTP sctp协议
IPPROTO_TIPC tipc协议
个人认为, socket就是对传输协议的封装, 使用sockt可以不用关注tcp等协议数据的处理,更接近程序员。
socket还可以结合epoll使用, 参考http://blog.csdn.net/songfreeman/article/details/51179213:
#!/usr/bin/env python import select import socket response = b'' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.bind(('0.0.0.0', 8080)) serversocket.listen(1) # 因为socket默认是阻塞的,所以需要使用非阻塞(异步)模式。 serversocket.setblocking(0) # 创建一个epoll对象 epoll = select.epoll() # 在服务端socket上面注册对读event的关注。一个读event随时会触发服务端socket去接收一个socket连接 epoll.register(serversocket.fileno(), select.EPOLLIN) try: # 字典connections映射文件描述符(整数)到其相应的网络连接对象 connections = {} requests = {} responses = {} while True: # 查询epoll对象,看是否有任何关注的event被触发。参数“1”表示,我们会等待1秒来看是否有event发生。 # 如果有任何我们感兴趣的event发生在这次查询之前,这个查询就会带着这些event的列表立即返回 events = epoll.poll(1) # event作为一个序列(fileno,event code)的元组返回。fileno是文件描述符的代名词,始终是一个整数。 for fileno, event in events: # 如果是服务端产生event,表示有一个新的连接进来 if fileno == serversocket.fileno(): connection, address = serversocket.accept() print('client connected:', address) # 设置新的socket为非阻塞模式 connection.setblocking(0) # 为新的socket注册对读(EPOLLIN)event的关注 epoll.register(connection.fileno(), select.EPOLLIN) connections[connection.fileno()] = connection # 初始化接收的数据 requests[connection.fileno()] = b'' # 如果发生一个读event,就读取从客户端发送过来的新数据 elif event & select.EPOLLIN: print("------recvdata---------") # 接收客户端发送过来的数据 requests[fileno] += connections[fileno].recv(1024) # 如果客户端退出,关闭客户端连接,取消所有的读和写监听 if not requests[fileno]: connections[fileno].close() # 删除connections字典中的监听对象 del connections[fileno] # 删除接收数据字典对应的句柄对象 del requests[connections[fileno]] print(connections, requests) epoll.modify(fileno, 0) else: # 一旦完成请求已收到,就注销对读event的关注,注册对写(EPOLLOUT)event的关注。写event发生的时候,会回复数据给客户端 epoll.modify(fileno, select.EPOLLOUT) # 打印完整的请求,证明虽然与客户端的通信是交错进行的,但数据可以作为一个整体来组装和处理 print('-' * 40 + '\n' + requests[fileno].decode()) # 如果一个写event在一个客户端socket上面发生,它会接受新的数据以便发送到客户端 elif event & select.EPOLLOUT: print("-------send data---------") # 每次发送一部分响应数据,直到完整的响应数据都已经发送给操作系统等待传输给客户端 byteswritten = connections[fileno].send(requests[fileno]) requests[fileno] = requests[fileno][byteswritten:] if len(requests[fileno]) == 0: # 一旦完整的响应数据发送完成,就不再关注写event epoll.modify(fileno, select.EPOLLIN) # HUP(挂起)event表明客户端socket已经断开(即关闭),所以服务端也需要关闭。 # 没有必要注册对HUP event的关注。在socket上面,它们总是会被epoll对象注册 elif event & select.EPOLLHUP: print("end hup------") # 注销对此socket连接的关注 epoll.unregister(fileno) # 关闭socket连接 connections[fileno].close() del connections[fileno] finally: # 打开的socket连接不需要关闭,因为Python会在程序结束的时候关闭。这里显式关闭是一个好的代码习惯 epoll.unregister(serversocket.fileno()) epoll.close() serversocket.close()
SSL
ssl个人喜欢把它理解为一种加密协议。采用对称算法进行加密。SSL协议的版本只有1和2只提供服务器认证, 版本3添加了客户端认证,此认证同时需要客户端和服务器的数字证书。
盗图:参考https://www.wosign.com/Basic/howsslwork.htm
tornado中将socket包装成ssl的代码:
#定义SSL的一些参数 _SSL_CONTEXT_KEYWORDS = frozenset(['ssl_version', 'certfile', 'keyfile', 'cert_reqs', 'ca_certs', 'ciphers']) def ssl_options_to_context(ssl_options): """尝试将ssl的option参数转化为ssl.SSLContext对象""" if isinstance(ssl_options, dict): # 要求ssl option的参数必须是_SSL_CONTEXT_KEYWORDS预定义的 assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options if (not hasattr(ssl, 'SSLContext') or isinstance(ssl_options, ssl.SSLContext)): return ssl_options context = ssl.SSLContext( ssl_options.get('ssl_version', ssl.PROTOCOL_SSLv23)) if 'certfile' in ssl_options: context.load_cert_chain(ssl_options['certfile'], ssl_options.get('keyfile', None)) if 'cert_reqs' in ssl_options: context.verify_mode = ssl_options['cert_reqs'] if 'ca_certs' in ssl_options: context.load_verify_locations(ssl_options['ca_certs']) if 'ciphers' in ssl_options: context.set_ciphers(ssl_options['ciphers']) if hasattr(ssl, 'OP_NO_COMPRESSION'): context.options |= ssl.OP_NO_COMPRESSION return context def ssl_wrap_socket(socket, ssl_options, server_hostname=None, **kwargs): #包装函数 context = ssl_options_to_context(ssl_options) if hasattr(ssl, 'SSLContext') and isinstance(context, ssl.SSLContext): if server_hostname is not None and getattr(ssl, 'HAS_SNI'): return context.wrap_socket(socket, server_hostname=server_hostname, **kwargs) else: return context.wrap_socket(socket, **kwargs) else: return ssl.wrap_socket(socket, **dict(context, **kwargs))