IP 地址
略
端口和端口号
什么是端口号?
端口号在计算机网络中用来标识特定的进程或网络服务。IP 地址用来定位计算机,而端口号用来定位计算机上的服务。
不同电脑上的飞秋之间进行数据通信,它是如何保证把数据给飞秋而不是给其它软件呢?
其实,每运行一个网络程序都会有一个端口,想要给对应的程序发送数据,找到对应的端口即可。

端口号分类
端口号共为 16 位,从 0 到 65535。常用的分类方式把端口号分成三个范围:
- 知名端口(Well-known Ports):
- 范围:0-1023
- 描述:这些端口通常分配给标准服务和协议。这些端口由互联网号码分配局 (IANA) 管理和分配,常用于一些常见网络服务。
- 示例端口:
- 20/21: FTP (File Transfer Protocol)
- 22: SSH (Secure Shell)
- 23: Telnet
- 25: SMTP (Simple Mail Transfer Protocol)
- 53: DNS (Domain Name System)
- 80: HTTP (Hypertext Transfer Protocol)
- 443: HTTPS (HTTP Secure)
- 注册端口(Registered Ports):
- 范围:1024-49151
- 描述:这些端口主要为用户进程和应用程序分配。可以注册给某些特定应用程序,但它们相对于知名端口要更灵活。
- 这些端口为未能在知名端口中注册的专用服务使用,但仍然可通过 IANA 注册。
- 示例:
- 1433: Microsoft SQL Server
- 3306: MySQL 数据库
- 动态或私有端口(Dynamic or Private Ports):
- 范围:49152-65535
- 描述:这些端口不被指定给任何特定的服务,通常在需要动态、临时通信时使用,特别是在客户端请求到达服务器时(即临时端口)。例如,在一个 TCP/IP 会话中,客户端设备会动态选择一个这部分范围中的端口来建立连接。
- 也被称为临时端口。
- 常用于短期用途,例如当应用程序向一个指定端口发起请求时,客户端会维护一个自身的私有端口以管理发出的请求。
TCP 介绍
TCP,或传输控制协议(Transmission Control Protocol),是互联网协议套件的核心协议之一。它提供了一种可靠的、面向连接的通信方式,用于数据在计算机网络中的传输。以下是 TCP 的一些关键特性和介绍:
特性:
- 面向连接:
- 在通信双方开始数据传输之前,TCP 通过三次握手建立连接。这是一个初始的设置过程,确保双方准备好进行通信。
- 可靠传输:
- TCP 通过确认、重传、序列号和校验和,保证数据包的传送是准确无误的。丢失的数据包会被重传,确保数据完整性。
- 流量控制:
- TCP 使用滑动窗口机制来管理数据传输,确保不会溢出接收方的缓冲区。这使得发送方能适应接收方的能力,并调整数据传输速率。
- 拥塞控制:
- TCP 实施拥塞控制算法,例如慢启动、拥塞避免、快恢复和快重传,以防止网络拥塞并优化数据流。
- 有序传输:
- 数据包按顺序传递。即使在数据传输期间包被重新排序或丢失,TCP 会使用序列号确保数据在接收端精确重构。
- 数据完整性:
- 每个 TCP 报文段(数据块)通过校验和验证数据完整性。如果在传输过程中发生错误,校验和帮助识别并修正这些错误。
socket
socket(简称 套接字)是进程之间网络通信一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间想要进行网络通信需要基于这个 socket。套接字在网络编程中用于在两台设备之间建立连接,并进行数据交换。
基本概念:
- 套接字可以视作通信的端点,一个允许程序读写网络流的数据结构。在网络编程中,套接字负责把应用程序分发到传输接收机制。
- 套接字支持 TCP、UDP 等协议,并提供了一种标准化的接口,使得应用程序能够使用这些协议进行网络通信。
类型:
- 流套接字(Stream Socket):
- 通常用于 TCP 协议。
- 提供可靠、面向连接的服务,确保数据传输的完整性和正确顺序。
- 数据报套接字(Datagram Socket):
- 通常用于 UDP 协议。
- 提供不可靠、无连接的服务,数据可能不按顺序到达,并且不保证数据的完整性。
- 原始套接字(Raw Socket):
- 允许直接访问底层协议对象,例如 IP。
- 用于需要更低级控制和定制的数据传输。
使用:
套接字的使用遵循特定的流程,一般包括以下几个步骤:
- 创建套接字:
- 使用适当的协议(TCP 或 UDP)创建一个新的套接字。例如使用
socket()
函数创建一个 TCP 套接字。
- 使用适当的协议(TCP 或 UDP)创建一个新的套接字。例如使用
- 绑定:
- 将套接字绑定到一个特定的地址和端口。通常服务器端使用此步骤,以准备接收客户端的连接请求。例如使用
bind()
将服务器套接字绑定到一个本地 IP 和端口。
- 将套接字绑定到一个特定的地址和端口。通常服务器端使用此步骤,以准备接收客户端的连接请求。例如使用
- 监听:
- 设置套接字的监听状态,等待客户端连接。在 TCP 中特有。例如使用
listen()
启动监听,等待客户端连接。
- 设置套接字的监听状态,等待客户端连接。在 TCP 中特有。例如使用
- 连接:
- 客户端尝试连接到服务器上的套接字,服务器接受连接请求。例如使用
accept()
接收客户端的连接请求,并返回一个新的套接字用于后续数据传输。
- 客户端尝试连接到服务器上的套接字,服务器接受连接请求。例如使用
- 数据传输:
- 在连接建立后进行数据交换。可以使用
send()
和recv()
方法,或者在 UDP 中使用sendto()
和recvfrom()
。
- 在连接建立后进行数据交换。可以使用
- 关闭连接:
- 传输结束后,双方关闭套接字以释放资源。使用
close()
关闭连接。
- 传输结束后,双方关闭套接字以释放资源。使用
tcp 网络应用程序开发流程
TCP 网络应用程序开发分为:
- TCP 服务端程序开发
- TCP 客户端程序开发

