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

socket通讯原理及例程(详解)

里面有疑问或者不正确的地方可以给我留言。

对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:

  1. 什么是TCP/IP、UDP?
  2. Socket在哪里呢?
  3. Socket是什么呢?
  4. 你会使用它们吗?

什么是TCP/IP、UDP?

TCP/IP(传输控制协议/互联网协议,Transmission Control Protocol/Internet Protocol)是用于网络通信的核心协议,构成了互联网的基础。它定义了计算机和网络设备如何通过网络传输数据,并提供了可靠的端到端数据传输机制。

从上述这段话可以看出,TCP/IP是一个集合。什么集合?协议的集合目的就是是实现 网络互联和数据通信,使不同设备能够通过网络可靠且有效地交换信息。它包括以下协议:

  • Ethernet:局域网中使用的常见协议,定义了数据帧在有线网络上的传输方式。
  • Wi-Fi (Wireless Fidelity):无线局域网协议,基于IEEE 802.11标准。
  • PPP (Point-to-Point Protocol):用于在两个直接相连的节点之间传输数据,如电话线上。
  • SLIP (Serial Line Internet Protocol):早期用于通过串行连接传输IP数据包的协议。
  • IP (Internet Protocol):负责路由和寻址,将数据包从源主机发送到目标主机。包括:
    • IPv4:最广泛使用的IP协议,使用32位地址。
    • IPv6:升级版IP协议,使用128位地址,提供更多的IP地址。
  • ICMP (Internet Control Message Protocol):用于发送错误报告和网络诊断信息(如ping操作)。
  • ARP (Address Resolution Protocol):用于将IP地址转换为网络硬件地址(如MAC地址)。
  • RARP (Reverse ARP):将硬件地址转换为IP地址(较少使用)。
  • TCP (Transmission Control Protocol):提供可靠的、面向连接的数据传输协议,确保数据按顺序无错地到达。
  • UDP (User Datagram Protocol):提供不可靠、无连接的数据传输协议,适用于对速度要求较高且能容忍少量数据丢失的应用,如视频流和在线游戏。
  • HTTP (Hypertext Transfer Protocol):用于网页浏览,传输超文本。
  • HTTPS (HTTP Secure):HTTP的加密版本,通过SSL/TLS保护数据 传输。
  • FTP (File Transfer Protocol):用于在计算机之间传输文件。
  • SMTP (Simple Mail Transfer Protocol):用于发送电子邮件。
  • POP3 (Post Office Protocol 3):用于从邮件服务器下载电子邮件。
  • IMAP (Internet Message Access Protocol):用于从服务器获取电子邮件,支持在服务器上管理邮件。
  • DNS (Domain Name System):用于将域名转换为IP地址。
  • Telnet:提供远程登录服务。
  • SSH (Secure Shell):加密的远程登录协议,替代Telnet。

看到这里,是不是瞬间头大了,这什么东西?这么多!这么复杂!当时的人也觉得这一堆东西往这一堆,很磕碜,很不讲究,太TM影响心情了。

所以,就引出了教科书上经典的那句话,TCP/IP协议集是一个分层、多协议的通信体系。

一群聪明蛋按照协议的功能分工和网络通信过程中的逻辑顺序把协议划分为四层。这种分层设计使得网络协议更加灵活、可扩展,并能够解决不同通信问题。如下所示:

