Python Socket编程

2023-12-14 13:38:31

Python Socket编程

1. 弄懂HTTP、Socket、TCP这几个概念

  • 整个计算机网络都是有协议组成的
  • 每一个应用程序只能占用1个端口

五层网络模型

  • OSI的标准网络模型是有7层的。应用层和传输层下面还有个表示层、还有个会话层。
  • 应用层中最常用的库就是requests这个第三方的包,走的就是HTTP协议;浏览器也是实现了HTTP协议。
  • HTTP协议是构建与TCP/IP协议之上的。
  • 例如我们要写一个聊天工具,这就不属于这里的应用层任何协议,那么我们如何与TCP/UDP这些底层协议打交道呢?如果我们要手动实现协议与TCP/UDP打交道是非常繁琐的,所以我们不会去实现协议与TCP/UDP打交道,这些与TCP/UDP协议打交道的活都是操作系统帮我们提供的。
  • 操作系统会提供给我们Socket,Socket我们可以理解为是一个API,Socket不属于任何协议,我们通过Socket就可以直接和TCP/UDP传输层打交道,因为聊天工具属于新的应用,而这里的应用层协议都无法满足我们的需求,所以我们就只需要使用操作系统提供的Socket编程即可,Windows、Linux、macOS等操作系统都给我们提供了Socket接口。
  • 所以我们只需要通过Socket就可以和底层的这些传输层、网络层、数据链路层、物理层进行打交道。
  • Socket在整个网络模型中,可以让我们直接脱离应用层而自动帮我们与底层协议打交道,这样我们就可以完成很多的功能哦。
  • Socket本身不属于计算机网络模型。Socket是用来连接我们的应用和TCP层,Socket使得我们自己的应用可以直接和TCP打交道,这样我们就可以实现自己的应用层协议。而这个我们自己定义的应用层协议就是使用Socket与TCP打交道,我们自己定义的应用层协议是和HTTP协议是同一层级的应用层协议。

在这里插入图片描述

2. client和server实现通信

Socket编程模式指南

  • 左侧就是server端:
    • 特点就是随时处于监听的状态,也就是服务的状态,因为不知道客户端什么时候会发送连接过来,所以server必须时刻处于等待状态。
    • accept():阻塞等待连接请求
    • recv():获取数据
    • send():服务端可以发送数据给客户端,这里就与HTTP有很大的区别了,即只要连接不断开,就可以一直向客户端发送数据。而HTTP请求一般就是发送请求等待响应返回数据后就停止了。
    • close():断开连接
  • 右侧就是client端

在这里插入图片描述

代码实现

# socket_server.py
import socket

# 新建server
# 参数1:是指明使用的类型,AF_INET就是指明使用IPv4,常用还有AF_INET6就是IPv6,AF_IPX是做linux系统上的进程间通信的
# 参数2:指明这个类型对应的协议,SOCK_STREAM就是TCP,SOCK_DGRAM就是UDP
# socket.AF_INET: 基于网络的socket通信,使用IPv4
# socket.SOCK_STREAM: 基于TCP的流式socket通信
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定IP地址和端口,必须传入一个tuple
# 如果你写127.0.0.1那么你通过局域网就访问不到了
server.bind(("0.0.0.0", 8000))

# 监听
server.listen()

# 等待连接
# 返回表示连接的新套接字以及客户端的地址,sock是一个新的套接字对象,addr是客户端的地址
sock, addr = server.accept()

# 接收数据,即获取从客户端发送的数据,指定最大的接收长度为1024字节(即一次获取1KB的数据),recv方法是一个阻塞方法,如果没有数据接收就会阻塞程序执行
data = sock.recv(1024)  # data是一个bytes对象
print(data.decode("utf8"))  # 把bytes对象转换成字符串

# 发送数据给客户端
sock.send("Hello client".encode("utf8"))

