《网络安全编程综合实训》报告
2023年 11月 28 日
目 录
目录
正文部分....................................................................................................................................................... 3
概述............................................................................................................................................................ 3
系统分析................................................................................................................................................... 3
系统实现................................................................................................................................................... 3
模块实现流程图................................................................................................................................ 4
各个模块实现的代码....................................................................................................................... 9
代码运行结果图.............................................................................................................................. 24
实验小结................................................................................................................................................ 32
正文部分
一.概述
这个银行排号系统是一个网络系统,由三个部分组成:一个服务器、一个取号程序、一个叫号程序和两个业务程序。其中服务器采用指定的TCPI/0复用模型,其它部分可以采用任意模型。这个系统的主要功能是为顾客提供便捷的银行排号服务,让顾客能够更加高效地办理业务,同时也能够提高银行的服务效率和工作效率。
具体来说,顾客可以通过取号程序获取一个唯一的排队号码,而业务程序则可以获取下一个可以办理的业务号码,两个业务程序可以同时运行,实现交叉测试。叫号程序则负责显示当前正在办理的业务号码,让顾客可以及时了解自己的排队情况。
这个银行排号系统的意义非常重要。首先,它可以有效地减少顾客排队的时间和等待的不安感,提高了顾客的满意度。其次,它可以帮助银行实现自动化、智能化的管理,提高了银行的工作效率和服务质量。最后,它也可以为银行提供更加全面、准确的数据分析,帮助银行更好地了解顾客需求和业务情况,进一步优化银行的服务和管理。
二.系统分析
- 系统有三个部分组成,分别包括:一个服务器,一个取号程序,一个叫号程序,两个业务程序。
- 服务器用指定的模型,其它部分可以采用任意模型
- 服务器功能
- 取号程序取号,要保证号码的唯一性。
- 业务程序取号
- 通知叫号程序叫号(即显示当前正在办业务号)
- 取号程序功能:通过服务器获取号码,并显示
- 业务程序功能:通过服务器获取下一个可以办业务号码,并显示。两个业务程序应同时运行,交叉测试。
- 叫号程序:显示当前正在办业务号
三.系统实现
(每个模块功能实现的流程图,各模块实现的代码及运行结果图)
每个模块功能实现的流程图:
以下为各个模块实现流程图:
服务器模块:
一直循环等待接收数据 |
判断接收是否成功 |
| |||||||||
| |||||||||
成功 失败
判断接收的数组第50位x |
连接失败 |
x>0 x<0
在处理的号码为X |
等待取号 |
创建一个新的缓冲区 -> 将"办理业务"复制到缓冲区中 -> 在缓冲区的第50位插入接收到数组第50位的值x -> 休眠20,000毫秒 -> 将缓冲区发送到服务器 |
|
取号程序模块
判断发送是否成功 |
| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
成功 失败
判断接收的数组是否接收成功x |
发送失败 |
result>0 result<0
接收成功 |
| ||||||||||
| ||||||||||
|
叫号程序模块
一直循环请求显示当前号码 |
判断接收是否成功 |
| |||||||||
| |||||||||
成功 失败
判断接收的数组是否成功 |
连接失败 |
result>0 result<0
| |||||||||||
| |||||||||||
| |||||||||||
|
服务器程序.cpp
- // server.cpp : Defines the entry point for the console application.
- // ws2_32.lib
- #include "stdafx.h"
- #include <stdio.h>
- #include <stdio.h>
- #include <winsock2.h>
- #include <stdlib.h>
- #include <map>
- #define MAX_CLIENTS 10 // 最大客户端连接数
- #define BUFFER_SIZE 1024 // 缓冲区大小
- // 在服务器端定义一个结构体来存储客户端的IP地址和端口号
- struct ClientInfo
- {
- char ip[16];
- int port;
- int socket; // 添加一个字段用于存储套接字
- };
- // 在服务器端定义一个数组来存储客户端信息
- struct ClientInfo client_info[MAX_CLIENTS];
- // std::mutex mtx_number;
- // 在服务器端接收到连接请求时,保存客户端的IP地址、端口号和套接字
- void saveClientInfo(struct sockaddr_in client_address, int client_socket, int index)
- {
- // inet_ntop(AF_INET, &(client_address.sin_addr), client_info[index].ip, INET_ADDRSTRLEN);
- strcpy(client_info[index].ip, inet_ntoa(client_address.sin_addr));
- client_info[index].port = ntohs(client_address.sin_port);
- client_info[index].socket = client_socket;
- }
- // 在服务器端根据客户端的索引查找对应的套接字
- int getClientSocket(int index)
- {
- return client_info[index].socket;
- }
- std::map<int, ClientInfo> client_map;
- int main()
- {
- WSADATA wsaData;
- SOCKET server_socket, client_sockets[MAX_CLIENTS], max_socket;
- struct sockaddr_in server_address, client_address;
- fd_set read_fds;
- int activity, i, valread, addrlen, client_socket, sd;
- char buffer[BUFFER_SIZE];
- int current_number = 0;
- int new_number = 0;
- int haoma[1000];
- int number = 0;
- bool flag = true;
- // 初始化Winsock库
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
- {
- printf("Winsock initialization failed.\n");
- return 1;
- }
- // 创建服务器套接字
- if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
- {
- printf("Socket creation failed.\n");
- return 1;
- }
- // 设置服务器地址和端口
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = INADDR_ANY;
- server_address.sin_port = htons(12345);
- // 绑定服务器套接字到指定地址和端口
- if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == SOCKET_ERROR)
- {
- printf("Socket bind failed.\n");
- return 1;
- }
- // 监听来自客户端的连接请求
- if (listen(server_socket, 5) == SOCKET_ERROR)
- {
- printf("Socket listen failed.\n");
- return 1;
- }
- printf("Server is listening on port 12345.\n");
- // 初始化客户端套接字数组
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- client_sockets[i] = 0;
- }
- addrlen = sizeof(client_address);
- std::map<int, ClientInfo> client_map;
- while (1)
- {
- // 清空可读套接字集合
- FD_ZERO(&read_fds);
- // 将服务器套接字添加到可读套接字集合
- FD_SET(server_socket, &read_fds);
- max_socket = server_socket;
- // 将客户端套接字添加到可读套接字集合
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- sd = client_sockets[i];
- // 如果客户端套接字有效,则添加到可读套接字集合
- if (sd > 0)
- {
- FD_SET(sd, &read_fds);
- }
- // 更新最大套接字值
- if (sd > max_socket)
- {
- max_socket = sd;
- }
- }
- // 使用select函数进行I/O复用
- activity = select(max_socket + 1, &read_fds, NULL, NULL, NULL);
- if (activity == SOCKET_ERROR)
- {
- printf("Select error.\n");
- return 1;
- }
- // 如果服务器套接字有可读数据,表示有新连接请求
- if (FD_ISSET(server_socket, &read_fds))
- {
- if ((client_socket = accept(server_socket, (struct sockaddr *)&client_address, &addrlen)) == INVALID_SOCKET)
- {
- printf("Accept error.\n");
- return 1;
- }
- printf("New connection from %s:%d.\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
- // 将新的客户端套接字添加到客户端套接字数组
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- if (client_sockets[i] == 0)
- {
- client_sockets[i] = client_socket;
- break;
- }
- }
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- ClientInfo new_client_info;
- saveClientInfo(client_address, client_socket, i);
- new_client_info = client_info[i]; // 获取刚保存的客户端信息
- client_map[client_socket] = new_client_info;
- }
- }
- // 处理客户端套接字的请求
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- sd = client_sockets[i];
- // 如果客户端套接字有可读数据,表示有请求到达
- if (FD_ISSET(sd, &read_fds))
- {
- if ((valread = recv(sd, buffer, BUFFER_SIZE, 0)) == SOCKET_ERROR)
- {
- printf("Receive error.\n");
- return 1;
- }
- // 处理客户端请求并发送响应
- if (client_map.find(sd) != client_map.end())
- {
- ClientInfo &client = client_map[sd];
- if (valread > 0)
- {
- char recv_buf[100];
- buffer[valread] = '\0';
- printf("Received request: %s\n", buffer);
- // 根据不同的请求进行处理和反馈
- /*
- //设置falg有2个作用
- //作用1 第一次得到取号动作的时候,这时就把号码数组里此时的值发送给业务程序1和2,他们竞争这次业务。
- //同时目的是让服务器和业务程序建立连接起来。因为如果不产生联系,服务器是不够凭空直接发送数据给业务程序的
- //并且我的业务程序始终是监听状态
- */
- if (strcmp(buffer, "取号") == 0)
- {
- char response[20];
- sprintf(response, "您的号码是%03d", new_number);
- send(sd, response, BUFFER_SIZE, 0);
- haoma[new_number] = new_number;
- new_number++;
- // printf("flag:%d\n",flag);
- if (flag)
- {
- char buf[100] = {0};
- memset(buf, 0, sizeof(buf));
- sprintf(&buf[50], "%d", haoma[number]);
- // send(b, buf, 100, 0);
- send(client_sockets[0], buf, 100, 0);
- send(client_sockets[1], buf, 100, 0);
- flag = FALSE;
- }
- // printf("number:%d\n",number);
- }
- /*
- //业务程序接收到对方,每次发送过来只需要取出头,则只拿“头部分就行”对方的此时处理的号码的值就是haoma[number]值
- 把此时的number值给到current_number,让叫号程序得到最新的处理号码值
- //判断就是等待取号,同时把此时的falg改为true 为的是在下次取号的时候再由取号程序去发送第一次数据,让服务器和业务程序建立连接
- //判断就是正在处理xx号码,然后number往后加1
- */
- // startnum=new_number;
- else if (strncmp(buffer, "办理业务", 12) == 0)
- {
- // printf("new_number为:%d\n",new_number);
- // saveClientInfo(client_address, sd,1);
- // int m = atoi(&buffer[50]);
- // int b=client_sockets[0];
- // new_number = 0;
- char buf[100] = {0};
- memset(buf, 0, sizeof(buf));
- sprintf(&buf[50], "%d", haoma[number]);
- send(client_sockets[0], buf, 100, 0);
- current_number = haoma[number];
- if (haoma[number] < 0)
- {
- printf("等待取号\n");
- flag = true;
- }
- else
- {
- printf("正在处理%d\n", haoma[number]);
- number = number + 1;
- }
- // number=number+1;
- // current_number=m;
- // send(a, response, BUFFER_SIZE, 0);
- // printf("number:%d\n",number);
- }
- else if (strncmp(buffer, "办理业务1", 13) == 0)
- {
- // printf("new_number为:%d\n",new_number);
- // saveClientInfo(client_address, sd,1);
- // int m = atoi(&buffer[50]);
- // int c=client_sockets[1];
- // printf("接收到业务程序发来,可以处理的下一个业务号码m的值为: %d\n", m);
- char buf[100] = {0};
- memset(buf, 0, sizeof(buf));
- sprintf(&buf[50], "%d", haoma[number]);
- send(client_sockets[1], buf, 100, 0);
- current_number = haoma[number];
- if (haoma[number] < 0)
- {
- printf("等待取号\n");
- flag = true;
- }
- else
- {
- printf("正在处理%d\n", haoma[number]);
- number = number + 1;
- }
- // number=number+1;
- // current_number=m;
- // send(a, response, BUFFER_SIZE, 0);
- // printf("number:%d\n",number);
- }
- else if (strcmp(buffer, "请求当前号码") == 0)
- {
- saveClientInfo(client_address, sd, 0);
- char response[20];
- int n = 0;
- if (current_number - 1 < 0)
- {
- sprintf(response, "等待取号\n");
- }
- else
- {
- sprintf(response, "当前号码是%03d\n", current_number);
- }
- send(sd, response, BUFFER_SIZE, 0);
- // printf("服务器当前号码为%d\n",current_number);
- }
- else if (strcmp(buffer, "退出") == 0)
- {
- send(sd, "退出成功", strlen("退出成功"), 0);
- closesocket(sd);
- client_sockets[i] = 0;
- client_map.erase(sd); // 从map中删除对应的客户端状态信息
- }
- else
- {
- send(sd, "无法识别的请求", strlen("无法识别的请求"), 0);
- }
- }
- }
- }
- }
- }
- // 关闭服务器套接字
- closesocket(server_socket);
- // 清理Winsock库资源
- WSACleanup();
- return 0;
- }
业务程序1.cpp:
- #include "stdafx.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <winsock2.h>
- #include <process.h>
- // 业务程序
- #define BUFFER_SIZE 1024 // 缓冲区大小
- int m = 0;
- int main()
- {
- WSADATA wsaData;
- SOCKET client_socket;
- struct sockaddr_in server_address;
- char buffer[BUFFER_SIZE];
- // 初始化Winsock库
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
- {
- printf("Winsock initialization failed.\n");
- return 1;
- }
- // 创建客户端套接字
- if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
- {
- printf("Socket creation failed.\n");
- return 1;
- }
- // 设置服务器地址和端口
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址
- server_address.sin_port = htons(12345); // 服务器端口号
- // 连接到服务器
- if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
- {
- printf("Connection failed.\n");
- return 1;
- }
- /*
- //业务程序只有服务器这里发送数据过来才会执行后面逻辑
- //先取出第50位的值 即发来正在处理的号码,打印出来
- //若小于0 则表示发送过来的haoma[number]还未赋值,则说明这个号码是未取出,打印”等待取号“
- */
- while (1)
- {
- int result=recv(client_socket, buffer, 100, 0);
- int x = atoi(&buffer[50]);
- if(x<0){
- printf("等待取号\n");
- }
- else{
- printf("在处理的号码为:%d\n",x);}
- if (result< 0)
- {
- printf("Receive response failed.\n");
- }
- else
- {
- char buf[100] = {0};
- memset(buf, 0, sizeof(buf));
- strcpy(buf, "办理业务");
- sprintf(&buf[50], "%d",x);
- Sleep(20000);
- int result = send(client_socket, buf, sizeof(buf), 0);
- if (result < 0)
- {
- printf("Send request failed.\n");
- return 1;
- }
- }
- }
- closesocket(client_socket);
- // 清理Winsock库资源
- WSACleanup();
- return 0;
- }
业务程序2.cpp:
- #include "stdafx.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <winsock2.h>
- // 业务程序
- #define BUFFER_SIZE 1024 // 缓冲区大小
- int main()
- {
- WSADATA wsaData;
- SOCKET client_socket;
- struct sockaddr_in server_address;
- char buffer[BUFFER_SIZE];
- // 初始化Winsock库
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
- {
- printf("Winsock initialization failed.\n");
- return 1;
- }
- // 创建客户端套接字
- if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
- {
- printf("Socket creation failed.\n");
- return 1;
- }
- // 设置服务器地址和端口
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址
- server_address.sin_port = htons(12345); // 服务器端口号
- // 连接到服务器
- if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
- {
- printf("Connection failed.\n");
- return 1;
- }
- while (1)
- {
- int result=recv(client_socket, buffer, 100, 0);
- // int x = atoi(&buffer[50]);
- // printf("在处理的号码为:%d\n",x);
- if (result < 0)
- {
- printf("Receive response failed.\n");
- }
- else
- {
- int x = atoi(&buffer[50]);
- if(x<0){
- printf("等待取号\n");
- }
- else{
- printf("在处理的号码为:%d\n",x);}
- char buf[100] = {0};
- memset(buf, 0, sizeof(buf));
- strcpy(buf, "办理业务1");
- sprintf(&buf[50], "%d",x);
- Sleep(20000);
- int result = send(client_socket, buf, sizeof(buf), 0);
- if (result < 0)
- {
- printf("Send request failed.\n");
- return 1;
- }
- }
- }
- closesocket(client_socket);
- // 清理Winsock库资源
- WSACleanup();
- return 0;
- }
叫号程序.cpp
- // 网络编程课程设计.cpp : Defines the entry point for the console application.
- //server服务器端
- //叫号
- #include "stdafx.h"
- #include <stdio.h>
- #include <winsock2.h>
- #define BUFFER_SIZE 1024 // 缓冲区大小
- int main() {
- WSADATA wsaData;
- SOCKET client_socket;
- struct sockaddr_in server_address;
- char buffer[BUFFER_SIZE];
- // 初始化Winsock库
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
- printf("Winsock initialization failed.\n");
- return 1;
- }
- // 创建客户端套接字
- if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
- printf("Socket creation failed.\n");
- return 1;
- }
- // 设置服务器地址和端口
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址
- server_address.sin_port = htons(12345); // 服务器端口号
- // 连接到服务器
- if (connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
- printf("Connection failed.\n");
- return 1;
- }
- // 循环获取当前号码
- while (1) {
- // 发送请求获取当前号码
- if (send(client_socket, "请求当前号码", strlen("请求当前号码"), 0) < 0) {
- printf("Send request failed.\n");
- return 1;
- }
- // 接收服务器的响应,并显示当前号码
- if (recv(client_socket, buffer, BUFFER_SIZE, 0) < 0) {
- printf("Receive response failed.\n");
- break;
- }
- else {
- printf("Current number: %s\n", buffer);
- }
- Sleep(6000);
- }
- // 关闭客户端套接字
- closesocket(client_socket);
- // 清理Winsock库资源
- WSACleanup();
- return 0;
- }
取号程序.cpp
- // 实验九基于事件模型的网络通信.cpp : Defines the entry point for the console application.
- //取号
- #include "stdafx.h"
- #include <stdio.h>
- #include <winsock2.h>
- #include <map>
- #define BUFFER_SIZE 1024 // 缓冲区大小
- int main() {
- WSADATA wsaData;
- SOCKET client_socket;
- struct sockaddr_in server_address;
- char buffer[BUFFER_SIZE];
- // 初始化Winsock库
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
- printf("Winsock initialization failed.\n");
- return 1;
- }
- // 创建客户端套接字
- if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
- printf("Socket creation failed.\n");
- return 1;
- }
- // 设置服务器地址和端口
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址
- server_address.sin_port = htons(12345); // 服务器端口号
- // 连接到服务器
- if (connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
- printf("Connection failed.\n");
- return 1;
- }
- // 循环取号
- // while(1){
- // 发送取号请求
- if (send(client_socket, "取号", strlen("取号"), 0) < 0) {
- printf("Send request failed.\n");
- return 1;
- }
- // 接收服务器的响应,并显示取到的号码
- if (recv(client_socket, buffer, BUFFER_SIZE, 0) < 0) {
- printf("Receive response failed.\n");
- }
- else {
- printf("Number received: %s\n", buffer);
- }
- // Sleep(8000);
- // }
- // 关闭客户端套接字
- closesocket(client_socket);
- // 清理Winsock库资源
- WSACleanup();
- return 0;
- }
运行结果图:
服务器程序:
1)启动服务器
2)连接到业务程序1
3)连接到业务程序2
4)连接到叫号程序,此时还未取号
5)启动取号程序,连接到取号程序
6)显示当前处理到的号码,交给业务程序交叉处理
7)业务程序处理完号码后,继续等待取号此时叫号程序也显示等待取号
8)处理后的各个程序结果
9)此时再次取号
10)业务程序再次交叉处理,同时叫号程序显示在处理的值
业务程序1:
1)启动,此时还未取号
2)业务程序处理过程
业务程序2:
1)启动,此时还未取号
2)业务程序2处理过程
叫号程序:
1)启动,此时还未取号
2)叫号程序显示过程
取号程序:
1)启动
2)一共取了6个号码
四.课程设计小结
银行叫号系统是一个典型的网络编程实验,它包括了一个服务器、一个取号程序、一个叫号程序以及两个业务程序。在这个系统中,服务器起着核心的管理和通知作用,取号程序负责向服务器请求号码并确保唯一性,业务程序负责获取下一个可办理业务的号码并显示给客户,而叫号程序则负责实时显示当前正在办理的业务号码。
在实验中我遇到了很多问题。最开始向老师请教了关于服务器的运行逻辑。发现服务器和客户端需要用类似协议报头的功能实现客户端发送过来的信息能够让服务器接收并分的清楚是哪个客户端发送过来的请求,处理逻辑后返回给相应的客户端。通过与老师的交流,我明确了服务器的核心功能,即确保号码的唯一性、处理取号程序和业务程序的请求,并通知叫号程序。
在之后的代码实现过程中,我还向老师请教了取号程序的运行逻辑。他发现取号程序需要通过服务器获取号码,并且需要保证号码的唯一性。在与老师的讨论中,我了解到取号程序需要与服务器进行通信,以确保号码的唯一性,同时及时获取新的号码并显示给客户。
另外,还向老师请教了关于业务程序的运行逻辑。他发现业务程序需要通过服务器获取下一个可办理业务的号码,并显示给客户。通过和老师的交流,我明白了业务程序需要与服务器进行交互,以获取下一个可办理业务的号码,并且需要与另一个业务程序进行交叉测试,及时改正了自己的代码,将交叉运行的处理逻辑放在了服务器中,而不是业务程序,降低了代码的逻辑复杂度和耦合度
最后,我还向老师请教了叫号程序的运行逻辑。最后通过自己的思考,想到了设置flag来实现判断号码的处理逻辑。
通过多次向老师请教,我逐渐理解了整个系统的运行逻辑,提高了自己的网络编程水平,实现了自己的程序的功能,并且在不断改代码和问代码的过程中,我自己的代码逻辑性和程序的理解能力都得到了非常大的提高。让我为以后工作中的代码设计能力,网络编程应用能力以及编程安全能力都有了非常大的帮助