计算机网络我爱编程

socket 以及 Python 实现

2018-04-13  本文已影响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'))

这是最原始的服务器,它有个严重的弊端: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 。

上一篇下一篇

猜你喜欢

热点阅读