Windows下网络编程(win32API+VS2022)

2024-01-23 08:04

本文主要是介绍Windows下网络编程(win32API+VS2022),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、开发环境

我这里介绍下我用的环境安装过程。 所有版本的VS都可以的。

我当前环境是在Windows下,IDE用的是地表最强IDE VS2022。

下载地址:https://visualstudio.microsoft.com/zh-hans/downloads/

image-20230913173131481

因为我这里只需要用到C++和C语言编程,那么安装的时候可以自己选择需要安装的包。

image-20230913173258088

安装好之后,创建项目。

image-20230913173330580

image-20230913173349914

二、网络编程的基础知识

2.1 什么是网络编程

网络编程是通过使用IP地址和端口号等网络信息,使两台以上的计算机能够相互通信,按照规定的协议交换数据的编程方式。

在网络编程中,程序员使用各种协议和技术,使得不同的设备可以通过网络进行数据交换和信息共享。

要实现网络编程,程序员需要了解并掌握各种网络通信协议,比如TCP/IP协议族,包括TCP、UDP、IP等,这些协议是实现设备间通信的基础。网络编程内部涉及到数据的打包、组装、发送、接收、解析等一系列过程,以实现信息的正确传输。

在TCP/IP协议族中,TCP和UDP是位于IP协议之上的传输层协议。 在OSI模型中,传输层是第四层,负责总体数据传输和数据控制,为会话层等高三层提供可靠的传输服务,为网络层提供可靠的目的地点信息。在TCP/IP协议族中,TCP和UDP正是位于这一层的协议。

这篇文章主要介绍 TCP 和 UDP 协议 以及 使用方法。

img

2.2 TCP 和 UDP协议介绍

TCP协议

TCP(传输控制协议)是一种面向连接的、可靠的传输层协议。在传输数据之前需要先建立连接,确保数据的顺序和完整性。TCP通过三次握手建立连接,并通过确认、超时和重传机制确保数据的可靠传输。TCP采用流量控制和拥塞控制机制,以避免网络拥塞,确保数据的顺利传输。因为TCP的这些特性,通常被应用于需要高可靠性和顺序性的应用,如网页浏览、电子邮件等。

UDP协议

UDP(用户数据报协议)是一种无连接的、不可靠的传输层协议。与TCP不同,UDP在传输数据之前不需要建立连接,直接将数据打包成数据报并发送出去。因此,UDP没有TCP的那些确认、超时和重传机制,也就不保证数据的可靠传输。UDP也没有TCP的流量控制和拥塞控制机制。因为UDP的简单性和高效性,通常被应用于实时性要求较高,但对数据可靠性要求不高的应用,如语音通话、视频直播等。

2.3 TCP通信的实现过程

要实现TCP通信,两端必须要知道对方的IP和端口号:

(1)IP地址:TCP协议是基于IP协议进行通信的,因此需要知道对方的IP地址,才能建立连接。

(2)端口号:每个TCP连接都有一个唯一的端口号,用于标识进程和应用程序。建立连接时,需要指定本地端口号和远端端口号。

(3)应用层协议:TCP协议只提供数据传输服务,应用程序需要定义自己的应用层协议,用于解析报文和处理数据。例如,HTTP协议就是基于TCP协议的应用层协议。

在正常的TCP通信过程中,第一步需要建立连接,这个过程称为“三次握手”。建立连接时,客户端向服务器发送一个SYN包,表示请求建立连接;服务器接收到SYN包后,向客户端发送一个ACK包,表示确认收到了SYN包;最后客户端再向服务器发送一个ACK包,表示确认收到了服务器的ACK包,此时连接建立成功。建立连接后,数据传输就可以开始了。

img

三、Windows下的API介绍

微软的官方文档地址:https://learn.microsoft.com/zh-cn/windows/win32/api/_winsock/

image-20231031132547022

3.1 常用的函数介绍

在Windows下进行网络编程,可以使用Winsock API(Windows Sockets API)来实现。Winsock API是Windows平台上的标准网络编程接口,提供了一系列函数和数据结构,用于创建、连接、发送和接收网络数据等操作。

下面是常用的Winsock API接口函数:

(1)WSAStartup:初始化Winsock库,必须在使用其他Winsock函数之前调用。

(2)socket:创建一个套接字,用于网络通信。

(3)bind:将套接字与本地地址(IP地址和端口号)绑定。

(4)listen:开始监听连接请求,将套接字设置为被动模式。

(5)accept:接受客户端的连接请求,创建一个新的套接字用于与客户端通信。

(6)connect:与远程服务器建立连接。

(7)send:发送数据到已连接的套接字。

(8)recv:从已连接的套接字接收数据。