数据链路层(功能:处理硬件接口与底层网络通信,负责数据帧的发送与接收。

  • ARP (Address Resolution Protocol):用于将IP地址解析为物理网络地址(如MAC地址),适用于局域网。
  • RARP (Reverse ARP):将物理地址(如MAC地址)映射为IP地址,较少使用,已被DHCP替代。
  • Ethernet:局域网协议,定义了数据帧在有线网络中的传输方式。
  • Wi-Fi (Wireless Fidelity):无线局域网协议,基于IEEE 802.11标准。
  • PPP (Point-to-Point Protocol):用于在两点间通过串行链路传输数据。
  • SLIP (Serial Line Internet Protocol):早期的串行数据传输协议,已被PPP取代。

网络层(功能:负责路由与寻址,确保数据包能够从源地址到达目的地址。

  • IP (Internet Protocol):负责路由和寻址,将数据包从源主机发送到目标主机。包括:
    • IPv4:使用32位地址的IP协议,常用。
    • IPv6:使用128位地址的升级版IP协议,解决了IPv4地址耗尽问题。
  • ICMP (Internet Control Message Protocol):用于发送错误报告和网络诊断信息(如ping操作)。
  • IGMP (Internet Group Management Protocol):用于管理多播组的成员,支持多播通信。
  • NAT (Network Address Translation):允许多个设备使用一个公有IP地址,进行IP地址转换。
  • OSPF (Open Shortest Path First):一种用于路由选择的内部网关协议,基于链路状态的路由。
  • BGP (Border Gateway Protocol):用于不同自治系统之间的路由选择。

传输层(功能:负责提供端到端的通信服务,包括数据的分段、传输、错误检测与修复等。

  • TCP (Transmission Control Protocol):提供可靠、面向连接的数据传输,确保数据按顺序无差错地到达。
  • UDP (User Datagram Protocol):提供不可靠、无连接的数据传输,适用于对速度要求高、能容忍少量数据丢失的应用(如视频流、在线游戏)。

应用层(功能:提供应用程序使用的网络服务。

  • HTTP (Hypertext Transfer Protocol):用于网页浏览,传输超文本。
  • HTTPS (HTTP Secure):HTTP的加密版本,通过SSL/TLS保护数据传输。
  • FTP (File Transfer Protocol):用于在计算机之间传输文件。
  • SMTP (Simple Mail Transfer Protocol):用于发送电子邮件。
  • POP3 (Post Office Protocol 3):用于从邮件服务器下载电子邮件。
  • IMAP (Internet Message Access Protocol):用于从服务器获取电子邮件,支持在服务器上管理邮件。
  • DNS (Domain Name System):用于将域名转换为IP地址。
  • Telnet:提供远程登录服务,但不安全,因为数据是明文传输。
  • SSH (Secure Shell):安全的远程登录协议,替代Telnet,支持加密通信。

看着是不是还是有些头大,其实主要结构如下:

举例说明分层:

  • 当你使用浏览器访问一个网站时,浏览器使用应用层的HTTP协议来发送请求,这个请求会通过传输层的TCP协议分段并加上校验信息,再通过互联网层的IP协议选择路径传输到目标服务器,最后通过数据链路层的以太网协议发送数据帧到网络中。各层互不干扰,但又紧密配合完成整个通信过程。

那这些和Socket有什么关系呢?来个图就一目了然了,如下图所示:

Socket是应用层和传输层之间的接口,它将应用层的协议请求映射到传输层上的具体传输服务。Socket充当了网络通信的桥梁,既不属于应用层,也不属于传输层,而是应用程序用于访问传输层服务的一种API。

所以,Socket(套接字)是计算机网络编程中的一种通信机制,允许两个程序在不同的设备上进行数据交换。它提供了在不同主机之间通过网络传输数据的接口,是实现网络通信的重要工具。

常见的Socket类型:

(1)TCP Socket(面向连接,可靠的传输):TCP是一种面向连接的协议,确保数据包按顺序到达且不丢失。TCP Socket用于建立可靠的、持久的连接,如网页浏览器与服务器之间的通信。

(2)UDP Socket(无连接,非可靠的传输):UDP是一种无连接的协议,传输速度快,但不保证数据的可靠传输。UDP Socket常用于对实时性要求高但允许丢包的应用,如视频流、在线游戏等。

Socket 的工作原理基于“客户端-服务器”模型:

  1. 服务器端

    • 服务器程序在特定的IP地址和端口上“监听”(等待连接请求)。
    • 当客户端请求连接时,服务器会接受连接,双方通过Socket进行数据传输。
  2. 客户端

    • 客户端程序向服务器发起连接请求,通过服务器的IP地址和端口号找到目标服务器。
    • 连接建立后,客户端与服务器可以相互发送和接收数据。

Socket编程的基本步骤:

服务器端

1.创建套接字:socket()

在C++中,socket() 函数用于创建套接字(socket),它是网络编程的基础。通过创建套接字,应用程序可以在网络上进行通信。

socket()函数的语法

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

参数说明

(1).domain(协议族/地址族): 指定使用的通信域,决定了套接字通信的地址格式。

常见的选项有:

  • AF_INET:IPv4协议的地址族。
  • AF_INET6:IPv6协议的地址族。
  • AF_UNIX:本地通信(也称为域套接字,主要用于同一台计算机上的进程通信)。

(2).type(套接字类型): 指定通信类型,决定了套接字的特性。

常见的选项有:

  • SOCK_STREAM:提供面向连接的可靠数据传输(TCP协议)。
  • SOCK_DGRAM:提供无连接的数据报传输(UDP协议)。
  • SOCK_RAW:提供对底层协议的直接访问,通常用于高级网络编程和自定义协议。

(3).protocol(协议): 通常指定为 0,表示使用默认协议。如果有多个协议可供选择,可以明确指定协议编号。例如:

  • IPPROTO_TCP:TCP协议。
  • IPPROTO_UDP:UDP协议。

返回值

  • 成功:返回一个文件描述符,表示新创建的套接字。
  • 失败:返回 -1,并设置 errno 以指示错误原因。

示例代码:

//
// Created by armstrong on 2024/9/9.
//
#include <sys/types.h>#include <sys/socket.h>#include <iostream>#include <unistd.h>#include <cstring>#include <cerrno>int main(){int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd == -1){std::cerr << "Failed to create socket. Error: " << strerror(errno) << std::endl;return -1;}std::cout << "Socket created successfully!" << std::endl;// 关闭socketclose(sockfd);return 0;}

运行指令:

在ubantu系统中(windows系统无法运行),先切换到代码文件所在路径,例如:文件名为socked.cpp,文件路径为/home/socket_learn/socked.cpp

cd /home/socket_learn

然后运行g++ -o sockfd sockfd.cpp 这条指令使用 GNU C++ 编译器(g++)来编译 sockfd.cpp 源代码文件,并生成一个名为 sockfd 的可执行文件。

g++ -o sockfd sockfd.cpp

最后运行

./sockfd

代码运行结果:

Socket created successfully!

说明

socket():首先创建一个 IPv4 (AF_INET)、面向连接的 TCP (SOCK_STREAM) 套接字。

close():关闭套接字,释放资源。

错误处理

  • 如果 socket() 返回 -1,则表示套接字创建失败,可以通过 errno 获取错误码,使用 strerror(errno) 打印错误信息。
  • 常见错误:
    • EACCES:权限问题,无法创建套接字。
    • ENFILE:系统中打开的文件(套接字)已达到上限。
    • EMFILE:进程中打开的文件(套接字)已达到上限。

总结

  • socket() 是 C++ 网络编程的基础,用于创建套接字。
  • 套接字可用于 TCP 和 UDP 等协议,创建后需要结合 connect()bind()send()recv() 等函数来实现通信。

2.绑定IP地址和端口:bind()

在网络编程中,bind() 函数用于将创建的套接字绑定到一个特定的IP地址和端口号。它通常用于服务器端程序,允许服务器在特定的地址和端口上监听客户端的连接请求。

bind()函数的语法

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

参数说明

  1. sockfd:由 socket() 函数返回的套接字文件描述符。
  2. addr:指向 sockaddr 结构体的指针,包含IP地址和端口号信息。
  3. addrlenaddr 结构体的大小,通常使用 sizeof(struct sockaddr_in)

sockaddr_in结构体

在绑定 IPv4 地址时,sockaddr_in 结构体通常用于表示IP地址和端口号。该结构体定义在 <netinet/in.h> 中,格式如下:

struct sockaddr_in {sa_family_t    sin_family;   // 地址族(AF_INET 表示 IPv4)in_port_t      sin_port;     // 端口号(需要使用 htons() 转换成网络字节序)struct in_addr sin_addr;     // IP 地址(使用 inet_addr() 或 INADDR_ANY)
};
  • sin_family:地址族,通常设置为 AF_INET(表示 IPv4)。
  • sin_port:端口号,必须使用 htons() 函数将端口号从主机字节序转换为网络字节序。
  • sin_addr:IP地址,使用 inet_addr() 函数将字符串形式的IP地址转换为 in_addr 类型,或者使用 INADDR_ANY 表示绑定到本地所有可用的网络接口。

返回值

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno 以指示错误原因。

常见错误

  • EADDRINUSE:指定的IP地址或端口已经被占用。
  • EINVAL:套接字已经绑定过一次,不能重复绑定。
  • EBADF:提供的文件描述符无效。

示例代码

以下示例演示如何在服务器端创建一个套接字,并将其绑定到本地IP地址和端口号 8080

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 包含 sockaddr_in 结构体定义
#include <arpa/inet.h>  // 包含 htons、inet_addr 等函数
#include <unistd.h>     // 包含 close 函数
#include <cstring>      // 包含 memset 函数int main() {// 1. 创建套接字 (IPv4, TCP)int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {std::cerr << "Failed to create socket. Error: " << strerror(errno) << std::endl;return -1;}// 2. 定义服务器地址结构体struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));  // 将结构体清零server_addr.sin_family = AF_INET;              // IPv4server_addr.sin_port = htons(8080);            // 设置端口号,使用 htons 转换为网络字节序server_addr.sin_addr.s_addr = INADDR_ANY;      // 绑定到本地所有可用地址// 3. 绑定套接字到IP地址和端口if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Failed to bind. Error: " << strerror(errno) << std::endl;close(sockfd);  // 关闭套接字return -1;}std::cout << "Bind successful. Socket is now bound to port 8080." << std::endl;// 4. 关闭套接字close(sockfd);return 0;
}

运行结果:

Bind successful. Socket is now bound to port 8080.

代码说明

socket():创建了一个 IPv4、TCP 套接字。

sockaddr_in 结构体:用于存储服务器的IP地址和端口信息。INADDR_ANY 表示服务器可以监听来自所有本地网络接口的连接。

bind():将套接字与指定的IP地址和端口绑定。如果绑定成功,套接字就可以在指定的端口上接收客户端的连接请求。

htons():将端口号从主机字节序转换为网络字节序,这是必要的步骤,因为不同的系统可能使用不同的字节序。

close():关闭套接字,释放资源。

如何处理多网络接口

如果服务器主机有多个网络接口(多个IP地址),可以使用不同的IP地址进行绑定:

  • 绑定到特定IP地址:将 server_addr.sin_addr.s_addr 设置为特定的IP地址(如 inet_addr("192.168.1.100"))。
  • 绑定到所有接口:使用 INADDR_ANY,这意味着套接字将绑定到主机的所有可用网络接口,可以接受来自任意接口的连接。例如:以太网接口(Ethernet):物理网络接口,通过网线连接到局域网。Wi-Fi 接口:无线网络接口,通过 Wi-Fi 连接到网络。环回接口(Loopback):通常是 127.0.0.1,用于本地程序之间的通信(即不经过网络,只在本机内部进行通信)。

绑定后的后续操作

通常,服务器端程序在成功绑定套接字后,会执行以下步骤:

  1. 监听连接请求:使用 listen() 函数开始监听来自客户端的连接请求。
  2. 接受连接:使用 accept() 函数接受客户端的连接,并生成一个新的套接字用于与客户端通信。

小结

  • bind() 函数将套接字绑定到一个IP地址和端口号,用于服务器端程序监听来自客户端的连接。
  • sockaddr_in 结构体用于指定绑定的IP地址和端口号。
  • 绑定后,服务器可以使用 listen()accept() 函数与客户端进行通信。

3.监听连接请求:listen()

在网络编程中,服务器端需要监听客户端的连接请求,以便处理它们。listen() 函数就是用来让服务器开始监听连接请求的。

listen() 函数的作用:

listen() 函数的作用是将套接字设为被动模式,从而告诉操作系统这个套接字将用于接受连接。这个函数的主要功能是:

  1. 让服务器开始监听来自客户端的连接请求。
  2. 设置连接队列的最大长度,即客户端连接请求的等待队列。

listen() 的函数原型(以 C/C++ 为例):

int listen(int sockfd, int backlog);
  • sockfd:由 socket() 函数返回的套接字描述符,这个套接字已经通过 bind() 函数绑定到一个本地 IP 地址和端口号。
  • backlog:指定在处理客户端连接之前,内核允许的最大等待连接数。它表示在服务器开始处理请求前,客户端连接可以在队列中等待的数量上限。

使用 listen() 的步骤

(1)创建套接字:通过 socket() 函数创建套接字。

  (2)绑定 IP 地址和端口:通过 bind() 函数将套接字绑定到一个本地 IP 地址和端口号。

  (3)开始监听:调用 listen() 函数,开始监听客户端的连接请求。

代码示例(C/C++):

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>int main() {// 1. 创建套接字int server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == -1) {std::cerr << "Failed to create socket\n";return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;  // 使用 IPv4server_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的网络接口server_addr.sin_port = htons(8080);  // 绑定端口 8080if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Bind failed\n";close(server_fd);return -1;}// 3. 开始监听,允许最多 10 个待处理连接if (listen(server_fd, 10) == -1) {std::cerr << "Listen failed\n";close(server_fd);return -1;}std::cout << "Server is listening on port 8080\n";// 4. 等待客户端连接(这里只是展示,未做实际 accept 操作)// ...// 关闭套接字close(server_fd);return 0;
}

参数说明:

  • server_fd:服务器套接字描述符,通过 socket() 函数创建。
  • 10backlog:最大连接等待队列长度。当多个客户端几乎同时尝试连接服务器时,服务器会把这些请求放在一个队列中,这个参数决定队列的大小。超过这个数量的连接请求将被拒绝,返回错误。

监听的实际含义

(1)被动模式listen() 函数将套接字转变为被动模式,被动模式意味着这个套接字将用于接受传入的连接,而不会主动向其他服务器发出连接请求。服务器将处于等待状态,直到有客户端请求连接。

(2)连接队列:当有多个客户端请求连接时,操作系统会把这些请求放在一个队列中。backlog 参数决定队列中最多可以有多少个未处理的连接请求。在队列满时,如果有新的连接请求,它们将被拒绝,客户端可能会收到错误信息。

队列的作用

  • 服务器处理每个客户端连接的速度可能不同,而客户端请求连接的速度可能较快。backlog 队列允许服务器有缓冲时间来处理请求。
  • 如果服务器忙于处理现有的连接,其他客户端的连接请求可以暂时存放在队列中等待处理。如果队列已满,额外的连接请求将被拒绝。

4.接受客户端连接:accept()

accept() 是服务器端网络编程中的一个关键函数,用于接受来自客户端的连接请求。服务器在调用 listen() 开始监听客户端的连接后,当有客户端尝试连接时,accept() 函数负责接受该连接并为此生成一个新的套接字以进行数据传输。

accept() 函数的作用

  • accept() 函数从服务器的等待队列中取出一个连接请求并与客户端建立连接。
  • 它为该连接分配一个新的套接字,该套接字将用于与客户端之间的实际通信。
  • 原来的监听套接字继续监听其他连接请求。

accept() 的函数原型(以 C/C++ 为例):

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:服务器的监听套接字文件描述符(由 socket()bind() 创建并监听)。
  • addr:指向一个 sockaddr 结构体的指针,用于存储客户端的地址信息(客户端的 IP 和端口)。
  • addrlenaddr 结构体的大小,传入时是该结构体的大小,返回时表示客户端地址的实际大小。

返回值

  • 成功时,accept() 返回一个新的套接字文件描述符,这个描述符用于与客户端进行后续的通信。
  • 如果出错,返回值为 -1,同时设置 errno 以表示错误类型。

使用场景

accept() 通常和 socket()bind()listen() 函数一起使用。以下是服务器接受客户端连接的典型步骤:

  1. 创建套接字:使用 socket() 创建服务器端的套接字。
  2. 绑定 IP 和端口:使用 bind() 将套接字绑定到特定的 IP 地址和端口。
  3. 监听连接请求:使用 listen() 函数监听客户端连接请求。
  4. 接受连接:使用 accept() 接受客户端连接并生成一个新的套接字进行通信。

代码示例(C/C++):

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {// 1. 创建套接字int server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == -1) {std::cerr << "Failed to create socket\n";return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;  // 使用 IPv4server_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的网络接口server_addr.sin_port = htons(8080);  // 绑定端口 8080if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Bind failed\n";close(server_fd);return -1;}// 3. 开始监听if (listen(server_fd, 10) == -1) {std::cerr << "Listen failed\n";close(server_fd);return -1;}std::cout << "Server is listening on port 8080\n";// 4. 接受客户端连接struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);if (client_fd == -1) {std::cerr << "Accept failed\n";close(server_fd);return -1;}std::cout << "Connection accepted from " << inet_ntoa(client_addr.sin_addr)<< ":" << ntohs(client_addr.sin_port) << "\n";// 与客户端通信代码(略)// 关闭客户端和服务器套接字close(client_fd);close(server_fd);return 0;
}

参数解释:

  • server_fd:由 socket() 函数创建的服务器监听套接字。
  • client_addr:这是一个 sockaddr_in 结构体,存储客户端的 IP 地址和端口号。
  • client_addr_len:表示 client_addr 结构体的大小。

accept() 的流程:

(1)阻塞行为accept() 是一个阻塞函数,意味着它会一直等待,直到有客户端连接请求到达。如果没有连接请求,服务器会在此函数上阻塞。

(2)返回客户端套接字:一旦连接请求到达,accept() 从连接队列中取出请求,返回一个新的套接字,用于与客户端进行通信。

(3)服务器继续监听:原来的监听套接字 (server_fd) 仍然处于监听状态,可以接受其他客户端连接请求,而当前客户端的通信使用新的套接字 (client_fd) 进行。

常见用法:

  • 服务器通过 accept() 获取与客户端通信的专用套接字。后续的数据接收和发送可以使用 read()write()recv()send()
  • 通信结束后,服务器需要调用 close() 关闭新的套接字以释放资源。

总结

  • accept() 用于从连接队列中取出客户端的连接请求,建立连接。
  • 返回一个新的套接字,用于服务器与客户端的通信。
  • 原监听套接字继续等待其他连接请求。

通过 accept(),服务器能够与多个客户端进行通信,每个客户端都有自己独立的套接字,而服务器的监听套接字则持续接收新的连接请求。

5.发送和接收数据:send()/recv()

在网络编程中,服务器和客户端建立连接后,可以通过 send()recv() 函数来发送和接收数据。这两个函数分别用于通过套接字向远程端发送数据和从远程端接收数据。

send() 函数

send() 函数用于通过套接字发送数据到连接的另一端。

send() 的函数原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd:套接字描述符,用于标识连接。
  • buf:指向存储待发送数据的缓冲区的指针。
  • len:要发送的数据长度(字节数)。
  • flags:发送选项,通常设置为 0。可以设置不同的标志来控制发送行为,例如 MSG_DONTWAIT(非阻塞发送)。
返回值:
  • 返回实际发送的字节数。如果返回值小于 len,表示数据未完全发送。
  • 如果返回 -1,表示发送失败,并设置 errno

recv() 函数

recv() 函数用于通过套接字从连接的另一端接收数据。

recv() 的函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd:套接字描述符,标识连接。
  • buf:指向接收数据的缓冲区的指针。
  • len:缓冲区的大小(即最多接收多少字节)。
  • flags:接收选项,通常设置为 0。可以设置不同的标志来控制接收行为,例如 MSG_WAITALL(等待所有数据)。
返回值:
  • 返回实际接收到的字节数。如果返回 0,表示连接已关闭。
  • 如果返回 -1,表示接收失败,并设置 errno

发送和接收数据的流程

  1. 服务器和客户端建立连接。
  2. 服务器使用 recv() 函数接收来自客户端的数据。
  3. 客户端使用 send() 函数向服务器发送数据,或反之。
  4. 在发送或接收完数据后,双方可以通过 close() 关闭套接字。

代码示例

以下是使用 send()recv() 进行数据发送和接收的代码示例:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {// 1. 创建套接字int server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == -1) {std::cerr << "Failed to create socket\n";return -1;}// 2. 绑定 IP 地址和端口号struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Bind failed\n";close(server_fd);return -1;}// 3. 开始监听if (listen(server_fd, 10) == -1) {std::cerr << "Listen failed\n";close(server_fd);return -1;}std::cout << "Server is listening on port 8080\n";// 4. 接受客户端连接struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);if (client_fd == -1) {std::cerr << "Accept failed\n";close(server_fd);return -1;}// 5. 接收数据char buffer[1024] = {0};ssize_t recv_len = recv(client_fd, buffer, sizeof(buffer), 0);if (recv_len == -1) {std::cerr << "Receive failed\n";close(client_fd);close(server_fd);return -1;}std::cout << "Received from client: " << buffer << std::endl;// 6. 发送数据const char *response = "Hello from server";if (send(client_fd, response, strlen(response), 0) == -1) {std::cerr << "Send failed\n";}// 7. 关闭套接字close(client_fd);close(server_fd);return 0;
}
服务端代码:
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {// 1. 创建套接字int client_fd = socket(AF_INET, SOCK_STREAM, 0);if (client_fd == -1) {std::cerr << "Failed to create socket\n";return -1;}// 2. 连接到服务器struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Connection failed\n";close(client_fd);return -1;}// 3. 发送数据const char *message = "Hello from client";if (send(client_fd, message, strlen(message), 0) == -1) {std::cerr << "Send failed\n";}// 4. 接收数据char buffer[1024] = {0};ssize_t recv_len = recv(client_fd, buffer, sizeof(buffer), 0);if (recv_len == -1) {std::cerr << "Receive failed\n";close(client_fd);return -1;}std::cout << "Received from server: " << buffer << std::endl;// 5. 关闭套接字close(client_fd);return 0;
}

关键点:

  1. 阻塞行为send()recv() 默认是阻塞的,意味着如果网络缓慢或没有数据到达,程序会等待。可以通过设置非阻塞模式改变这种行为。
  2. 返回值检查:检查 send()recv() 的返回值很重要,以确保数据正确发送和接收,并处理错误情况。
  3. 数据传输的长度send() 可能不会一次发送所有数据,程序需要处理这种情况,确保所有数据都被发送。
  4. 连接关闭:如果 recv() 返回 0,表示对方关闭了连接。

总结:

  • send() 用于通过套接字发送数据。
  • recv() 用于接收来自远程端的数据。
  • 它们是实现服务器与客户端通信的基础工具,通常与 socket()bind()connect() 等函数配合使用。

6.关闭连接:close()

在网络编程中,close() 函数用于关闭套接字并释放与该套接字相关的资源。当服务器或客户端不再需要与对方通信时,调用 close() 可以终止连接。

close() 函数的作用

close() 函数不仅仅是关闭文件描述符(在这种情况下是套接字),还会终止与该套接字关联的 TCP 连接,释放所有资源。如果套接字是连接的一部分(如 TCP 连接),则会通知对端连接已关闭,后续通信不再可能。

close() 函数的原型:

int close(int sockfd);
  • sockfd:套接字描述符,标识要关闭的连接。
返回值:
  • 返回 0 表示成功。
  • 返回 -1 表示失败,并设置 errno,以提供错误的具体信息。

关闭连接的流程

  1. 当服务器或客户端调用 close() 函数时,系统会开始执行 TCP 的四次挥手(Four-Way Handshake)协议,以优雅地关闭连接。
  2. 在这个过程中,系统会将套接字的状态从 ESTABLISHED(已建立连接)变为 FIN_WAIT 等不同状态,直到连接完全关闭。
  3. 调用 close() 后,系统会释放与该套接字关联的所有内存资源,包括文件描述符、缓冲区等。

客户端

1.创建套接字:socket()

2.连接到服务器:connect()

3.发送和接收数据:send()/recv()

4.关闭连接:close()

Socket广泛应用于各种网络通信场景,如HTTP、FTP、电子邮件等协议的底层实现。

示例:

服务端:

#include <iostream> 
#include <cstring>
#include <sys/socket.h>  //包含socket函数和数据结构
#include <netinet/in.h>  //包含Internet地址簇
#include <unistd.h>     //包含标准符号常数和类型using namespace std;
const int PORT=9006;int main(){int server_fd,new_socket;//定义套接字文件描述符/*server_fd表示服务器的文件描述符,在网络编程中,套接字(socket)通过文件描述符来操作,就像操作文件一样。new_socket表示新的连接套接字,当服务器接受客户端的连接请求时,它会创建一个新的套接字来处理这个连接,这个新的套接字通过 new_socket 变量来表示。*/struct sockaddr_in address;//定义地址结构体/*sockaddr_in 结构体是一个非常重要的数据结构,它用于存储网络地址信息。这个结构体是 sockaddr 结构体的一个特化版本,专门用于IPv4地址。在 <netinet/in.h> 头文件中定义*/int addrlen=sizeof(address);//地址长度char buffer[1024]={0};//定义缓冲区/*定义了一个大小为1024字节的字符数组,用于存储接收到的数据或将要发送的数据。{0} 初始化数组中的所有元素为0,这是一个常见的做法,用于确保缓冲区不包含任何随机数据。当然了,它还有:保证数据完整性,提高效率,数据格式化,防止数据丢失等功能,这里是简单示例,主要就是用于存储接收到的数据或将要发送的数据,就不做解释了,*/const char *message="你说得对!";//定义服务器发送的消息,使用 const 可以防止函数意外修改字符串内容。安全//创建套接字,用于在C++网络编程中创建一个TCP套接字的。/* if((server_fd=socket(AF_INET,SOCK_STREAM,0))==0){perror("socket failed");exit(EXIT_FAILURE);}*/if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {perror("socket failed");exit(EXIT_FAILURE);}/*socket() 是一个系统调用,用于创建一个新的套接字。它返回一个文件描述符,该文件描述符是用于后续套接字操作(如绑定、监听、连接等)的索引。AF_INET表示IPv4地址族,用于创建一个基于IPv4的套接字。SOCK_STREAM表示创建一个提供序列化、可靠、双向连接的字节流套接字,这通常用于TCP连接。传递0表示使用默认的协议,对于AF_INET和SOCK_STREAM,这意味着使用TCP协议。== 0检查server_fd是否为0,即检查套接字是否创建失败。perror()函数将打印出错误信息到标准错误流exit()函数用于终止当前程序,并返回一个状态码给操作系统。EXIT_FAILURE是一个宏,通常定义为非零值,表示程序异常终止
*///设置套接字选项,允许地址重用int opt=1;if(setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt))){perror("setsockopt");exit(EXIT_FAILURE);}/*opt这个变量用于表示套接字选项的状态,1 表示启用该选项。setsockopt() 是一个系统调用,用于设置套接字选项。它可以改变套接字的行为,例如如何接收数据或者如何处理特殊的网络条件。SOL_SOCKET是一个指定套接字选项级别的常量,表示这是针对套接字本身的选项,而不是针对某个特定协议的。SO_REUSEADDR 是一个套接字选项,允许套接字绑定到一个已经被使用(在TIME_WAIT状态)的本地地址和端口。SO_REUSEPORT 是一个扩展选项,允许多个套接字绑定到同一个端口,只要它们的地址不同。如果 setsockopt() 返回非零值,表示设置选项失败。*///绑定套接字到端口address.sin_family=AF_INET;//地址簇,address.sin_addr.s_addr=INADDR_ANY;//地址。INADDR_ANY表示接受任意IP的连接address.sin_port=htons(PORT);//端口号,htons函数将主机字节序转换为网络字节序/*这三段代码是设置服务器端套接字地址信息的一部分,它们配置了服务器将监听的协议族、IP地址和端口号sin_family 是 sockaddr_in 结构体的一个成员,它指定了地址族。设置 sin_family 为 AF_INET 表示服务器将使用 IPv4 协议。sin_addr 是 sockaddr_in 结构体的一个成员,它是一个 in_addr 结构体,用于存储 IPv4 地址。INADDR_ANY 是一个特殊的常量,当绑定套接字时使用。它告诉操作系统自动绑定到所有可用的网络接口的 IPv4 地址。sin_port 是 sockaddr_in 结构体的一个成员,用于指定端口号。htons() 函数是 "host to network short" 的缩写,它将一个短整型(16位)从主机字节序转换为网络字节序。*///绑定套接字if(bind(server_fd,(struct sockaddr *)&address,sizeof(address))<0){perror("bind failed");exit(EXIT_FAILURE);}/*bind() 是一个系统调用,用于将一个套接字(server_fd)绑定到指定的地址和端口上。(struct sockaddr *)&address将address变量的地址传递给bind()函数其他基本的东西都和上面提到过的大相径庭*///监听套接字if(listen(server_fd,3)<0){//监听,参数3表示最大连接数perror("listen");exit(EXIT_FAILURE);}/*listen() 是一个系统调用,用于告诉内核准备接受连接请求。这个函数将一个被动套接字(也称为监听套接字)转变为监听状态。参数 3 表示服务器将允许最多3个连接请求在队列中等待接受。*/cout<<"Listening on port"<<PORT<<endl;//接受客户端连接if((new_socket=accept(server_fd,(struct sockaddr*)&address,(socklen_t*)&addrlen))<0){perror("accept");exit(EXIT_FAILURE);}/*accept() 是一个系统调用,用于从监听队列中取出第一个连接请求,并为该请求创建一个新的套接字。*///读取客户端发送的数据read(new_socket,buffer,1024);//读取数据到缓冲区cout<<"消息来自丽宝:"<<buffer<<endl;/*read() 是一个系统调用,用于从文件描述符(在这里是套接字)读取数据。new_socket是通过 accept() 函数创建的新套接字的文件描述符,用于与客户端进行通信*///发送数据回客户端send(new_socket,message,strlen(message),0);//发送消息cout<<"消息发送成功\n";//关闭套接字close(new_socket);//关闭客户端套接字close(server_fd);//关闭服务器套接字return 0;
}

客户端:

#include <iostream>  
#include <sys/socket.h> // 包含socket函数和数据结构
#include <arpa/inet.h> // 包含inet函数,用于IP地址转换
#include <unistd.h>  // 包含标准符号常数和类型
#include <string.h>  using namespace std;const int PORT = 9006; // 定义服务器的端口号int main() {int sock = 0; // 定义套接字文件描述符struct sockaddr_in serv_addr; // 定义服务器地址结构体const char *hello = "302女的美,男的帅!"; // 定义客户端发送的消息char buffer[1024] = {0}; // 定义接收数据的缓冲区/*sock 用于存储客户端套接字的文件描述符,它是网络通信中的一个重要概念,代表了网络通信的一端。*/// 创建套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {cout << "\n Socket creation error \n";return -1;}/*如果创建成功,sock 将包含一个非负值;如果创建失败,则为 -1。*/serv_addr.sin_family = AF_INET; // 服务器地址族serv_addr.sin_port = htons(PORT); // 服务器端口号// 将文本形式的IP地址转换为二进制形式if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {cout << "\nInvalid address / Address not supported \n";return -1;}/*inet_pton() 用于将表示网络地址的字符串转换为网络字节序的二进制形式。*/// 连接到服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {cout << "\nConnection Failed \n";return -1;}/*connect() 用于客户端套接字与服务器套接字建立连接。*/// 发送数据send(sock, hello, strlen(hello), 0); // 发送消息cout << "消息发送成功:\n";// 接收服务器发送的数据read(sock, buffer, 1024); // 从服务器接收数据cout << "消息来自赟赟: " << buffer << std::endl;// 关闭套接字close(sock); // 关闭套接字return 0;
}

运行指令:

g++ -o server server.cpp
g++ -o client client.cpp
./server
./client

相关文章:

socket通讯原理及例程(详解)

里面有疑问或者不正确的地方可以给我留言。 对TCP/IP、UDP、Socket编程这些词你不会很陌生吧&#xff1f;随着网络技术的发展&#xff0c;这些词充斥着我们的耳朵。那么我想问&#xff1a; 什么是TCP/IP、UDP&#xff1f;Socket在哪里呢&#xff1f;Socket是什么呢&#xff1…...

vue3使用provide和inject传递异步请求数据子组件接收不到

前言 一般接口返回的格式是数组或对象&#xff0c;使用reactive定义共享变量 父组件传递 const data reactive([])// 使用settimout模拟接口返回 setTimeout(() > {// 将接口返回的数据赋值给变量Object.assign(data, [{ id: 10000 }]) }, 3000);provide(shareData, dat…...

对称矩阵的压缩存储

1.给自己出题&#xff1a;自己动手创造&#xff0c;画一个5行5列的对称矩阵 2.画图&#xff1a;按“行优先”压缩存储上述矩阵&#xff0c;画出一维数组的样子 3.简答&#xff1a;写出元素 i,j 与 数组下标之间的对应关系 4.画图&#xff1a;按“列优先”压缩存储上述矩阵&a…...

高阶数据结构之哈希表基础讲解与模拟实现

程序猿的读书历程&#xff1a;x语言入门—>x语言应用实践—>x语言高阶编程—>x语言的科学与艺术—>编程之美—>编程之道—>编程之禅—>颈椎病康复指南。 前言&#xff1a; 哈希表&#xff08;Hash Table&#xff09;是一种高效的键值对存储数据结构&…...

基于STM32设计的智能货架(华为云IOT)(225)

文章目录 一、前言1.1 项目介绍【1】项目背景【2】项目支持的功能【3】项目硬件模块组成【4】ESP8266工作模式配置【5】Android手机APP开发思路【6】项目模块划分1.2 项目开发背景【1】选题来源与背景【2】国内外研究现状【3】课题研究的目的和内容【4】参考文献【5】研究内容【…...

JDBC API详解一

DriverManager 驱动管理类&#xff0c;作用&#xff1a;1&#xff0c;注册驱动&#xff1b;2&#xff0c;获取数据库连接 1&#xff0c;注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); 查看Driver类源码 static{try{DriverManager.registerDriver(newDrive…...

工厂安灯系统在设备管理中的重要性

在现代制造业中&#xff0c;设备管理是确保生产效率和产品质量的关键环节。随着工业4.0的推进&#xff0c;越来越多的企业开始采用智能化的设备管理系统&#xff0c;其中安灯系统作为一种有效的管理工具&#xff0c;逐渐受到重视。安灯系统最初源于日本的丰田生产方式&#xff…...

【LabVIEW学习篇 - 23】:简单状态机

文章目录 简单状态机状态机的创建和了解状态机实现红绿灯 简单状态机 一个优秀的应用程序离不开好的程序框架&#xff0c;不仅要很好满足用户的功能需求&#xff0c;还要考虑到系统的稳定性、实时性、可扩展性、可维护性&#xff0c;执行效率等方面。借用一些成熟的设计框架&a…...

【CSS】 Grid布局:现代网页设计的基石

引言 最近接到一个网页布局比较复杂的页面&#xff0c;看了半天还是决定用grid布局来写&#xff0c;记录一下 布局是构建用户界面的关键部分。CSS Grid布局提供了一种简单而强大的方式来创建复杂的网格布局&#xff0c;它让设计师和开发者能够更直观、更灵活地控制网页的结构。…...

jQuery UI API 文档

关于《jQuery UI API 文档》&#xff0c;我找到了一些有用的信息。jQuery UI 是建立在 jQuery JavaScript 库上的一组用户界面交互、特效、小部件及主题。如果您是 jQuery 新手&#xff0c;建议您先查看 jQuery 教程。目前&#xff0c;我找到的资料主要是关于 jQuery UI 1.10 版…...

盘点2024年大家都在用的录屏工具

现在录屏工具的使用范围越来越广了。我的深切体验是有很多人愿意为知识付费了&#xff0c;但是到线下培训的话很多人时间不一定能协调的来&#xff0c;这就导致涌现了不少的录屏课程。这次我们来探讨下要怎么录屏才能呈现更好的效果。 1.福昕录屏大师 链接达达&#xff1a;ww…...

【大数据】探索怎么从一段话中解析关键信息(寄件人相关信息)

本文由ChatGPT生成&#xff0c;主要用于学习&#xff0c;大家有疑问请及时提出。 使用NLP实现文本信息解析功能&#xff1a;以提取姓名、地址和电话号码为例 在这个博客中&#xff0c;我们将通过自然语言处理&#xff08;NLP&#xff09;技术来实现一个简单的文本信息解析功能…...

初学者指南:MyBatis 入门教程

主要介绍了Mybatis的基本使用、JDBC、数据库连接池、lombok注解&#xff01; 文章目录 前言 什么是Mybatis? 快速入门 使用Mybatis查询所有的用户信息 配置SQL提示 JDBC介绍 Mybatis 数据库连接池 lombok 总结 前言 主要介绍了Mybatis的基本使用、JDBC、数据库连接…...

reader-lm:小模型 html转markdown

参考&#xff1a; https://huggingface.co/jinaai/reader-lm-0.5b 在线demo&#xff1a; https://colab.research.google.com/drive/1wXWyj5hOxEHY6WeHbOwEzYAC0WB1I5uA#scrollTo0mG9ISzHOuKK 输入网址&#xff1a;https://www.galaxy-geely.com/E5 结果&#xff1a; 代码…...

进击J6:ResNeXt-50实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、实验目的&#xff1a; 阅读ResNeXt论文&#xff0c;了解作者的构建思路对比之前介绍的ResNet50V2、DenseNet算法使用ResNeXt-50算法完成猴痘病识别 二、实…...

新代机床采集数据

新代集團1995年成立於台灣新竹,事業版圖遍布全球,以台灣為中心向外發展,據點橫跨歐洲、美洲、亞洲三大洲。新代長期深耕於機床控制器的軟體及硬體技術研發,專注於運動控制領域,目前已成為亞太市場中深具影響力的控制器領導品牌之一。主營產品包括:機床數控系統、伺服驅動…...

景联文科技:专业数据标注公司,推动AI技术革新

数据标注作为AI技术发展的重要支撑&#xff0c;对于训练高质量的机器学习模型以及推动应用领域的创新具有不可替代的作用。 景联文科技作为专业的数据标注公司&#xff0c;致力于提供专业的数据标注服务&#xff0c;帮助客户解决AI链条中的数据处理难题&#xff0c;共同推动人工…...

k8s以及prometheus

#生成控制器文件并建立控制器 [rootk8s-master ~]# kubectl create deployment bwmis --image timinglee/myapp:v1 --replicas 2 --dry-runclient -o yaml > bwmis.yaml [rootk8s-master ~]# kubectl expose deployment bwmis --port 80 --target-port 80 --dry-runclient…...

android 权限说明

1. 权限的定义语法 注&#xff1a; 任何应用都可以定义权限 <permission 标签是定义权限 <uses-permission 标签是使用权限。 <permission android:description"string resource"android:icon"drawable resource"android:label"string res…...

<winsock>重叠IO模型

基于事件判断io完成 send程序 #include <stdio.h> #include <winsock2.h>#pragma comment(lib, "Ws2_32.lib") #pragma warning(disable : 4996)int main() {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) ! 0){printf("WSAStart…...

Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化

Android Tools | 如何使用Draw.io助力Android开发&#xff1a;从UI设计到流程优化 1. 引言 在Android开发中&#xff0c;视觉化设计与流程管理至关重要。虽然开发工具如Android Studio强大&#xff0c;但它并不适用于所有设计场景。Draw.io是一款免费的在线绘图工具&#xff…...

Java 每日一刊(第5期):变量守护者

前言 这里是分享 Java 相关内容的专刊&#xff0c;每日一更。 本期将为大家带来以下内容&#xff1a; 量子数据宇宙的变量守护者第一章&#xff1a;能源错配与基本数据类型第二章&#xff1a;引用类型与通讯网络的崩溃第三章&#xff1a;作用域冲突与系统崩溃终章&#xff1…...

【C++二分查找】2517. 礼盒的最大甜蜜度

本文涉及的基础知识点 C二分查找 贪心&#xff08;决策包容性) LeetCode 2517. 礼盒的最大甜蜜度 给你一个正整数数组 price &#xff0c;其中 price[i] 表示第 i 类糖果的价格&#xff0c;另给你一个正整数 k 。 商店组合 k 类 不同 糖果打包成礼盒出售。礼盒的 甜蜜度 是礼…...

【详解】数据库E-R图——医院计算机管理系统

题目 某医院病房计算机管理中需要如下信息&#xff1a; 科室&#xff1a;科室名&#xff0c;科室地址&#xff0c;科室电话&#xff0c;医生姓名 病房&#xff1a;病房号&#xff0c;床位号&#xff0c;所属科室名 医生&#xff1a;工作证号&#xff0c;姓名&#xff0c;性别&a…...

分类预测|基于改进的灰狼IGWO优化支持向量机SVM的数据分类预测matlab程序 改进策略:Cat混沌与高斯变异

分类预测|基于改进的灰狼IGWO优化支持向量机SVM的数据分类预测matlab程序 改进策略&#xff1a;Cat混沌与高斯变异 文章目录 一、基本原理原理流程1. **定义目标函数**2. **初始化GWO**3. **评估适应度**4. **更新狼的位置**5. **更新狼的等级**6. **重复迭代**7. **选择最佳解…...

圆锥曲线练习

设 A ( x 1 , y 1 ) , B ( x 2 , y 2 ) A\left( x_{1}, y_{1} \right), B\left( x_{2}, y_{2} \right) A(x1​,y1​),B(x2​,y2​) l : y k ( x 2 ) l: y k\left( x2 \right) l:yk(x2) 显然 y 0 y0 y0符合题意 当 k ≠ 0 k\neq 0 k0 联立 l l l和 C C C ( k 2 1 2 ) x…...

STM32时钟树

1 什么是时钟 2 时钟数简图...

NX—UI界面生成的文件在VS上的设置

UI界面保存生成的三个文件 打开VS创建项目&#xff0c;删除自动生成的cpp文件&#xff0c;将生成的hpp和cpp文件拷贝到项目的目录下&#xff0c;并且在VS项目中添加现有项目。 修改VS的输出路径&#xff0c;项目右键选择属性&#xff0c;链接器中的常规&#xff0c;文件路径D:…...

Wine容器内程序执行sh脚本问题研究

问题背景 wpf程序在wine环境执行sh脚本&#xff0c;不能等待脚本执行完成自动退出的问题进行了研究&#xff0c;需求很简单&#xff0c;在wpf程序使用cmd&#xff0c;或者bat &#xff0c;又或者是直接执行sh脚本&#xff0c;想到脚本执行完成才处理后面的逻辑。但是实际验证过…...

《深度学习》OpenCV轮廓检测 模版匹配 解析及实现

目录 一、模型匹配 1、什么是模型匹配 2、步骤 1&#xff09;提取模型的特征 2&#xff09;在图像中查找特征点 3&#xff09;进行特征匹配 4&#xff09;模型匹配 3、参数及用法 1、用法 2、参数 1&#xff09;image&#xff1a;待搜索对象 2&#xff09;templ&am…...