ssl 与 socket

第一次接触到这个问题是由于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))  

 
 

 

猜你喜欢

转载自chenying.iteye.com/blog/2390316
SSL