套接字编程准备
- 选择协议: TCP 是面向连接且可靠的协议,适用于需要确保数据全部传输并且顺序无误的场景。
- 架构设计: 确定网络架构,决定应用程序是需要单服务器多客户端、多服务器多客户端还是其他结构。
1、开发服务器端应用
创建套接字: 使用 socket.socket()
创建一个 TCP 套接字。
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
地址绑定: 使用 bind()
方法绑定套接字到指定的地址和端口。
server_address = ('localhost', 65432)
server_socket.bind(server_address)
监听连接: 使用 listen()
方法使套接字开始监听来自客户端的连接请求。
server_socket.listen(5) # 可以同时处理的连接数
等待连接并接受请求: 使用 accept()
方法接收连接请求。
connection, client_address = server_socket.accept()
数据处理和传输: 使用 recv()
方法接收数据,使用 sendall()
方法发送响应数据。
data = connection.recv(1024)
connection.sendall(response)
关闭连接: 处理完成后关闭连接。
connection.close()
2、开发客户端应用
步骤:
- 创建客户端套接字:使用
socket()
创建 TCP 套接字。 - 连接服务器:使用
connect()
方法连接到服务器的 IP 和端口。 - 数据交换:通过
send()
和recv()
方法与服务器进行数据传输。 - 关闭连接:完成数据传输后,关闭套接字。
client_socket.connect(server_address)
- 发送请求与接收响应: 使用
sendall()
发送请求,使用recv()
接收服务器的响应。 - 关闭连接: 数据交换完成后关闭套接字。
3、测试与优化
- 功能测试: 确保所有功能正常工作,模拟并实际测试。
- 性能优化: 解决发现的问题,增强处理能力、减少延迟、优化处理大规模连接或数据传输。
- 异常处理: 集中处理可能出现的异常,比如网络中断、数据错误等。
4、部署与维护
- 部署: 根据网络应用的需求部署到合适的环境,包括所需的服务器和网络设施。
- 维护与更新: 定期更新和维护应用程序,包括优化代码、增加新功能等。
tcp 服务端程序开发
import socket # 导入socket模块,用于网络通信 def start_server(): # 定义一个函数来启动服务器 # 创建一个TCP套接字,使用IPv4地址 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 允许重用地址和端口 # server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 将套接字绑定到所有可用的网络接口和端口8888 server_socket.bind(('0.0.0.0', 8888)) # 开始监听客户端连接,最大连接请求队列长度为5 server_socket.listen(5) # 打印出服务器正在监听的消息 print("服务器正在监听端口 8888...") # 无限循环,持续接受客户端连接 while True: # 阻塞等待客户端连接,一旦有连接,返回客户端套接字和地址 client_socket, client_address = server_socket.accept() # 打印连接的客户端地址 print(f"连接来自: {client_address}") # 接收客户端发送的数据,最多接收1024字节 data = client_socket.recv(1024) # 将接收到的字节数据解码为字符串并打印 print(f"收到数据: {data.decode('utf-8')}") # 创建HTTP响应报文 response = "HTTP/1.1 200 OK\r\n\r\nHello, TCP Client!" # 将响应编码为字节并发送给客户端 client_socket.sendall(response.encode('utf-8')) # 关闭与客户端的连接 client_socket.close() # 检查是否是直接运行该脚本 if __name__ == "__main__": # 调用启动服务器的函数 start_server()
tcp 客户端程序开发
import socket def start_client(): # 创建一个TCP套接字 # AF_INET表示 IPv4,SOCK_STREAM 表示 TCP 协议 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接到服务器 # connect() 方法用于连接到指定的服务器地址和端口,里面是元祖:字符串类的 ip 地址和数字类型的端口号 client_socket.connect(('服务端 IP', 8888)) # 准备发送的数据 # 将字符串 "Hello, TCP Server!" 转换为字节流 # 使用 UTF-8 编码,将字符串编码为字节 # 相比于 sendall():此方法也是用于发送数据,但它会确保所有的数据都被发送完毕。该方法会在发送完所有数据之前不会返回,因此它会处理在发送过程中可能出现的部分发送的情况。 message = "Hello, TCP Server!" client_socket.sendall(message.encode('utf-8')) # 等待并接收来自服务器的数据 # recv() 方法用于从连接中读取数据,参数 1024 指定一次最多接收 1024 字节 data = client_socket.recv(1024) # 将接收到的字节数据解码为字符串,并打印出来 print(f"收到服务器响应: {data.decode('utf-8')}") # 关闭客户端套接字 # 断开与服务器的连接,释放系统资源 client_socket.close() # 如果当前模块是主程序入口 # 则执行 start_client 函数 if __name__ == "__main__": start_client()
通讯展示:
服务端:

