15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > (计算机网络实验1)HTTP 代理服务器的设计与实现

(计算机网络实验1)HTTP 代理服务器的设计与实现

时间:2023-09-01 03:24:01 | 来源:网站运营

时间:2023-09-01 03:24:01 来源:网站运营

(计算机网络实验1)HTTP 代理服务器的设计与实现:@toc

#include <stdio.h>#include <Windows.h>#include <process.h>#include <string.h>#include <tchar.h> #pragma comment(lib,"Ws2_32.lib")#define MAXSIZE 65507 //发送数据报文的最大长度#define HTTP_PORT 80 //http 服务器端口//Http重要头部数据struct HttpHeader { char method[4]; // POST 或者 GET,注意有些为 CONNECT,本实验暂不考虑 char url[1024]; // 请求的 url char host[1024]; // 目标主机 char cookie[1024 * 10]; //cookie HttpHeader() { ZeroMemory(this, sizeof(HttpHeader)); };};//禁止访问的网站和钓鱼网站是否可以输入选择char Invilid_web[1024] = "http://today.hit.edu.cn/";//不允许访问的网站//char Invilid_web[1024] = "https://today.edu.cn/";char Target_web[1024] = "http://ids-hit-edu-cn.ivpn.hit.edu.cn";//统一身份认证网站char Fish_web[1024] = "http://jwes.hit.edu.cn/";//钓鱼网站char Fish_host[1024] = "jwes.hit.edu.cn"; //钓鱼主机名BOOL InitSocket();void ParseHttpHead(char* buffer, HttpHeader* httpHeader);BOOL ConnectToServer(SOCKET* serverSocket, char* host);unsigned int __stdcall ProxyThread(LPVOID lpParameter);//代理相关参数SOCKET ProxyServer;//代理服务器sockaddr_in ProxyServerAddr;//代理服务器地址const int ProxyPort = 10240;//设置代理窗口//缓存相关参数boolean haveCache = false;boolean needCache = true;void getfileDate(FILE* in, char* tempDate);void sendnewHTTP(char* buffer, char* datestring);void makeFilename(char* url, char* filename);void storefileCache(char* buffer, char* url);void checkfileCache(char* buffer, char* filename);//由于新的连接都使用新线程进行处理,对线程的频繁的创建和销毁特别浪费资源//可以使用线程池技术提高服务器效率//const int ProxyThreadMaxNum = 20;//HANDLE ProxyThreadHandle[ProxyThreadMaxNum] = {0};//DWORD ProxyThreadDW[ProxyThreadMaxNum] = {0};;;struct ProxyParam { SOCKET clientSocket; SOCKET serverSocket;};//主程序int _tmain(int argc, _TCHAR* argv[]){ printf("代理服务器正在启动/n"); printf("初始化.../n"); if (!InitSocket()) { printf("socket 初始化失败/n"); return -1; } printf("代理服务器正在运行,监听端口 %d/n", ProxyPort); SOCKET acceptSocket = INVALID_SOCKET; //把socket设置成无效套接字 ProxyParam* lpProxyParam; HANDLE hThread; DWORD dwThreadID;//unsigned long,无符号32位整型 //代理服务器不断监听 while (TRUE) { acceptSocket = accept(ProxyServer, NULL, NULL); lpProxyParam = new ProxyParam;//新的客户端和服务器端 if (lpProxyParam == NULL) { continue; } lpProxyParam->clientSocket = acceptSocket; //线程开始 hThread = (HANDLE)_beginthreadex(NULL, 0, &ProxyThread, (LPVOID)lpProxyParam, 0, 0); CloseHandle(hThread); Sleep(2000); } closesocket(ProxyServer); WSACleanup(); return 0;}//************************************// Method: InitSocket// FullName: InitSocket// Access: public// Returns: BOOL// Qualifier: 初始化套接字//************************************BOOL InitSocket() { //加载套接字库(必须) WORD wVersionRequested; WSADATA wsaData; //套接字加载时错误提示 int err; //版本 2.2 wVersionRequested = MAKEWORD(2, 2); //加载 dll 文件 Scoket 库 err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { //找不到 winsock.dll printf("加载 winsock 失败,错误代码为: %d/n", WSAGetLastError()); return FALSE; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("不能找到正确的 winsock 版本/n"); WSACleanup(); return FALSE; } //创建套接字 ProxyServer = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == ProxyServer) { printf("创建套接字失败,错误代码为: %d/n", WSAGetLastError()); return FALSE; } ProxyServerAddr.sin_family = AF_INET;//地址族 ProxyServerAddr.sin_port = htons(ProxyPort); // 设置代理端口 //ProxyServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;//设置IP地址 ProxyServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//设置IP地址 //bind绑定 if (bind(ProxyServer, (SOCKADDR*)& ProxyServerAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) { printf("绑定套接字失败/n"); return FALSE; } //listen监听,SOMAXCONN由系统来决定请求队列长度 if (listen(ProxyServer, SOMAXCONN) == SOCKET_ERROR) { printf("监听端口%d 失败", ProxyPort); return FALSE; } return TRUE;}//************************************// Method: ProxyThread// FullName: ProxyThread// Access: public// Returns: unsigned int __stdcall// Qualifier: 线程执行函数// Parameter: LPVOID lpParameter//************************************unsigned int __stdcall ProxyThread(LPVOID lpParameter) { char Buffer[MAXSIZE]; char* CacheBuffer; char* DateBuffer; char filename[100] = { 0 }; _Post_ _Notnull_ FILE* in; char date_str[30]; //保存字段Date的值 ZeroMemory(Buffer, MAXSIZE); SOCKADDR_IN clientAddr; int length = sizeof(SOCKADDR_IN); int recvSize; int ret; FILE* fp; //第一次接收客户端请求,将该请求缓存下来,存到本地文件中 recvSize = recv(((ProxyParam*)lpParameter)->clientSocket, Buffer, MAXSIZE, 0); HttpHeader* httpHeader = new HttpHeader(); if (recvSize <= 0) { goto error; } CacheBuffer = new char[recvSize + 1]; ZeroMemory(CacheBuffer, recvSize + 1); memcpy(CacheBuffer, Buffer, recvSize); ParseHttpHead(CacheBuffer, httpHeader); //printf("HTTP请求报文如下:/n%s/n", Buffer); ZeroMemory(date_str, 30); printf("httpHeader->url : %s/n", httpHeader->url); makeFilename(httpHeader->url, filename); //printf("filename是 %s/n", filename); if ((fopen_s(&in, filename, "r")) == 0) { printf("/n有缓存/n"); //fread_s(fileBuffer, MAXSIZE, sizeof(char), MAXSIZE, in); getfileDate(in,date_str);//得到本地缓存文件中的日期date_str fclose(in); //printf("date_str:%s/n", date_str); sendnewHTTP(Buffer, date_str); //向服务器发送一个请求,该请求需要增加 “If-Modified-Since” 字段 //服务器通过对比时间来判断缓存是否过期 haveCache = TRUE; } //printf("httpHeader的url是%s,不允许访问的是%s/n", httpHeader->url, Invilid_web); //网站过滤功能 if (strcmp(httpHeader->url, Invilid_web) == 0) { printf("%s网站被拒绝访问", Invilid_web); goto error; } //添加钓鱼功能 if (strstr(httpHeader->url, Target_web) != NULL) { printf("%s网站钓鱼成功,被转移至%s/n", Target_web, Fish_web); memcpy(httpHeader->host, Fish_host, strlen(Fish_host) + 1);//替换主机名 memcpy(httpHeader->url, Fish_web, strlen(Fish_web) + 1);//替换url } //此时数据报存储在了httpHeader中 delete CacheBuffer; //连接发送数据报所在的服务器 if (!ConnectToServer(&((ProxyParam*)lpParameter)->serverSocket, httpHeader->host)) { printf("连接目的服务器失败/n"); goto error; } printf("代理连接主机 %s 成功/n", httpHeader->host); //将客户端发送的 HTTP 数据报文直接转发给目标服务器 ret = send(((ProxyParam*)lpParameter)->serverSocket, Buffer, strlen(Buffer) + 1, 0); //等待目标服务器返回数据 recvSize = recv(((ProxyParam*)lpParameter)->serverSocket, Buffer, MAXSIZE, 0); if (recvSize <= 0) { goto error; } //printf("服务器响应报文如下:/n%s/n", Buffer); if (haveCache == true) { checkfileCache(Buffer, httpHeader->url); } if (needCache == true) { storefileCache(Buffer, httpHeader->url); } //将目标服务器返回的数据直接转发给客户端 ret = send(((ProxyParam*)lpParameter)->clientSocket, Buffer, sizeof(Buffer), 0); //错误处理error: printf("关闭套接字/n"); Sleep(200); closesocket(((ProxyParam*)lpParameter)->clientSocket); closesocket(((ProxyParam*)lpParameter)->serverSocket); delete lpParameter; _endthreadex(0); //终止线程 return 0;}//************************************// Method: ParseHttpHead// FullName: ParseHttpHead// Access: public// Returns: void// Qualifier: 解析 TCP 报文中的 HTTP 头部// Parameter: char * buffer// Parameter: HttpHeader * httpHeader//************************************void ParseHttpHead(char* buffer, HttpHeader* httpHeader) { char* p; char* ptr; const char* delim = "/r/n"; p = strtok_s(buffer, delim, &ptr);//提取第一行 printf("%s/n", p); if (p[0] == 'G') {//GET 方式 memcpy(httpHeader->method, "GET", 3); memcpy(httpHeader->url, &p[4], strlen(p) - 13); } else if (p[0] == 'P') {//POST 方式 memcpy(httpHeader->method, "POST", 4); memcpy(httpHeader->url, &p[5], strlen(p) - 14); } printf("%s/n", httpHeader->url); p = strtok_s(NULL, delim, &ptr); while (p) { switch (p[0]) { case 'H'://Host memcpy(httpHeader->host, &p[6], strlen(p) - 6); break; case 'C'://Cookie if (strlen(p) > 8) { char header[8]; ZeroMemory(header, sizeof(header)); memcpy(header, p, 6); if (!strcmp(header, "Cookie")) { memcpy(httpHeader->cookie, &p[8], strlen(p) - 8); } } break; default: break; } p = strtok_s(NULL, delim, &ptr); }}//************************************// Method: ConnectToServer// FullName: ConnectToServer// Access: public// Returns: BOOL// Qualifier: 根据主机创建目标服务器套接字,并连接// Parameter: SOCKET * serverSocket// Parameter: char * host//************************************BOOL ConnectToServer(SOCKET* serverSocket, char* host) { sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(HTTP_PORT); HOSTENT* hostent = gethostbyname(host); if (!hostent) { return FALSE; } in_addr Inaddr = *((in_addr*)* hostent->h_addr_list); serverAddr.sin_addr.s_addr = inet_addr(inet_ntoa(Inaddr)); *serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (*serverSocket == INVALID_SOCKET) { return FALSE; } if (connect(*serverSocket, (SOCKADDR*)& serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { closesocket(*serverSocket); return FALSE; } return TRUE;}//访问本地文件,获取本地缓存中的日期void getfileDate(FILE* in, char* tempDate) { char field[5] = "Date"; char* p, * ptr, temp[5]; char buffer[MAXSIZE]; ZeroMemory(buffer, MAXSIZE); fwrite(buffer, sizeof(char), MAXSIZE, in); const char* delim = "/r/n";//换行符 ZeroMemory(temp, 5); p = strtok_s(buffer, delim, &ptr); int len = strlen(field) + 2; while (p) { if (strstr(p, field) != NULL) {//调用strstr后指针会指向匹配剩余的第一个字符 memcpy(tempDate, &p[len], strlen(p) - len); return; } p = strtok_s(NULL, delim, &ptr); }}//改造HTTP请求报文void sendnewHTTP(char* buffer, char* datestring) { const char* field = "Host"; const char* newfield = "If-Modified-Since: "; //const char *delim = "/r/n"; char temp[MAXSIZE]; ZeroMemory(temp, MAXSIZE); char* pos = strstr(buffer, field);//获取请求报文段中Host后的部分信息 int i = 0; for (i = 0; i < strlen(pos); i++) { temp[i] = pos[i];//将pos复制给temp } *pos = '/0'; while (*newfield != '/0') { //插入If-Modified-Since字段 *pos++ = *newfield++; } while (*datestring != '/0') {//插入对象文件的最新被修改时间 *pos++ = *datestring++; } *pos++ = '/r'; *pos++ = '/n'; for (i = 0; i < strlen(temp); i++) { *pos++ = temp[i]; }}//根据url构造文件名void makeFilename(char* url, char* filename) { while (*url != '/0') { if ('a' <= *url && *url <= 'z') { *filename++ = *url; } url++; } strcat_s(filename, strlen(filename) + 9, "110.txt");}//检测主机返回的状态码,如果是200则本地获取缓存void storefileCache(char* buffer, char* url) { char* p, * ptr, tempBuffer[MAXSIZE + 1]; //num中是状态码 const char* delim = "/r/n"; ZeroMemory(tempBuffer, MAXSIZE + 1); memcpy(tempBuffer, buffer, strlen(buffer)); p = strtok_s(tempBuffer, delim, &ptr);//提取第一行 //printf("tempbuffer = %s/n", p); if (strstr(tempBuffer, "200") != NULL) { //状态码是200时缓存 char filename[100] = { 0 }; makeFilename(url, filename); printf("filename : %s/n", filename); FILE* out; fopen_s(&out, filename, "w+"); fwrite(buffer, sizeof(char), strlen(buffer), out); fclose(out); printf("/n===================网页已经被缓存==================/n/n"); }}//检测主机返回的状态码,如果是304则从本地获取缓存进行转发,否则需要更新缓存void checkfileCache(char* buffer, char* filename) { char* p, * ptr, tempBuffer[MAXSIZE + 1]; const char* delim = "/r/n"; ZeroMemory(tempBuffer, MAXSIZE + 1); memcpy(tempBuffer, buffer, strlen(buffer)); p = strtok_s(tempBuffer, delim, &ptr);//提取状态码所在行 //主机返回的报文中的状态码为304时返回已缓存的内容 if (strstr(p, "304") != NULL) { printf("/n=================从本机获得缓存====================/n/n"); ZeroMemory(buffer, strlen(buffer)); FILE* in = NULL; if ((fopen_s(&in, filename, "r")) == 0) { fread(buffer, sizeof(char), MAXSIZE, in); fclose(in); } needCache = FALSE; }}

