python 网络编程
socket编程
Socket(也称套接字)是一种通信协议,用于在两个计算机之间进行通信。它使得不同计算机上的应用程序能够通过网络互相通信。
在计算机网络中,数据被封装到一个数据包中,并通过网络传输到目标计算机。使用 Socket 可以完成数据包的封装、传输和解码等操作。一般来说,Socket 包含了 IP 地址、协议类型、端口号等信息。
在 Python 中,Socket 是一个标准库,提供了对 Socket 的支持,可以方便地建立和管理 Socket 连接。Python 的 Socket 库支持 TCP 和 UDP 两种协议,同时也支持 IPv4 和 IPv6 这两种网络协议。
使用 Python 的 Socket 库,用户可以创建客户端和服务器端 Socket,并实现基于 Socket 的网络通信。通过 Socket,用户可以在网络上发送和接收数据,实现各种应用程序,如 Web 服务器、聊天应用、远程控制等。
socket实现tcp通讯
TCP(Transmission Control Protocol)是一种面向连接的、可靠的数据传输协议。在 Python 中,可以使用 Socket 库的 TCP 协议实现网络通信。
TCP 服务器端
下面是 TCP 服务器端的连接步骤:
创建服务器的套接字:
import socket
# 创建服务器的套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.socket()
函数用于创建一个新的套接字对象。该函数需要传入两个参数:
Address Family,即地址族(Address Family):表示所使用的协议族类型。
该参数指定了套接字所使用的网络协议族,常见的有
socket.AF_INET
(IPv4),socket.AF_INET6
(IPv6),socket.AF_UNIX
(Unix 域套接字用于同一台机器进程间通信)等。Socket Type,即套接字类型:表示套接字的传输层协议类型。
该参数指定了套接字所使用的传输层协议类型,常见的有
socket.SOCK_STREAM
(流式套接字,例如 TCP),socket.SOCK_DGRAM
(数据报套接字,例如 UDP),socket.SOCK_RAW
(原始套接字,用于网络协议的底层实现)等。
除此之外,还可以使用可选的第三个参数,用于指定使用的协议(例如 TCP、UDP 等)。如果不指定协议,则会根据第二个参数自动选择合适的默认协议。
总之,socket.socket(Address Family, Socket Type,[protocol])
函数用于创建一个新的套接字对象,其中 Address Family 指定套接字的协议族类型,Socket Type 指定套接字的传输层协议类型。
设置端口号是否可以复用:
# 设置端口号是否可以复用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
是一个用于设置套接字选项的函数调用,用于指定在套接字关闭之后,本地地址和端口是否能够立即被重用。
具体来说,setsockopt()
函数用于设置套接字的各种选项,其中 SOL_SOCKET
表示当前套接字级别,表示操作的选项仅适用于当前套接字。SO_REUSEADDR
是一个选项名,表示在套接字关闭之后,可以立即使用该端口号,而不必等待 TIME_WAIT
状态消失。
1
表示开启 SO_REUSEADDR
选项,即在套接字关闭之后允许地址重用。如果使用的是 Python 3.2 及以上版本,也可以将参数值设为 True。
使用这个选项有两个优点:
避免了 Address already in use 错误:在某些情况下,如果服务器进程意外退出,操作系统并没有立即释放该进程所占用的端口,此时如果再次启动服务器进程,就会出现 Address already in use 错误。使用该选项可以避免这种错误的发生。
提高服务器性能:在流式套接字使用 TCP 协议时,当套接字关闭时,内核会保留连接状态一段时间,以确保完全关闭连接。如果一个客户端在这段时间内再次发起连接请求,那么服务器将会处理新的连接请求,而不必等待
TIME_WAIT
状态消失。因此,使用该选项可以提高服务器的性能。
总之,server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
用于设置套接字选项,指定在套接字关闭之后,本地地址和端口是否能够立即被重用。
设置本地 ip 地址和端口,并绑定 ip:
# 设置本地ip地址和端口,并绑定ip
server_local_addr = ("127.0.0.1", 9999)
server_socket.bind(server_local_addr)
开启监听端口:
# 开启监听端口
server_socket.listen(100)
server_socket.listen(100)
是一个用于启动 TCP 服务器的函数调用,用于监听客户端连接请求,以及设置服务器最多能够同时处理的连接数。
具体来说,listen()
函数用于将套接字设为被动监听模式,等待客户端连接请求。其中参数 100
表示服务器最多可以处理的连接数,也就是说,在某一时刻,服务器最多只能与 100 个客户端建立连接(同时存在)。
等待客户端的连接请求:
# 等待客户端的连接请求
# accept会返回一个元组,元组中的第一个数据为客户端套接字对象,第二个数据为客户端的地址
# 在客户端连接进来之前,会一直阻塞
client_socket, client_addr = server_socket.accept()
client_socket, client_addr = server_socket.accept()
是一个用于接受客户端连接请求的函数调用,返回与客户端通信的套接字对象和客户端地址信息。
具体来说,accept()
函数用于在服务器端接受客户端的连接请求,并返回一个新的套接字对象 client_socket
和客户端的地址信息 client_addr
。通过这个 client_socket
对象,可以和客户端进行通信。
需要注意的是,accept()
函数会阻塞程序执行,直到有客户端连接请求到来。如果服务器需要同时处理多个客户端连接,那么可以将 accept()
函数放在一个循环中,不断接受新的客户端连接请求,并在每次接受到连接后,开启一个新的线程或者进程来处理这个客户端连接。
6.接受客户端的消息:
while True:
# 接受客户端的消息
try:
recv_data = client_socket.recv(1024)
except ConnectionResetError:
print(f'{client_addr}断开了连接')
break
if not recv_data:
print(f'{client_addr}断开了连接')
break
print(f'接收到{client_addr}的消息:{recv_data.decode()}')
# 发送数据到客户端
if client_socket.fileno() != -1:
send_data = input('请回复客户端的内容:')
client_socket.send(send_data.encode())
else:
print(f'{client_addr}已经断开连接')
break
这里加入了异常处理,当客户端断开连接时,使用 recv()
方法会抛出 ConnectionResetError
异常,我们捕获这个异常,然后退出循环。同时,在接受数据和发送数据前都要先检查客户端是否还连接着,如果已经断开连接就退出循环。
fileno()
方法是获取套接字的文件描述符,返回一个整数。如果套接字已经关闭,那么 fileno()
返回的值为 -1
。
在这个代码中,通过检查套接字的文件描述符是否为 -1
来判断客户端是否已经关闭连接。如果客户端已经关闭连接,就不应该再向其发送数据了。
因此,在发送数据之前,使用 fileno()
方法检查客户端是否还连接着。如果客户端已经断开连接,就不会再向其发送数据,并打印出相应的提示信息。
关闭服务器套接字
# 关闭服务器套接字
server_socket.close()
server_socket.close()
是调用套接字的 close()
方法来关闭服务器套接字。调用这个方法会释放相关资源,包括套接字占用的端口等系统资源。
在服务器程序中,如果不关闭服务器套接字,那么服务器程序将一直占用套接字绑定的端口,此时其他应用程序将无法使用该端口。此外,如果服务器程序意外退出或崩溃,未关闭的服务器套接字可能会导致端口仍然被占用,需要手动释放该端口才能再次使用。
因此,在服务器程序运行结束时,一定要调用 close()
方法来关闭服务器套接字,释放相关资源,避免这些问题的发生。
import socket
# 1.创建服务器的套接字
# socket.socket(地址类型, 套接字类型)
# 参数1:地址类型
# socket.AF_INET: ipv4
# socket.AF_INET6: ipv6
# 参数2:套接字类型
# socket.SOCK_STREAM: tcp套接字
# socket.SOCK_DGRAM: udp套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口号是否可以复用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.设置本地ip地址和端口,并绑定ip
server_local_addr = ("127.0.0.1", 9999)
server_socket.bind(server_local_addr)
# 3.开启监听端口
# listen(最大连接数)
server_socket.listen(100)
print('等待客户端连接...')
while True:
# 4. 接受客户端连接请求
client_socket, client_addr = server_socket.accept()
print(f'与客户端{client_addr}建立连接')
try:
while True:
# 5. 接收客户端发送的数据
recv_data = client_socket.recv(1024)
if not recv_data:
print(f'客户端{client_addr}断开连接')
break
# 6. 处理接收到的数据
print(f'从客户端{client_addr}接收到的数据:{recv_data.decode()}')
# 7. 发送响应数据给客户端
response_data = f'已经收到你的消息:{recv_data.decode()}'
client_socket.send(response_data.encode())
except ConnectionResetError:
print(f'客户端{client_addr}强制断开连接')
finally:
# 8. 关闭套接字
client_socket.close()
TCP 客户端
TCP 客户端用于连接到 TCP 服务器,与服务器进行通信。TCP 客户端的使用流程如下:
创建客户端套接字:使用
socket.socket()
方法创建客户端套接字。连接服务器:使用客户端套接字的
connect()
方法连接服务器。发送数据:使用客户端套接字的
send()
方法发送数据到服务器。接收数据:使用客户端套接字的
recv()
方法接收服务器发回来的数据。关闭客户端套接字:使用客户端套接字的
close()
方法关闭客户端套接字。
import socket
# 1. 创建客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接到服务器
server_addr = ('127.0.0.1', 9999)
client_socket.connect(server_addr)
# 设置超时时间为5秒
client_socket.settimeout(5)
try:
# 3. 循环发送和接收消息
while True:
# 发送数据到服务器
infomation = input('请输入消息:')
if not infomation:
continue
client_socket.send(infomation.encode())
# 接收从服务端响应的数据
try:
recv_data = client_socket.recv(1024)
except socket.timeout:
print('接收超时,断开连接')
break
if not recv_data:
print('服务器断开连接')
break
print(f'从服务端接收到的数据为:{recv_data.decode()}')
except ConnectionRefusedError:
print('连接被拒绝,无法连接服务器')
except Exception as e:
print(f'发生异常:{e}')
finally:
# 4. 关闭套接字
client_socket.close()
socket实现HTTP通讯
服务端
具体的逻辑思路如下:
创建一个基于 TCP 协议的 socket 对象,并设置 SO_REUSEADDR 选项来避免地址被占用。
绑定指定的 IP 地址和端口号,并开始监听连接请求。
使用多线程池来处理客户端连接,每当有一个客户端连接进来,就提交给线程池中的一个线程来处理。
在处理函数
handle_connect
中,首先接收客户端发送的 HTTP 请求数据。解析 HTTP 请求,并根据请求的路径或其他信息来决定要返回的响应内容。
构造 HTTP 响应头和响应体,并使用 socket 发送响应数据到客户端。
关闭客户端连接,等待下一个连接请求。
import socket
from concurrent.futures.thread import ThreadPoolExecutor
class WebServer:
def __init__(self, ip: str = '127.0.0.1', port: int = 6666, connectMax: int = 100):
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.ip = ip
self.port = port
self.connectMax = connectMax
# self.server_local_addr = ("127.0.0.1", 9999)
self.server_socket.bind((self.ip, self.port))
self.server_socket.listen(self.connectMax)
def run(self):
with ThreadPoolExecutor(max_workers=8) as tp:
while True:
client_socket, client_addr = self.server_socket.accept()
tp.submit(self.handle_connect, client_socket, client_addr)
def handle_connect(self, client_socket, client_addr):
recv_data = client_socket.recv(1024)
print(f'接收到了{client_addr}的消息为:{recv_data.decode()}')
response_header = 'HTTP/1.1 200 OK\r\n'
response_header += 'Content-Type: text/html; charset=utf-8\r\n'
response_header += 'Set-Cookie: ketangpai_home_slb=pi3.1415926pi; path=/; secure; HttpOnly\r\n'
response_header += '\r\n\r\n'
response_body = open('index.html', 'r', encoding='utf-8').read()
response_full_data = response_header + response_body
client_socket.send(response_full_data.encode())
client_socket.close()
if __name__ == '__main__':
WebServer().run()
客户端
具体的逻辑思路如下:
创建客户端 socket 对象,使用
AF_INET
表示使用 IPv4 协议族,使用SOCK_STREAM
表示使用流式套接字。连接到服务器,指定服务器 IP 地址和端口号。
构造 HTTP 请求头,并使用
encode()
方法将字符串转换为 bytes 类型。使用
send()
方法向服务器发送请求头。使用
recv()
方法接收从服务器响应的数据,并根据需要使用decode()
方法将 bytes 类型转换为字符串类型。关闭客户端连接。
import socket
# 1.创建客户端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.连接到服务器
# 服务器的连接地址
server_addr = ('127.0.0.1', 6666)
client_socket.connect(server_addr)
request_header = 'POST //Futurev2/OpenMoocSmart/getRecommendMoocResourceList HTTP/1.1\r\n'
request_header += 'Referer: https://www.ketangpai.com/\r\n'
request_header += '\r\n\r\n'
client_socket.send(request_header.encode())
# 接收从服务端响应的数据
recv_data = client_socket.recv(1024)
print(f'从服务端接收到的数据为:{recv_data.decode()}')
评论