Python Socket编程

Python Socket编程

文章目录

  • Python Socket编程
    • 1. 弄懂HTTP、Socket、TCP这几个概念
      • 五层网络模型
    • 2. client和server实现通信
      • Socket编程模式指南
      • 代码实现
    • 3. socket实现聊天和多用户连接
    • 4. socket模拟http请求
    • 5. socket使用I/O多路复用模式模拟http请求

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 socketclient = 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 socketserver = 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 socketclient = 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 threadingserver = 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 urlparsedef get_url(url):"""通过socket请求html"""url = urlparse(url)  # 解析urlhost = 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 += delse:breakdata = data.decode("utf8")html_data = data.split("\r\n\r\n")[1]  # 丢弃响应头,只取HTMLprint(html_data)client.close()if __name__ == "__main__":get_url("http://www.baidu.com")
  • 注意,这里每次发送请求都会先去建立socket连接,然后close。下次如果我们再发生请求的时候还要这样,这就比较耗时。这里只是体验socket编程的代码示例。而正常我们是应该保持长连接的。
  • socket编程是理解异步IO、协程的基础。是必须要掌握的。
  • socket编程过于底层,所以需要手动完成的功能比较多。

5. socket使用I/O多路复用模式模拟http请求

  • 这里的代码了解即可。采用了 “select或者epoll + 回调 + 事件循环” 的编程模式
  • 这样写的好处就是并发行高,而且这里没有线程的切换,都在一个线程里面执行。而且省内存,因为1个线程消耗的内存是远远高于回调函数的模式的。
  • 回调函数其实就是一个指向函数的句柄而已。
  • 这里的代码就很好的展示了协程的核心原理(事件循环 + I/O多路复用)。
  • 这种方式其实就是当我们代码进行下一步操作的时候是让 事件循环loop 去驱动我们的回调函数
  • 缺点会让代码割裂的四分五裂。
