当前位置: 首页 > news >正文

UNIX下C语言编程与实践62-UNIX UDP 编程:socket、bind、sendto、recvfrom 函数的使用

在 UNIX 网络编程中,UDP(用户数据报协议)以其“无连接、轻量级”的特性,成为实时性要求高(如流媒体、游戏)场景的首选。 udps1.c(UDP 服务器端)与 udpk1.c(UDP 客户端)的核心实例,本文将详细讲解 UDP 编程的四个核心函数——socket(创建数据报套接字)、bind(绑定地址端口)、sendto(发送数据报)、recvfrom(接收数据报),通过完整代码演示 UDP 通信流程,深入解析 UDP 与 TCP 编程的差异,并梳理常见错误与拓展场景。

一、UDP 编程核心函数解析

UDP 编程与 TCP 的核心差异在于“无连接”特性——无需 listenacceptconnect 流程,直接通过 sendto 与 recvfrom 完成数据收发。以下从函数原型、参数含义、实例关联三个维度展开解析。

1.1 socket 函数:创建 UDP 数据报套接字

UDP 套接字的创建是 UDP 编程的第一步,与 TCP 不同,其类型需指定为 SOCK_DGRAM(数据报套接字),以匹配 UDP 协议的无连接特性。

(1)函数原型与参数

函数定义在 <sys/socket.h> 头文件中,原型与 TCP 共享,但参数取值不同:

#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);

UDP 场景下三个参数的核心取值与实例对应关系如下表所示:

参数名数据类型UDP 场景取值实例中的使用说明
domainintAF_INET UDP 编程唯一使用的协议族,对应 IPv4 网络,支持 UDP 数据报传输(与 TCP 一致)
typeintSOCK_DGRAM数据报套接字,对应 UDP 协议,无连接、不可靠、面向数据报( udps1.c 与 udpk1.c 均使用此类型,区别于 TCP 的 SOCK_STREAM
protocolint0由内核根据 domainAF_INET)与 typeSOCK_DGRAM)自动选择 UDP 协议(无手动指定场景)
(2)返回值与关键说明

执行成功时返回非负的套接字描述符(用于后续 bindsendtorecvfrom 操作);失败时返回 -1,并通过 errno 标识错误(如 EAFNOSUPPORT 表示协议族不支持)。

实例印证: udps1.c 中通过 socket(AF_INET, SOCK_DGRAM, 0) 创建服务器端套接字,udpk1.c 客户端创建套接字的方式完全相同,体现了 UDP 客户端与服务器端套接字类型的一致性。

1.2 bind 函数:UDP 服务器端的地址绑定

UDP 服务器端必须调用 bind 函数将套接字绑定到固定的本地端口——客户端需要通过该端口定位服务器,而 UDP 客户端通常无需绑定(系统自动分配临时端口)。

(1)函数原型与参数

函数原型与 TCP 完全一致,但 UDP 场景下的使用场景更单一(仅服务器端必要),参数含义如下(与文中 CreateUdpSock 函数逻辑一致):

#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

三个参数的 UDP 场景解读与实例关联如下表所示:

参数名数据类型UDP 服务器端使用逻辑实例代码片段
sockfdintsocket 函数返回的 UDP 套接字描述符(服务器端专用)CreateUdpSock 函数中 *pnSock(创建的 UDP 套接字)
my_addrstruct sockaddr *指向 sockaddr_in 结构的指针,存储服务器端本地 IP 与端口(需强制转换为通用地址结构)struct sockaddr_in addrin; memset(&addrin, 0, sizeof(addrin)); addrin.sin_family = AF_INET; addrin.sin_addr.s_addr = htonl(INADDR_ANY); addrin.sin_port = htons(nPort);(绑定所有本地 IP 与指定端口)
addrlensocklen_tsockaddr_in 结构的字节大小,告诉内核地址结构的长度sizeof(addrin)( CreateUdpSock 函数的取值)
(2)UDP 与 TCP 绑定的差异

通过实例可总结两者差异: - TCPbind 后需调用 listen 进入侦听状态; - UDPbind 后直接进入数据接收状态(无需侦听,因无连接),客户端可随时发送数据报。

1.3 sendto 函数:发送 UDP 数据报