(9)sendto:发送数据到指定的目标地址。

(10)recvfrom:从指定的地址接收数据。

(11)closesocket:关闭套接字。

(12)getaddrinfo:根据主机名和服务名获取地址信息。

(13)gethostbyname:根据主机名获取主机的IP地址。

(14)gethostname:获取本地主机名。

3.2 函数参数介绍

下面是常用的几个Winsock API函数及其函数原型和参数含义的介绍:

(1)WSAStartup

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
  • wVersionRequested:请求的Winsock版本号。
  • lpWSAData:指向WSADATA结构的指针,用于接收初始化结果和相关信息。

(2)socket

SOCKET socket(int af, int type, int protocol);
  • af:地址族(Address Family),如AF_INET表示IPv4。
  • type:套接字类型,如SOCK_STREAM表示面向连接的TCP套接字。
  • protocol:指定协议。通常为0,表示根据type自动选择合适的协议。

(3)bind

int bind(SOCKET s, const struct sockaddr* name, int namelen);
  • s:要绑定的套接字。
  • name:指向sockaddr结构的指针,包含要绑定的本地地址信息。
  • namelenname结构的长度。

(4)listen

int listen(SOCKET s, int backlog);
  • s:要监听的套接字。
  • backlog:等待连接队列的最大长度。

(5)accept

SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
  • s:监听套接字。
  • addr:用于存储客户端地址信息的sockaddr结构。
  • addrlenaddr结构的长度。

(6)connect

int connect(SOCKET s, const struct sockaddr* name, int namelen);
  • s:要连接的套接字。
  • name:指向目标地址信息的sockaddr结构指针。
  • namelenname结构的长度。

(7)send

int send(SOCKET s, const char* buf, int len, int flags);
  • s:要发送数据的套接字。
  • buf:要发送的数据缓冲区。
  • len:要发送的数据长度。
  • flags:额外选项,如MSG_DONTROUTE等。

(8)recv

int recv(SOCKET s, char* buf, int len, int flags);
  • s:要接收数据的套接字。
  • buf:用于存储接收数据的缓冲区。
  • len:要接收的数据长度。
  • flags:额外选项。

(9)sendto

int sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen);
  • s:要发送数据的套接字。
  • buf:要发送的数据缓冲区。
  • len:要发送的数据长度。
  • flags:额外选项。
  • to:指向目标地址信息的sockaddr结构指针。
  • tolento结构的长度。

(10)recvfrom

int recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen);
  • s:要接收数据的套接字。
  • buf:用于存储接收数据的缓冲区。
  • len:要接收的数据长度。
  • flags:额外选项。
  • from:用于存储发送方地址信息的sockaddr结构指针。
  • fromlenfrom结构的长度。

(11)closesocket

int closesocket(SOCKET s);
  • s:要关闭的套接字。

(12)getaddrinfo

int getaddrinfo(const char* nodename, const char* servname, const struct addrinfo* hints, struct addrinfo** res);
  • nodename:目标主机名或IP地址。
  • servname:服务名或端口号。
  • hints:指向addrinfo结构的指针,提供关于地址查找的提示。
  • res:指向addrinfo结构链表的指针,用于接收查找结果。

(13)gethostbyname

struct hostent* gethostbyname(const char* name);
  • name:要查询的主机名。

(14)gethostname

int gethostname(char* name, int namelen);
  • name:用于接收主机名的缓冲区。
  • namelenname缓冲区的长度。

四、基本示例代码

4.1 创建TCP服务器

下面代码实现一个简单的TCP服务器。

