socket
整个计算机网络是由协议构成, 想要通信必须遵守对应的协议, 如 web 中的 http 协议, 传输协议 TCP 和 UDP 等等. 在网络工程师的眼中, 可能现在网络上的一切都是 socket, 一切皆 socket, 我们一般接触到的是应用层应用程序, 本质上两个应用想通信, 则必须通过 socket 实现通信, socket 直接和传输层后下面的底层网络协议打交道 (socket 本身让我们直接与 TCP 打交道), 底层 socket 已经建立好则可以互相通信. 互联网现在主流的网络层协议是 IPv4,IPv6 是下一代网络层协议但不主流, IPv6 解决的是 IPv4 地址耗尽的问题, 其实为了应对 IPv4 资源少的问题产生了局域网和网关.
网络模型
其发展过程, 是一次解决需求的迭代过程. 当计算机刚发明并投入使用, 两台计算机想实现点对点通信, 于是产生了数据链路层, 当加入更多的计算机实现通信的时候, 就产生了网络层, 实现通信还不能满足需求, 需要通过网络传输数据, 则产生了传输层, 对于可靠性的需求产生了 TCP 和 UDP 两种传输协议, 不同的用户有不同的需求, 于是应用层就被划分出来了. 用户在应用层使用各种 app, 数据依次往下组包直至物理层发送到网络, 接收数据则往上拆包得到最终数据. 本质上是需求推动了网络层次的产生, 当前把网络七层模型中的会话, 表示, 应用层统称为应用层.
应用层 文件传输, 文件服务, 电子邮件 http ftp smtp dns
传输层 通过端对端的接口 TCP UDP
网络层 为数据包选择路由 IP ICMP
数据链路层 传输有地址的帧, 错误检测功能 ARP
物理层 物理媒体
北门吹雪: http://www.cnblogs.com/2bjiujiu/
一般建立在 socket 上通信的应用都采用 C/S 模型, 这里来理清 C/S 架构
客户端:
需要申明协议, 请求建立连接, 发送和接收数据, 关闭连接这些过程
服务端:
申明协议实例监听者 (来源三体), 绑定监听 IP 和端口, 启动监听者监听, 建立连接, 接收和发送数据, 断开连接这些过程
# 下面这个是单线程服务端监听者实例过程
客户端实例实现过程
- import socket
- import time
- def beimenchuixue_client():
- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- client.connect(('127.0.0.1', 8000))
- while True:
- data = input("输入命令:")
- if not data.strip():
- continue
- client.send(data.encode('utf-8'))
- response = client.recv(1024)
- print(response.decode('utf-8'))
- if __name__ == '__main__':
- while True:
- # 一个小重启
- try:
- beimenchuixue_client()
- except ConnectionResetError as e:
- time.sleep(2)
单线程客户端监听者实例实现过程
- import socket
- def beimenchuixue_alone_socket():
- listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- listener.bind(('0.0.0.0', 8000))
- listener.listen()
- while True:
- conn, remote_address = listener.accept()
- print("%s:%s 建立连接" % remote_address)
- while True:
- # 处理客户端端口异常
- try:
- data = conn.recv(1024).decode("utf-8")
- except ConnectionResetError as e:
- print("%s:%s 断开连接" % remote_address)
- conn.close()
- break
- conn.send(bytes(data.upper(), encoding='utf-8'))
- if __name__ == '__main__':
- beimenchuixue_alone_socket()
- # 这个服务端实现了把小写转换为大写的服务, 其他客户端连接来需要排队等待正在处理的客户端断开请求, 也就是其他客户端进入堵塞状态
北门吹雪: http://www.cnblogs.com/2bjiujiu/
多线程或协程处理连接请求架构图
线程处理连接架构图
多线程客户端监听者实例实现过程
- import socket
- from threading import Thread
- def beimenchuixue_handler(conn, remote_address):
- """线程处理处理每个连接"""
- # 处理客户端断开异常
- while True:
- try:
- data = conn.recv(1024).decode("utf-8")
- except ConnectionResetError as e:
- print("%s:%s 断开连接" % remote_address)
- break
- conn.send(bytes(data.upper(), encoding='utf-8'))
- conn.close()
- def beimenchuixue_socket():
- listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- listener.bind(('0.0.0.0', 8000))
- listener.listen()
- while True:
- conn, remote_address = listener.accept()
- print("%s:%s 建立连接" % remote_address)
- handler = Thread(target=beimenchuixue_handler, args=(conn, remote_address))
- handler.start()
- if __name__ == '__main__':
- beimenchuixue_socket()
面向对象方式写多线程 socket
- import socket
- from threading import Thread
- class BeiMenChuiXueHandle(Thread):
- def __init__(self, conn, remote_address):
- super().__init__()
- self.conn = conn
- self.remote_address = remote_address
- def run(self):
- while True:
- try:
- data = self.conn.recv(1024).decode("utf-8")
- except ConnectionResetError as e:
- print("北门吹雪")
- print("%s:%s 断开连接" % self.remote_address)
- break
- self.conn.send(bytes(data.upper(), encoding='utf-8'))
- self.conn.close()
- class BeiMenChuiXueHandleSocket:
- def __init__(self):
- self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- def _handle(self, conn, remote_address):
- handler = BeiMenChuiXueHandle(conn, remote_address)
- handler.start()
- def socket_run(self, ip, port):
- self.listener.bind((ip, port))
- self.listener.listen()
- while True:
- conn, remote_address = self.listener.accept()
- print("北门吹雪")
- print("%s:%s 建立连接" % remote_address)
- self._handle(conn, remote_address)
- if __name__ == '__main__':
- listener = BeiMenChuiXueHandleSocket()
- listener.socket_run(ip='0.0.0.0', port=8000)
协程方式写 socket(需要安装协程模块 gevent, 在 Python3.5 版本中本身就支持协程语义 async await)
- import socket
- import gevent
- from gevent import monkey
- class BeiMenChuiXueSocket:
- def __init__(self, ip, port):
- # 自动标记堵塞, 要放到所有堵塞实例之前
- monkey.patch_all()
- self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.ip = ip
- self.port = port
- def _handler(self, conn, remote_address):
- while True:
- try:
- data = conn.recv(1024).decode("utf-8")
- except ConnectionResetError as e:
- print("北门吹雪")
- print("%s:%s 断开连接" % remote_address)
- break
- conn.send(bytes(data.upper(), encoding='utf-8'))
- conn.close()
- def forever_run(self):
- self.listener.bind((self.ip, self.port))
- self.listener.listen()
- while True:
- conn, remote_address = self.listener.accept()
- print("北门吹雪")
- print("%s:%s 建立连接" % remote_address)
- # 启动协程处理连接
- gevent.spawn(self._handler, conn, remote_address)
- if __name__ == '__main__':
- beimenchuixue = BeiMenChuiXueSocket('0.0.0.0', 8000)
- beimenchuixue.forever_run()
经验:
1. socket 是网络最为基础的通信方式, 也是上层应用的通信基础
2. 服务端为了同时响应多个请求则可以选择进程, 线程, 协程等多种方式处理连接进来的请求, 一般选择线程或协程
3. CPython 由于 GIL 保证一颗核只能在同一时刻跑一个线程, 多进程在某种意义上只是开启了多个线程, 而进程开销这么大, 不是好方法, 也就是说对 Python 的异步编程还是要使用线程或者协程进行
4. Python 线程, 协程只适于 IO 密集型, 不适用 CPU 密集型, 由于 GIL 的存在本质上是个单线程
来源: https://www.cnblogs.com/2bjiujiu/p/9148754.html