【网络编程】WSAAsyncSelect 模型

十、基于I/O模型的网络开发

接着上次的博客继续分享:select模型

10.8 异步选择模型WSAAsyncSelect

10.8.1 基本概念

  • WSAAsyncSelect模型是Windows socket的一个异步I/O 模型,利用这个模型,应用程序 可在一个套接字上接收以Windows 消息为基础的网络事件通知。

  • Windows sockets应用程序在 创建套接字后,调用WSAAsyncSelect 函数注册感兴趣的网络事件,当该事件发生时Windows 窗口收到消息,应用程序就可以对接收到的网络事件进行处理了。

  • 利用WSAAsyncSelect 函数, 将socket 消息发送到hWnd 窗口上,然后在那里处理相应的FD_READ 、FD_WRITE 等消息。

  • WSAAsyncSelect 模型与select 模型的相同点是它们都可以对多个套接字进行管理。

  • 但它 们也有不小的区别。首先WSAAsyncSelect 模型是异步的,且通知方式不同。更重要的一点是: WSAAsyncSelect 模型应用在基于消息的Windows 环境下,使用该模型时必须创建窗口,而 select 模型可以广泛应用在UNIX/Linux 系统,使用该模型不需要创建窗口。最后一点区别是:应用程序在调用WSAAsyncSelect 函数后,套接字就被设置为非阻塞状态;而使用select 函数 不改变套接字的工作方式。

  • 由于要关联一个Windows 窗口来接收消息,因此如果处理成千上万的套接字就力不从心 了。这也是该模型的一个缺点。另外,由于调用WSAAsyncSelect 后,套接字被设为非阻塞模 式,那么其他一些函数调用不一定能成功返回,必须要对这些函数的调用返回做处理。对于这 一点,可以从accept() 、receive() 和 send() 等函数的调用中得到验证。

  • WSAAsyncSelect模型也有其优点,即提供了读写数据能力的异步通知。而且,该模型为 确保接收所有数据提供了很好的机制,通过注册FD_CLOSE网络事件,可以从容关闭服务器与客户端的连接,保证了数据的全部接收。

10.8.2 WSAAsyncSelect函数

WSAAsyncSelect函数会自动将套接字设置为非阻塞模式,并且把发生在该套接字上且是 你所感兴趣的事件以Windows 消息的形式发送到指定的窗口。

WSAAsyncSelect函数声明如下:

int WSAAsyncSelect(
    in SOCKET s,
    in HWND hWnd,
    __in unsigned int wMsg,
    __in long lEvent);
  • s: 标识一个需要事件通知的套接口的描述符。

  • hWnd: 标识一个在网络事件发生时需要接收消息的窗口句柄。

  • wMsg: 在网络事件发生时要接收的消息。

  • IEvent: 位屏蔽码,用于指明应用程序感兴趣的网络事件集合。IEvent 参数可取下列 值:

    • FD_READ: 欲接收读准备好的通知。发生FD_READ 的条件是:
      • 调 用recv 或 者recvfrom 函数后,仍然有数据可读。
      • 调用WSAAsyncSelect 有数据可读。
    • FD_WRITE: 欲接收写准备好的通知。发生FD_WRITE 的条件是:
      • 当调用WSAAsyncSelect 函数时,如果调用能够发送数据。
      • 调用connect 或 者accept 函数后,当连接已经建立时。
      • 调用send 或 者sendto, 返 回WSAWOULDBLOCK 错误码,再次调用send 或 者sendto 函数可能成功时。
    • FD_OOB: 欲接收带边数据到达的通知。
    • FD_ACCEPT: 欲接收将要连接的通知。
    • FD_CONNECT: 欲接收已连接好的通知。
    • FD_CLOSE: 欲接收套接口关闭的通知。发生FD CLOSE 的条件是:
      • 当调用WSAAsyncSelect 函数时,套接字连接关闭时。
      • 对方执行从容关闭后,没有数据可读时,如果数据已经到达并等待读取,FD_CLOSE 事件不会被发送,直到所有数据都被接收。
      • 调用shutdown 函数执行从容关闭,对方应答FIN 后,此时无数据可读。
      • 对方结束了连接,并且lparam 包 含WSAECONNRESET 错误时。
    • FD_QOS: 欲接收套接字服务质量发生变化的通知。
    • FD_GROUP_QOS: 欲接收套接字组服务质量发生变化的通知。
    • FD_ADDRESS_LIST_CHANGE: 欲接收针对套接字的协议簇,本地地址列表发生变化的通知。
    • FD ROUTING INTERFACE CHANGE: 欲在指定方向上与路由接口发生变化的通知 。

