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 。
网友评论