# 关闭连接
# server.close()是关闭服务器套接字,停止接受新的客户端连接,sock.close()是关闭一个已经建立的客户端连接。
# server.close() # 关闭连接,真正的server一般是不会close的
# sock.close() # 关闭套接字,表示和客户端的连接也中断了
# socket_client.py
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 建立连接
# 参数是一个tuple,包含地址和端口号,这里是本机的地址,所以是127.0.0.1,端口号是8000
client.connect(("127.0.0.1", 8000))

# 发送数据,要求发送的数据必须是bytes格式
client.send("Hello Server".encode("utf8"))

# 接收数据
data = client.recv(1024)
print(data.decode("utf8"))

# 关闭连接,一般是先关闭发送数据的一方,然后再关闭接收数据的一方,这是一个好习惯
client.close()

先启动server端,再启动client端,发现server端打印了“Hello Server”,而后client端打印了“Hello client”,最后两者程序都退出了。

3. socket实现聊天和多用户连接

# socket_server.py
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 8000))
server.listen()
sock, addr = server.accept()

while True:
    data = sock.recv(1024)
    print(data.decode("utf8"))
    re_data = input() # 使用终端进行发送数据的输入
    sock.send(re_data.encode("utf8"))
# socket_client.py
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8000))
while True:
    re_data = input()
    client.send(re_data.encode("utf8"))
    data = client.recv(1024)
    print(data.decode("utf8"))

这样的代码有个问题:

  • server只能连接一个client,因为当客户端连接后就在while循环中出不来了。可以使用线程,每一个socket都做一个线程,在线程里面去循环,那么我们主线程就可以不停的接收其他client的连接请求
# socket_server.py
import socket
import threading

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 8000))
server.listen()


def handle_sock(sock, addr):
    while True:
        data = sock.recv(1024)
        print(data.decode("utf8"))
        re_data = input()
        sock.send(re_data.encode("utf8"))


while True:
    sock, addr = server.accept()
    # 用线程去处理新接收的连接(用户)
    client_thread = threading.Thread(target=handle_sock, args=(sock, addr))
    client_thread.start()

4. socket模拟http请求

  • requests包是基于urllib完成的,而urllib是基于socket完成的。所以说最底层就是socket的。
  • 凡事在网络相关的连接中,比如数据库连接、进程之间的通信等,这些网络请求最底层都是用socket完成的
  • 再次强调socket是操作系统所提供的接口,只是说不同的开发语言会根据不同的操作系统所提供的socket,再次封装成接口而已。
  • 下面我们将实现通过socket 去完成urllib当中的去获取请求的实现。
import socket

# 用来解析URL, 会将URL解析为下面6个部分
# <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
from urllib.parse import urlparse


def get_url(url):
    """通过socket请求html"""
    url = urlparse(url)  # 解析url
    host = url.netloc  # 获取域名
    path = url.path  # 获取域名后的子路径
    if path == "":
        path = "/"  # 如果直接请求的域名则把子路径改成/

    # 建立socket连接
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((host, 80))  # HTTP是80端口

    # 发送数据,注意这里的数据的格式是要按照HTTP协议的规范来写
    client.send(f"GET {path} HTTP/1.1\r\nHost:{host}\r\nConnection:close\r\n\r\n".encode("utf8"))

    # 接收数据
    data = b""
    while True:
        d = client.recv(1024)
        if d:
            data += d
        else:
            break
    data = data.decode("utf8")
    html_data = data.split("\r\n\r\n")[1]  # 丢弃响应头,只取HTML
    print(html_data)
    client.close()


if __name__ == "__main__":
    get_url("http://www.baidu.com")

  • 注意,这里每次发送请求都会先去建立socket连接,然后close。下次如果我们再发生请求的时候还要这样,这就比较耗时。这里只是体验socket编程的代码示例。而正常我们是应该保持长连接的。
  • socket编程是理解异步IO、协程的基础。是必须要掌握的。
  • socket编程过于底层,所以需要手动完成的功能比较多。

文章来源:https://blog.csdn.net/JENREY/article/details/134992470
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。