socket 以及 Python 实现

作者: 一块大番薯 | 来源:发表于2018-04-13 20:54 被阅读15次

    2018.04.14

    由于 TCP 协议非常复杂,三次握手、累积确认、分组缓存这些应该是属于操作系统内核的部分, 没必要重复开发。操作系统抽象出一个概念,让上层应用去编程。这个概念就是 socket

    一个完整的 socket 可以看成 (网络层协议,运输层协议, 客户端 IP, 客户端 Port, 服务器 IP, 服务器 Port)

    UDP

    recvfrom 返回的数据和地址。
    sendto 的参数也是数据和地址。

    # 服务器
    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(('localhost', 8888))
    while True:
        data, addr = s.recvfrom(1024)
        print('Received from {}, data: {}'.format(addr, data.decode('utf-8')))
        s.sendto(b'Hello!', addr)
    
    # 客户端
    import socket
    
    c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    c.sendto(b'good!', ('localhost', 8888))
    data = c.recv(1024)
    print(data.decode('utf-8'))
    

    TCP

    比 UDP 多了客户端连接,服务器监听,三次握手。

    客户端

    import socket
    
    clientfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clientfd.connect(('localhost', 8888))
    data = clientfd.recv(1024)
    print('Received: ' + data.decode('utf-8'))
    clientfd.send(b'Haved Received!')
    

    服务器

    import socket
    
    listenfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listenfd.bind(('localhost', 8888))
    listenfd.listen()
    
    while True:
        sock, addr = listenfd.accept()
        print('Connected to {}'.format(addr))
        sock.send(b'Hello, socket!')
        data = sock.recv(1024)  
        print('Received: ' + data.decode('utf-8'))
    
    • 这个 aceept 不简单,它与客户端的 connect 完成了三次握手。
    • 发送的数据是 bytes 型,接受后要解码。
    • sock 才是一个完整的 socket,而 clientfd 与 listenfd 都只是半个 socket。
    • listenfd 一般占用 80 端口(http 协议),每个客户端共享服务器这个端口,而每个连接(也就是程序中的 sock)依靠客户端的 IP 和 port 标识。

    这是最原始的服务器,它有个严重的弊端:recv 方法,如果客户端一直没有发数据过来,服务器就一直阻塞(block)在那里,于是多进程并发编程出现了。

    多进程

    思路:当 accept 连接后,创建的新 socket 不在主进程中处理,而是新创建一个子进程进行托管,主进程只负责监听 80 端口、接受连接请求、创建子进程处理。也就是 recv 的锅,accept 来背,哈哈。

    但是进程很多,每个进程都消耗大量的系统资源,况且进程间切换也是一笔大消耗。而一个 socket 就是一个文件描述符,是个整数,背后是一个简单的数据结构,用非常重量级的进程来对一个简单的数据结构进行读写,有点杀鸡用牛刀了!于是 select 模型诞生了!

    select 模型

    缕一缕前面:有两样东西,一个是 http server,另一个是操作系统,多进程是指 http server 占据操作系统的多个进程,并且每个进程遇到 recv 不到数据时就会阻塞。

    select 模型思路:现在还是只有 http server 和操作系统,但是现在 http server 只占据操作系统的一个进程。http server 把所有 socket 的 fd (文件描述符)交给操作系统,然后阻塞。虽然还是阻塞,但是现在只是单进程。而操作系统在后台检查这些编号的 socket,如果发现有 socket 可以读写,就把这个 socket 打个标记,然后唤醒 http server,http server 就会遍历所有的 socket,哪个有标记就处理哪个。

    但是,很多时候,活跃的 socket 只占少部分,而 http server 不得不遍历所有的 socket,这个不太好。最好的方式就是操作系统直接告诉 http server 哪些 socket 可以读写了,这样 http server 连遍历都不用了,这种方案就叫 epoll 。

    epoll 模型

    Nginx 采用的就是 epoll 模型,而 Apache 采用的是 select 。

    相关文章

      网友评论

        本文标题:socket 以及 Python 实现

        本文链接:https://www.haomeiwen.com/subject/gutqkftx.html