实现的功能:初始化Winsock、创建套接字、绑定到本地地址和指定端口、监听连接请求、接受客户端连接、发送和接收数据,最后关闭套接字和清理Winsock资源。

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib") // 链接到ws2_32库int main()
{WSADATA wsaData;int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // 初始化Winsockif (result != 0){std::cout << "初始化Winsock失败 " << result << std::endl;return 1;}SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建套接字if (listenSocket == INVALID_SOCKET){std::cout << "创建套接字失败: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}sockaddr_in service;service.sin_family = AF_INET;service.sin_addr.s_addr = INADDR_ANY;service.sin_port = htons(12345);result = bind(listenSocket, (SOCKADDR*)&service, sizeof(service)); // 将套接字绑定到本地地址和指定端口if (result == SOCKET_ERROR){std::cout << "端口绑定失败: " << WSAGetLastError() << std::endl;closesocket(listenSocket);WSACleanup();return 1;}result = listen(listenSocket, SOMAXCONN); // 监听连接请求if (result == SOCKET_ERROR){std::cout << "监听连接请求失败: " << WSAGetLastError() << std::endl;closesocket(listenSocket);WSACleanup();return 1;}std::cout << "等待客户端连接:" << std::endl;SOCKET clientSocket = accept(listenSocket, NULL, NULL); // 接受客户端连接if (clientSocket == INVALID_SOCKET){std::cout << "accept执行失败: " << WSAGetLastError() << std::endl;closesocket(listenSocket);WSACleanup();return 1;}std::cout << "客户端已连接..." << std::endl;char sendBuffer[1024] = "Hello, client!";result = send(clientSocket, sendBuffer, sizeof(sendBuffer), 0); // 发送数据给客户端if (result == SOCKET_ERROR){std::cout << "发送消息执行错误: " << WSAGetLastError() << std::endl;closesocket(clientSocket);WSACleanup();return 1;}char recvBuffer[1024];result = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0); // 接收来自客户端的数据if (result == SOCKET_ERROR){std::cout << "接收消息执行错误: " << WSAGetLastError() << std::endl;closesocket(clientSocket);WSACleanup();return 1;}std::cout << "收到来着客户端发送的消息: " << recvBuffer << std::endl;closesocket(clientSocket); // 关闭客户端套接字closesocket(listenSocket); // 关闭监听套接字WSACleanup(); // 清理Winsock资源return 0;
}

运行效果:

image-20231031133009747

4.2 创建TCP客户端

下面代码实现一个TCP客户端,连接到指定的服务器并完成通信。

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib") //告诉编译器链接Winsock库int main()
{WSADATA wsaData; //创建一个结构体变量,用于存储关于Winsock库的信息int result = WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化Winsock库,指定版本号2.2,检查返回值if (result != 0){std::cout << "WSAStartup failed: " << result << std::endl; //输出错误信息并退出程序return 1;}SOCKET connectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建一个TCP套接字,检查返回值if (connectSocket == INVALID_SOCKET){std::cout << "socket failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序WSACleanup(); //清除Winsock库return 1;}sockaddr_in service; //创建一个结构体变量,用于存储服务器地址信息service.sin_family = AF_INET; //指定地址族为IPv4inet_pton(AF_INET, "127.0.0.1", &service.sin_addr); //将字符串类型的IP地址转换为二进制网络字节序的IP地址,并存储在结构体中service.sin_port = htons(12345); //将端口号从主机字节序转换为网络字节序,并存储在结构体中result = connect(connectSocket, (SOCKADDR*)&service, sizeof(service)); //连接到服务器,检查返回值if (result == SOCKET_ERROR){std::cout << "connect failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序closesocket(connectSocket); //关闭套接字WSACleanup(); //清除Winsock库return 1;}std::cout << "Connected to server." << std::endl; //连接成功,输出消息char sendBuffer[1024] = "Hello, server!"; //创建发送缓冲区,存储待发送的数据result = send(connectSocket, sendBuffer, sizeof(sendBuffer), 0); //向服务器发送数据,检查返回值if (result == SOCKET_ERROR){std::cout << "send failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序closesocket(connectSocket); //关闭套接字WSACleanup(); //清除Winsock库return 1;}char recvBuffer[1024]; //创建接收缓冲区,用于存储从服务器接收到的数据result = recv(connectSocket, recvBuffer, sizeof(recvBuffer), 0); //从服务器接收数据,检查返回值if (result == SOCKET_ERROR){std::cout << "recv failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序closesocket(connectSocket); //关闭套接字WSACleanup(); //清除Winsock库return 1;}std::cout << "Received message from server: " << recvBuffer << std::endl; //输出从服务器收到的数据closesocket(connectSocket); //关闭套接字WSACleanup(); //清除Winsock库return 0;
}

运行效果:

image-20231031134243184

4.3 TCP客户端循环接收消息

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>#pragma comment(lib, "ws2_32.lib") //告诉编译器链接Winsock库int main()
{WSADATA wsaData; //创建一个结构体变量,用于存储关于Winsock库的信息int result = WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化Winsock库,指定版本号2.2,检查返回值if (result != 0){std::cout << "WSAStartup failed: " << result << std::endl; //输出错误信息并退出程序return 1;}SOCKET connectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建一个TCP套接字,检查返回值if (connectSocket == INVALID_SOCKET){std::cout << "socket failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序WSACleanup(); //清除Winsock库return 1;}sockaddr_in service; //创建一个结构体变量,用于存储服务器地址信息service.sin_family = AF_INET; //指定地址族为IPv4inet_pton(AF_INET, "127.0.0.1", &service.sin_addr); //将字符串类型的IP地址转换为二进制网络字节序的IP地址,并存储在结构体中service.sin_port = htons(12345); //将端口号从主机字节序转换为网络字节序,并存储在结构体中result = connect(connectSocket, (SOCKADDR*)&service, sizeof(service)); //连接到服务器,检查返回值if (result == SOCKET_ERROR){std::cout << "connect failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序closesocket(connectSocket); //关闭套接字WSACleanup(); //清除Winsock库return 1;}std::cout << "Connected to server." << std::endl; //连接成功,输出消息char recvBuffer[1024]; //创建接收缓冲区,用于存储从服务器接收到的数据while (true){result = recv(connectSocket, recvBuffer, sizeof(recvBuffer), 0); //从服务器接收数据,检查返回值if (result == SOCKET_ERROR){std::cout << "recv failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出循环break;}else if (result > 0) //判断是否有数据接收到{std::cout << "Received message from server: " << recvBuffer << std::endl; //输出从服务器收到的数据}else //连接断开{std::cout << "Server disconnected." << std::endl;break;}}closesocket(connectSocket); //关闭套接字WSACleanup(); //清除Winsock库return 0;
}