教训

  1. 不了解 fopen_s与fopen区别,如果不想用安全函数fopen_s,可以添加_CRT_SECURE_NO_WARNINGS(项目->属性->C/C++->预处理器->预处理器定义中添加_CRT_SECURE_NO_WARNINGS)
  2. 不了解 strtok_s 与strtok
  3. 文件路径无效(例如.110txt),filename,产生报错: Debug assertion failed. Program:xxxxxxxxx Line xxx Expression:(stream!=NULL)
  4. 目标服务器屡次连接失败(可能是校园网断了……)

参考知识

代理服务器定义

允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。如图所示,为普通 Web 应用通信方式与采用代理服务器的通信方式的对比。

在这里插入图片描述



代理服务器原理

代理服务器在指定端口(例如 8080) 监听浏览器的访问请求(需要在客户端浏览器进行相应的设置), 接收到浏览器对远程网站的浏览请求时,代理服务器开始在代理服务器的缓存中检索 URL 对应的对象(网页、图像等对象),找到对象文件后, 提取该对象文件的最新被修改时间; 代理服务器程序在客户的请求报文首部插入,并向原 Web 服务器转发修改后的请求报文。 如果代理服务器没有该对象的缓存,则会直接向原服务器转发请求报文, 并将原服务器返回的响应直接转发给客户端,同时将对象缓存到代理服务器中。 代理服务器程序会根据缓存的时间、大小和提取记录等对缓存进行清理。本实验需实现一个简单的 HTTP 代理服务器, 可以分为两个步骤:(首先请设置浏览器开启本地代理,注意设置代理端口与代理服务器监听端口保持一致)。

