IOCP模型与网络编程

article/2025/11/8 10:38:21

IOCP模型与网络编程

一。前言:
        在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模式,装饰模式之类的那些呢?嘿嘿,不过好像是一个挺好玩的东西,挺好奇是什么东西来的,又是一个新知识啦~于是,开始去寻找一大堆的资料,为这个了解做准备,只是呢,有时还是想去找一本书去系统地学习一下,毕竟网络的资料还是有点零散。话说,本人学习这个模型的基础是,写过一个简单的Socket服务器及客户端程序,外加一个简单的Socket单服务器对多客户端程序,懂一点点的操作系统原理的知识。于是,本着一个学习与应用的态度开始探究这个IOCP是个什么东西。

 

二。提出相关问题:
       1.  IOCP模型是什么?
       2.  IOCP模型是用来解决什么问题的?它为什么存在?
       3.  使用IOCP模型需要用到哪些知识?
       4.  如何使用IOCP模型与Socket网络编程结合起来?
       5.  学会了这个模型以后与我之前写过的简单的socket程序主要有哪些不同点?

 

三。部分问题探究及解决:(绝大多数是个人理解,再加上个人是菜鸟,如果有什么不对的地方,欢迎指正)
       1.  什么是IOCP?什么是IOCP模型?IOCP模型有什么作用?
              1) IOCP(I/O Completion Port),常称I/O完成端口。
              2) IOCP模型属于一种通讯模型,适用于(能控制并发执行的)高负载服务器的一个技术。
              3) 通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。
              4) 或者可以说,就是能异步I/O操作的模型。
              5) 只是了解到这些会让人很糊涂,因为还是不知道它究意具体是个什么东东呢?


下面我想给大家看三个图:
第一个是IOCP的内部工作队列图。(整合于《IOCP本质论》文章,在英文的基础上加上中文对照)
 

第二个是程序实现IOCP模型的基本步骤。(整合于《深入解释IOCP》,加个人观点、理解、翻译)
 

第三个是使用了IOCP模型及没使用IOCP模型的程序流程图。(个人理解绘制)
 

 

2.  IOCP的存在理由(IOCP的优点)及技术相关有哪些?
        之前说过,很通俗地理解可以理解成是用于高效处理很多很多的客户端进行数据交换的一个模型,那么,它具体的优点有些什么呢?它到底用到了哪些技术了呢?在Windows环境下又如何去使用这些技术来编程呢?它主要使用上哪些API函数呢?呃~看来我真是一个问题多多的人,跟前面提出的相关问题变种延伸了不少的问题,好吧,下面一个个来解决。

 

1) 使用IOCP模型编程的优点
       ① 帮助维持重复使用的内存池。(与重叠I/O技术有关)
       ② 去除删除线程创建/终结负担。
       ③ 利于管理,分配线程,控制并发,最小化的线程上下文切换。
       ④ 优化线程调度,提高CPU和内存缓冲的命中率。

2) 使用IOCP模型编程汲及到的知识点(无先后顺序)
       ① 同步与异步
       ② 阻塞与非阻塞
       ③ 重叠I/O技术
       ④ 多线程
       ⑤ 栈、队列这两种基本的数据结构

3) 需要使用上的API函数
  ① 与SOCKET相关
       1、链接套接字动态链接库:int WSAStartup(...);
       2、创建套接字库:        SOCKET socket(...);
       3、绑字套接字:          int bind(...);
       4、套接字设为监听状态: int listen(...);
       5、接收套接字:          SOCKET accept(...);
       6、向指定套接字发送信息:int send(...);
       7、从指定套接字接收信息:int recv(...);

  ② 与线程相关
       1、创建线程:HANDLE CreateThread(...);

  ③ 重叠I/O技术相关
       1、向套接字发送数据:    int WSASend(...);
       2、向套接字发送数据包:  int WSASendFrom(...);
       3、从套接字接收数据:    int WSARecv(...);
       4、从套接字接收数据包:  int WSARecvFrom(...);

  ④ IOCP相关
       1、创建完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
       2、关联完成端口: HANDLE WINAPI CreateIoCompletionPort(...);
       3、获取队列完成状态: BOOL WINAPI GetQueuedCompletionStatus(...);
       4、投递一个队列完成状态:BOOL WINAPI PostQueuedCompletionStatus(...);

 

