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 服务器端的连接步骤:

  1. 创建服务器的套接字:

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 指定套接字的传输层协议类型。

  1. 设置端口号是否可以复用:

# 设置端口号是否可以复用
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。

使用这个选项有两个优点:

  1. 避免了 Address already in use 错误:在某些情况下,如果服务器进程意外退出,操作系统并没有立即释放该进程所占用的端口,此时如果再次启动服务器进程,就会出现 Address already in use 错误。使用该选项可以避免这种错误的发生。

  2. 提高服务器性能:在流式套接字使用 TCP 协议时,当套接字关闭时,内核会保留连接状态一段时间,以确保完全关闭连接。如果一个客户端在这段时间内再次发起连接请求,那么服务器将会处理新的连接请求,而不必等待 TIME_WAIT 状态消失。因此,使用该选项可以提高服务器的性能。

总之,server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 用于设置套接字选项,指定在套接字关闭之后,本地地址和端口是否能够立即被重用。

  1. 设置本地 ip 地址和端口,并绑定 ip:

# 设置本地ip地址和端口,并绑定ip
server_local_addr = ("127.0.0.1", 9999)
server_socket.bind(server_local_addr)
  1. 开启监听端口:

# 开启监听端口
server_socket.listen(100)

server_socket.listen(100) 是一个用于启动 TCP 服务器的函数调用,用于监听客户端连接请求,以及设置服务器最多能够同时处理的连接数。

具体来说,listen() 函数用于将套接字设为被动监听模式,等待客户端连接请求。其中参数 100 表示服务器最多可以处理的连接数,也就是说,在某一时刻,服务器最多只能与 100 个客户端建立连接(同时存在)。

  1. 等待客户端的连接请求:

# 等待客户端的连接请求
# 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() 方法检查客户端是否还连接着。如果客户端已经断开连接,就不会再向其发送数据,并打印出相应的提示信息。

  1. 关闭服务器套接字

  # 关闭服务器套接字
    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 客户端的使用流程如下:

  1. 创建客户端套接字:使用 socket.socket() 方法创建客户端套接字。

  2. 连接服务器:使用客户端套接字的 connect() 方法连接服务器。

  3. 发送数据:使用客户端套接字的 send() 方法发送数据到服务器。

  4. 接收数据:使用客户端套接字的 recv() 方法接收服务器发回来的数据。

  5. 关闭客户端套接字:使用客户端套接字的 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通讯

服务端

具体的逻辑思路如下:

  1. 创建一个基于 TCP 协议的 socket 对象,并设置 SO_REUSEADDR 选项来避免地址被占用。

  2. 绑定指定的 IP 地址和端口号,并开始监听连接请求。

  3. 使用多线程池来处理客户端连接,每当有一个客户端连接进来,就提交给线程池中的一个线程来处理。

  4. 在处理函数 handle_connect 中,首先接收客户端发送的 HTTP 请求数据。

  5. 解析 HTTP 请求,并根据请求的路径或其他信息来决定要返回的响应内容。

  6. 构造 HTTP 响应头和响应体,并使用 socket 发送响应数据到客户端。

  7. 关闭客户端连接,等待下一个连接请求。

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()

客户端

具体的逻辑思路如下:

  1. 创建客户端 socket 对象,使用 AF_INET 表示使用 IPv4 协议族,使用 SOCK_STREAM 表示使用流式套接字。

  2. 连接到服务器,指定服务器 IP 地址和端口号。

  3. 构造 HTTP 请求头,并使用 encode() 方法将字符串转换为 bytes 类型。

  4. 使用 send() 方法向服务器发送请求头。

  5. 使用 recv() 方法接收从服务器响应的数据,并根据需要使用 decode() 方法将 bytes 类型转换为字符串类型。

  6. 关闭客户端连接。

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()}')