单用户代理服务器

单用户的简单代理服务器可以设计为一个非并发的循环服务器。 首先, 代理服务器创建 HTTP 代理服务的 TCP 主套接字, 通过该主套接字监听等待客户端的连接请求。 当客户端连接之后, 读取客户端的 HTTP请求报文, 通过请求行中的 URL, 解析客户期望访问的原服务器 IP 地址;创建访问原(目标)服务器的 TCP 套接字,将 HTTP 请求报文转发给目标服务器,接收目标服务器的响应报文,当收到响应报文之后, 将响应报文转发给客户端, 最后关闭套接字, 等待下一次连接。

多用户代理服务器

多用户的简单代理服务器可以实现为一个多线程并发服务器。 首先,代理服务器创建 HTTP 代理服务的 TCP 主套接字, 通过该主套接字监听等待客户端的连接请求。 当客户端连接之后, 创建一个子线程,由子线程执行上述一对一的代理过程, 服务结束之后子线程终止。 与此同时,主线程继续接受下一个客户的代理服务。

实验目的

熟悉并掌握 Socket 网络编程的过程与技术;深入理解 HTTP协议,掌握 HTTP 代理服务器的基本工作原理;掌握 HTTP 代理服务器设计与编程实现的基本技能。

实验内容

  1. 设计并实现一个基本 HTTP 代理服务器。 要求在指定端口(例如8080) 接收来自客户的 HTTP 请求并且根据其中的 URL 地址访问该地址所指向的 HTTP 服务器(原服务器), 接收 HTTP 服务器的响应报文,并将响应报文转发给对应的客户进行浏览。
  2. 设计并实现一个支持 Cache 功能的 HTTP 代理服务器。 要求能缓存原服务器响应的对象,并能够通过修改请求报文(添加 if-modified-since头行),向原服务器确认缓存对象是否是最新版本。(选作内容,加分项目,可以当堂完成或课下完成)
  3. 扩展 HTTP 代理服务器,支持如下功能: a) 网站过滤:允许/不允许访问某些网站; b) 用户过滤:支持/不支持某些用户访问外部网站; c) 网站引导:将用户对某个网站的访问引导至一个模拟网站(钓鱼)。