如果函数成功就返回0,如果出错就返回 SOCKET_ERROR,此时可用函数 WSAGetLastError 获取更多信息。

可根据需要同时注册多个网络事件,这时要把网络事件类型执行按位或(OR) 运算,然 后将它们分配给 IEvent 参数。例如,应用程序希望在套接字上接收连接完成、数据可读和套 接字关闭的网络事件,可调用如下函数:

WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_CLOSE);

当该套接字连接完成、有数据可读或者套接字关闭的网络事件发生时,就会有 WM_SOCKET消息发送给窗口句柄为hwnd 的窗口。

值得注意的是,启动一个 WSAAsyncSelect 将使为同一个套接口启动的所有先前的 WSAAsyncSelect 作废。

使用WSAAsyncSelect 函数需要注意的地方:

  • (1)调用该函数后,套接字被设置为非阻塞模式,要想恢复为阻塞模式,必须再次调用 该函数,取消掉注册过的事件,再调用ioctlsocket 设为阻塞模式。如果要取消所有的网络事件通知,告知windows sockets实现不再为该套接字发送任何网
    络事件相关的消息,要以参数IEvent 值为0调用函数,即
WSAAsyncSelect(s, hwnd,0,0)

尽管应用程序调用上述函数取消了网络事件通知,但是在应用程序消息队列中,可能还有 网络消息在排队。所以调用上述函数取消网络事件消息后,应用程序还应该继续准备接收网络 事 件 。

  • (2)消息函数的wParam 参数为事件发生的套接字,LParam 对应错误消息和相应的事件, 可以调用宏WSAGETSELECTERROR(IParam) 、WSAGETSELECTEVENT(IParam) 来获取具体 的 信 息 。
  • (3)多次调用WSAAsyncSelect 函数在同一个套接字上注册不同的事件(多次调用采用 同样或者不同样的消息),最后一次调用将取消前面注册的事件。比如前后两次调用:
WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_READ);
WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_WRITE);

此时虽然消息相同,都是WM_SOCKET, 但是应用程序只能接收到FD_WRITE 网络事件。

还有一种情况是消息不同、网络事件也不同,比如:

WSAAsyncSelect(s, hwnd, wMsg1, FD_READ);
WSAAsyncSelect(s, hwnd, wMsg2, FD_WRITE);

第二次函数调用依旧将会取消第一次函数调用的作用,只有 FD_WRITE 网络事件通过wMsg2 通知到窗口。
这也是很多初学者发现接收不到网络事件的原因。因为最后一次调用将取消前面注册的事 件。

  • (4)使用accept 函数建立的套接字与监听套接字具有同样的属性,也就是说,在监听套 接字上注册的事件同样会对建立连接的套接字起作用,如果一个监听套接字请求 FD_READ 和 FD_WRITE 网络事件,那么在该监听套接字上接受的任何套接字也会请求 FD_READ 和 FD_WRITE 网络事件,以及发送同样的消息。