# socket_select_http.py
"""
通过I/O多路复用模型中的select模式或者epoll模式实现http请求
"""import socket# selectors包的作用是根据不同的操作系统自动选择最佳的I/O多路复用模型
# 我们不要使用select包,而是要使用基于select包再次封装的selectors包
# DefaultSelector会自动帮我们根据代码运行的操作系统去选择使用poll或者epoll
# 在Windows下就是select,在Linux下就是epoll
# 而且DefaultSelector还为我们提供了注册机制
from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
from urllib.parse import urlparseselector = DefaultSelector()  # 创建一个selector对象(选择器)class Fetcher:def __init__(self):self.host = ""  # 域名self.path = ""  # 路径self.data = b""  # 存储返回的数据self.client = None  # socket对象def get_url(self, url):"""通过socket请求html"""url = urlparse(url)  # 解析urlself.host = url.netloc  # 获取域名self.path = url.path  # 获取域名后的子路径self.data = b""  # 存储返回的数据if self.path == "":self.path = "/"  # 如果直接请求的域名则把子路径改成/# 创建socket对象<class 'socket.socket'>, socket.AF_INET表示ipv4, socket.SOCK_STREAM表示TCPself.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 将socket设置为阻塞(true)或非阻塞(false), 默认为阻塞self.client.setblocking(False)  # 使用非阻塞try:# 连接self.client.connect((self.host, 80))  # 注意这里是非阻塞的,所以不会等待连接成功,而是会立刻返回并继续执行后续代码,所以会抛出BlockingIOError异常except BlockingIOError as e:pass  # 抛出BlockingIOError异常是合理的,虽然抛出了异常,但是与host的建立连接请求已经发出去了。# 将socket注册到selector中,监听socket是否是可写的状态# 参数1: socket的文件描述符# 参数2: 事件,有EVENT_WRITE和EVENT_READ# 参数3: 回调函数,即当socket的状态变为可写的时候,就会调用此回调函数selector.register(self.client.fileno(), EVENT_WRITE, self.connected)  # 使用事件监听, 等待连接成功, 然后再执行后面的代码# 注意这后面不能再写代码了,如果写了代码就是阻塞式IO了,所以我们才要使用事件监听,采用回调的方式来处理def connected(self, key):"""连接成功的回调函数,当socket的状态变为可写的时候,就会调用此回调函数,进行发送数据(发送HTTP请求)"""# 注销掉监控的事件,即取消注册,因为我们已经连接成功了,所以就不需要再监听socket是否是可写的状态了,所以要注销掉,否则会一直监听下去selector.unregister(key.fd)  # fd是file descriptor文件描述符# 发送数据,这里不用try...except...,是因为我们使用事件监听,当回调此方法的时候连接的状态一定是就绪的状态self.client.send(f"GET {self.path} HTTP/1.1\r\nHost:{self.host}\r\nConnection:close\r\n\r\n".encode("utf8"))# 注册,即使用事件监听,因为我们要接收服务器返回的响应数据,所以要再次监听socket是否是可读的状态,当socket的状态变为可读的时候,就会调用此回调readable方法接收数据,然后打印出来selector.register(self.client.fileno(), EVENT_READ, self.readable)def readable(self, key):"""接收数据(接收HTTP响应),当socket的状态变为可读的时候,就会调用此回调函数,进行接收数据(接收HTTP响应),然后打印出来"""d = self.client.recv(1024)  # 接收数据if d:  # 当数据没有读完的时候,继续读取self.data += delse:  # 当数据读完的时候,就注销掉监控的事件,即取消注册,因为我们已经接收完了,所以就不需要再监听socket是否是可读的状态了,所以要注销掉,否则会一直监听下去selector.unregister(key.fd)data = self.data.decode("utf8")html_data = data.split("\r\n\r\n")[1]print(html_data)self.client.close()def loop():"""回调+事件循环+select(poll/epoll)驱动整个程序运行,可以理解为心脏,这样不会去阻塞建立连接、等待请求等IO操作"""# 事件循环,不停的请求socket的状态并调用对应的回调函数# 所以这是个主循环# 1.select本身是不支持register模式# 2.socket状态变化以后的回调是由程序员完成的while True:# 注意在Windows下使用的是select模式,而select(r,w,w,timeout)的参数如果为空列表会抛出OSError异常# 而在Linux或者macOS下会使用epoll模式,就不会抛出OSError异常,会一直阻塞在这里# 不停的向操作系统请求有哪些socket已经准备好了ready = selector.select()  # 返回的是SelectorKey数据类型,是namedtuple类型for key, mask in ready:# key是一个SelectorKey类型的对象,包含了文件描述符fd、事件event、回调函数data等属性# mask是一个事件的遮罩,表示事件是可读的还是可写的,EVENT_READ或EVENT_WRITE,取决于我们注册的时候监听的是可读还是可写,即key.eventscall_back = key.data  # 获取回调函数,<bound method Fetcher.connected of <__main__.Fetcher object at 0x1025d55b0>>call_back(key)  # 执行回调函数,即调用connected方法if __name__ == "__main__":fetcher = Fetcher()fetcher.get_url("http://www.baidu.com")loop()

下面是解决Windows下使用的是select模式抛出OSError异常的问题

import socketfrom selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
from urllib.parse import urlparseselector = DefaultSelector()urls = ["http://www.baidu.com"]
stop = Falseclass Fetcher:def get_url(self, url):self.spider_url = urlurl = urlparse(url)self.host = url.netlocself.path = url.pathself.data = b""if self.path == "":self.path = "/"self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.client.setblocking(False)try:self.client.connect((self.host, 80))except BlockingIOError as e:passselector.register(self.client.fileno(), EVENT_WRITE, self.connected)def connected(self, key):selector.unregister(key.fd)self.client.send(f"GET {self.path} HTTP/1.1\r\nHost:{self.host}\r\nConnection:close\r\n\r\n".encode("utf8"))selector.register(self.client.fileno(), EVENT_READ, self.readable)def readable(self, key):d = self.client.recv(1024)if d:self.data += delse:selector.unregister(key.fd)data = self.data.decode("utf8")html_data = data.split("\r\n\r\n")[1]print(html_data)self.client.close()urls.remove(self.spider_url)if not urls:global stopstop = Truedef loop():while not stop:ready = selector.select()for key, mask in ready:call_back = key.datacall_back(key)if __name__ == "__main__":fetcher = Fetcher()fetcher.get_url("http://www.baidu.com")loop()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/223425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51单片机的外部中断的以及相关寄存器的讲解

