套接字是用于网络通信的数据结构。在任何类型的通信开始之前,都必须创建Socket,可以将它们比作电话插孔,没有它就无法通信。
在计算机网络中,根据通信协议的不同,套接字(Socket)可以分为两大类:
面向连接的Socket(SOCK_STREAM)
- 协议:传输控制协议(TCP, Transmission Control Protocol)
-
特点:
- 提供可靠的、有序的、面向连接的数据传输服务。
- 在数据传输之前,需要在发送方和接收方之间建立一个稳定的连接。
- 确保数据包的顺序正确,并且没有重复的数据包。
- 自动处理丢包和流量控制。
- 用途:适用于需要高可靠性传输的应用,如Web浏览器、电子邮件、文件传输等。
无连接Socket(SOCK_DGRAM)
- 协议:用户数据报协议(UDP, User Datagram Protocol)
-
特点:
- 提供不可靠的、无连接的数据传输服务。
- 每个数据包(称为数据报)都是独立发送的,不需要建立连接。
- 不保证数据包的顺序,也不保证数据包的可靠性。
- 传输速度快,但可能会出现丢包、重复或顺序错误。
-
用途:适用于对实时性要求较高,但可以容忍一定丢包率的场景,如视频会议、在线游戏、实时音频流等。
在编程时,根据具体的应用需求选择合适的套接字类型非常重要。以下是创建TCP和UDP套接字的简单示例:
创建TCP套接字(SOCK_STREAM)
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_socket == -1) {
// 处理错误
}
创建UDP套接字(SOCK_DGRAM)
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1) {
// 处理错误
}
在实际应用中,无论是TCP还是UDP套接字,都需要进行适当的配置和错误处理,以确保网络通信的稳定性和效率。
在Python中创建一个基本的TCP Socket服务端程序的步骤。下面是一个简单的示例代码:
import socket
# 1. 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定Socket服务端端口号
host = 'localhost' # 也可以使用特定的IP地址,如 '192.168.1.2'
port = 12345
server_socket.bind((host, port))
# 3. 监听端口号
server_socket.listen(5) # 参数5表示最大连接数
print(f"Server is listening on {host}:{port}")
# 4. 等待客户端连接
client_socket, client_address = server_socket.accept()
print(f"Connected to {client_address}")
# 5. 读取客户端发送过来的数据
data = client_socket.recv(1024) # 1024表示接收的最大数据量
print(f"Received from client: {data.decode()}")
# 6. 向客户端发送数据
message = "Hello, Client!"
client_socket.send(message.encode())
# 7. 关闭客户端连接
client_socket.close()
# 8. 关闭服务端连接
server_socket.close()
请注意以下几点:
-
socket.AF_INET
表示IPv4地址族。 -
socket.SOCK_STREAM
表示使用TCP协议。 -
server_socket.bind((host, port))
需要一个包含主机名和端口号的元组。 -
server_socket.listen(backlog)
中的backlog
参数指定了未完成连接的最大数量,超过这个数量的连接将被拒绝。 -
server_socket.accept()
方法会阻塞程序执行,直到接收到客户端的连接请求。 -
client_socket.recv(buffer_size)
用于接收数据,buffer_size
指定了一次可以接收的最大数据量。 -
client_socket.send(data)
用于发送数据,data
必须是字节串。 - 在发送和接收数据时,通常需要对字符串进行编码和解码。
在实际应用中,服务端程序通常需要处理多个客户端连接,可能需要使用多线程或多进程来实现并发处理。此外,还需要考虑异常处理和资源清理,以确保程序的健壮性。
如果客户端传给服务器的数据过多,需要分多次读取,每次最多读取缓冲区尺寸的数据。可以根据当前读取的字节数是否小于缓冲区的尺寸来判断是否后面还有未读的数据,如果没有,终止循环。
import socket
# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口号
host = 'localhost'
port = 12345
server_socket.bind((host, port))
# 监听端口号
server_socket.listen(5)
print(f"Server is listening on {host}:{port}")
# 等待客户端连接
client_socket, client_address = server_socket.accept()
print(f"Connected to {client_address}")
# 设置缓冲区大小
buffer_size = 1024
# 循环读取数据
data_all = b'' # 用于存储所有接收到的数据
while True:
data = client_socket.recv(buffer_size)
if not data: # 如果没有数据,则客户端关闭了连接
break
data_all += data # 累积接收到的数据
# 如果接收到的数据小于缓冲区大小,可能已经读完所有数据
if len(data) < buffer_size:
break
# 打印接收到的所有数据
print(f"Received from client: {data_all.decode()}")
# 向客户端发送响应
response = "Data received successfully"
client_socket.send(response.encode())
# 关闭连接
client_socket.close()
server_socket.close()
在服务端Socket有一个请求队列,如果服务器暂时无法处理客户端请求,会先将客户端请求放到这个队列里,而每次调用accept方法,都会在从这个队列中取一个客户端请求进行处理。
在TCP服务器编程中,当服务器的listen
方法被调用时,它会创建一个请求队列(也称为完成连接队列),用于存储那些已经完成TCP三次握手但尚未被应用程序通过accept
方法接受处理的客户端连接。
以下是关于请求队列的一些详细信息:
请求队列的工作原理:
-
监听队列大小:
listen
方法有一个参数叫做backlog
,它定义了请求队列的最大长度。如果队列满了,新的连接请求可能会被系统拒绝,这取决于具体的操作系统和网络配置。 - 三次握手:当一个客户端尝试连接到服务器时,它会执行TCP的三次握手过程。如果握手成功,客户端的连接请求就会被放入服务器的请求队列中。
-
处理连接:服务器调用
accept
方法时,它会从请求队列中取出第一个连接请求,创建一个新的套接字用于与该客户端通信,并从队列中移除该请求。 -
队列溢出:如果请求队列满了,新的连接请求可能会被拒绝。在Linux系统中,这通常会导致客户端收到一个错误,比如
ECONNREFUSED
。
示例代码中的listen
方法:
server_socket.listen(5)
在这个例子中,backlog
被设置为5,这意味着请求队列可以容纳最多5个未处理的连接请求。
处理请求队列中的连接:
while True:
client_socket, client_address = server_socket.accept()
# 处理客户端连接...
在这个无限循环中,服务器会不断地调用accept
方法来处理请求队列中的连接。每次调用accept
时,如果队列中有等待的连接,它就会处理一个连接;如果没有,accept
方法会阻塞,直到有新的连接到来。
注意事项:
- 如果服务器处理连接的速度跟不上新连接的到达速度,请求队列可能会持续增长,导致新的连接被拒绝。
- 服务器应该能够高效地处理连接,以避免请求队列长时间处于满载状态。
- 适当的异常处理和资源管理是确保服务器稳定运行的关键。
时间戳服务端:当客户端向服务端发送数据时,服务端给客户端发送数据,并附带上服务器当前的时间,使用time模块中的ctime函数获取当前时间。
import socket
import time
# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口号
host = 'localhost' # 也可以使用特定的IP地址
port = 12345
server_socket.bind((host, port))
# 监听端口号
server_socket.listen(5)
print(f"Server is listening on {host}:{port}")
try:
while True:
# 等待客户端连接
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address} has been established.")
# 接收客户端数据
data = client_socket.recv(1024)
if data:
# 打印接收到的数据
print(f"Received: {data.decode()}")
# 获取当前时间
current_time = time.ctime()
# 向客户端发送当前时间
response = f"Server time: {current_time}"
client_socket.send(response.encode())
# 关闭客户端连接
client_socket.close()
except KeyboardInterrupt:
# 关闭服务端连接
server_socket.close()
print("Server has been shut down.")
以下是使用Python实现客户端Socket连接的步骤,以及一个简单的示例代码:
客户端Socket步骤:
- 创建客户端socket。
- 连接到服务端socket。
- 发送数据到服务端。
- 从服务端接收数据。
- 关闭客户端socket。
示例代码:
import socket
# 1. 创建客户端socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务器地址和端口号
server_host = 'localhost' # 或者服务器的IP地址
server_port = 12345
try:
# 2. 连接到服务端socket
client_socket.connect((server_host, server_port))
print(f"Connected to server at {server_host}:{server_port}")
# 3. 发送数据到服务端
message = "Hello, Server!"
client_socket.send(message.encode())
# 4. 从服务端接收数据
response = client_socket.recv(1024)
print(f"Received from server: {response.decode()}")
finally:
# 5. 关闭客户端socket
client_socket.close()
print("Connection closed.")
在这个示例中,客户端首先创建了一个socket对象,然后连接到指定的服务器地址和端口。客户端发送一条消息到服务器,并等待接收服务器的响应。最后,客户端关闭了socket连接。
请注意,在实际应用中,应该添加异常处理来确保在出现错误时能够正确地关闭socket连接,并处理可能发生的异常情况。此外,如果服务器发送的数据可能超过1024字节,客户端可能需要在一个循环中接收数据,直到所有数据都被接收完毕。
UDP Socket接收数据的方法是recvfrom,发送数据的方法是sendto
以下是使用Python实现UDP服务端和客户端的示例代码:
UDP服务端示例代码:
import socket
# 1. 创建socket链接
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定
server_address = ('localhost', 12345)
server_socket.bind(server_address)
try:
while True:
# 3. 接收数据
print("Waiting to receive message...")
data, client_address = server_socket.recvfrom(4096)
print(f"Received message: {data.decode()} from {client_address}")
# 4. 发送数据
message = f"Server time: {time.ctime()}"
server_socket.sendto(message.encode(), client_address)
finally:
# 5. 关闭socket
server_socket.close()
print("Server socket closed.")
UDP客户端示例代码:
import socket
# 1. 创建socket链接
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 服务端地址
server_address = ('localhost', 12345)
try:
# 2. 向服务端发送数据
message = "Hello, UDP Server!"
client_socket.sendto(message.encode(), server_address)
# 3. 接收服务端返回数据
print("Waiting for response...")
response, server_address = client_socket.recvfrom(4096)
print(f"Received from server: {response.decode()}")
finally:
# 4. 关闭socket
client_socket.close()
print("Client socket closed.")
在这个示例中,UDP服务端创建了一个socket,并将其绑定到一个地址和端口上。然后它进入一个无限循环,等待接收客户端发送的数据。一旦接收到数据,服务端会向客户端发送一条包含当前时间的消息。
UDP客户端创建了一个socket,然后向服务端发送一条消息,并等待接收服务端的响应。在接收到响应后,客户端关闭了socket。
请注意,UDP是无连接的协议,因此不需要显式地建立连接。UDP客户端和服务端之间的通信是通过发送和接收数据报文来完成的。另外,由于UDP不保证数据的可靠传输,因此在实际应用中可能需要额外的机制来确保数据的完整性和顺序。
socketsever是标准库的一个模块,目的是让Socket编程更简单。
socketsever模块中提供了个TCPServer类,用于实现TCP服务端。TCPServer类的构造方法有两个参数:第1个参数需要传入 host和port(元组形式);第二个参数需要传入一个回调类,该类必须是StreamRequestHandler类的子类。在StreamRequestHandler类中需要实现一个handle方法,如果接收到客户端的响应,那么系统就会调用handle方法进行处理,通过handle方法中的self参数中的响应API可以与客户端进行交互。(self.wfile.write(...), self.rfile.readlines)
让服务端等待客户端的连接:tcpServer.serve_forever()
。以下是如何使用socketserver.TCPServer
类和socketserver.StreamRequestHandler
类来创建一个简单的TCP服务端的示例:
import socketserver
# 定义请求处理器类,继承自StreamRequestHandler
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
self.data = self.rfile.readline().strip()
print(f"Received data: {self.data}")
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
# 主函数,设置服务器
if __name__ == "__main__":
# 设置服务器地址和端口
HOST, PORT = "localhost", 9999
# 创建TCPServer实例,并将处理器类传递给它
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# 启动服务器,serve_forever将处理到来的连接,直到停止
server.serve_forever()
在这个例子中,MyTCPHandler
类继承自socketserver.StreamRequestHandler
。在handle
方法中,我们读取客户端发送的数据,将其转换为大写,然后发送回客户端。
以下是代码中各个部分的解释:
-
self.rfile
和self.wfile
:这些是文件对象,分别用于从客户端读取数据和向客户端写入数据。 -
self.data
:从客户端接收到的数据。 -
socketserver.TCPServer((HOST, PORT), MyTCPHandler)
:创建一个TCPServer
实例,传入主机地址和端口号,以及请求处理器类。 -
server.serve_forever()
:启动服务器,使其持续运行并监听客户端连接。
通过使用socketserver
模块,可以避免手动处理底层的socket连接细节,从而简化服务器的创建过程。
网友评论