基于WSAAsyncSelect模型的通信程序设计
一、问题描述
编写Win32程序模拟实现基于WSAAsyncSelect模型的两台计算机之间的通信,要求编程实现服务器端与客户端之间双向数据传递。客户端向服务器端发送“请输出从1到1000内所有的质数”,服务器回应客户端给出结果。
二、代码实现
①CInitSock.h具体代码:
1 #pragma once 2 #include <winsock2.h> 3 #pragma comment(lib,"WS2_32") 4 class CInitSock 5 { 6 public: 7 CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) 8 { 9 //初始化W2_32.dll 10 WSADATA wsaData; 11 WORD sockVersion = MAKEWORD(minorVer, majorVer); 12 if (::WSAStartup(sockVersion, &wsaData) != 0) 13 { 14 exit(0); 15 } 16 } 17 ~CInitSock() 18 { 19 ::WSACleanup(); 20 } 21 };
②服务器端代码:
1 #include "CInitSock.h" 2 #include<iostream> 3 #include<sstream> 4 using namespace std; 5 CInitSock initSock; 6 7 //自定义网络通知消息: 8 #define WM_SOCKET (WM_USER + 1) 9 10 //判断质数的函数: 11 boolean PNumJudgment(int num); 12 char* AllThePNum(int start, int end); 13 //判断质数的函数2: 14 string calculatePrimeNumbers(int start, int end); 15 16 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 17 int main() 18 { 19 char szClassName[] = "MainWClass"; 20 WNDCLASSEX wndclass; 21 22 //用描述主窗口的参数填充WNDCLASSEX结构 23 wndclass.cbSize = sizeof(wndclass); 24 wndclass.style = CS_HREDRAW | CS_VREDRAW; 25 wndclass.lpfnWndProc = WindowProc; 26 wndclass.cbClsExtra = 0; 27 wndclass.cbWndExtra = 0; 28 wndclass.hInstance = NULL; 29 wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION); 30 wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW); 31 wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); 32 wndclass.lpszMenuName = NULL; 33 wndclass.lpszClassName = szClassName; 34 wndclass.hIconSm = NULL; 35 ::RegisterClassEx(&wndclass); 36 37 38 //创建主窗口 39 HWND hWnd = ::CreateWindowEx( 40 0, 41 szClassName, 42 "", 43 WS_OVERLAPPEDWINDOW, 44 CW_USEDEFAULT, 45 CW_USEDEFAULT, 46 CW_USEDEFAULT, 47 CW_USEDEFAULT, 48 NULL, 49 NULL, 50 NULL, 51 NULL); 52 if (hWnd == NULL) 53 { 54 ::MessageBox(NULL, "创建窗口出错!", "error", MB_OK); 55 return -1; 56 } 57 USHORT nPort = 4567; 58 59 //创建监听套接字 60 SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 61 sockaddr_in sin; 62 sin.sin_family = AF_INET; 63 sin.sin_port = htons(nPort); 64 sin.sin_addr.S_un.S_addr = INADDR_ANY; 65 66 //绑定套接字到本地机器 67 if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) 68 { 69 printf("Failed bind()\n"); 70 return -1; 71 } 72 73 //将套接字设为窗口通知消息类型 74 ::WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE); 75 ::listen(sListen, 5); 76 77 //从消息队列中取出消息 78 MSG msg; 79 while (::GetMessage(&msg, NULL, 0, 0)) 80 { 81 ::TranslateMessage(&msg);//转化键盘消息 : 将键盘消息翻译成对应的字符消息 82 ::DispatchMessage(&msg);//将消息发送到相应的窗口函数进行处理 83 } 84 return msg.wParam; //当GetMessage返回0时的程序结束 85 86 } 87 88 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 89 { 90 switch (uMsg) 91 { 92 case WM_SOCKET: 93 { 94 SOCKET s = wParam;//取得有事件发生的套接字句柄 95 //查看是否出错 96 if (WSAGETSELECTERROR(lParam)) 97 { 98 ::closesocket(s); 99 return 0; 100 } 101 //处理发生的事件 102 switch (WSAGETSELECTEVENT(lParam)) 103 { 104 case FD_ACCEPT: //监听中的套接字检测到有连接进入 105 { 106 SOCKET client = ::accept(s, NULL, NULL); 107 ::WSAAsyncSelect(client, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);//sListen client 108 } 109 break; 110 case FD_WRITE: 111 {} 112 break; 113 case FD_READ: 114 { 115 char szText[1024] = { 0 }; 116 if (::recv(s, szText, 1024, 0) == -1) 117 ::closesocket(s); 118 else 119 { 120 printf("接收到消息: %s", szText); 121 122 /*补充:向客户端发送数据*/ 123 int start, end, add_sum; 124 char response[256]; 125 string calString; 126 if (sscanf(szText, "请输出%d到%d内的所有质数!", &start, &end) == 2) {//请输出从1到1000内所有的质数 127 string calString = calculatePrimeNumbers(start, end); 128 ::send(wParam, calString.c_str(), calString.length(), 0); 129 } 130 else { 131 strcpy(response, "无效的指令,请重新发送指令"); 132 ::send(wParam, response, strlen(response), 0); 133 } 134 } 135 } 136 break; 137 case FD_CLOSE: 138 { 139 ::closesocket(s); 140 } 141 break; 142 } 143 } 144 return 0; 145 case WM_DESTROY: 146 ::PostQuitMessage(0); 147 return 0; 148 } 149 //我们不需要的消息,就交给系统默认处理 150 return ::DefWindowProc(hWnd, uMsg, wParam, lParam); 151 } 152 153 boolean PNumJudgment(int num) 154 { 155 boolean flag = true; 156 for (int i = 2; i < (num / 2); i++) { 157 if (num % i == 0) 158 flag == false; 159 } 160 return flag; 161 } 162 163 char* AllThePNum(int start, int end) 164 { 165 char PNumArr[4096]; 166 for (int i = start; i <= end; i++) 167 { 168 if (PNumJudgment(i)) 169 { 170 PNumArr[i] = i; 171 172 } 173 } 174 return PNumArr; 175 } 176 177 string calculatePrimeNumbers(int start, int end) 178 { 179 stringstream calResult; 180 for (int i = start; i <= end; i++) 181 { 182 if (i == 1) 183 continue; //1既不是质数也不是合数 184 bool isPrime = true; 185 for (int j = 2; j < i; j++) 186 { 187 if (i % j == 0) 188 { 189 isPrime = false; 190 break; 191 } 192 } 193 if (isPrime) 194 { 195 calResult << i << " "; 196 } 197 } 198 return calResult.str(); 199 }
③客户端代码:
1 #include "CInitSock.h" 2 #include<stdio.h> 3 CInitSock initSock; 4 int main() 5 { 6 //创建套接字 7 SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 8 if (s == INVALID_SOCKET) 9 { 10 printf("Failed socket()\n"); 11 return 0; 12 } 13 //填充sockaddr_in结构 14 sockaddr_in servAddr; 15 servAddr.sin_family = AF_INET; 16 servAddr.sin_port = htons(4567); 17 18 // 19 servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 20 //绑定这个套接字到一个本地地址 21 if (::connect(s, (LPSOCKADDR)&servAddr, sizeof(servAddr)) == -1) 22 { 23 printf("Falied connect()\n"); 24 return 0; 25 } 26 //给服务器端发信息 27 char szText1[] = "请输出1到1000内的所有质数!"; 28 ::send(s, szText1, strlen(szText1), 0); 29 30 //接收数据 31 char buff[4096]; 32 int nRecv = ::recv(s, buff, 4096, 0); 33 if (nRecv > 0) 34 { 35 buff[nRecv] = '\0'; 36 printf("接收到数据:%s", buff); 37 } 38 39 //关闭监听套接字 40 ::closesocket(s); 41 return 0; 42 }
三、运行结果
①服务器端接收到请求消息:
②客户端收到服务器端的回复:
四、关于质数求解的不同方法
①基本的嵌套循环判断每个数是否为质数
这是最常使用的方法,也是上述服务器中使用到的方法:
1 bool isPrime(int num) { 2 if (num < 2) { 3 return false; 4 } 5 for (int i = 2; i * i <= num; i++) { 6 if (num % i == 0) { 7 return false; 8 } 9 } 10 return true; 11 } 12 13 void findPrimes(int start, int end) { 14 printf("质数列表: "); 15 for (int num = start; num <= end; num++) { 16 if (isPrime(num)) { 17 printf("%d ", num); 18 } 19 } 20 printf("\n"); 21 }
服务器上判断质数的代码:(和上述代码的思路相同)
1 string calculatePrimeNumbers(int start, int end) 2 { 3 stringstream calResult; 4 for (int i = start; i <= end; i++) 5 { 6 if (i == 1) 7 continue; //1既不是质数也不是合数 8 bool isPrime = true; 9 for (int j = 2; j < i; j++) 10 { 11 if (i % j == 0) 12 { 13 isPrime = false; 14 break; 15 } 16 } 17 if (isPrime) 18 { 19 calResult << i << " "; 20 } 21 } 22 return calResult.str(); 23 }
②埃及筛法判断质数
思路:
一个素数的整数倍必定为合数
算法思想:
1.创建一个长度为n+1的数组sieve;
(长度为n+1是因为数组的下标是从0开始的,若n=10,你创建一个长度为n的数组,数组是从0--9的,并不包含10。)
2.初始化sieve数组的所有元素都为true,即默认所有的数都是质数;
3.进入for循环,进行判断;此时范围为for(int i=2;i<=n;i++);
(i从2开始,是因为数组中下标为0的数用不上,下标为1的数是用来表示数字1的,而1不是质数,所以要从数字2进行判断。)
4.在for循环中,如果prim[i]==1(i为质数),则将i的所有整数倍置为合数。
对i进行整数倍置为合数操作的时候,本应该 2i 3i 4i......都要置为合数的,但是有些数在之前的i-1已经操作过,所以此时不必在进行操作,我们只需要从i*i开始即可。
举个例子:
i=2,prim[i]==1---->if(i*i<=n)---->4 6 8 10 12 14 16 18 20 22......都置为合数
i=3,prim[i]==1---->if(i*i<=n)----->6 9 12 15 18 21 24 ......置为合数,此时6已经操作过了
i=5,prim[i]==1---->if(i*i<=n)----->10 15 20 25 30 35 ......置为合数,此时10、15、20已经置为合数过了,所以我们应从i*i开始操作
1 #include <stdio.h> 2 #include <stdbool.h> 3 #include <math.h> 4 5 void findPrimes(int start, int end) { 6 bool sieve[1024]; 7 for (int i = 0; i <= end; i++) { 8 sieve[i] = true; 9 } 10 for (int p = 2; p <= sqrt(end); p++) { 11 if (sieve[p]) { 12 for (int i = p * p; i <= end; i += p) { 13 sieve[i] = false; 14 } 15 } 16 } 17 printf("使用优化的Sieve of Eratosthenes求出的质数列表: "); 18 for (int num = start; num <= end; num++) { 19 if (num >= 2 && sieve[num] == true) { 20 printf("%d ", num); 21 } 22 } 23 printf("\n"); 24 } 25 26 int main() { 27 int start = 1; 28 int end = 1000; 29 findPrimes(start, end); 30 return 0; 31 }
运行结果:
五、总结
①实验中遇到的问题及其解决办法:
错误①:
问题描述:
Unicode字符集下,不可将char*类型的值分配到“LPCWSTR”类型的实体。
原因:
“从Visual C2005开始,编译器不再进行从char到LPCWSTR的隐式转换了”
解决方法:
修改工程属性,项目属性->高级->字符集->使用Unicode字符集改为未设置。
修改完之后便可正常赋值:
错误②:
问题描述:
Visual Studio中找不到WM_SOCKET这个消息常量
解决办法:
在 Visual Studio 中,WM_SOCKET 不是一个内置的消息常量,这是因为 WM_SOCKET 是一个自定义的消息,它通常与网络编程和异步套接字(Socket)相关联。
需要补充自定义网络通知消息代码:
#define WM_SOCKET (WM_USER + 1)
理解:
在 Windows 系统中,当使用异步套接字编程模型时,可以通过 WSAAsyncSelect 函数为套接字关联一个窗口,并指定一个自定义的消息号,以便在套接字事件发生时通过消息机制通知该窗口。具体来说,WSAAsyncSelect 函数将 FD_SOCKET 事件与指定的套接字关联,并在事件发生时向关联的窗口发送一个消息。
②对于WSAAsyncSelect模型的了解:
1.WSAAsyncSelect模型的工作过程分析:
①创建套接字:
首先,需要使用socket函数创建一个套接字。套接字是网络通信的端点,可以在应用程序中发送和接收数据。
②注册事件通知:
使用WSAAsyncSelect函数将套接字与特定的窗口关联起来,并为套接字注册感兴趣的网络事件,例如连接请求、数据到达等。该函数还指定了在事件发生时应该通知的窗口消息。
③创建消息循环:
在接收事件通知之前,需要创建一个消息循环来处理系统发送的消息。消息循环可以使用GetMessage函数来获取消息,并将其分派给相应的窗口过程进行处理。
④接收事件通知:
当套接字接收到感兴趣的网络事件时,系统将发送一个消息给消息循环。应用程序可以使用TranslateMessage和DispatchMessage函数将该消息传递给关联的窗口过程进行处理。
⑤处理事件:
在窗口过程中,应用程序可以通过解析消息的参数来确定发生的事件类型。例如,如果收到FD_READ事件,表示有数据可以从套接字中读取。
⑥处理数据:
根据具体的应用需求,可以使用相应的函数(如recv函数或send函数)来接收或发送数据。数据的读取或发送过程可以在事件处理函数中完成。
⑦继续循环:
处理完事件后,窗口过程返回,并再次进入消息循环等待下一个事件的到来。循环将一直进行,直至应用程序退出或调用WSACleanup函数释放资源。
标签:SOCKET,int,模型,WSAAsyncSelect,wndclass,程序设计,接字,NULL,质数 From: https://www.cnblogs.com/refrain-yu/p/17861269.html