我们一般会在监听套接字建立连接后重新为其注册事件。

  • (5)为一个FD_READ网络事件不要多次调用recv(函数,如果应用程序为一个FD_READ 网络事件调用多个recv()函数,就会使得该应用程序收到多个FD_READ 网络事件。如果在一 次接收FD_READ 网络事件时需要调用多次 recv()函数,应用程序就应该在调用recv()函数之 前关闭FD_READ消息。

  • (6)使用FD_CLOSE 事件来判断套接字是否已经关闭,错误代码指示套接字是从容关闭 还是硬关闭:错误码为0,代表从容关闭;错误码为WSAECONNERESET, 则为硬关闭。如 果套接字从容关闭,数据全部接收,应用程序就会收到FD_CLOSE。

  • (7)发送数据出现失败。一个应用程序当接收到第一个FD_WRITE 网络事件后,便认为 在该套接字上可以发送数据。当调用输出函数发送数据时,会收到 WSAEWOULDBLOCKE 错误。经过这样的失败后,要在下一次接收到FD_WRITE网络事件后再次发送数据,才能够 将数据成功发送。

10.8.3 实战WSAAsyncSelect 模型

WSAAsyncSelect 传参需要窗口句柄。为了简化代码,这里直接创建了一个mfc 对话框程 序,用m_hwnd 给 WSAAsyncSelect 传参。对话框类名为WSAAsyncSelecDlg。

服务端

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")

#define WM_SOCKET (WM_USER + 101)

//-------------------窗口过程----------------------
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SOCKET:
    {
        SOCKET ss = wParam;   // wParam 参数标志了网络事件发生的套接口
        long event = WSAGETSELECTEVENT(lParam); // 事件
        int error = WSAGETSELECTERROR(lParam);  // 错误码

        if (error)
        {
            closesocket(ss);
            return 0;
        }

        switch (event)
        {
        case FD_ACCEPT:   //-----①连接请求到来
        {
            sockaddr_in Cadd;
            int Cadd_len = sizeof(Cadd);
            SOCKET sNew = accept(ss, (sockaddr*)&Cadd, &Cadd_len);
            if (sNew == INVALID_SOCKET)
            {
                MessageBox(hwnd, L"调用accept()失败!", L"标题栏提示", MB_OK);
            }
            else
            {
                WSAAsyncSelect(sNew, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
            }
        } break;

        case FD_READ:   //-----②数据发送来
        {
            char cbuf[256];
            memset(cbuf, 0, sizeof(cbuf));
            int cRecv = recv(ss, cbuf, sizeof(cbuf), 0);
            if ((cRecv == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET) || cRecv == 0)
            {
                MessageBox(hwnd, L"调用recv()失败!", L"标题栏提示", MB_OK);
                closesocket(ss);
            }
            else if (cRecv > 0)
            {
                // 转换消息为宽字符
                wchar_t wbuf[256];
                MultiByteToWideChar(CP_ACP, 0, cbuf, -1, wbuf, sizeof(wbuf) / sizeof(wchar_t));
                MessageBox(hwnd, wbuf, L"收到的信息", MB_OK);

                char Sbuf[] = "Hello client! I am server";
                int isend = send(ss, Sbuf, sizeof(Sbuf), 0);
                if (isend == SOCKET_ERROR || isend <= 0)
                {
                    MessageBox(hwnd, L"发送消息失败!", L"标题栏提示", MB_OK);
                }
                else
                {
                    MessageBox(hwnd, L"已经发信息到客户端!", L"标题栏提示", MB_OK);
                }
            }
        } break;

        case FD_CLOSE:    //----③关闭连接
        {
            closesocket(ss);
        }
        break;
        }
    }
    break;

    case WM_CLOSE:
        if (IDYES == MessageBox(hwnd, L"是否确定退出?", L"message", MB_YESNO))
            DestroyWindow(hwnd);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}


//----------------WinMain()函数------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    HBRUSH hbrush = CreateSolidBrush(RGB(0, 128, 25));
    wc.hbrBackground = hbrush;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Test";

    //---注册窗口类(使用宽字符版本函数)---- 
    RegisterClassW(&wc);

    //---创建窗口---- 
    HWND hwnd = CreateWindowW(L"Test", L"WSAAsyncSelect模型-服务端窗口", WS_SYSMENU, 300, 0, 600, 400, NULL, NULL, hInstance, NULL);
    if (hwnd == NULL)
    {
        MessageBoxW(NULL, L"创建窗口出错", L"标题栏提示", MB_OK);
        return 1;
    }

    //---显示窗口---- 
    ShowWindow(hwnd, SW_SHOWNORMAL);
    UpdateWindow(hwnd);

    //---初始化WSA---
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(2, 2);
    if (WSAStartup(wVersionRequested, &wsaData) != 0)
    {
        MessageBoxW(NULL, L"WSAStartup() Failed", L"调用失败", 0);
        return 1;
    }

    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == INVALID_SOCKET)
    {
        MessageBoxW(NULL, L"socket() Failed", L"调用失败", 0);
        return 1;
    }

    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(6000);
    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    if (bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        MessageBoxW(NULL, L"bind() Failed", L"调用失败", 0);
        return 1;
    }

    if (listen(s, 3) == SOCKET_ERROR)
    {
        MessageBoxW(NULL, L"listen() Failed", L"调用失败", 0);
        return 1;
    }
    else
        MessageBoxW(hwnd, L"进入监听状态!", L"标题栏提示", MB_OK);

    WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);

    //---消息循环----
    MSG msg;
    while (GetMessageW(&msg, 0, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    closesocket(s);
    WSACleanup();
    return msg.wParam;
}

客户端

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include<stdlib.h>
#include<WINSOCK2.H>
#include <windows.h> 
#include <process.h>  

#include<iostream>
#include<string>
using namespace std;

#define BUF_SIZE 64
#pragma comment(lib,"wS2_32.lib")


void recv(PVOID pt)
{
	SOCKET  sHost = *((SOCKET*)pt);

	while (true)
	{
		char buf[BUF_SIZE];//清空接收数据的缓冲区
		memset(buf, 0, BUF_SIZE);
		int retVal = recv(sHost, buf, sizeof(buf), 0);
		if (SOCKET_ERROR == retVal)
		{
			int  err = WSAGetLastError();
			//无法立即完成非阻塞Socket上的操作
			if (err == WSAEWOULDBLOCK)
			{
				Sleep(1000);
				//printf("\nwaiting  reply!");
				continue;
			}
			else if (err == WSAETIMEDOUT || err == WSAENETDOWN || err == WSAECONNRESET)//已建立连接
			{
				printf("recv failed!");
				closesocket(sHost);
				WSACleanup();
				return;
			}

		}

		Sleep(100);

		printf("\n%s", buf);
		//break;
	}
}


int main()
{
	WSADATA wsd;
	SOCKET sHost;
	SOCKADDR_IN servAddr;//服务器地址
	int retVal;//调用Socket函数的返回值
	char buf[BUF_SIZE];
	//初始化Socket环境
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		printf("WSAStartup failed!\n");
		return -1;
	}
	sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	//设置服务器Socket地址
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//在实际应用中,建议将服务器的IP地址和端口号保存在配置文件中
	servAddr.sin_port = htons(6000);
	//计算地址的长度
	int sServerAddlen = sizeof(servAddr);



	//调用ioctlsocket()将其设置为非阻塞模式
	int iMode = 1;
	retVal = ioctlsocket(sHost, FIONBIO, (u_long FAR*) & iMode);


	if (retVal == SOCKET_ERROR)
	{
		printf("ioctlsocket failed!");
		WSACleanup();
		return -1;
	}


	//循环等待
	while (true)
	{
		//连接到服务器
		retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));
		if (SOCKET_ERROR == retVal)
		{
			int err = WSAGetLastError();
			//无法立即完成非阻塞Socket上的操作
			if (err == WSAEWOULDBLOCK || err == WSAEINVAL)
			{
				Sleep(1);
				printf("check  connect!\n");
				continue;
			}
			else if (err == WSAEISCONN)//已建立连接
			{
				break;
			}
			else
			{
				printf("connection failed!\n");
				closesocket(sHost);
				WSACleanup();
				return -1;
			}
		}
	}


	unsigned long     threadId = _beginthread(recv, 0, &sHost);//启动一个线程接收数据的线程   



	while (true)
	{
		//向服务器发送字符串,并显示反馈信息
		printf("input a string to send:\n");
		std::string str;
		//接收输入的数据
		std::cin >> str;
		//将用户输入的数据复制到buf中
		ZeroMemory(buf, BUF_SIZE);
		strcpy_s(buf, str.c_str());
		if (strcmp(buf, "quit") == 0)
		{
			printf("quit!\n");
			break;
		}

		while (true)
		{
			retVal = send(sHost, buf, strlen(buf), 0);
			if (SOCKET_ERROR == retVal)
			{
				int err = WSAGetLastError();
				if (err == WSAEWOULDBLOCK)
				{
					//无法立即完成非阻塞Socket上的操作
					Sleep(5);
					continue;
				}

				else
				{
					printf("send failed!\n");
					closesocket(sHost);
					WSACleanup();
					return -1;
				}
			}
			break;
		}


	}

	return 0;
}