客户端:

新的优化需求:
1、如果重新启动服务端的接受脚本时,会出现一个错误:OSError: [Errno 98] Address already in use
如果想要马上释放需要设置一下 socket 选项。server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 这个注释放开即可
2、当使用 client_socket.recv(1024)
接收数据时,函数会阻塞直到有数据到来或连接关闭。一个典型的 TCP 客户端关闭连接后,recv()
会返回一个空字节串 b""
。在 Python 中,空容器和空字节串在布尔上下文中被视为 False
,因此 if not data:
判断就能捕获这种情况,表明客户端已经断开连接。
3、客户端断开连接/脚本执行异常 导致资源无法被释放:
- 如果在
recv()
调用时客户端突然断开连接,可能会引发异常。没有try except
会导致程序崩溃。例如用户按下Ctrl+C
强制退出程序,程序会异常退出。没有try except
会导致套接字没有正确关闭,可能会导致资源泄漏。 - 没有
finally
,如果出现异常,套接字server_socket
和client_socket
可能不会被关闭,从而导致系统资源被浪费。
需要使用 try...except...finally 优化此代码
import socket # 导入socket模块,用于网络通信 def start_server(): # 定义一个函数来启动服务器 # 创建一个TCP套接字,使用IPv4地址 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 允许重用地址和端口,在服务器重启时不出现“地址已在使用”错误 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 将套接字绑定到所有可用的网络接口(0.0.0.0)和端口8888 server_socket.bind(('0.0.0.0', 8888)) # 开始监听客户端连接,最大连接请求队列长度为5 server_socket.listen(5) print("服务器正在监听端口 8888...") # 打印出服务器正在监听的消息 try: while True: # 无限循环,持续接受客户端连接 # 阻塞等待客户端连接,一旦有连接则返回新的套接字和客户端地址 client_socket, client_address = server_socket.accept() print(f"连接来自: {client_address}") # 打印连接的客户端地址 # 接收客户端发送的数据,最大缓冲区为1024字节 data = client_socket.recv(1024) # 如果没有接收到数据,即接收到空字节串,表示客户端已关闭连接 # 因为 recv() 返回空意味着客户端已断开,在 Python 中空值在布尔上下文中视为 False if not data: print(f"客户端 {client_address} 已关闭连接,主动退出监听。") client_socket.close() # 关闭当前客户端连接 break # 退出循环,结束服务器 print(f"收到数据: {data.decode('utf-8')}") # 打印接收到的数据 # 创建一个简单的HTTP响应报文并准备发送给客户端 response = "HTTP/1.1 200 OK\r\n\r\nHello, TCP Client!" client_socket.sendall(response.encode('utf-8')) # 发送响应到客户端 client_socket.close() # 关闭与客户端的连接 except KeyboardInterrupt: # 捕获键盘中断信号(Ctrl+C),以便优雅地关闭服务器 print("\n服务器正在关闭...") finally: server_socket.close() # 确保服务器正常关闭时释放资源 # 检查该模块是否是被直接执行 if __name__ == "__main__": start_server() # 如果是直接运行,则调用启动服务器的函数