实验过程

设置浏览器代理

以IE浏览器设置为例:打开浏览器工具浏览器选项——连接——局域网设置——代理服务器。设置地址为127.0.0.1,端口号为10240。

在这里插入图片描述



实现一个基本的HTTP代理服务器

HTTP代理服务器用于一个网络终端(一般为客户端)通过代理服务与另一个网络终端(一般为服务器)进行非直接的连接。设计的流程图如下:

在这里插入图片描述
(1) InitSocket()函数 功能:初始化套接字 (2) ProxyThread()函数 功能:线程执行函数 (3) ParseHttpHead()函数 功能:解析 TCP 报文中的 HTTP 头部 (4) ConnectToServer()函数 功能:根据主机创建目标服务器套接字,并连接

Cache功能

  1. 客户端第一次请求服务器中的数据时,代理服务器将该请求返回的响应缓存下来,存到本地的文件下。
  2. 当客户端第二次访问该数据时,代理服务器检查本地是否有该请求的响应,如果没有,则继续缓存;如果有,则通过向服务器发送一个请求,对比最后修改时间来判断缓存是否过期,如果服务器返回状态码304,则没过期;如果服务器返回状态码200,则缓存过期,则更新本地缓存
  3. 相应函数 (1)getfileDate()函数 功能:访问本地文件,获取本地缓存中的日期 (2)sendnewHTTP()函数 功能:修改请求报文,添加 if-modified-since头行 先查看请求报文格式: (3)checkfileCache()函数 功能:检测主机返回的状态码,如果是304则从本地获取缓存进行转发,否则需要更新缓存 (4)storefileCache()函数 功能:检测主机返回的状态码,如果是200则本地获取缓存

实现扩展功能

网站过滤

首先设置不允许访问网站的url

在这里插入图片描述
在处理客户端请求时,检查请求消息中的url是否被允许访问,如果不允许访问,则拒绝

在这里插入图片描述



用户过滤

将代理服务器的网络通信IP地址从INADDR_ANY更改为特定的某个IP地址,从而只有该IP地址能通过 代理服务器访问外部网站,其他IP均不能

在这里插入图片描述



网站引导

首先设置目标网站和相应的钓鱼网站和主机名

在这里插入图片描述
在处理客户端请求时,将请求消息中的url和host替换成事先设置好的模拟网站的url和host

在这里插入图片描述



实验结果

  1. 设计并实现一个基本 HTTP 代理服务器。
  2. 设计并实现一个支持 Cache 功能的 HTTP 代理服务器。
  3. 扩展 HTTP 代理服务器,支持如下功能: a) 网站过滤:允许/不允许访问某些网站; b) 用户过滤:支持/不支持某些用户访问外部网站; c) 网站引导:将用户对某个网站的访问引导至一个模拟网站(钓鱼)。

题目与源码

github代码链接

关键词:设计,实现,服务,网络,实验,代理,计算机

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