组播模式相比单播模式可以提高网络的效率和带宽利用率,因为组播数据包只需要发送一次,就可以被多个接收者接收,而不需要每个接收者都单独发送一份数据包。这在需要同时向多个接收者发送相同数据的场景下特别有用,如视频会议、在线教育、流媒体等。组播模式可以减少网络拥塞,降低网络延迟,并且可以减少网络中的冗余数据。
通过构建组播服务器端与客户端,并配合键盘鼠标控制接口,当服务器端执行一个操作时客户端同步执行,通过此方法读者可轻易的实现一个简单的镜像服务器,当服务器规模庞大而主机系统版本相同时,该功能可实现服务器端执行一次客户端即可实现批量部署的效果。
先来看服务端是如何实现的功能,首先服务端定义umsg
结构体,该结构用于存储鼠标坐标值以及鼠标按键状态,当服务器运行后开启组播模式等待客户端上线,当客户端上线则我们通过动态获取本机鼠标位置并封装成结构体传输给上线的客户端,以此来实现镜像功能。
#include <winsock.h>
#include <iostream>#pragma comment(lib, "WSOCK32.lib")using namespace std;// 鼠标状态结构体
typedef struct umsg
{int cursor_pos_x; // 鼠标X坐标int cursor_pos_y; // 鼠标Y坐标int cursor_key_state; // 鼠标按键状态umsg() :cursor_pos_x(), cursor_pos_y(), cursor_key_state(){cursor_pos_x = 0;cursor_pos_y = 0;cursor_key_state = 0;}
}umsg;// 获取鼠标按键
int GetKeyState()
{if (GetAsyncKeyState(VK_LBUTTON) & 0x8000){Sleep(15);return 1;}if (GetAsyncKeyState(VK_RBUTTON) & 0x8000){Sleep(15);return 2;}return 0;
}int main(int argc, char *argv[])
{WSADATA wsaData;struct sockaddr_in addr;int fd;struct ip_mreq mreq;// 初始化套接字if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){std::cout << "初始化失败" << std::endl;return 0;}// 创建套接字 SOCK_DGRAM 采用UDPif ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){std::cout << "套接字创建失败" << std::endl;return 0;}// 设置套接字为组播模式u_int yes = 1;if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) < 0){std::cout << "设置组播模式失败" << std::endl;return 0;}// 0-同一台主机,1-跨主机UCHAR uLoop = 1;setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&uLoop, sizeof(uLoop));memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(9999);// 绑定套接字if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0){std::cout << "绑定失败" << std::endl;return 0;}// 设置组播模式中的组信息mreq.imr_multiaddr.s_addr = inet_addr("228.2.3.1");mreq.imr_interface.s_addr = htonl(INADDR_ANY);if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0){int err = GetLastError();std::cout << "设置组失败: " << err << std::endl;return 0;}// 循环while (1){char recv_buffer[4096] = { 0 };char send_buffer[4096] = { 0 };int addrlen = sizeof(addr);int nbytes;// 接收组播数据if ((nbytes = recvfrom(fd, recv_buffer, 4096, 0, (struct sockaddr *) &addr, (int *)&addrlen)) < 0){std::cout << "接收数据包失败" << std::endl;return 0;}recv_buffer[nbytes] = '\0';std::cout << "接收组播数据包: " << recv_buffer << std::endl;umsg msg;// 获取鼠标状态POINT pt;BOOL ref = GetCursorPos(&pt);// 设置鼠标坐标msg.cursor_pos_x = pt.x;msg.cursor_pos_y = pt.y;// 获取鼠标状态值int key_flag = GetKeyState();if (key_flag == 0){msg.cursor_key_state = 0;}else if (key_flag == 1){msg.cursor_key_state = 1;}else if (key_flag == 2){msg.cursor_key_state = 2;}std::cout << "鼠标X = " << msg.cursor_pos_x << " 鼠标Y = " << msg.cursor_pos_y << std::endl;std::cout << "鼠标键位 = " << msg.cursor_key_state << std::endl;// 发送组播数据包sendto(fd, (char *)&msg, 4096, 0, (struct sockaddr *) &addr, sizeof(addr));}return 0;
}
与服务端功能类似,对于客户端
来说,收到数据包以后,将其转换为umsg
格式结构体,读取其中坐标信息,并执行指定函数对鼠标的状态进行设置,实现鼠标的同步执行。
#include <winsock.h>
#include <iostream>#pragma comment(lib, "WSOCK32.lib")using namespace std;// 鼠标状态结构体
typedef struct umsg
{int cursor_pos_x; // 鼠标X坐标int cursor_pos_y; // 鼠标Y坐标int cursor_key_state; // 鼠标按键状态umsg() :cursor_pos_x(), cursor_pos_y(), cursor_key_state(){cursor_pos_x = 0;cursor_pos_y = 0;cursor_key_state = 0;}
}umsg;int main(int argc, char *argv[])
{WSADATA wsaData;struct sockaddr_in addr;int fd;// 初始化套接字if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){std::cout << "初始化失败" << std::endl;return 0;}// 创建套接字 SOCK_DGRAM 采用UDPif ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){std::cout << "套接字创建失败" << std::endl;return 0;}UCHAR uLoop = 1; // 0-同一台主机,1-跨主机setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&uLoop, sizeof(uLoop));// 设置组播模式组信息memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("228.2.3.1");addr.sin_port = htons(9999);// 循环while (1){// 发送组播数据包char send_buffer[4096] = "Hello, World!";if (sendto(fd, send_buffer, strlen(send_buffer), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0){std::cout << "发送失败" << std::endl;return 0;}// 接收组播数据int addrlen = sizeof(addr);char recv_buffer[4096] = { 0 };recvfrom(fd, recv_buffer, 4096, 0, (struct sockaddr *) &addr, (int *)&addrlen);std::cout << "接收组播数据包: " << recv_buffer << std::endl;// 格式化数据包为umsg格式umsg* recv_message = (umsg*)recv_buffer;int pos_x = recv_message->cursor_pos_x;int pos_y = recv_message->cursor_pos_y;int key_stat = recv_message->cursor_key_state;// 判断键位并设置if (key_stat == 0){std::cout << "鼠标X: " << pos_x << " 鼠标Y: " << pos_y << endl;SetCursorPos(pos_x, pos_y);}else if (key_stat == 1){std::cout << "左键按下" << std::endl;mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);key_stat = 0;}else if (key_stat == 2){std::cout << "右键按下" << std::endl;mouse_event(MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);key_stat = 0;}}return 0;
}
读者可以编译上方两段代码,并首先在物理机内启动服务端,在虚拟机内启动客户端,此时当服务端鼠标发生移动时客户端也会跟随移动,服务端执行的操作客户端也会被执行,如下图所示;
本文作者: 王瑞
本文链接: https://www.lyshark.com/post/ae8bf78c.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!