refrain-yu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

基于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 //判断质数的函数1:
 11 string calculatePrimeNumbers(int start, int end);
 12 //判断质数的函数2:
 13 string calculatePrimeNumbers_2(int start, int end);
 14 
 15 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
 16 int main()
 17 {
 18     char szClassName[] = "MainWClass";
 19     WNDCLASSEX wndclass;
 20     
 21     //用描述主窗口的参数填充WNDCLASSEX结构
 22     wndclass.cbSize = sizeof(wndclass);
 23     wndclass.style = CS_HREDRAW | CS_VREDRAW;
 24     wndclass.lpfnWndProc = WindowProc;
 25     wndclass.cbClsExtra = 0;
 26     wndclass.cbWndExtra = 0;
 27     wndclass.hInstance = NULL;
 28     wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
 29     wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW);
 30     wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
 31     wndclass.lpszMenuName = NULL;
 32     wndclass.lpszClassName = szClassName;
 33     wndclass.hIconSm = NULL;
 34     ::RegisterClassEx(&wndclass);
 35 
 36 
 37     //创建主窗口
 38     HWND hWnd = ::CreateWindowEx(
 39         0,
 40         szClassName,
 41         "",
 42         WS_OVERLAPPEDWINDOW,
 43         CW_USEDEFAULT,
 44         CW_USEDEFAULT,
 45         CW_USEDEFAULT,
 46         CW_USEDEFAULT,
 47         NULL,
 48         NULL,
 49         NULL,
 50         NULL);
 51     if (hWnd == NULL)
 52     {
 53         ::MessageBox(NULL, "创建窗口出错!", "error", MB_OK);
 54         return -1;
 55     }
 56     USHORT nPort = 4567;
 57 
 58     //创建监听套接字
 59     SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 60     sockaddr_in sin;
 61     sin.sin_family = AF_INET;
 62     sin.sin_port = htons(nPort);
 63     sin.sin_addr.S_un.S_addr = INADDR_ANY;
 64 
 65     //绑定套接字到本地机器
 66     if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
 67     {
 68         printf("Failed bind()\n");
 69         return -1;
 70     }
 71 
 72     //将套接字设为窗口通知消息类型
 73     ::WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
 74     ::listen(sListen, 5);
 75 
 76     //从消息队列中取出消息
 77     MSG msg;
 78     while (::GetMessage(&msg, NULL, 0, 0))
 79     {
 80         ::TranslateMessage(&msg);//转化键盘消息 : 将键盘消息翻译成对应的字符消息
 81         ::DispatchMessage(&msg);//将消息发送到相应的窗口函数进行处理
 82     }
 83     return msg.wParam; //当GetMessage返回0时的程序结束
 84 
 85 }
 86 
 87 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 88 {
 89     switch (uMsg)
 90     {
 91     case WM_SOCKET:
 92     {
 93         SOCKET s = wParam;//取得有事件发生的套接字句柄
 94         //查看是否出错
 95         if (WSAGETSELECTERROR(lParam))
 96         {
 97             ::closesocket(s);
 98             return 0;
 99         }
100         //处理发生的事件
101         switch (WSAGETSELECTEVENT(lParam))
102         {
103         case FD_ACCEPT: //监听中的套接字检测到有连接进入
104         {
105             SOCKET client = ::accept(s, NULL, NULL);
106             ::WSAAsyncSelect(client, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);//sListen client
107         }
108         break;
109         case FD_WRITE:
110         {}
111         break;
112         case FD_READ:
113         {
114             char szText[1024] = { 0 };
115             if (::recv(s, szText, 1024, 0) == -1)
116                 ::closesocket(s);
117             else
118             {
119                 printf("接收到消息: %s", szText);
120 
121                 /*补充:向客户端发送数据*/
122                 int start, end;
123                 char response[256];
124                 string calString;
125                 if (sscanf(szText, "请输出%d到%d内的所有质数!", &start, &end) == 2) {//请输出从1到1000内所有的质数
126                     //string calString = calculatePrimeNumbers(start, end); //基于嵌套循环的质数判断
127                     string calString = calculatePrimeNumbers_2(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 string calculatePrimeNumbers(int start, int end)
154 {
155     stringstream calResult;
156     for (int i = start; i <= end; i++)
157     {
158         if (i == 1)
159             continue; //1既不是质数也不是合数
160         bool isPrime = true;
161         for (int j = 2; j < i; j++)
162         {
163             if (i % j == 0)
164             {
165                 isPrime = false;
166                 break;
167             }
168         }
169         if (isPrime)
170         {
171             calResult << i << " ";
172         }
173     }
174     return calResult.str();
175 }
176 
177 string calculatePrimeNumbers_2(int start, int end) { //埃及筛法
178     stringstream calResult;
179     bool* sieve = new bool[end + 1];
180     for (int i = 0; i <= end; i++) {
181         sieve[i] = true;
182     }
183     for (int p = 2; p <= sqrt(end); p++) {
184         if (sieve[p]) {
185             for (int i = p * p; i <= end; i += p) {
186                 sieve[i] = false;
187             }
188         }
189     }
190     
191     for (int num = start; num <= end; num++) {
192         if (num >= 2 && sieve[num] == true) {
193             calResult << num << " ";
194         }
195     }
196     return calResult.str();
197 }

 

③客户端代码:

 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 = new bool[end + 1];
 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函数释放资源。

posted on 2023-11-28 11:20  ref·雨  阅读(27)  评论(0编辑  收藏  举报