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

Windows C/C++ Socket 编程

承接上文:socket 编程

本文目录

  • Windows Client 端
    • WSADATA 结构体
    • WSAStartup() 函数
    • SOCKET 以及 socket() 函数
    • sockaddr_in
    • inet_pton() 函数
    • in_addr struct
    • memcpy()
    • connect() 函数
    • send() 函数
    • recv() 函数
  • Windows Server 端

在进行 socket 编程之前,你要有一些计算机网络的知识,了解 TCP/UDP 、客户端服务器模型。

Windows Client 端

Windows socket 编程 client 端 大致如下:

char buffer[buffer_size] = { 0 };      //接收数据的缓存WSADATA wsaData;
//初始化 Winsock
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {...}SOCKET sock;
//创建 Socket
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {...}struct sockaddr_in server;
// 配置服务器地址结构
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
in_addr serverIP;
if (inet_pton(AF_INT, "IPv4 Address", &server.sin_addr) < 0) {...}
memcpy(&(serverAddr.sin_addr), &serverIP, sizeof(serverIP));//连接服务器
if (connet(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {...}// 发送数据
const char* message = "message";
send(sock, message, strlen(message), 0);// 接收数据
char buffer[BUFFER_SIZE] = { 0 };
int recvResult = recv(sock, buffer, BUFFER_SIZE - 1, 0);if (recvResult > 0) { //数据接受成功 } 
else if ( recvResult == 0 ) { //连接关闭} 
else { //数据接受失败 }closesocket(sock);
WSACleanup();

WSADATA 结构体

该结构体定义了与 Winsock 库相关的一些信息。在 Windows 上使用 Socket 编程时,必须先调用 WSAStartup() 函数初始化 Winsock 库,而 WSADATA 结构体则是这个初始化过程的一部分。

WSADATA 结构体在 winsock2.h 头文件中定义,通常包含以下信息:

typedef struct _WSADATA {WORD wVersion;         // 使用的 Winsock 版本WORD wHighVersion;     // 支持的最高版本char szDescription[256]; // 描述字符串char szSystemStatus[128]; // 系统状态字符串unsigned short iMaxSockets; // 最大套接字数unsigned short iMaxUdpDg;   // 最大 UDP 数据报文长度char *lpVendorInfo;    // 供应商特定的附加信息
} WSADATA;

WSAStartup() 函数

用于初始化 Winsock 库并准备网络通信的环境。在 Windows 上进行网络编程时,必须先调用 WSAStartup() 才能使用任何与网络相关的功能(如套接字、连接、数据传输等)。

int WSAStartup(WORD wVersionRequested,  // 请求的 Winsock 版本LPWSADATA lpWSAData      // 指向 WSADATA 结构体的指针
);

WSAStartup() 为我们应用程序提供了使用 Windows 套接字的能力,会为应用程序提供所需的 Winsock 资源,并允许操作系统在程序退出时释放这些资源(通过 WSACleanup())。

返回值:
如果函数调用成功,返回值为 0。
如果函数调用失败,返回值是一个错误代码,表示初始化失败的原因。可以通过调用 WSAGetLastError() 获取详细的错误信息。(WSAGetLastError() 用于获取上一次 Winsock 操作的错误码。)

使用示例:

int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {std::cerr << "WSAStartup failed: " << iResult << std::endl;return 1;
}

MAKEWORD(2, 2) 是一个宏,用于将两个 BYTE(字节)组合成一个 WORD(字,通常是 16 位的整数)。在 Windows 编程中,MAKEWORD 常用于将两个字节值合并为一个 16 位的数值,通常用于指定版本号或类似的参数。

  • 在 MAKEWORD(2, 2) 中,它将 2 和 2 合并,得到表示 “2.2” 版本号的 WORD 值。
  • 在 WSAStartup() 中,MAKEWORD(2, 2) 表示请求使用 Winsock 2.2 版本。

MAKEWORD 宏定义:

#define MAKEWORD(a, b)      ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))// WORD:
typedef unsigned short      WORD;      // 无符号短整型 2字节
// BYTE:
typedef unsigned char       BYTE;      // 无符号字符型 1字节-8位
  • low:低字节(低 8 位),表示低位。
  • high:高字节(高 8 位),表示高位。

MAKEWORD 的位运算 (了解,不重要可跳过)

  1. (DWORD_PTR)(a)(DWORD_PTR)(b)

    • 这部分将 ab 强制转换为 DWORD_PTR 类型(通常是 unsigned int,在 64 位系统上为 unsigned long long,在 32 位系统上为 unsigned int)。
    • 这个转换的目的是确保 ab 被当作一个整数来处理,方便进行位操作。
  2. (DWORD_PTR)(a) & 0xff(DWORD_PTR)(b) & 0xff

    • 这里使用了 按位与运算符 &
      • 0xff 是一个 8 位的掩码,二进制表示为 11111111
      • 按位与运算 & 用于保留 ab 中的最低 8 位(即低字节)。即使 ab 是更大的类型(比如 DWORD_PTR,它的大小可能是 32 位或 64 位),通过 & 0xff 运算后,它们会被限制在 8 位范围内。
      • 举个例子,如果 a 的值是 0x12345678(32 位整数),a & 0xff 会返回 0x78,即最后一个字节的值。
  3. (BYTE)(((DWORD_PTR)(a)) & 0xff)(BYTE)(((DWORD_PTR)(b)) & 0xff)

    • 这两部分将前面的结果转换为 BYTE 类型。BYTE 是 8 位的类型,确保结果是 8 位整数,去除多余的位数。
  4. (WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff)) << 8

    • b 被处理成一个字节后,通过 左移操作 << 8 将它移到高字节的位置(16 位整数的高 8 位)。
    • 具体来说,左移 8 位的效果是将字节 b 提高 8 位,使其成为 16 位整数的高字节。例如,如果 b = 0x34,那么左移后就是 0x3400
  5. ((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8

    • 按位或运算符 | 用于将 ab 合并成一个 16 位的整数。
      • a 是低字节,b << 8 是高字节,二者按位或运算合并成一个 16 位整数。
      • 举个例子:如果 a = 0x12b = 0x34,那么 a | (b << 8) 的结果是 0x3412
  6. (WORD)

    • 最后,通过强制类型转换将结果转换为 WORD 类型。WORD 是 16 位的整数类型,确保最终结果符合预期的类型。

SOCKET 以及 socket() 函数

SOCKET 是 Windows 套接字编程中用于表示一个网络套接字的类型。它是一个句柄(或标识符),用来标识一个网络连接或通信通道。

  • SOCKET 本质上是一个整数类型(通常是 unsigned int),用于表示一个网络连接。

socket() 函数是创建一个套接字(socket)的函数。它通过指定地址族、套接字类型和协议类型来创建一个网络通信的通道。

SOCKET socket(int af, int type, int protocol);

af (地址族):指定套接字所使用的地址族,决定了套接字能够处理的数据类型(如 IPv4、IPv6 等)。

常用的值:

  • AF_INET:IPv4 地址族,表示使用 IPv4 地址进行通信。
  • AF_INET6:IPv6 地址族,表示使用 IPv6 地址进行通信。
  • AF_UNIX:Unix 域套接字,通常用于同一台机器上的进程间通信。

type (套接字类型):指定套接字的类型,决定了数据的传输方式。

常用的值:

  • SOCK_STREAM:流套接字,表示面向连接的 TCP 协议。适用于可靠的、连接导向的通信(如 HTTP、FTP)。
  • SOCK_DGRAM:数据报套接字,表示无连接的 UDP 协议。适用于不保证可靠性的、无连接的通信(如 DNS 查询、视频流)。
  • SOCK_RAW:原始套接字,允许直接访问网络层协议(通常是 ICMP 或自定义协议)。这种套接字用于较底层的网络操作,常见于网络工具。

protocol (协议):指定所用的协议,通常与 type 配合使用,决定了套接字的协议标准。

常用的值:

  • IPPROTO_TCP:表示 TCP 协议。一般与 SOCK_STREAM 搭配使用。
  • IPPROTO_UDP:表示 UDP 协议。一般与 SOCK_DGRAM 搭配使用。
  • IPPROTO_IP:表示通用的 IP 协议,通常可以与 SOCK_RAW 配合使用。

socket() 返回值

  • 成功时返回:如果套接字创建成功,socket() 函数返回一个 SOCKET 类型的值,这个值是一个非负整数,表示新创建的套接字的句柄。
  • 失败时返回:如果创建套接字失败,返回值是 INVALID_SOCKET,这通常是一个特殊值(在 Windows 中通常为 -1)。(#define INVALID_SOCKET (SOCKET)(~0)

sockaddr_in

sockaddr_in 是一个结构体,用于存储 IPv4 地址和端口信息,通常用于 TCP 或 UDP 套接字编程中。它是 sockaddr 结构的一个变种,专门用于 IPv4。

//
// IPv4 Socket address, Internet style
//typedef struct sockaddr_in {#if(_WIN32_WINNT < 0x0600)short   sin_family;
#else //(_WIN32_WINNT < 0x0600)ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)USHORT sin_port;IN_ADDR sin_addr;CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
serverAddr.sin_family = AF_INET;      // internetwork: UDP, TCP, etc.
serverAddr.sin_port = htons(PORT);       // htons() 将主机字节序的 IP 端口转换成 网络字节序。
// The htons function can be used to convert an IP port number in host byte order to the IP port number in network byte order.

inet_pton() 函数

该函数用于将标准文本类型的 IPv4 & IPv6 地址转换成二进制形式。

in_addr struct

in_addr 结构体代表了一个 IPv4 地址

in_addr serverIP;
// in_addr 定义:
typedef struct in_addr {union {struct {UCHAR s_b1;UCHAR s_b2;UCHAR s_b3;UCHAR s_b4;} S_un_b;struct {USHORT s_w1;USHORT s_w2;} S_un_w;ULONG S_addr;} S_un;
} IN_ADDR, *PIN_ADDR, *LPIN_ADDR;

memcpy()

memcpy(&(serverAddr.sin_addr), &serverIP, sizeof(serverIP)); 这行代码使用memcpy函数将serverIP的内容复制到serverAddr.sin_addr中。memcpy函数接受三个参数:目标地址、源地址、以及要复制的字节数。

connect() 函数

connect() 函数是用来建立与远程服务器连接的标准函数,通常在客户端程序中使用。它的作用是向指定的服务器地址发起连接请求,并完成三次握手过程。如果连接成功,connect() 函数返回值为 0,否则返回一个错误代码。

connect() 是一个阻塞函数(在默认情况下),即在连接建立之前,它会阻塞调用线程直到连接成功或失败(返回错误)。

    iResult = connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));if (iResult == SOCKET_ERROR) { ... // 错误处理 }

(SOCKADDR*)&serverAddr:这是一个类型转换,将serverAddr的地址转换为一个指向SOCKADDR结构体的指针。serverAddr是一个sockaddr_in类型的变量,它包含了服务器的地址信息(如IP地址和端口号)。sockaddr_in是sockaddr的一个特定于IPv4的实现,这种转换是必要的,因为connect()函数的第二个参数是一个指向SOCKADDR的指针,而SOCKADDR是一个通用的地址结构体,sockaddr_in是它的一个具体实现。

send() 函数

int send(SOCKET s,                // 套接字描述符const char *buf,         // 数据缓冲区,发送数据的指针int len,                 // 数据长度(字节数)int flags                // 发送标志
);

flags: 控制发送行为的标志。通常是以下几种:

  • 0: 默认行为,数据将尽可能一次性发送。
  • MSG_OOB: 发送带外数据。
  • MSG_DONTROUTE: 不使用路由表(直接发送到目标地址)。
  • MSG_MORE: 发送更多数据,通常与大数据流一起使用。
  • MSG_NOSIGNAL: 不触发 SIGPIPE 信号(适用于一些 Unix 系统,Windows 中该标志没有实际影响)。

除了 send() 函数用来发送数据以外,还有 sendto() UDP 的发送数据,以及 WSASend() 函数, 是 send() 的扩展(异步)。

recv() 函数

int recv(SOCKET s,        // 套接字char *buf,       // 数据接收缓冲区int len,         // 缓冲区的大小(字节数)int flags        // 控制接收行为的标志
);

int iRecvResult = recv(clientSocket, recvbuf, BUFFER_SIZE - 1, 0);
BUFFER_SIZE - 1 是为了确保缓冲区末尾有足够的空间来存储 空字符(\0),以确保接收到的数据能够作为一个有效的 C 字符串使用。recv() 不会自动为接收到的数据添加 \0(空字符),因此,如果期望将接收到的数据当作字符串处理,就必须在缓冲区的末尾手动添加 \0。如果只是把接收的数据当作二进制数据处理,那么 BUFFER_SIZE - 1 大可不必。

返回值

  • recvResult > 0:表示成功接收了数据,返回的是接收到的字节数。

  • recvResult == 0:表示连接已关闭。通常,这意味着对方已经关闭了连接,不能再发送数据。如果服务器关闭了连接,客户端调用 recv() 时会返回 0,表示连接被关闭。

  • recvResult == SOCKET_ERROR:表示发生了错误,调用 recv() 函数失败。

长期不调用 recv() 函数可能会导致数据丢失

每个套接字(无论是发送端还是接收端)都有 发送缓冲区接收缓冲区。这两个缓冲区由操作系统的 TCP/IP 协议栈管理。

  • 接收缓冲区:用于临时存储接收到的网络数据,直到应用程序调用 recv() 将数据读取出来。
  • 发送缓冲区:用于存储待发送的数据,直到 TCP 协议栈将其传输到网络。

如果应用程序不及时调用 recv() 来读取数据,接收缓冲区中的数据就会积压。

  • 接收缓冲区满:当接收缓冲区满了,TCP 会根据流量控制(Flow Control)机制采取措施,确保不会接收到过多的数据,导致丢失。

  • 流量控制:TCP 会通过接收方的 接收窗口(receive window) 通知发送方接收缓冲区的剩余空间。如果接收方的接收缓冲区已满,接收窗口大小会变为 0,这就意味着发送方无法继续发送数据,直到接收方的缓冲区有足够的空间来接收新数据。

这时,发送方会减缓发送速率,甚至暂停发送,直到接收方的接收缓冲区有空闲空间为止。

如果应用程序 长时间不调用 recv() 来读取接收到的数据,并且接收缓冲区已满:

  • TCP 保证可靠交付 的情况下,TCP 协议栈会通过调整接收窗口来控制发送方的发送速率。如果缓冲区满了,发送方会暂停发送,直到接收方读取部分数据并释放缓冲区空间。
  • 然而,如果 接收缓冲区仍然没有被及时读取,并且长时间保持满的状态,某些系统会发生 缓冲区溢出 或者 丢失数据。具体取决于操作系统的实现。

Windows Server 端

//server 端
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>  // 包含 InetNtop() 函数的头文件
#include <stdlib.h>using namespace std;#pragma comment(lib, "ws2_32.lib")  // 链接 Winsock 库#define SERVER_PORT 12345  // 设置监听端口
#define BUFFER_SIZE 1024  // 缓冲区大小int main() {system("chcp 65001");  // 设置命令行 utf-8 字符集WSADATA wsaData;SOCKET serverSocket, clientSocket;struct sockaddr_in serverAddr, clientAddr;int clientAddrSize = sizeof(clientAddr);char buffer[BUFFER_SIZE];int recvResult;char clientIP[INET_ADDRSTRLEN];  // 存储客户端 IP 地址字符串// 1. 初始化 Winsockif (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {cout << u8"Winsock 初始化失败:" << WSAGetLastError();return 1;}// 2. 创建 TCP 套接字serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (serverSocket == INVALID_SOCKET) {cout << u8"创建套接字失败:" << WSAGetLastError();WSACleanup();return 1;}// 3. 设置服务器地址信息serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有本地地址serverAddr.sin_port = htons(SERVER_PORT); // 设置监听端口// 4. 绑定套接字if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {cout << u8"绑定套接字失败:" << WSAGetLastError();closesocket(serverSocket);WSACleanup();return 1;}// 5. 开始监听if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {cout << u8"监听失败:" << WSAGetLastError();closesocket(serverSocket);WSACleanup();return 1;}cout << u8"服务器正在监听端口:" << SERVER_PORT;// 6. 接受客户端连接clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrSize);if (clientSocket == INVALID_SOCKET) {cout << u8"接受连接失败:" << WSAGetLastError();closesocket(serverSocket);WSACleanup();return 1;}// 使用 inet_ntop 替代 inet_ntoainet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP));cout << u8"客户端已连接,IP 地址:" << clientIP;// 7. 与客户端进行数据交换while (1) {recvResult = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);if (recvResult > 0) {buffer[recvResult] = '\0';  // 确保数据为字符串cout << u8"接收到的数据:" << buffer << endl;// 向客户端发送回应const char* response = u8"Hello from server. 你好我是服务器端。";int sendResult = send(clientSocket, response, strlen(response), 0);if (sendResult == SOCKET_ERROR) {cout << u8"发送数据失败:" << WSAGetLastError();break;}}else if (recvResult == 0) {// 客户端正常关闭连接cout << u8"客户端关闭了连接.\n";break;}else {// 发生错误cout << u8"接收数据失败: %d\n" << WSAGetLastError();break;}}// 8. 关闭套接字和清理closesocket(clientSocket);closesocket(serverSocket);WSACleanup();return 0;
}

server 端和 client 端大致相同,但也有些许差别。

server 端要有一个一直打开的欢迎套接字(serverSocket),等待来自 client 端的连接。server 端比 client 多了绑定套接字 bind() 和 监听操作 listen()。client 使用 connect() 来连接 server 端的欢迎套接字,server 端使用 accept() 接受来自 client 端的连接,并返回一个新的套接字,用来和该 client 端交换数据。bind() 函数和 accept() 函数的参数在代码中都很简单明了,这里就不多介绍。

bind() 操作:将一个本地的套接字地址(包括 IP 地址和端口号)与一个套接字绑定,确保服务器能够在一个特定的端口上监听连接请求。服务器需要绑定到某个具体的端口号,以便客户端能够通过该端口访问服务器。绑定后,操作系统会知道要将来自客户端的请求通过该端口路由到服务器。(在 client 端,操作系统会为客户端分配一个临时端口,用于与服务器进行通信。因此,客户端不需要使用 bind() 来指定本地的端口号。)

listen() 操作:listen() 函数告诉操作系统,服务器套接字准备好接受客户端的连接请求。它实际上是使套接字处于监听状态,等待客户端发起连接。

#include <winsock2.h>int listen(SOCKET s,int backlog// backlog 等待连接队列的最大长度。表示在 accept() 函数被调用之前,系统允许的等待连接请求的最大数量。如果队列已满,新到的连接请求会被拒绝,直到队列有空位。
);

在代码中,listen() 函数的第二个参数 SOMAXCONN 是一个宏定义 #define SOMAXCONN 0x7fffffff,一个 16进制数,0x7fffffff 只是一个理论值,实际情况中可能有所不同。我们也可以自己指定 backlog 这个值,但不能超过一个最大限制,一旦超过这个最大限制,操作系统会自动调整为最大限制的值,至于这个最大限制是多少,不同的操作系统中可能会不同。backlog 值过小可能导致连接请求被拒绝(连接队列已满),如果设置得太大,可能会消耗过多的系统资源(端口号在一台机器上是有限的资源)。对于高负载的服务器,backlog 可能需要更高的值。

server 端可以接受多个 client 端的连接,欢迎套接字的作用就是监听端口,等待来自客户端的连接,并为每一个 client 端单独创建一个套接字与之进行通信。在上面的示例代码中,仅接受一个 client 端的连接,因为只创建了一个 clientSocket。

server 端和 client 端可以在同一台电脑上运行,这样就实现了同一台机器上两个进程之间的通信,client 端 使用 127.0.0.1 IP 地址连接 server 端。

server 端 和 client 端也可以在不同的主机上运行,如果在同一个局域网中,client 端就使用运行 server 端进程的主机的局域网 IP 地址进行连接。如果 server 端处于公共的网络环境中,client 端就使用 server 端主机的公网 IP 地址进行连接。

下面这段 client 代码用来连接以上 server 端:

//client 端
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>
#include <stdlib.h>#pragma comment(lib, "Ws2_32.lib")#define PORT 12345
#define BUFFER_SIZE 50000int main() {system("chcp 65001");  // 设置命令行 utf-8 字符集// 初始化 WinsockWSADATA wsaData;int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {std::cerr << "WSAStartup failed: " << iResult << std::endl;return 1;}// 创建套接字SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (clientSocket == INVALID_SOCKET) {std::cerr << "Error at socket(): " << WSAGetLastError() << std::endl;WSACleanup();return 1;}// 设置服务器地址和端口sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(PORT);// 将服务器地址从文本转换为二进制形式in_addr serverIP;if (inet_pton(AF_INET, "127.0.0.1", &serverIP) <= 0) {std::cerr << "Invalid address/ Address not supported" << std::endl;closesocket(clientSocket);WSACleanup();return 1;}memcpy(&(serverAddr.sin_addr), &serverIP, sizeof(serverIP));// 连接到服务器iResult = connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));if (iResult == SOCKET_ERROR) {std::cerr << "Error at connect(): " << WSAGetLastError() << std::endl;closesocket(clientSocket);WSACleanup();return 1;}std::cout << "Connected to server" << std::endl;// 发送和接收数据的循环while (true) {// 读取用户输入std::cout << "Enter message to send (or type 'exit' to quit): ";std::string inputMessage;std::getline(std::cin, inputMessage);  // 获取用户输入// 如果用户输入 'exit',则退出循环if (inputMessage == "exit") {break;}// 发送消息到服务器int iSendResult = send(clientSocket, inputMessage.c_str(), inputMessage.length(), 0);if (iSendResult == SOCKET_ERROR) {std::cerr << "send failed: " << WSAGetLastError() << std::endl;break;}std::cout << "Message sent to server: " << inputMessage << std::endl;// 接收来自服务器的响应char recvbuf[BUFFER_SIZE] = { 0 };int iRecvResult = recv(clientSocket, recvbuf, BUFFER_SIZE - 1, 0);if (iRecvResult > 0) {recvbuf[iRecvResult] = '\0';  // 确保字符串结束std::cout << "Message from server: " << recvbuf << std::endl;}else if (iRecvResult == 0) {// 连接关闭std::cout << "Connection closed by server." << std::endl;break;}else {std::cerr << "recv failed: " << WSAGetLastError() << std::endl;break;}}// 关闭套接字closesocket(clientSocket);// 清理 WinsockWSACleanup();return 0;
}

运行:server端 监听 12345 端口,client 端连接本地的 12345 端口不断地从命令行接收 message,并发送给 server 端,server 每次收到消息 都只发送 “Hello from server. 你好我是服务器端。”
在这里插入图片描述

相关文章:

Windows C/C++ Socket 编程

承接上文&#xff1a;socket 编程 本文目录 Windows Client 端WSADATA 结构体WSAStartup() 函数SOCKET 以及 socket() 函数sockaddr_ininet_pton() 函数in_addr structmemcpy()connect() 函数send() 函数recv() 函数 Windows Server 端 在进行 socket 编程之前&#xff0c;你要…...

计算两个结构的乘法

在行列可自由变换的平面上&#xff0c;2点结构有3个 3点结构有6个 计算2*2 2a1*2a14a6 2a1*2a24a8 2a1*2a34a12 显然2a1*2a14a6因为这3个结构都分布在同一列上&#xff0c;就是整数乘法。2a1*2a2的结果有2种写法&#xff0c;一种外形像2a1细节为2a2&#xff0c;一种外形为2…...

学校服务器连接pycharm配置2

上一个可能还是有点问题&#xff0c;因为实际在跑的时候读取的其实是本地的anaconda&#xff0c;这个重新整了一下流程 首先在学校服务器先激活自己创建的虚拟环境&#xff0c;这里就不截图了 然后在pycharm里面打开设置 选择这个python解释器 这里有添加解释器 选择SSH …...

AI赋能电商:创新应用提升销售与用户体验

目录 一、引言 二、AI技术在电商领域的创新应用 三、AI技术提高电商销售效率和用户体验的实践路径 一、引言 随着人工智能&#xff08;AI&#xff09;技术的不断成熟&#xff0c;电商行业正迎来一场深刻的变革。AI技术在购物推荐、会员分类、商品定价等方面的创新应用&…...

详解kafka消息发送重试机制的案例

在 Kafka 生产者中实现消息发送的重试机制&#xff0c;可以通过配置 KafkaProducer 的相关属性来实现。以下是一些关键的配置项&#xff1a; retries&#xff1a;设置生产者发送失败后重试的次数。 retry.backoff.ms&#xff1a;设置生产者在重试前等待的时间。 buffer.memo…...

linux文本管理!!!

文章目录 第1章 文本过滤/查看命令1.echo&#xff1a;输出文本2.cat&#xff1a;合并文件或查看文件内容3.head&#xff1a;显示文件头部信息4.tail&#xff1a;显示文件尾部信息5.wc: 统计文本行号6.less&#xff1a;分页显示文件内容7.grep&#xff1a;文本过滤工具8.定向符号…...

软件设计师-计算机体系结构分类

计算机体系结构分类 Flynn分类法 根据不同的指令流数据流组织方式分类单指令流但数据流SISD,单处理器系统单指令多数据流SIMD&#xff0c;单指令流多数据流是一种采用一个控制器来控制多个处理器&#xff0c;同时对一组数据&#xff08;又称“数据矢量”&#xff09;中的每一…...

《基于深度学习的车辆行驶三维环境双目感知方法研究》

复原论文思路&#xff1a; 《基于深度学习的车辆行驶三维环境双目感知方法研究》 1、双目测距的原理 按照上述公式算的话&#xff0c;求d的话&#xff0c;只和xl-xr有关系&#xff0c;这样一来&#xff0c;是不是只要两张图像上一个测试点的像素位置确定&#xff0c;对应的深…...

jwt用户登录,网关给微服务传递用户信息,以及微服务间feign调用传递用户信息

1、引入jwt依赖 <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency> 2、Jwt工具类&#xff0c;生成token以及解析token package com.niuniu.gateway.uti…...

ubontu安装anaconda

1.下载 Anaconda 安装脚本 2. 复制到服务器上/home/username文件夹中&#xff0c;进入文件夹&#xff0c;执行&#xff1a; bash Anaconda3-2024.10-1-Linux-x86_64.sh一直按回车&#xff0c;然后输入yes同意协议。 3. 初始化 Anaconda 环境&#xff0c;会自动配置环境变量&a…...

【Docker容器化技术】docker安装与配置、常用命令、容器数据卷、应用部署实战、Dockerfile、服务编排docker-compose、私有仓库

文章目录 一、Docker的安装与配置1、docker概述2、安装docker3、docker架构4、配置镜像加速器 二、Docker命令1、服务相关命令2、镜像相关命令3、容器相关命令 三、Docker容器数据卷1、数据卷概念及作用2、配置数据卷3、配置数据卷容器 四、Docker应用部署实战1、部署MySQL2、部…...

Python模拟A卷实操题

1.某机械公司生产两种产品。A的单件利润分别是100元&#xff0c;B的单件利润是150元。 每种产品由三种材料构成&#xff0c;现给出每种材料的库存&#xff08;库存小于100000&#xff09;&#xff0c;求利润最大的生产方案。输入说明&#xff1a;第一行给出生产每件A产品所需要…...

Leetcode 检测相邻递增子数组

3349. 检测相邻递增子数组 I 给你一个由 n 个整数组成的数组 nums &#xff0c;请你找出 k 的 最大值&#xff0c;使得存在 两个 相邻 且长度为 k 的 严格递增 子数组 。具体来说&#xff0c;需要检查是否存在从下标 a 和 b (a < b) 开始的 两个 子数组&#xff0c;并满…...

rockylinux 8安装 gcc11.2

方法 1&#xff1a;从源代码编译安装最新版本的 GCC 下载 GCC 源代码&#xff1a; 访问 GCC 官方网站下载最新版本的源代码&#xff0c;例如&#xff1a; wget https://ftp.gnu.org/gnu/gcc/gcc-11.2.0/gcc-11.2.0.tar.gz tar -xf gcc-11.2.0.tar.gz cd gcc-11.2.0安装依赖项&a…...

【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-奇数序列排序

C L13 奇数序列排序 给定一个长度为N的正整数序列&#xff0c; 请将其中的所有奇数取出&#xff0c;并按增序&#xff08;从小到大&#xff09;输出。 输入&#xff1a; 共2行 第1行是一个正整数 N&#xff08;不大于500&#xff09;&#xff1b; 第2行有 N 个正整数&#x…...

【AI】好用的AI记录

好用的AI 一、国内 KIMI通义 二、国外 GPT4Cursorv0...

linux安装boost.python

前言 boost.python库被用于C与Python代码间的交互&#xff0c;提供了两者间大部分数据类型的转换 相关环境 操作系统&#xff1a;Ubuntu 20.04 python版本&#xff1a;Python 3.8 boost版本&#xff1a;boost 1.78.0 安装 1.boost.python检查与卸载 在安装boost之前需要检…...

AI 扩展开发者思维方式:以 SQL 查询优化为例

在现代软件开发中&#xff0c;AI 技术的兴起让开发者的思维方式发生了显著变化。尤其是在 SQL 查询优化、代码重构以及算法设计等领域&#xff0c;AI 提供的建议不仅扩展了开发者的思考路径&#xff0c;还帮助他们发现以往没有意识到的潜在解决方案。 1. 传统思维模式下的 SQL…...

自定义面板,高效的游戏性能分析利器

为了更有效地聚焦并解决性能问题&#xff0c;UWA报告采用了分模块监控策略&#xff0c;确保每个模块独立成章&#xff0c;各司其职。然而&#xff0c;随着对性能分析需求的不断升级&#xff0c;我们已经意识到&#xff0c;在深入分析某些跨模块的性能瓶颈或优化点时&#xff0c…...

【Linux进程特别篇】深度理解辨识僵尸进程和孤儿进程

--------------------------------------------------------------------------------------------------------------------------------- 每日鸡汤&#xff1a;每一份坚持都是成功的积累&#xff0c;只要相信自己&#xff0c;总会遇到惊喜。 -----------------------------…...

喜报|超维机器人荣获昇腾AI创新大赛铜奖

近日&#xff0c;在备受瞩目的昇腾AI创新大赛中&#xff0c;超维机器人凭借扎实的技术实力和创新产品&#xff0c;荣获大赛铜奖。这一荣誉不仅展现了超维机器人在智能巡检领域的技术创新与突破&#xff0c;也标志着超维机器人的智能巡检解决方案在人工智能领域获得了广泛认可&a…...

从五种架构风格推导出HTTP的REST架构

在分布式系统中,架构风格(Architectural Style)决定了系统组件如何交互、通信、存储和管理数据。每种架构风格都有其独特的特性和适用场景。本文将从五种典型的架构风格出发,逐步探讨它们如何影响了REST(Representational State Transfer,表述性状态转移)架构风格的设计…...

vue-h5:在h5中实现相机拍照加上身份证人相框和国徽框

方案1&#xff1a;排出来照片太糊了&#xff0c;效果不好 1.基础功能 参考&#xff1a; https://blog.csdn.net/weixin_45148022/article/details/135696629 https://juejin.cn/post/7327353533618978842?searchId20241101133433B2BB37A081FD6A02DA60 https://www.freesio…...

免费HTML模板和CSS样式网站汇总

HTML模板&#xff1a;&#xff08;注意版权&#xff0c;部分不可商用&#xff09; 1、Tooplate&#xff0c;免费HTML模板下载 Download 60 Free HTML Templates for your websitesDownload 60 free HTML website templates or responsive Bootstrap templates instantly from T…...

Mac打开time machine(时间机器)备份特殊文件

Mac 打开time machine&#xff08;时间机器&#xff09;备份特殊文件 设置“时间机器”的作用具体操作办法 前言&#xff1a;今天在使用Nas同步文件时发现有部分重要文件没有同步&#xff0c;为了省事手动拖拽复制文件&#xff0c;导致其中一份非常重要的文件丢失&#xff0c;尝…...

Qt 学习第十六天:文件和事件

一、创建widget对象&#xff08;文件&#xff09; 二、设计ui界面 放一个label标签上去&#xff0c;设置成box就可以显示边框了 三、新建Mylabel类 四、提升ui界面的label标签为Mylabel 五、修改mylabel.h&#xff0c;mylabel.cpp #ifndef MYLABEL_H #define MYLABEL_H#incl…...

nvm 切换 Node.js 版本

nvm 切换 Node.js 版本 0. nvm 安装1. 查看装了哪些 Node.js 版本2. 安装 Node.js 版本安装最新稳定版本.安装个18 3. 切换 Node.js 版本4. 设置默认 Node.js 版本5. 卸载 Node.js 版本6.与项目的配合使用参考资料 0. nvm 安装 安装教程就不写了&#xff0c;直接看别人的。 脚…...

AI绘图最强软件stable diffusion,一文带你迅速了解!

有需要stable diffusion整合包可以扫描下方&#xff0c;免费获取 01 — 什么是 SD ​ Stable Difusion(简称 SD) 其三种概念。 1.用来指代稳定扩散(Stable Diffusion) 技术,如 Midjourney是基于Stable Difusion技术实现的就是指它运用了 Stable Diffusion 的技术原理。 …...

VMware重磅官宣!Workstation和Fusion彻底全部免费:支持商用

VMware 官网宣布&#xff1a;VMware Workstation Pro: Now Available Free for Personal Use 别问&#xff0c;问就是正版用户&#xff01;&#xff01;&#xff01; VMware宣布&#xff0c;其桌面虚拟化产品VMware Workstation和VMware Fusion将对所有用户彻底免费&#xff0…...

CCS 学习记录

1.导入项目 在CCS菜单中选择Project->Import Existing CCS Eclipse Project&#xff0c;点击Browse找到CCS workspace所在文件夹&#xff0c;点击OK&#xff0c;CCS会自动将所选文件夹及其子文件夹下所有的CCS Projects列出。从列表中找到所要导入的项目文件夹&#xff0c;…...