监听状态:

在这里插入图片描述

收到客户端消息:

在这里插入图片描述

参考书籍:《Visual C++2017 网络编程实战》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从0开始的操作系统手搓教程43——实现一个简单的shell

目录 添加 read 系统调用&#xff0c;获取键盘输入 :sys_read putchar和clear 上班&#xff1a;实现一个简单的shell 测试上电 我们下面来实现一个简单的shell 添加 read 系统调用&#xff0c;获取键盘输入 :sys_read /* Read count bytes from the file pointed to by fi…

鸿蒙应用开发—数据持久化之SQLite

文章目录 SQLite简介创建数据库添加数据查询数据更新数据删除数据升级数据库使用事务参考 SQLite简介 SQLite是一个轻量级关系数据库&#xff0c;占用资源很少&#xff0c;只有几百KB的大小&#xff0c;无需服务器支撑&#xff0c;是一个零配置、事务性的SQL数据库引擎。 相对…

应急响应--流量分析

&#xff08;一&#xff09;Cobalt Strike流量特征分析 1.HTTP特征 源码特征&#xff1a; 在流量中&#xff0c;通过http协议的url路径&#xff0c;在checksum8解密算法计算后&#xff0c;32位的后门得到的结果是92&#xff0c;64位的后门得到的结果是93&#xff0c;该特征符…