四。完整的简单的IOCP服务器与客户端代码实例:

 

// IOCP_TCPIP_Socket_Server.cpp
#include <WinSock2.h>
#include <Windows.h>
#include <vector>
#include <iostream>
using namespace std;
#pragma comment(lib, "Ws2_32.lib")		// Socket编程需用的动态链接库
#pragma comment(lib, "Kernel32.lib")	// IOCP需要用到的动态链接库
/**
* 结构体名称:PER_IO_DATA
* 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
const int DataBuffSize  = 2 * 1024;
typedef struct
{
OVERLAPPED overlapped;
WSABUF databuff;
char buffer[ DataBuffSize ];
int BufferLen;
int operationType;
}PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
/**
* 结构体名称:PER_HANDLE_DATA
* 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
* 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
**/
typedef struct
{
SOCKET socket;
SOCKADDR_STORAGE ClientAddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
// 定义全局变量
const int DefaultPort = 6000;		
vector < PER_HANDLE_DATA* > clientGroup;		// 记录客户端的向量组
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
DWORD WINAPI ServerSendThread(LPVOID IpParam);
// 开始主函数
int main()
{
// 加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
WSADATA wsaData;	// 接收Windows Socket的结构信息
DWORD err = WSAStartup(wVersionRequested, &wsaData);
if (0 != err){	// 检查套接字库是否申请成功
cerr << "Request Windows Socket Library Error!\n";
system("pause");
return -1;
}
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){// 检查是否申请了所需版本的套接字库
WSACleanup();
cerr << "Request Windows Socket Version 2.2 Error!\n";
system("pause");
return -1;
}
// 创建IOCP的内核对象
/**
* 需要用到的函数的原型:
* HANDLE WINAPI CreateIoCompletionPort(
*    __in   HANDLE FileHandle,		// 已经打开的文件句柄或者空句柄,一般是客户端的句柄
*    __in   HANDLE ExistingCompletionPort,	// 已经存在的IOCP句柄
*    __in   ULONG_PTR CompletionKey,	// 完成键,包含了指定I/O完成包的指定文件
*    __in   DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2
* );
**/
HANDLE completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == completionPort){	// 创建IO内核对象失败
cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 创建IOCP线程--线程里面创建线程池
// 确定处理器的核心数量
SYSTEM_INFO mySysInfo;
GetSystemInfo(&mySysInfo);
// 基于处理器的核心数量创建线程
for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i){
// 创建服务器工作器线程,并将完成端口传递到该线程
HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);
if(NULL == ThreadHandle){
cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
CloseHandle(ThreadHandle);
}
// 建立流式套接字
SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);
// 绑定SOCKET到本机
SOCKADDR_IN srvAddr;
srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
srvAddr.sin_family = AF_INET;
srvAddr.sin_port = htons(DefaultPort);
int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
if(SOCKET_ERROR == bindResult){
cerr << "Bind failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 将SOCKET设置为监听模式
int listenResult = listen(srvSocket, 10);
if(SOCKET_ERROR == listenResult){
cerr << "Listen failed. Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 开始处理IO数据
cout << "本服务器已准备就绪,正在等待客户端的接入...\n";
// 创建用于发送数据的线程
HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
while(true){
PER_HANDLE_DATA * PerHandleData = NULL;
SOCKADDR_IN saRemote;
int RemoteLen;
SOCKET acceptSocket;
// 接收连接,并分配完成端,这儿可以用AcceptEx()
RemoteLen = sizeof(saRemote);
acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
if(SOCKET_ERROR == acceptSocket){	// 接收客户端失败
cerr << "Accept Socket Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 创建用来和套接字关联的单句柄数据信息结构
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));	// 在堆中为这个PerHandleData申请指定大小的内存
PerHandleData -> socket = acceptSocket;
memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);
clientGroup.push_back(PerHandleData);		// 将单个客户端数据指针放到客户端组中
// 将接受套接字和完成端口关联
CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), completionPort, (DWORD)PerHandleData, 0);
// 开始在接受套接字上处理I/O使用重叠I/O机制
// 在新建的套接字上投递一个或多个异步
// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务	
// 单I/O操作数据(I/O重叠)
LPPER_IO_OPERATION_DATA PerIoData = NULL;
PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED));
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0;	// read
DWORD RecvBytes;
DWORD Flags = 0;
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
system("pause");
return 0;
}
// 开始服务工作线程函数
DWORD WINAPI ServerWorkThread(LPVOID IpParam)
{
HANDLE CompletionPort = (HANDLE)IpParam;
DWORD BytesTransferred;
LPOVERLAPPED IpOverlapped;
LPPER_HANDLE_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD RecvBytes;
DWORD Flags = 0;
BOOL bRet = false;
while(true){
bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
if(bRet == 0){
cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;
return -1;
}
PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
// 检查在套接字上是否有错误发生
if(0 == BytesTransferred){
closesocket(PerHandleData->socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
// 开始数据处理,接收来自客户端的数据
WaitForSingleObject(hMutex,INFINITE);
cout << "A Client says: " << PerIoData->databuff.buf << endl;
ReleaseMutex(hMutex);
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0;	// read
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
return 0;
}
// 发送信息的线程执行函数
DWORD WINAPI ServerSendThread(LPVOID IpParam)
{
while(1){
char talk[200];
gets(talk);
int len;
for (len = 0; talk[len] != '\0'; ++len){
// 找出这个字符组的长度
}
talk[len] = '\n';
talk[++len] = '\0';
printf("I Say:");
cout << talk;
WaitForSingleObject(hMutex,INFINITE);
for(int i = 0; i < clientGroup.size(); ++i){
send(clientGroup[i]->socket, talk, 200, 0);	// 发送信息
}
ReleaseMutex(hMutex); 
}
return 0;
}


 

// IOCP_TCPIP_Socket_Client.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <Windows.h>
using namespace std;
#pragma comment(lib, "Ws2_32.lib")		// Socket编程需用的动态链接库
SOCKET sockClient;		// 连接成功后的套接字
HANDLE bufferMutex;		// 令其能互斥成功正常通信的信号量句柄
const int DefaultPort = 6000;
int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData;	// 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( 2, 2 );	// 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {	// 返回值为零的时候是表示成功申请WSAStartup
return -1;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||	HIBYTE( wsaData.wVersion ) != 2 ) { // 检查版本号是否正确
WSACleanup( );
return -1; 
}
// 创建socket操作,建立流式套接字,返回套接字号sockClient
sockClient = socket(AF_INET, SOCK_STREAM, 0);
if(sockClient == INVALID_SOCKET) { 
printf("Error at socket():%ld\n", WSAGetLastError()); 
WSACleanup(); 
return -1; 
} 
// 将套接字sockClient与远程主机相连
// int connect( SOCKET s,  const struct sockaddr* name,  int namelen);
// 第一个参数:需要进行连接操作的套接字
// 第二个参数:设定所需要连接的地址信息
// 第三个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");		// 本地回路地址是127.0.0.1; 
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(DefaultPort);
while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){
// 如果还没连接上服务器则要求重连
cout << "服务器连接失败,是否重新连接?(Y/N):";
char choice;
while(cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N')))){
cout << "输入错误,请重新输入:";
cin.sync();
cin.clear();
}
if (choice == 'Y'){
continue;
}
else{
cout << "退出系统中...";
system("pause");
return 0;
}
}
cin.sync();
cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";
send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
bufferMutex = CreateSemaphore(NULL, 1, 1, NULL); 
DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);  
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);  
WaitForSingleObject(sendThread, INFINITE);  // 等待线程结束
closesocket(sockClient);
CloseHandle(sendThread);
CloseHandle(receiveThread);
CloseHandle(bufferMutex);
WSACleanup();	// 终止对套接字库的使用
printf("End linking...\n");
printf("\n");
system("pause");
return 0;
}
DWORD WINAPI SendMessageThread(LPVOID IpParameter)
{
while(1){
string talk;
getline(cin, talk);
WaitForSingleObject(bufferMutex, INFINITE);		// P(资源未被占用)  
if("quit" == talk){
talk.push_back('\0');
send(sockClient, talk.c_str(), 200, 0);
break;
}
else{
talk.append("\n");
}
printf("\nI Say:(\"quit\"to exit):");
cout << talk;
send(sockClient, talk.c_str(), 200, 0);	// 发送信息
ReleaseSemaphore(bufferMutex, 1, NULL);		// V(资源占用完毕) 
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
while(1){	
char recvBuf[300];
recv(sockClient, recvBuf, 200, 0);
WaitForSingleObject(bufferMutex, INFINITE);		// P(资源未被占用)  
printf("%s Says: %s", "Server", recvBuf);		// 接收信息
ReleaseSemaphore(bufferMutex, 1, NULL);		// V(资源占用完毕) 
}
return 0;
}


 


五。本次学习资料
       几翻周折,终于写出一个比较简单的IOCP模型的服务器与客户端啦,并且也大概了解这个模型的思路啦~没有买书的娃,伤不起啊,只能从网上搜罗资料,幸好有这些文章在,最后为下列这些文章的作者说声谢谢~

 

 


http://chatgpt.dhexx.cn/article/ZjFviIAS.shtml

相关文章

Windows下的IOCP模型(一):介绍与简单使用

一、IOCP简介 IOCP&#xff08;I/O Completion Port&#xff0c;I/O完成端口&#xff09;是Windows操作系统中伸缩性最好的一种I/O模型。I/O 完成端口是应用程序使用线程池处理异步 I/O 请求的一种机制。处理多个并发异步I/O请求时&#xff0c;使用 I/O 完成端口比在 I/O 请求时…

完成端口(IOCP)编程探讨

FW: http://www.zhuaxia.com/item/473350387 完成端口(IOCP)编程探讨 2007-08-26 16:06:00 来自&#xff1a;C博客-首页原创精华区 新建目录...根目录新手试用频道 本文主要探讨一下windows平台上的完成端口开发及其与之相关的几个重要的技术概念&#xff0c;这些概念都是与…

异步通信之IOCP详解

一、 概述 学习完网络基础&#xff0c;在写C/S应用程序时&#xff0c;大多童靴写服务器基本都没有用到io模型&#xff0c;基本都是采用“accept同步拥塞通讯和多线程方式”与客户端通讯。但当有成千上万客户端请求连接并与服务器通讯时&#xff0c;多线程的创建与CPU上下文的切…

IOCP详解

IOCP详解 IOCP&#xff08;I/O Completion Port&#xff0c;I/O完成端口&#xff09;是性能最好的一种I/O模型。它是应用程序使用线程池处理异步I/O请求的一种机制。在处理多个并发的异步I/O请求时&#xff0c;以往的模型都是在接收请求是创建一个线程来应答请求。这样就有很多…

IOCP技术详解

这几周我接触了Windows网络通讯中的IOCP模型,自己在网上找了相关的知识进行学习&#xff0c;自己又下了好多服务器端的代码&#xff0c;但都运行不了&#xff0c;也是自己菜&#xff0c;能力还需加强。幸好我师父资助了我一个能运行的服务端IOCP代码&#xff0c;自己参照网上的…

Python正则表达式模式

在 Python 程序中&#xff0c;模式字符串使用如下特殊的语法来表示一个正则表达式&#xff1a; 字母和数字表示它们自身&#xff0c;一个正则表达式模式中的字母和数字匹配同样的字符串&#xff1b;当大多数字母和数字前加一个反斜杠时&#xff0c;它们会拥有不同的含义&#…

Python正则表达式实例详解

一、正则表达式语法 正则表达式是用匹配或者描述字符串的工具。 用处&#xff1a; a.判断字符串是否满足某个条件—判断输入的字符串是否是邮箱/手机号码。是否是ip地址 b.提取满足条件的字符串 c.字符串替换 Python中通过re模块中相应的方法来支持正则表达式的匹配、查找和替…

Python正则表达式语法快速入门

文章目录 1 正则符号初阶代码举例1&#xff1a;不同符号的组合代码举例2&#xff1a;符号加&#xff0c;代表连续的一个或多个代码举例3&#xff1a;匹配字符串开始代码举例4&#xff1a;匹配字符串结束代码举例5&#xff1a;匹配单词边界 2 正则符号进阶代码举例1&#xff1a;…

python正则表达式入门

&#x1f64a;今天我们来学习python的正则表达式的部分&#xff0c;先说下为什么要学习这一部分呢&#xff0c;当然是因为正则表达式处理文本类型的数据实在是太方便了。为以后进入nlp领域打打基础&#xff01; 先给大家推荐一个网站: 用于正则表达式验证. 大致就长这个样子。…

python使用正则表达式

一、使用正则表达式步骤 1、寻找规律&#xff1b; 2、使用正则符号表示规律&#xff1b; 3、提取信息&#xff0c;如果每一个字符都能匹配&#xff0c;则匹配成功&#xff1b;一旦有匹配不成功的字符则匹配失败。 二、正则表达式中常见的基本符号 1&#xff0e;点号“.”一…

Python 正则表达式

1.正则表达式的定义: 正则表达式是对字符串进行解析&#xff08;获取一大串字符串信息中&#xff0c;你想要的部分&#xff09;。 正则表达式是一种文本模式&#xff0c;模式描述在搜索文本时要匹配的一个或多个字符串。 正则表达式&#xff0c;又成正规表示式&#xff0c;正规…

python正则表达式详解

正则表达式是一个很强大的字符串处理工具&#xff0c;几乎任何关于字符串的操作都可以使用正则表达式来完成&#xff0c;作为一个爬虫工作者&#xff0c;每天和字符串打交道&#xff0c;正则表达式更是不可或缺的技能&#xff0c;正则表达式的在不同的语言中使用方式可能不一样…

详解Python正则表达式(含丰富案例)

前言&#xff1a;正则表达式在网络爬虫、数据分析中有着广泛使用&#xff0c;掌握正则表达式能够达到事半功倍的效果。本文详细介绍正则表达式中各种规则及其符号含义&#xff0c;并结合Python中的Re库进行演示&#xff0c;由浅入深&#xff0c;即学即练即用&#xff0c;内容丰…

Python超详细的正则表达式

目录 介绍 常用的正则表达式匹配规则表格 1.match() .group() 常用知识点 1.通用匹配 2.贪婪与非贪婪 3.修饰符 4.转义匹配 2.search() 3.findall() 4.sub() 总结 Hello大家好我是&#xff0c;PYmili&#xff01;今天给大家带来正则表达式的使用教程即匹配规则表格…

Python正则表达式(一看就懂)

目录 哈喽O(∩_∩)O&#x1f604; 什么是正则表达式(⊙_⊙) 简单说&#xff0c;正则表达式是… 正则表达式怎么用❓ sreach的用法&#x1f34a; 匹配连续的多个数值&#x1f349; 字符""重复前面一个匹配字符一次或者多次&#x1f349; 字符"*"重复前…

Python正则表达式详解 (超详细,看完必会!)

正则表达式详解 正则表达式 英文名称叫 Regular Expression简称RegEx&#xff0c;是用来匹配字符的一种工具&#xff0c;它常被用在网页爬虫&#xff0c;文稿整理&#xff0c;数据筛选等方面&#xff0c;最常用的就是用在网页爬虫&#xff0c;数据抓取。 一、正则表达式的各种…

Python 正则表达式详解(建议收藏!)

目录 match 匹配字符串 单字符匹配 . 匹配任意一个字符 \d 匹配数字 \D 匹配非数字 \s 匹配特殊字符&#xff0c;如空白&#xff0c;空格&#xff0c;tab等 \S 匹配非空白 \w 匹配单词、字符&#xff0c;如大小写字母&#xff0c;数字&#xff0c;_ 下划线 \W 匹配非…

Android工程中方法数超过65536解决方法(Kotlin)

Android Studio报错&#xff1a; The number of method references in a .dex file cannot exceed 64K. Caused by: com.android.tools.r8.utils.AbortException: Error: Cannot fit requested classes in a single dex file (# methods: 68815 > 65536) 解决方案 build.…

postman/EOLINKER测试报错 RangeError:Port should be 0 and 65536. Received 80892.

postman/EOLINKER测试报错 RangeError&#xff1a;Port should be > 0 and < 65536. Received 80892. RangeError&#xff1a;范围错误 RangeError是当一个只超出有效范围时发生的错误。主要的有几种情况&#xff0c;第一是数组长度为负数&#xff0c;第二是Number对象…

Android Studio Cannot fit requested classes in a single dex file (# methods: 72633 > 65536)解决办法

今天在Android Studio中构建Android工程时&#xff0c;出现了这样报错&#xff1a;“Cannot fit requested classes in a single dex file (# methods: 72633 &#xff1e; 65536)” 在网上找了一圈儿&#xff0c;总结个简单快速的解决方法 问题&#xff1a; Cannot fit req…