UDP 无连接特性决定了其发送数据时需明确指定目标地址——sendto 函数正是为此设计,它将数据报发送到指定的服务器端地址,无需提前建立连接(区别于 TCP 的 send)。

(1)函数原型与参数

函数定义在 <sys/socket.h> 头文件中,原型如下(文中 SendMsgByUdp 函数的核心封装对象):

#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

六个参数的 UDP 场景含义与实例对应关系如下表所示:

参数名数据类型核心作用实例(SendMsgByUdp 函数)
sintUDP 套接字描述符(客户端/服务器端均可使用)客户端创建的临时套接字(nSock = socket(AF_INET, SOCK_DGRAM, 0)
msgconst void *指向待发送数据缓冲区的指针(如字符串、二进制数据)客户端要发送的字符串(如 "第0次发送"
lensize_t待发送数据的字节数(不含字符串结束符 '\0'strlen(szBuf)(文中客户端发送字符串的长度)
flagsint发送标志,通常设为 0(默认方式),特殊场景用 MSG_DONTWAIT(非阻塞)文中设为 0(默认阻塞发送,数据交给内核即返回)
toconst struct sockaddr *指向目标地址结构(sockaddr_in)的指针,存储服务器端 IP 与端口初始化服务器端地址:addrin.sin_addr.s_addr = inet_addr(szAddr); addrin.sin_port = htons(nPort);
tolensocklen_t目标地址结构(sockaddr_in)的字节大小sizeof(struct sockaddr)(文中通用取值)
(2)返回值与关键特性

执行成功时返回实际发送的字节数;失败时返回 -1。文中特别强调 UDP sendto 的“成功”含义——仅表示数据已交给内核发送缓冲区,不保证对方能接收(UDP 无确认机制),这是与 TCP send(成功表示数据已写入 TCP 发送缓冲区,TCP 会负责重传)的核心差异。

1.4 recvfrom 函数:接收 UDP 数据报

UDP 服务器端通过 recvfrom 函数接收客户端发送的数据报,同时获取发送方(客户端)的地址信息,以便后续回复(如回声服务器)。文中 RecvMsgByUdp 函数正是对该函数的封装。

(1)函数原型与参数

函数定义在 <sys/socket.h> 头文件中,原型如下(与 sendto 参数结构对称):

#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

六个参数的 UDP 场景含义与实例对应关系如下表所示:

参数名数据类型核心作用实例(RecvMsgByUdp 函数)
sint已绑定端口的 UDP 套接字描述符(服务器端专用)udps1.c 中 CreateUdpSock 返回的 nSock
bufvoid *指向接收数据缓冲区的指针,需预先分配内存char szBuf[256];(文中服务器端接收缓冲区)
lensize_t接收缓冲区的最大容量(字节数),避免数据溢出sizeof(szBuf)(文中服务器端缓冲区大小)
flagsint接收标志,通常设为 0(默认阻塞),非阻塞场景用 MSG_DONTWAIT文中设为 0(默认阻塞,直到接收数据报或出错)
fromstruct sockaddr *输出参数,指向 sockaddr_in 结构,存储发送方(客户端)的地址信息文中简化处理设为 NULL(仅接收数据,不回复);若需回复,需初始化该结构
fromlensocklen_t *输入输出参数,传入时为 from 结构的大小,返回时为实际地址结构的大小文中简化处理设为 NULL;若需回复,需设为 &from_lenfrom_len = sizeof(struct sockaddr_in)
(2)返回值与关键特性

执行成功时返回实际接收的字节数;失败时返回 -1;无数据且连接关闭(UDP 无连接,此情况极少)时返回 0。文中强调其默认阻塞特性——若套接字无数据可接收,recvfrom 会一直阻塞,直到有数据报到达或被信号中断(如 SIGINT)。

二、实战实例:UDP 服务器端与客户端完整流程

文中 udps1.cudpk1.c 与 udp.h 头文件的设计思想,编写完整的 UDP 服务器端与客户端程序,演示“客户端循环发送数据报→服务器端接收并打印”的完整流程,还原文中的编程风格与核心逻辑。

2.1 头文件封装(udp.h)

文中的封装思路,将 UDP 核心函数(创建套接字、发送数据、接收数据)声明在头文件中,提高代码复用性:

#ifndef _UDP_H_
#define _UDP_H_#include 
#include 
#include 
#include 
#include 
#include 
#include // 创建 UDP 服务器端套接字(绑定指定端口)
int CreateUdpSock(int *pnSock, int nPort);// 发送 UDP 数据报到指定服务器
int SendMsgByUdp(void *pMsg, int nSize, const char *szAddr, int nPort);// 从 UDP 套接字接收数据报
int RecvMsgByUdp(int nSock, void *pData, int *pnSize);#endif

2.2 UDP 服务器端程序(udps1.c)

服务器端流程:创建 UDP 套接字→绑定本地端口(9999)→循环调用 recvfrom 接收数据→打印数据内容,符合文中 udps1.c 的核心逻辑:

#include "udp.h"// 实现 CreateUdpSock:创建并绑定 UDP 服务器端套接字
int CreateUdpSock(int *pnSock, int nPort) {struct sockaddr_in addrin;struct sockaddr *paddr = (struct sockaddr *)&addrin;// 参数合法性检查assert(pnSock != NULL && nPort > 0 && nPort <= 65535);memset(&addrin, 0, sizeof(addrin));// 1. 创建 UDP 套接字*pnSock = socket(AF_INET, SOCK_DGRAM, 0);if (*pnSock <= 0) {perror("socket failed");return 1;}// 2. 初始化地址结构(绑定所有本地 IP + 指定端口)addrin.sin_family = AF_INET;addrin.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有本地 IPaddrin.sin_port = htons(nPort);             // 端口转换为网络字节顺序// 3. 绑定套接字到本地地址if (bind(*pnSock, paddr, sizeof(addrin)) != 0) {perror("bind failed");close(*pnSock);return 1;}printf("UDP server bind port %d success\n", nPort);return 0;
}// 实现 RecvMsgByUdp:接收 UDP 数据报
int RecvMsgByUdp(int nSock, void *pData, int *pnSize) {assert(nSock > 0 && pData != NULL && pnSize != NULL && *pnSize > 0);// 接收数据报(简化处理,不获取发送方地址)ssize_t recv_len = recvfrom(nSock, pData, *pnSize, 0, NULL, NULL);if (recv_len < 0) {perror("recvfrom failed");return 1;} else if (recv_len == 0) {printf("no data received (connection closed)\n");return 1;}// 更新实际接收的字节数*pnSize = recv_len;return 0;
}// 服务器端主函数:循环接收客户端数据
int main() {int nSock;char szBuf[256];int nBufSize = sizeof(szBuf);// 1. 创建并绑定 UDP 套接字(端口 9999)if (CreateUdpSock(&nSock, 9999) != 0) {fprintf(stderr, "Create UDP server failed\n");return 1;}// 2. 循环接收数据printf("UDP server waiting for data...\n");while (1) {// 重置缓冲区memset(szBuf, 0, sizeof(szBuf));nBufSize = sizeof(szBuf);// 接收数据报if (RecvMsgByUdp(nSock, szBuf, &nBufSize) != 0) {continue;}// 打印接收的数据printf("Received UDP data: [len=%d] %s\n", nBufSize, szBuf);}// 理论上不会执行到此处(循环无退出条件)close(nSock);return 0;
}

2.3 UDP 客户端程序(udpk1.c)

客户端流程:创建 UDP 套接字→循环调用 sendto 发送数据(每隔 1 秒)→打印发送结果,符合文中 udpk1.c 的周期性发送逻辑:

#include "udp.h"
#include // 实现 SendMsgByUdp:发送 UDP 数据报到服务器
int SendMsgByUdp(void *pMsg, int nSize, const char *szAddr, int nPort) {int nSock;struct sockaddr_in addrin;// 参数合法性检查assert(pMsg != NULL && nSize > 0 && szAddr != NULL && nPort > 0 && nPort <= 65535);memset(&addrin, 0, sizeof(addrin));// 1. 创建 UDP 客户端套接字(无需绑定,系统自动分配临时端口)nSock = socket(AF_INET, SOCK_DGRAM, 0);if (nSock <= 0) {perror("socket failed");return 1;}// 2. 初始化服务器端地址结构addrin.sin_family = AF_INET;// IP 地址转换:字符串 → 网络字节顺序整数if (inet_aton(szAddr, &addrin.sin_addr) == 0) {fprintf(stderr, "invalid server IP: %s\n", szAddr);close(nSock);return 1;}// 端口转换为网络字节顺序addrin.sin_port = htons(nPort);// 3. 发送 UDP 数据报ssize_t send_len = sendto(nSock, pMsg, nSize, 0, (struct sockaddr *)&addrin, sizeof(addrin));if (send_len != nSize) {perror("sendto failed");close(nSock);return 1;}// 4. 关闭临时套接字(客户端每次发送可创建新套接字)close(nSock);return 0;
}// 客户端主函数:循环发送数据到服务器
int main() {int i = 0;char szBuf[100];const char *server_ip = "127.0.0.1"; // 服务器端 IP(本地回环)int server_port = 9999;              // 服务器端端口// 循环发送数据(每隔 1 秒)while (1) {// 构造发送数据snprintf(szBuf, sizeof(szBuf), "第%d次发送", i);int data_len = strlen(szBuf);// 发送数据报if (SendMsgByUdp(szBuf, data_len, server_ip, server_port) == 0) {printf("Send success: %s\n", szBuf);} else {printf("Send failed: %s\n", szBuf);}// 休眠 1 秒sleep(1);i++;}return 0;
}

2.4 程序执行与结果

1. 编译程序

# 编译服务器端
gcc udps1.c -o udps1
# 编译客户端
gcc udpk1.c -o udpk1

2. 启动服务器端

./udps1
UDP server bind port 9999 success
UDP server waiting for data...

3. 启动客户端(新终端)

./udpk1
Send success: 第0次发送
Send success: 第1次发送
Send success: 第2次发送
Send success: 第3次发送
...

4. 服务器端输出( udps1.c 执行结果一致):

Received UDP data: [len=9] 第0次发送
Received UDP data: [len=9] 第1次发送
Received UDP data: [len=9] 第2次发送
Received UDP data: [len=9] 第3次发送
...

三、UDP 编程关键特性与差异解析

基于实例与 UDP 协议特性,梳理 UDP 编程与 TCP 的核心差异,以及 UDP 客户端/服务器端的特殊设计逻辑,帮助理解 UDP 的适用场景。

3.1 UDP 服务器端绑定端口的必要性

文中反复强调,UDP 服务器端必须调用 bind 绑定固定端口,原因如下: - 客户端定位服务器的需要:UDP 无连接,客户端发送数据报时必须知道服务器端的 IP 与端口(如文中客户端指定服务器端口 9999),若服务器端不绑定固定端口,客户端无法定位; - 避免端口动态变化:若服务器端不绑定端口,系统会自动分配临时端口,但服务器端重启后端口可能变化,导致客户端无法连接; - 端口复用与业务标识:固定端口便于业务识别(如 DNS 用 53 端口、DHCP 用 67/68 端口),文中选择 9999 作为示例端口,正是基于固定端口的设计思想。

3.2 UDP 客户端不绑定端口的原因

文中 udpk1.c 客户端未调用 bind,而是由系统自动分配临时端口,原因如下: - 简化客户端逻辑:客户端无需管理端口,系统分配的临时端口(通常在 1024~65535 之间)可避免端口冲突; - 无固定端口需求:服务器端接收数据报后,若需回复,可通过 recvfrom 获取客户端的临时端口,无需客户端绑定固定端口; - 多客户端并发:多个客户端同时运行时,系统分配不同的临时端口,避免端口冲突(若客户端绑定固定端口,多实例运行会失败)。

实例印证:文中 udpk1.c 客户端每次发送数据都创建新套接字,系统自动分配临时端口,发送完成后关闭套接字,完全无需绑定操作,体现了客户端的简化设计。

3.3 UDP 与 TCP 编程核心差异

对比维度UDP 编程(实例)TCP 编程(实例)
套接字类型SOCK_DGRAM(数据报套接字)SOCK_STREAM(流式套接字)
连接流程无连接,无需 listen/accept/connect面向连接,需 socket→bind→listen→accept(服务器端)、socket→connect(客户端)
数据收发函数sendto(需指定目标地址)、recvfrom(可获取发送方地址)sendrecv(基于已连接套接字,无需指定地址)
数据可靠性不可靠,sendto 成功不保证对方接收(无确认、重传)可靠,TCP 协议保证数据有序、无丢失、无重复
数据边界有边界,recvfrom 一次接收一个完整数据报无边界,recv 可能分批次接收数据(需处理粘包)
服务器端核心逻辑socket→bind→循环 recvfrom( udps1.csocket→bind→listen→循环 accept→处理连接( tcp1.c

四、UDP 编程常见错误与解决方案

结合文中的错误处理思想(如 VerifyErr 宏)与 UDP 协议特性,梳理 socketbindsendtorecvfrom 函数使用过程中常见的错误场景,分析原因并给出解决方案。

  • 错误 1:sendto 发送失败,errno = EINVAL

    原因: - 目标地址结构(sockaddr_in)初始化错误(如未设置 sin_family = AF_INET,或端口号未转换为网络字节顺序); - tolen 参数取值错误(如设为 sizeof(int),而非地址结构大小);

    解决方案: 1. 确保地址结构初始化完整:

    struct sockaddr_in addrin;
    memset(&addrin, 0, sizeof(addrin));
    addrin.sin_family = AF_INET; // 必须设置为 AF_INET
    addrin.sin_addr.s_addr = inet_addr("127.0.0.1");
    addrin.sin_port = htons(9999); // 端口必须转换为网络字节顺序
    2. tolen 参数设为 sizeof(struct sockaddr_in) 或 sizeof(struct sockaddr),确保与地址结构匹配。

  • 错误 2:recvfrom 接收数据截断,实际长度小于发送长度

    原因:接收缓冲区(buf)大小(len 参数)小于 UDP 数据报的实际长度,导致超出部分被内核截断(UDP 无数据分片重组机制,超出缓冲区的数据直接丢弃);

    解决方案: 1. 接收缓冲区大小设为 UDP 最大数据报长度(通常为 65507 字节,因 IP 数据报最大为 65535 字节,减去 IP 头 20 字节与 UDP 头 8 字节); 2. 接收前通过 setsockopt 设置 UDP 接收缓冲区大小,避免内核缓冲区溢出:

    int recv_buf_size = 65507;
    setsockopt(nSock, SOL_SOCKET, SO_RCVBUF, &recv_buf_size, sizeof(recv_buf_size));
    3. 文中 udps1.c 使用 256 字节缓冲区,适用于小数据场景;若传输大数据,需增大缓冲区。

  • 错误 3:UDP 数据报丢失,服务器端未接收但客户端发送成功

    原因: - UDP 协议本身不可靠,网络丢包、延迟会导致数据报丢失; - 服务器端未绑定正确端口或 IP,客户端发送到错误地址; - 服务器端 recvfrom 调用不及时,内核接收缓冲区满,新数据报被丢弃;

    解决方案: 1. 应用层实现确认机制:服务器端接收数据后,通过 sendto 回复确认信息,客户端未收到确认则重发(文中未涉及,但工业级 UDP 应用必备); 2. 验证客户端发送地址:确保服务器端 IP 与端口正确(如文中客户端指定 127.0.0.1:9999); 3. 增大内核 UDP 接收缓冲区:通过 setsockopt 设置 SO_RCVBUF,减少缓冲区满导致的丢包。

  • 错误 4:UDP 服务器端 bind 失败,errno = EADDRINUSE

    原因:服务器端要绑定的端口已被其他进程占用(如文中重复启动 udps1.c,9999 端口已被占用);

    解决方案: 1. 通过 netstat -uln | grep <port> 查看端口占用情况(如 netstat -uln | grep 9999),停止占用进程; 2. 设置 SO_REUSEADDR 选项,允许端口复用(即使端口处于 TIME_WAIT 状态):

    int opt = 1;
    setsockopt(nSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    3. 更换未被占用的端口(如文中使用 9999,可改为 8888)。

五、拓展:UDP 广播与多播编程

文中未涉及 UDP 广播与多播,但基于 UDP 的无连接特性,这两种通信方式在局域网场景(如设备发现、实时通知)中广泛应用。以下简要介绍其实现原理与编程方法,拓展 UDP 编程的应用范围。

5.1 UDP 广播

原理:UDP 广播是指客户端向局域网内的所有主机发送数据报(目标地址为广播地址,如 192.168.1.255),局域网内所有绑定对应端口的 UDP 服务器端均可接收。

编程关键步骤: 1. 创建 UDP 套接字(与普通 UDP 一致); 2. 设置套接字允许广播(默认不允许):

int opt = 1;
setsockopt(nSock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));

3. 发送数据报到广播地址(如 192.168.1.255:9999):

struct sockaddr_in broadcast_addr;
memset(&broadcast_addr, 0, sizeof(broadcast_addr));
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_addr.s_addr = inet_addr("192.168.1.255"); // 广播地址
broadcast_addr.sin_port = htons(9999);
sendto(nSock, "broadcast data", strlen("broadcast data"), 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr));

4. 服务器端绑定对应端口(9999),即可接收广播数据报。

5.2 UDP 多播

原理:UDP 多播(组播)是指客户端向特定的多播组地址(如 224.0.0.0~239.255.255.255)发送数据报,只有加入该多播组的 UDP 服务器端才能接收,避免广播的“泛洪”问题。

编程关键步骤: 1. 服务器端加入多播组:

struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1"); // 多播组地址
mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 绑定的本地网卡
// 加入多播组
setsockopt(nSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

2. 客户端发送数据报到多播组地址(224.0.0.1:9999); 3. 服务器端绑定对应端口(9999),即可接收多播数据报; 4. 服务器端退出时离开多播组:

setsockopt(nSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));

六、总结

基于《精通UNIX下C语言编程与项目实践笔记.pdf》中 UDP 编程的核心实例,本文系统梳理了 UDP 编程的四个核心函数与关键特性,可总结为以下关键点:

  • 函数核心作用socket 创建 SOCK_DGRAM 类型套接字,bind 为服务器端绑定固定端口,sendto 向指定地址发送数据报,recvfrom 接收数据报并可选获取发送方地址,四者共同构成 UDP 通信的基础;
  • 客户端/服务器端差异:服务器端必须 bind 固定端口(客户端定位需要),客户端无需绑定(系统分配临时端口),体现了 UDP 简化客户端设计的思想;
  • 协议特性影响:UDP 无连接、不可靠的特性导致 sendto 成功不保证接收,recvfrom 需处理数据截断,实际应用需在应用层补充确认、重传机制;
  • 拓展场景:UDP 广播与多播基于普通 UDP 编程扩展,适用于局域网内多设备通信,是 UDP 协议在实时场景中的重要应用。

掌握 UDP 编程的核心函数与特性,是理解 UNIX 网络编程中“无连接”通信模型的关键。在实际开发中,需结合文中的简化设计思想(如客户端无需绑定),针对 UDP 的不可靠性补充应用层保障机制,并根据场景选择普通 UDP、广播或多播,才能构建出高效、稳定的 UDP 应用程序。

http://www.dtcms.com/a/465313.html

相关文章:

  • UNIX下C语言编程与实践64-UNIX 并发 Socket 编程:I/O 多路复用 select 函数与并发处理
  • 世界杯哪个网站做代理跨境电商网站系统开发
  • SNK施努卡CCD视觉检测系统
  • 杨和勒流网站建设网站建设制作设计
  • SQLite架构
  • 初识Linux和Linux基础指令详细解析及shell的运行原理
  • Python容器内存三要素
  • NumPy 矩阵库(numpy.matlib)用法与作用详解
  • Web 开发 26
  • 正规app软件开发费用漯河网站优化
  • 人工智能学习:线性模型,损失函数,过拟合与欠拟合
  • 开篇词:为何要懂攻防?—— 实战化安全思维的建立
  • 怎么在qq上自己做网站wordpress是一款强大的
  • 网站建设公司 成本结转ppt之家模板免费下载
  • Android Vibrator学习记录
  • pop、push、unshift、shift的作用?
  • 大模型激活值相关公式说明(114)
  • unity升级对ab变更的影响
  • 谁是远程控制软件的“最优选”?UU远程、ToDesk、向日葵深度横测
  • 天机学堂升级版,海量新功能加入
  • vuedraggable拖拽任意组件并改变数据排序
  • {MySQL查询性能优化索引失效的八大场景与深度解决方案}
  • 网站整体建设方案360网站免费推广怎么做
  • 方舟优品:生产型撮合模式如何推动电商行业创新发展
  • 无人机芯片模块技术要点分析
  • 使用手机检测的智能视觉分析技术与应用 加油站使用手机 玩手机检测
  • 门户网站建设的重要性如何优化网页
  • 怎么在工商网站做实名认证海淀商城网站建设
  • 加餐 结束语
  • 做网站都需要用到什么3d建模一般学费多少