4.4 TCP服务器并发处理客户端请求

下面示例代码中,使用了std::vector<std::thread>来存储线程对象,在每个客户端连接时创建一个新线程来处理该连接。使用多线程可以让服务器同时处理多个客户端连接,提高并发性能。

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <thread>
#include <vector>#pragma comment(lib, "ws2_32.lib")// 处理客户端连接的函数
void HandleClient(SOCKET clientSocket)
{char recvBuffer[1024];int result;while (true){result = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);if (result == SOCKET_ERROR){std::cout << "recv failed with error: " << WSAGetLastError() << std::endl;break;}else if (result > 0){std::cout << "Received message from client: " << recvBuffer << std::endl;}else{std::cout << "Client disconnected." << std::endl;break;}}closesocket(clientSocket);
}int main()
{WSADATA wsaData;int result = WSAStartup(MAKEWORD(2, 2), &wsaData);if (result != 0){std::cout << "WSAStartup failed: " << result << std::endl;return 1;}SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (listenSocket == INVALID_SOCKET){std::cout << "socket failed with error: " << WSAGetLastError() << std::endl;WSACleanup();return 1;}sockaddr_in service;service.sin_family = AF_INET;service.sin_addr.s_addr = INADDR_ANY;service.sin_port = htons(12345);result = bind(listenSocket, (SOCKADDR*)&service, sizeof(service));if (result == SOCKET_ERROR){std::cout << "bind failed with error: " << WSAGetLastError() << std::endl;closesocket(listenSocket);WSACleanup();return 1;}result = listen(listenSocket, SOMAXCONN);if (result == SOCKET_ERROR){std::cout << "listen failed with error: " << WSAGetLastError() << std::endl;closesocket(listenSocket);WSACleanup();return 1;}std::cout << "Server is listening for incoming connections." << std::endl;std::vector<std::thread> threads; // 存储线程对象while (true){SOCKET clientSocket = accept(listenSocket, NULL, NULL);if (clientSocket == INVALID_SOCKET){std::cout << "accept failed with error: " << WSAGetLastError() << std::endl;continue;}std::cout << "Client connected." << std::endl;// 创建一个新线程来处理客户端连接std::thread thread(HandleClient, clientSocket);// 存储线程对象threads.push_back(std::move(thread));}// 等待所有线程执行完毕for (auto& thread : threads){thread.join();}closesocket(listenSocket);WSACleanup();return 0;
}

运行效果:

image-20231031140758443

这篇关于Windows下网络编程(win32API+VS2022)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/635745

相关文章

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

javafx 如何将项目打包为 Windows 的可执行文件exe

《javafx如何将项目打包为Windows的可执行文件exe》文章介绍了三种将JavaFX项目打包为.exe文件的方法:方法1使用jpackage(适用于JDK14及以上版本),方法2使用La... 目录方法 1:使用 jpackage(适用于 JDK 14 及更高版本)方法 2:使用 Launch4j(

windows端python版本管理工具pyenv-win安装使用

《windows端python版本管理工具pyenv-win安装使用》:本文主要介绍如何通过git方式下载和配置pyenv-win,包括下载、克隆仓库、配置环境变量等步骤,同时还详细介绍了如何使用... 目录pyenv-win 下载配置环境变量使用 pyenv-win 管理 python 版本一、安装 和

Python使用pysmb库访问Windows共享文件夹的详细教程

《Python使用pysmb库访问Windows共享文件夹的详细教程》本教程旨在帮助您使用pysmb库,通过SMB(ServerMessageBlock)协议,轻松连接到Windows共享文件夹,并列... 目录前置条件步骤一:导入必要的模块步骤二:配置连接参数步骤三:实例化SMB连接对象并尝试连接步骤四:

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了