中断系统 本文主要涉及8051单片机的中断系统的讲解与使用 其中包括中断相关寄存器的介绍与使用以及外部中断初始化的代码分析。 文章目录 中断系统一、 中断的介绍二、 中断结构及相关寄存器2.1 中断源 2.2 中断请求控制器2.2.1 TCON寄存器2.2.2 SCON寄存器2.2.3 中断允许寄存器…

【每日一题】【12.15】2415.反转二叉树的奇数层

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 2415. 反转二叉树的奇数层https://leetcode.cn/problems/reverse-odd-levels-of-binary-tree/ 今天终于碰到了一个mid题目&#x…

数据库常用分库分表方案

为什么需要分库分表 分库分表是因应数据库处理大规模数据时所面临的挑战而出现的解决方案. // 提高性能 单个数据库在数据量增加时容易出现性能瓶颈。分库分表可以减轻单个数据库的负担&#xff0c;提高系统的读写性能和响应速度. // 提高并发能力 大量用户同时访问数据库可能…

N-Channel Trench Power MOSFET FMA30H150SL

FMA30H150SL N-Channel Trench Power MOSFET FMA30H150SL Application &#xff1a;  LCD TV  Notebook  Elevator  Inductive heating  Power tools  Broadband FMA30H150SL Features &#xff1a;  30V,150A  RDS(ON)2.4mΩ (Typ.) VGS 10V …

若依 ruoyi-vue3 集成aj-captcha实现滑块、文字点选验证码

目录 0. 前言0.1 说明 1. 后端部分1.1 添加依赖1.2. 修改 application.yml1.3. 新增 CaptchaRedisService 类1.4. 添加必须文件1.5. 移除不需要的类1.6. 修改登录方法1.7. 新增验证码开关获取接口1.8. 允许匿名访问 2. 前端部分&#xff08;Vue3&#xff09;2.1. 新增依赖 cryp…

“一键调整尺寸,轻松完成视频批量剪辑:批量放大视频尺寸“

你是否曾经遇到过需要批量调整视频尺寸的情况&#xff1f;无论是为了适应不同的播放平台&#xff0c;还是为了满足客户的特定需求&#xff0c;批量调整视频尺寸都是一项繁琐而耗时的工作。但是&#xff0c;现在有一种方法可以让你轻松完成这项任务&#xff0c;那就是使用我们的…

Excel高效办公:文秘与行政办公的智能化革新

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f91f; 代理 IP 推荐&#xff1a;&#x1f449;品易 HTTP 代理 IP &#x1f485; 想寻找共同学习交流的小伙伴&#xff0c…

超声波清洗机怎么买不会踩坑?减少踩坑机会超声波清洗机推荐

在当今快节奏的生活中&#xff0c;超声波清洗机已经成为了我们生活中不可或缺的助手。无论是清洗餐具、首饰&#xff0c;还是清洗医疗设备、实验室仪器&#xff0c;超声波清洗机都能以其高效、便捷的特性&#xff0c;帮助我们解决各种清洁难题。然而&#xff0c;面对市场上琳琅…

ZeroSSL-ip证书配置

1.申请证书 Free SSL Certificates and SSL Tools - ZeroSSL 2.填入公网 IP 地址 3.选择90天免费 SSL 4.自动生成CSR 5.选择文件验证方式 使用80端口,建立对应的文件并进行访问测试 6. 进行认证 7.下载证书并进行配置 8.合并ssl证书 对于 Nginx 服务器,需要将 ca_bundle.crt…