详细解释:
server_socket.setsockopt()
: 确保服务器重启时可以立即重新绑定到相同的端口,解决因 socket 短暂占用导致的“地址已在使用”错误。accept()
: 阻塞等待客户端连接,一旦有连接请求到达,它会接受连接,创建一个新的 socket 对象用于与客户端通信。recv()
和sendall()
: 用于接收和发送数据。recv()
在没有数据时返回空,触发if not data
的条件部分。据此来判断是否关闭连接。try...except...finally
结构: 用于处理程序中的错误,并确保即使程序出现错误或中断时,socket 连接也能正常关闭以释放系统资源。
多任务版 tcp 服务器程序开发
import socket import threading def handle_client(client_socket, client_address): print(f"连接已建立: {client_address}") try: while True: # 接收数据 data = client_socket.recv(1024) if not data: # 如果没有数据,说明客户端已经关闭连接 break print(f"收到来自 {client_address} 的数据: {data.decode('utf-8')}") # 回送数据给客户端 client_socket.sendall(data) except socket.error as e: print(f"Socket error: {e}") finally: # 关闭客户端连接 client_socket.close() print(f"连接已关闭: {client_address}") def start_server(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('0.0.0.0', 8888)) server_socket.listen(5) print("服务器已启动,等待连接...") while True: # 接受新的连接 client_socket, client_address = server_socket.accept() # 为每个新客户端启动一个线程进行通信 client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address)) client_thread.start() if __name__ == "__main__": start_server()

代码解释:
- 创建服务器套接字:
- 使用
socket.socket()
创建服务器套接字,AF_INET
表示使用 IPv4,SOCK_STREAM
表示使用 TCP。
- 使用
- 绑定和监听:
- 使用
bind()
绑定到指定的地址和端口。在这里,0.0.0.0
表示服务器监听所有可用接口上的请求。 - 使用
listen()
进行监听,最大等待连接数量为5。
- 使用
- 接受连接和处理:
- 使用
accept()
接受客户端连接,该函数会阻塞,直到有客户端连接。 - 每有一个客户端连接,就启动一个新的线程来处理客户端的输入输出。
threading.Thread()
用于创建新线程。
- 使用
- 处理客户端线程:
- 在
handle_client()
函数中处理与单个客户端的交互:接收数据,并将数据回送给客户端。
- 在
- 关闭连接:
- 当客户端断开连接或发生异常时,确保关闭套接字。
socket 之 send 和 recv 原理剖析
recv是不是直接从客户端接收数据?
不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据。

- 发送数据是发送到发送缓冲区
- 接收数据是从接收缓冲区
发布者:LJH,转发请注明出处:https://www.ljh.cool/42360.html