初始化E9环境,安装Sqlserver数据库

title: 初始化E9环境,安装Sqlserver数据库 date: 2025-03-10 19:27:19 tags: E9SqlServer初始化E9环境,安装Sqlserver数据库 安装E9本地环境安装Sql server 数据库1、检查SQL Server服务是否开启2、检查SQL Server网络网络配置是否开启创建一个ecology数据库点击初始化数据库…

自然语言处理:无监督朴素贝叶斯模型

介绍 大家好&#xff0c;博主又来和大家分享自然语言处理领域的知识了&#xff0c;今天给大家介绍的是无监督朴素贝叶斯模型。 在自然语言处理这个充满挑战又极具魅力的领域&#xff0c;如何从海量的文本数据中挖掘有价值的信息&#xff0c;一直是研究者们不断探索的课题。无…

API调试工具的无解困境:白名单、动态IP与平台设计问题

引言 你是否曾经在开发中遇到过这样的尴尬情形&#xff1a;你打开了平台的API调试工具&#xff0c;准备一番操作&#xff0c;结果却发现根本无法连接到平台&#xff1f;别急&#xff0c;问题出在调试工具本身。今天我们要吐槽的就是那些神奇的开放平台API调试工具&#xff0c;…

VSCode 2025最新前端开发必备插件推荐汇总(提效指南)

&#x1f31f;前言: 如果你是一名前端开发工程师&#xff0c;合适的开发工具能大大提高工作效率。Visual Studio Code (VSCode) 凭借其轻量级、高扩展性的特点&#xff0c;已成为众多前端开发者在win系电脑的首选IDE。 名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。—…

小程序事件系统 —— 33 事件传参 - data-*自定义数据

事件传参&#xff1a;在触发事件时&#xff0c;将一些数据作为参数传递给事件处理函数的过程&#xff0c;就是事件传参&#xff1b; 在微信小程序中&#xff0c;我们经常会在组件上添加一些自定义数据&#xff0c;然后在事件处理函数中获取这些自定义数据&#xff0c;从而完成…

初阶数据结构(C语言实现)——4.2队列

目录 2.队列2.1队列的概念及结构2.2队列的实现2.2.1 初始化队列2.2.2 销毁队列2.2.3 队尾入队列2.2.4 队头出队列2.2.5获取队列头部元素2.2.6 获取队列队尾元素2.2.7获取队列中有效元素个数2.2.8 检测队列是否为空&#xff0c;如果为空返回非零结果&#xff0c;如果非空返回0 3…

linux 命令 cat

cat 是 Linux 中用于查看、创建和合并文件的常用命令&#xff0c;全称 concatenate&#xff08;连接&#xff09;。其核心功能是将文件内容输出到终端或重定向到其他文件/命令中。以下是详细用法及场景示例&#xff1a; 基本语法 cat [选项] [文件1] [文件2] ... 选项…