ARM KEIL 安装

根据设备类型安装开发工具及环境 Arm,Cortex ----> MDK-Arm 8051 ----> C51 80251 ----> C251 C166,XC166,XC2000 MCU设备 ----> C155 填写信息提交后下载 点击MDK539.EXE下载 : MDK539.EXE 双击MDK539安装 点击Next 默认安装路径,点击Ne…

MySQL运维5-Mycat配置

一、schema.xml 1.1 简介 schema.xml作为Mycat中最重要的配置文件之一&#xff0c;涵盖了Mycat的逻辑库、逻辑表、分片规则、分片节点即数据源的配置。主要包括一下三组标签 schema标签 datanode标签 datahost标签 1.2 schema标签 用于定于Mycat实例中的逻辑库&#xff0c;一个…

FPGA - 1、Simulink HDL coder模型例化到FPGA

Simulink HDL coder模型例化到FPGA 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右…

匿名内部类与Lambda表达式

深入了解Java的匿名内部类 Java作为一种面向对象的编程语言&#xff0c;提供了许多灵活的特性&#xff0c;其中之一就是匿名内部类。匿名内部类是一种没有名字的局部内部类&#xff0c;通常用于创建只需在一个地方使用的类的实例。 什么是匿名内部类&#xff1f; 匿名内部类是…

Android12编译kernel4.9解决:scripts/gcc-wrapper.py, line 79, in run_gcc(一百六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

CLIP 改进工作串讲(上)【论文精读·42】

大家好&#xff0c;今天这个串讲其实是主要想说一下在过去一年中&#xff0c;大家是怎么把 CLIP 这个模型或者 CLIP 这个思想应用到其他领域中去的。本来是想先做一下这个多模态的串讲的&#xff0c;但是就在前几天看到了 CLICK puzzle 这篇论文&#xff0c;获得了这个今年 c G…

Java 反射:探索运行时行为的强大工具

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、浅层理解 1.1 定义 1.2 我的理解 二、反射基础与应用 2.1 反射基础 2.2 反射的高级应用 三、一些反射的例子 3.1 获取类…

常见Web开发安全漏洞的防御手段

一、Web开发安全漏洞的防御手段方案 输入验证和过滤&#xff1a;对用户输入进行严格的验证和过滤&#xff0c;确保输入的数据符合预期的格式和类型&#xff0c;防止恶意输入或注入攻击。参数化查询&#xff1a;使用预编译的SQL语句和参数化查询接口&#xff0c;避免将用户输入…

Notion开源平替知识库软件AFFiNE本地部署与公网访问远程协作

文章目录 前言1. 使用Docker安装AFFINE2. 安装cpolar内网穿透工具3. 配置AFFINE公网访问地址4. 实现公网远程访问AFFINE5. 结语 前言 本篇文章讲解Notion开源平替全能知识库工具AFFINE如何本地部署&#xff0c;并实现公网远程访问。AFFiNE 是一个全新的开源项目&#xff0c;旨…

分析若依的文件上传处理逻辑

分析若依的文件上传处理逻辑 注&#xff1a;已经从若依框架完成拆分&#xff0c;此处单独分析一下人家精彩的封装&#xff0c;也来理解一下怎么做一个通用的上传接口&#xff01;如有分析的&#xff0c;理解的不透彻的地方&#xff0c;大家多多包含&#xff0c;欢迎批评指正&am…

LabVIEW在高铁温度与振动监测中的应用

​LabVIEW在高铁温度与振动监测中的应用 高速铁路的可靠性和安全性是现代铁路运输系统设计和运营的重中之重。LabVIEW软件作为一个多功能、可扩展的图形编程环境&#xff0c;提供了一个理想的平台&#xff0c;用于开发高铁监测系统&#xff0c;不仅监测实时数据&#xff0c;也…