哈工大计网实验一

这是一个C++编写的HTTP代理服务器程序,它初始化套接字,监听指定端口,接收客户端请求,解析HTTP头部,处理缓存,过滤非法和钓鱼网站,并能与目标服务器建立连接,转发数据。程序包含了套接字操作,线程处理以及HTTP协议解析等功能。
该文章已生成可运行项目,

鼠鼠我呀,做实验的时候的虽然也是找源码,但是这个🐎是这个报错,那个不对,这改改那看看,所以鼠鼠我直接把我调试好的代码直接发出来。

#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 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"; //钓鱼主机名

char InvalidIP[] = "127.0.0.1";//屏蔽的用户IP
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;//设置代理窗口
int addrlen = 32;

//缓存相关参数
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设置成无效套接字
    SOCKADDR_IN acceptAddr; //自定义变量,用来获得用户的IP
    ProxyParam* lpProxyParam;
    HANDLE hThread;
    DWORD dwThreadID;//unsigned long,无符号32位整型
    //代理服务器不断监听
    while (TRUE) {
        acceptSocket = accept(ProxyServer, (SOCKADDR*)&acceptAddr, &addrlen);
        printf("===============用户IP地址为%s===============\n", inet_ntoa(acceptAddr.sin_addr));
        if (strcmp(inet_ntoa(acceptAddr.sin_addr), InvalidIP) == 0)//屏蔽用户ip
        {
            printf("\n\n===============该用户已经被屏蔽===============\n\n");
        }
        else {
            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 = 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);     //解析HTTP报文头部
    //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网站被拒绝访问\n", 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\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);
    fread(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");
    }
}

//检测主机返回的状态码,如果是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");
        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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值