TON基金会确认冠名赞助2025香港Web3嘉年华,并将于4月8日重磅呈现“TON生态日”

近日&#xff0c;由万向区块链实验室与HashKey Group联合推出的Web3年度盛典——2025香港Web3嘉年华正式宣布&#xff0c;TON基金会确认成为本届嘉年华的冠名赞助商&#xff0c;并将于4月8日在主会场特别举办“TON生态日”专题Side Event&#xff0c;集中展现TON生态的最新技术…

【Java代码审计 | 第七篇】文件上传漏洞成因及防范

未经许可&#xff0c;不得转载。 文章目录 文件上传漏洞漏洞成因未验证文件类型和扩展名未限制文件上传路径 防范验证文件类型和扩展名验证文件内容限制文件上传路径使用安全的文件上传库 标准代码 文件上传漏洞 文件上传漏洞是指攻击者通过上传恶意文件&#xff08;如可执行脚…

【无人机路径规划】基于麻雀搜索算法(SSA)的无人机路径规划(Matlab)

效果一览 代码获取私信博主基于麻雀搜索算法&#xff08;SSA&#xff09;的无人机路径规划&#xff08;Matlab&#xff09; 一、算法背景与核心思想 麻雀搜索算法&#xff08;Sparrow Search Algorithm, SSA&#xff09;是一种受麻雀群体觅食行为启发的元启发式算法&#xff0…

狮子座大数据分析(python爬虫版)

十二星座爱情性格 - 星座屋 首先找到一个星座网站&#xff0c;作为基础内容&#xff0c;来获取信息 网页爬取与信息提取 我们首先利用爬虫技术&#xff08;如 Python 中的 requests 与 BeautifulSoup 库&#xff09;获取页面内容。该页面&#xff08;xzw.com/astro/leo/&…

DeepSeek教我写词典爬虫获取单词的音标和拼写

Python在爬虫领域展现出了卓越的功能性&#xff0c;不仅能够高效地抓取目标数据&#xff0c;还能便捷地将数据存储至本地。在众多Python爬虫应用中&#xff0c;词典数据的爬取尤为常见。接下来&#xff0c;我们将以dict.cn为例&#xff0c;详细演示如何编写一个用于爬取词典数据…

AI智能导航站HTML5自适应源码帝国cms7.5模板

源码名称&#xff1a;AI导航站HTML5自适应源码帝国cms7.5模板 开发环境&#xff1a;帝国cms 7.5 安装环境&#xff1a;phpmysql var code "4d33ef8e-9e38-43b9-b37b-38f75944ecc9" 带软件采集&#xff0c;可以挂着自动采集发布&#xff0c;无需人工操作&#xff0…

【贪心算法】将数组和减半的最小操作数

1.题目解析 2208. 将数组和减半的最少操作次数 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 使用当前数组中最大的数将它减半&#xff0c;&#xff0c;直到数组和减小到一半为止&#xff0c;从而快速达到目的 重点是找到最大数&#xff0c;可以采用大根堆快速达到…

Apache XTable:在数据湖仓一体中推进数据互作性

Apache XTable 通过以多种开放表格式提供对数据的访问&#xff0c;在增强互作性方面迈出了一大步。移动数据很困难&#xff0c;在过去&#xff0c;这意味着在为数据湖仓一体选择开放表格式时&#xff0c;您被锁定在该选择中。一个令人兴奋的项目当在数据堆栈的这一层引入互作性…

hive面试题--left join的坑

student 表&#xff1a; 课程表course: 1、key为null, 不关联 select * from student s left join course c on s.id c.s_id;2、on中过滤条件 与 where 过滤条件区别 on and c.id<>‘1001’ 先过滤右表数据&#xff0c;然后与左表关联 select * from student s le…

2路模拟量同步输出卡、任意波形发生器卡—PCIe9100数据采集卡

品牌&#xff1a;阿尔泰科技 型号&#xff1a; PCIe9100、PCIe9101、PXIe9100、PXIe9101 产品系列&#xff1a;任意波形发生器 支持操作系统&#xff1a;XP、Win7、Win8、Win10 简要介绍&#xff1a; 910X 系列是阿尔泰科技公司推出的 PCIe、PXIe 总线的任意波形发生器&…