我会在近期尽快更新好之前写的博客, 会添加新的知识点和注意问题, 排版和内容都会较之前有很大的改观, 感谢大家一直的支持!
1 客户端 / 服务器架构
客户端 / 服务器架构也称主从式架构, 简称 C/S 架构, 它是一种网络结构, 把客户端 (Client)(通常是一个采用图形界面的程序) 与服务器 (server) 区分开来, 在 C/S 架构中, 服务器是一系列的硬件或软件, 客户端是提交服务请求的用户, 客户端提供用户请求接口, 服务端响应请求进行对应的处理, 并返回给客户端客户端 / 服务器架构既可以应用于计算机硬件, 也可以应用于软件
1.1 硬件客户端 / 服务器架构
典型的硬件客户端 / 服务器架构就是打印机, 在企业中, 员工通过局域网将个人电脑连接到打印机上, 作为客户端向打印机发送打印请求, 打印机作为服务端完成响应处理相应的请求
1.2 软件客户端 / 服务器架构
软件服务器也是运行在硬件之上的, 典型的软件服务器是 web 服务器在一台或多台电脑上搭建 Web 服务器, 以提供用户访问所需的 Web 页面和应用程序, Web 服务器一旦启动, 都将可能永远运行, 除非受到一些外力驱使才会停止, 如人为关闭, 服务器硬件故障等它的工作就是接收客户端的请求, 并响应请求给客户端返回相应的 Web 页面, 然后等待下一个客户端的请求
2 套接字
套接字是网络编程中的一个基本组件, 如果想要服务器能够响应客户端发来的请求, 首先要建立一个通信端点, 使服务器能够监听服务, 当通信端点建立后, 就会进入无限循环的等待请求状态, 当接收到客户端的请求, 就会响应该请求
套接字就是两个程序之间的信息通道, 可以理解为上面提到的通信端点的概念在通信开始之前, 网络应用程序必须创建套接字套接字是网络通信过程中端点的抽象表示, 包含进行网络通信必须的五种信息: 连接使用的协议, 本地主机的 IP 地址, 本地进程的协议端口号, 远程主机的 IP 地址, 远程进程的协议端口号
套接字起源于 20 世纪 70 年代, 它是加利福尼亚大学伯克利分校版本的 Unix 的一部分, 即人们所说的 BSD Unix 因此, 套接字也被人们称为伯克利套接字或 BSD 套接字套接字最初被设计用于同一台主机上多个应用程序之间的通讯, 这也就是所谓的进程间通讯(IPC)
TCP 用主机的 IP 地址加上主机的端口号作为 TCP 连接的端点, 这种端点就叫做套接字 (socket) 或插口套接字用 (IP 地址: 端口号) 表示
套接字有两种类型, 分别是基于文件的和基于网络的
基于文件的套接字家族名字叫做 AF_UNIX, 代表地址家族(address family):UNIX 在 Unix 和 linux 操作系统中, 熟为人知的一句话就是: 一切皆文件, 一个或多个进程运行在同一台机器上, 所以套接字是基于文件的, 它就可以通过访问底层的基础结构来实现进程之间的通信
基于网络的套接字家族名字叫做 AF_INET, 代表地址家族 (address family):INET(因特网) 它使用 IPv4 进行通信, 因为 IPv4 使用 32 位地址, 相比于 IPv6 的 128 位来说, 计算更快, 更适合于局域网的通信目前它也是使用最广泛的
在本文中, 重点讲网络编程, 所以在后面的涉及最多的还是 AF_INET
2.1 流式套接字(SOCK_STREAM)
不论使用哪种地址家族, 都只有两种套接字的连接方式, 一种是面向连接的, 一种是无连接的
面向连接的套接字连接方式, 意味着在进程通信之前必须先建立好一个连接, 这种套接字就称为流式套接字
流式套接字用于提供面向连接可靠的数据传输服务该服务将保证数据能够实现无差错无重复发送, 并按顺序接收流式套接字之所以能够实现可靠的数据服务, 原因在于其使用了传输控制协议, 即 TCP(The Transmission Control Protocol)协议在 Python 中, 创建 TCP 套接字, 就必须声明 SOCK_STREAM 作为套接字类型
2.2 数据报套接字(SOCK_DGRAM)
数据报套接字提供了一种无连接的服务这也意味着, 使用这种连接方式不需要在进程通信前建立连接在数据的传输过程中, SOCK_DGRAM 并不能保证数据传输的可靠性, 数据有可能在传输过程中丢失或出现数据重复, 且无法保证顺序地接收到数据数据报套接字使用 UDP(User Datagram Protocol)协议进行数据的传输由于数据报套接字不能保证数据传输的可靠性, 对于有可能出现的数据丢失情况, 需要在程序中做相应的处理
虽然存在数据丢失重复数据无序接受等很多缺点, 但它也有优势所在, 在流式套接字中, 因为是面向连接并提供了可靠的数据传输服务, 这对于虚拟电路连接的维护需要很大的开销, 但数据报套接字就不需要这些额外的开销, 所以维护资源占用成本更低
3 网络编程
Python 是一个很强大的网络编程工具, Python 内有很多针对网络协议的库, 这些库对网络协议的各个层次进行抽象封装, 这对于程序员来说就意味着: 不必关心网络协议的原理, 只需要通过对程序的逻辑处理, 就可以实现网络数据的传输
3.1 创建套接字
在 Python 中, 创建套接字需要使用 socket 模块, 通过 socket()函数创建套接字对象
- class socket(_socket.socket):
- -- skip --
- def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
- -- skip --
从 socket 函数的的构造方法中可以看出, 可以指定地址家族和套接字的连接方式, proto 默认是 0, 通常都省略即创建套接字对象的时候:
- import socket
- # 创建 TCP/IP 套接字, 地址家族 AF_INET
- tcp_socket = socket.socket(socket.AF_INET,socket.SOCKET_STREAM)
- # 创建 UDP/IP 套接字, 地址家族 AF_INET
- udp_socket = socket.socket(socket.AF_INET,socket.SOCKET_DGRAM)
3.2 套接字的内置方法
常见的套接字内置函数
方法 | 功能 |
st.recv() | 接受 TCP 的消息 |
st.recv_into() | 接受 TCP 的消息到指定的缓存区 |
st.send() | 发送 TCP 的消息 (当待发送的消息量大于缓存区剩余内存时,数据会丢失) |
st.sendall() | 完整的发送 TCP 消息(当待发送的消息量大于缓存区剩余内存时,数据不会丢失,循环调用 send 直到发完为止) |
st.recvfrom() | 接收 UDP 的消息 |
st.recvfrom_into() | 接收 UDP 的消息到指定的缓存区 |
st.sendto() | 发送 UDP 的消息 |
st.getpeername() | 连接到套接字的远程地址(TCP) |
st.getsockname() | 获取当前套接字的地址 |
st.getsockopt() | 获取指定套接字的参数 |
st.setsockopt() | 设置指定套接字的参数 |
st.close() | 关闭套接字 |
st.shutdown() | 关闭连接 |
服务端套接字方法
方法 | 功能 |
st.bind() | 将 IP 地址 + 端口号绑定到套接字上 |
st.listen() | 开启 TCP 监听功能 |
st.accept() | 被动的接受 TCP 客户端的连接,(阻塞式)一直等待连接直到连接到达 |
客户端套接字方法
方法 | 功能 |
st.connect() | 主动发起 TCP 服务器连接 |
st.connect_ex() | connect() 的扩展版本,以错误代码的形式返回问题,而不是抛出异常 |
面向阻塞的套接字方法
方法 | 功能 |
st.setblocking() | 设置套接字为阻塞模式或非阻塞模式 |
st.settimeout() | 设置阻塞套接字的操作超时时间 |
st.gettimoout() | 获取阻塞套接字的操作超时时间 |
面向文件的套接字方法
方法 | 功能 |
st.fileno() | 套接字的文件描述符 |
st.makefile() | 创建与套接字相关联的文件对象 |
数据属性
属性 | 功能 |
st.family | 套接字家族 |
st.type | 套接字类型 |
st.proto | 套接字协议 |
3.3 Tcp 服务器和客户端的通信
上面提到过, 套接字对象都是通过 socket.socket()函数来创建的, 下面模拟一个 TCP 服务器和客户端, 来实现进程间的通信
Tcp 服务端:
- import socket
- tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) // 创建服务器套接字
- tcp_server.bind(("127.0.0.1",8000)) // 将套接字与地址绑定
- tcp_server.listen(5) // 建立监听连接
- print("The server has started")
- while True:
- conn,addr= tcp_server.accept() // 接受客户端的连接
- while True:
- try:
- data = conn.recv(1024) // 会话的接收(或发送)
- print("msg is",data.decode("utf-8")) // 要将收到的会话数据进行解码
- conn.send(data.title()) // 会话的发送(或接受)
- except Exception:
- break
- conn.close() // 关闭连接
- tcp_server.close() // 关闭服务器套接字
在 Tcp 服务端, 先创建服务器套接字并指定类型为流式套接字 (SOCK_STREAM) 因为服务器需要占用一个端口并等待客户端的请求, 所以它们必须绑定到一个本地地址 Tcp 是一种面向连接的通信方式, 所以必须建立监听连接, listen(5)的意义是允许传入连接的最大数为 5 个当调用 accept()函数后, 服务端就会进入一个等待状态, 默认情况下, accept()处于阻塞状态, 也就意味着, 执行到此处, 程序会暂停, 直到有新的连接到达, 才会进行下一步的收发操作
Tcp 客户端:
- import socket
- tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) // 创建客户端套接字
- tcp_client.connect(("127.0.0.1",8000)) // 连接服务器
- while True:
- msg = input("Please input your message:").strip()
- if not msg:continue
- tcp_client.send(msg.encode("utf-8")) // 会话接收(或发送)
- data = tcp_client.recv(1024)
- print("reply is",data.decode("utf-8"))
- tcp_client.close() // 关闭客户端套接字
创建客户端比服务端要简单很多, 客户端一旦拥有了套接字, 就可以利用套接字的 connect()方法直接创建一个服务器的连接, 建立好连接, 就可以参与到服务端的会话中, 当客户端的需求全部完成, 就会关闭套接字, 终止此次连接
3.4 Udp 服务端和客户端的通信
Udp 服务器不需要 Tcp 服务器那么多的配置, 因为它不是面向连接的, 除了等待传入的连接, 它基本不需要其他的操作
Udp 服务端:
- import socket
- ip_port = ("127.0.0.1",8000)
- udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) // 创建服务端套接字
- udp_server.bind(ip_port) // 绑定本地地址
- print("the server has started")
- while True:
- data,addr = udp_server.recvfrom(1024) // 关闭接收(或发送)
- print(data)
- udp_server.sendto(data.title(),addr) // 关闭发送(或接受)
从上面代码中可以看出, 除了创建套接字并绑定本地地址后, 基本
没有其它的操作, 它是无连接的, 这也就意味着, 它无需为了成功通信而使一个客户端连接到一个特定的套接字进行转换操作, 服务器端仅仅是接收数据并进行回复
Udp 客户端:
- import socket
- ip_port = ("127.0.0.1",8000)
- udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) // 创建服务端套接字
- while True:
- msg = input(">>>").strip()
- udp_client.sendto(msg.encode("utf-8"),ip_port) // 发送
- data,addr = udp_client.recvfrom(1024) // 接收
- print(data)
- udp_client.close() // 关闭套接字
Udp 客户端, 一旦创建了套接字, 就可以进行会话循环中, 当会话结束, 关闭套接字
在使用 Udp 进行通信的时候, 服务端可以同时接收多个客户端的会话请求并返回请求结果
来源: https://www.cnblogs.com/Chen-Zhipeng/p/8472936.html