首先你需要有C/C++語言基礎(chǔ),且有服務(wù)器、客戶端概念,如果你了解TCP或者HTTP協(xié)議的話,那么將會(huì)幫助你更快的學(xué)會(huì)如何搭" />
時(shí)間:2023-07-13 16:03:01 | 來源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-07-13 16:03:01 來源:網(wǎng)站運(yùn)營(yíng)
如何利用服務(wù)器搭建自己的個(gè)人網(wǎng)站:這篇教程主要是告訴大家如何利用TCP和HTTP協(xié)議來完成網(wǎng)站的搭建。<!DOCTYPE html><html lang="ch-ZH"><head> <meta charset="UTF-8"> <title>My Website</title> <link rel="icon" href="img/sad.png"> <link rel="stylesheet" href="index.css"></head><body> <div style="width: 100px;height:100px;"> </div> <span>HelloWorld</span> <div style="background-color: blue; border: 1px solid orange"> Hi.Im david </div></body></html>
你可以直接賦值該文件并保存為html形式,文件名保存為 index.html。#include <WinSock2.h>#include <Windows.h>int main(){ WSADATA wsaData; //在Windows之中創(chuàng)建套接字需要使用到的對(duì)象 HANDLE hComPort; //完成端口CP對(duì)象 SYSTEM_INFO sysInfo; //獲取系統(tǒng)信息 WSAEVENT wEvent; //重疊事件 /// 關(guān)于客戶端的信息與io相關(guān)的信息一定要是動(dòng)態(tài)分配的。否則在傳遞給其他線程的時(shí)候?qū)?huì)引用原本的地址且無法重新分配新的客戶端與io數(shù)據(jù) LPCLNTINFO pClntInfo; //保存客戶端信息 LP_IO_DATA pIoData; //保存著io相關(guān)的數(shù)據(jù) SOCKET servSock, clntSock; //服務(wù)器與客戶端套接字 SOCKADDR_IN servAddr, clntAddr; //服務(wù)器與客戶端地址 int clntAddrSz; //客戶端地址長(zhǎng)度 DWORD recvBytes = 0,flag = 0; //接收的數(shù)據(jù)大小和標(biāo)志 int port = 0; //端口號(hào)// std::ofstream connectLog("connectLog.txt"); //保存客戶端連接信息 //這是一個(gè)處理輸入的函數(shù),我們將會(huì)在之后講到。 getPortNumber(port); //處理輸入數(shù)據(jù)并獲取端口號(hào)。 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NULL) ErrorMsg("WSAStartup() error"); ///創(chuàng)建完后端口號(hào)。 hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); GetSystemInfo(&sysInfo); //獲取系統(tǒng)信息 ///創(chuàng)建多個(gè)線程來分離IO處理 for (int i = 0; i < sysInfo.dwNumberOfProcessors; ++i) _beginthreadex(NULL, 0, ClntHandle, (LPVOID)hComPort, 0, NULL); //開始線程處理。 servSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); //創(chuàng)建重疊套接字 ///錯(cuò)誤處理 if (servSock == INVALID_SOCKET) ErrorMsg("WSASocket() Error"); ///初始化IP地址和端口號(hào)。 memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; //IPV4協(xié)議 servAddr.sin_port = htons(port); //小端序轉(zhuǎn)位網(wǎng)路大端序 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自動(dòng)獲取本地IP地址 ///綁定服務(wù)器地址信息 if (bind(servSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) ErrorMsg("bind() error"); ///監(jiān)聽來自客戶端的連接請(qǐng)求。 500表示最大接代500個(gè)客戶端同時(shí)請(qǐng)求。 if (listen(servSock, 500) == SOCKET_ERROR) ErrorMsg("listen() error");clntAddrSz = sizeof(clntAddr); //獲取客戶端地址結(jié)構(gòu)的大小 std::cout << std::left; //設(shè)置左對(duì)齊 處理來自客戶端的連接請(qǐng)求 while (1) { clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &clntAddrSz); wEvent = WSACreateEvent(); //存放事件句柄 if (clntSock == INVALID_SOCKET) //處理連接請(qǐng)求出錯(cuò) { ErrorMsg("accept() error"); } ///動(dòng)態(tài)分配內(nèi)存處理客戶端的連接 pClntInfo = (LPCLNTINFO)malloc(sizeof(CLNTINFO)); pClntInfo->hClntSock = clntSock; memcpy(&(pClntInfo->hClntAddr), &clntAddr, clntAddrSz); //復(fù)制客戶端套接字的地址信息 /// 建立套接字到完后端口的連接 CreateIoCompletionPort((HANDLE)clntSock, hComPort, (DWORD)pClntInfo, 0); //傳遞的四clntInfo整個(gè)結(jié)構(gòu)的地址。之后可以提取出來。 /// 動(dòng)態(tài)分配保存著數(shù)據(jù)傳輸信息的IO對(duì)象 pIoData = (LP_IO_DATA)malloc(sizeof(IO_DATA)); memset(&(pIoData->overlapped), 0, sizeof(OVERLAPPED)); pIoData->wsaBuf.buf = pIoData->buf; pIoData->wsaBuf.len = BUF_SIZE; ///接收客戶端信息 //當(dāng)客戶端未向服務(wù)器發(fā)送數(shù)據(jù)的時(shí)候,此時(shí)可以判定,客戶端需要請(qǐng)求服務(wù)器的數(shù)據(jù) if (WSARecv(clntSock, &(pIoData->wsaBuf), 1, (LPDWORD)&recvBytes, (LPDWORD)&flag, &(pIoData->overlapped), NULL) == SOCKET_ERROR) //注意此處傳遞的是ioData的整個(gè)結(jié)構(gòu)地址 { // if (WSAGetLastError() == WSA_IO_PENDING) //數(shù)據(jù)仍在接收中 // { // std::cout << "數(shù)據(jù)仍在接收中..." << std::endl; // } } } closesocket(servSock); WSACleanup(); return 0;}
其中相關(guān)數(shù)據(jù)結(jié)構(gòu)如下:/// 利用了結(jié)構(gòu)指針的地址就是結(jié)構(gòu)首成員地址該點(diǎn)來傳遞IO端口完成信息typedef struct{ SOCKET hClntSock; //客戶端套接字 SOCKADDR_IN hClntAddr; //客戶端IP地址}CLNTINFO, *LPCLNTINFO; //保存著客戶端套接字屬性typedef struct{ OVERLAPPED overlapped; //重疊屬性結(jié)構(gòu) WSABUF wsaBuf; //存放緩沖數(shù)據(jù)的結(jié)構(gòu) 主要存放了 待傳輸數(shù)據(jù)的大小和緩沖地址值 char buf[BUF_SIZE]; //存放數(shù)據(jù)的緩沖區(qū)}IO_DATA, *LP_IO_DATA; //保存著IO相關(guān)的數(shù)據(jù)
之前 提到的getPortNumber函數(shù)。 使用的C++實(shí)現(xiàn)輸入,如果你對(duì)C語言有了解可以換為C代碼模式。需要注意的是,我的代碼為C/C++共同實(shí)現(xiàn)。 該函數(shù)主要是保證用戶輸入流是正確的。void getPortNumber(int & port){ ///輸入端口號(hào)進(jìn)行監(jiān)聽 std::cout << "輸入有效的監(jiān)聽端口號(hào):"; ///確保輸入了有效的換行符 while (!(std::cin >> port)) { std::cin.clear(); //將cin中的錯(cuò)誤位置位 std::cin.ignore(224, '/n'); //輸入有誤時(shí)忽略輸入流中的緩沖信息.忽略最多224個(gè)字符,直到遇到了換行符為止 std::cout << "輸入的端口號(hào)有誤,重新輸入:"; }}
接下來需要分別講解在main函數(shù)之中調(diào)用的各個(gè)函數(shù):/// 建立套接字到完后端口的連接CreateIoCompletionPort((HANDLE)clntSock, hComPort, (DWORD)pClntInfo, 0);
該函數(shù)是一個(gè)關(guān)鍵點(diǎn),主要是將連接進(jìn)入的客戶端與我們的輸入輸出完成端口進(jìn)行綁定。WSARecv(clntSock, &(pIoData->wsaBuf), 1, (LPDWORD)&recvBytes, (LPDWORD)&flag, &(pIoData->overlapped), NULL);
之后每次當(dāng)客戶端發(fā)出請(qǐng)求數(shù)據(jù),都會(huì)在以下函數(shù)中處理unsigned WINAPI ClntHandle(LPVOID cp){ DWORD recvBytes = 0, flag = 0; LP_IO_DATA pIoData; //保存著IO對(duì)象的數(shù)據(jù)。 調(diào)用WSARecv傳遞的overlapped結(jié)構(gòu) LPCLNTINFO pClntInfo; //保存客戶端的相關(guān)信息。調(diào)用CreateIoCompletionPort傳遞的第三個(gè)參數(shù) SOCKET sock; //主要指的是客戶端的套接字 HANDLE hComPort = (HANDLE)cp; //CP對(duì)象 while (1) { ///確定IO完成狀態(tài) if (!GetQueuedCompletionStatus(hComPort, (LPDWORD)&recvBytes, (LPDWORD)&pClntInfo, (LPOVERLAPPED*)&pIoData, INFINITE)) { ///在客戶端請(qǐng)求數(shù)據(jù)的過程中如果退出了請(qǐng)求,那么此時(shí)該函數(shù)則會(huì)出現(xiàn)錯(cuò)誤,那么我們需要從錯(cuò)誤中恢復(fù)過來 std::cout << GetLastError() << std::endl; closesocket(pClntInfo->hClntSock); //關(guān)閉該套接字。 FreeData(&pIoData, &pClntInfo); //釋放動(dòng)態(tài)分配的內(nèi)存。 continue; } ///插入連接的客戶端 IP地址信息 connectCnt[inet_ntoa(pClntInfo->hClntAddr.sin_addr)]++; sock = pClntInfo->hClntSock; pIoData->wsaBuf.buf[recvBytes] = 0; if (recvBytes == 0) //接收數(shù)據(jù)為0的時(shí)候,此時(shí)沒有接收到數(shù)據(jù) { closesocket(sock); FreeData(&pIoData, &pClntInfo); continue; } // std::cout << "開始接收客戶端信息:" << pIoData->wsaBuf.buf << std::endl; std::string fileName; //文件名 std::string compleHead(pIoData->wsaBuf.buf, 100); //部分頭信息。最多獲取100個(gè)字符 char cntType[SMALL_SIZE]; int mFind = compleHead.find('/'); //查找請(qǐng)求方式 int fFind = compleHead.find("HTTP/"); //查找文件名 ///1. 當(dāng)非GET請(qǐng)求,不理會(huì)。 2.當(dāng)非HTTP/發(fā)出的請(qǐng)求不理會(huì) 3.當(dāng)請(qǐng)求格式錯(cuò)誤,不理會(huì) if (mFind == std::string::npos || fFind == std::string::npos || std::string(compleHead, 0, mFind - 1) != "GET") //請(qǐng)求方式錯(cuò)誤 { closesocket(sock); ExcptionRequst(&(pClntInfo->hClntAddr), pIoData->wsaBuf.buf); //發(fā)生異常連接 寫入文件日志中 FreeData(&pIoData, &pClntInfo); continue; } ///獲取完整文件名 在推斷了多種可能的情況下,此種情況下獲取文件名應(yīng)該是安全的 fileName = std::string(compleHead.cbegin() + mFind + 1, compleHead.cbegin() + fFind - 1); if (fileName.empty()) //當(dāng)文件名為空,表示請(qǐng)求的是首頁 { fileName = "index.html"; strcpy_s(cntType, "text/html"); } else { char * cs = GetContentType(fileName); if (!cs) //返回的指針為空的時(shí)候表示文件不存在后綴 { ErrorFile(sock); FreeData(&pIoData, &pClntInfo); continue; } strcpy_s(cntType, cs); //復(fù)制請(qǐng)求文件類型 } ///給客戶端發(fā)送數(shù)據(jù) SendDataFile(sock, fileName.c_str(), cntType, pIoData); ///釋放內(nèi)存 closesocket(sock); FreeData(&pIoData, &pClntInfo); // std::cout << "網(wǎng)頁數(shù)據(jù)發(fā)送完成..." << std::endl; } return 0;}
其中該函數(shù)使用到一個(gè)非常重要的函數(shù)如下:該函數(shù)負(fù)責(zé)向客戶端發(fā)送請(qǐng)求文件int SendDataFile(SOCKET sock, const char * fileName, const char *contType, LP_IO_DATA &pIoData){ std::ifstream inFile(fileName, std::fstream::binary); //以讀的方式打開文件 if (!inFile) //請(qǐng)求的文件不存在時(shí) { ErrorFile(sock); return -1; } ///傳輸回應(yīng)頭信息 ///r是回車(Carriage return) /n是換行 (New line) char protocol[] = "HTTP/1.1 200 OK/r/n"; //狀態(tài) char servName[] = "Server: TD Web server/r/n"; //服務(wù)器名字 char cntEncode[] = "Content-Encoding: gzip/r/n"; //壓縮方式 char transEncode[] = "Transfer-Encoding: chunked/r/n"; //傳輸編碼 char vary[] = "Vary: Accept-Encoding/r/n"; //接受編碼 char cntType[SMALL_SIZE]; //接收類型 char buf[BUF_SIZE]; //數(shù)據(jù) char end[] = "/r/n"; //結(jié)束符 char cntLen[SMALL_SIZE]; //內(nèi)容長(zhǎng)度 WSABUF wsaBuf; //存放數(shù)據(jù)緩沖 DWORD sendBytes = 0; //WSAEVENT wsaEvent = WSACreateEvent(); sprintf_s(cntType, "Content-type:%s/r/n", contType); ///向客戶端發(fā)送回應(yīng)頭信息 send(sock, protocol, strlen(protocol), 0); //傳遞狀態(tài)行 send(sock, servName, strlen(servName), 0); //傳遞消息頭的服務(wù)端名 send(sock, cntType, strlen(cntType), 0); //傳遞content-type /// 獲取文件長(zhǎng)度. 當(dāng)?shù)谝淮伟l(fā)送一個(gè)文件的時(shí)候,計(jì)算出該文件的大小值,之后每次只需要提取該文件對(duì)應(yīng)的大小即可。 if (fileSize.find(fileName) == fileSize.cend()) { fileSize[fileName] = MyGetFileSize(fileName); } LARGE_INTEGER size = fileSize[fileName]; ///向客戶端發(fā)送文件大小長(zhǎng)度 sprintf_s(cntLen, "Content-length:%lld/r/n", size.QuadPart); send(sock, cntLen, strlen(cntLen), 0); send(sock, end, strlen(end), 0); wsaBuf.buf = buf; wsaBuf.len = BUF_SIZE; ///讀取請(qǐng)求文件的數(shù)據(jù)并傳遞給客戶端 do { inFile.read(wsaBuf.buf, BUF_SIZE - 1); int cnt = inFile.gcount(); wsaBuf.buf[cnt] = 0; send(sock, buf, cnt, 0); } while (!inFile.eof()); inFile.close(); //關(guān)閉文件 return 0;}
該函數(shù)非常重要,注意觀察該函數(shù)中的回應(yīng)頭信息,頭信息必須按照HTTP協(xié)議的固定格式進(jìn)行發(fā)送。ARGE_INTEGER MyGetFileSize(const char * fileName){ ///創(chuàng)建文件句柄并依此來獲取到文件的大小信息。 對(duì)于操作系統(tǒng)來講,每次創(chuàng)建句柄和調(diào)用系統(tǒng)函數(shù)將會(huì)存在額外的開銷,絕對(duì)會(huì)影響性能 HANDLE hFile = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); LARGE_INTEGER size; ::GetFileSizeEx(hFile, &size); //獲取文件大小 return size;}
獲取請(qǐng)求文件的格式char * GetContentType(const std::string& fileName){ ///通過string的反向查找函數(shù)首先查找.符號(hào) size_t index = fileName.find_last_of('.'); if (index == std::string::npos || index == fileName.size()) //請(qǐng)求的文件無后綴名的話 返回一個(gè)空指針 return nullptr; std::string s(fileName, index + 1); //如果存在后綴的話,那么則檢查后綴 ///通過后綴名來進(jìn)行判斷請(qǐng)求數(shù)據(jù)的類型 std::string type = dataType[s]; if (type.empty()) return "text/plain"; //如果在關(guān)聯(lián)容器中沒有找到相對(duì)應(yīng)的類型 ///返回轉(zhuǎn)換為char*的類型 static char str[20]; strcpy_s(str, type.c_str()); return str;}
當(dāng)然其中也牽扯到其他的結(jié)構(gòu)。這里并不一一舉例,如果需要請(qǐng)發(fā)郵件給我。關(guān)鍵詞:利用,服務(wù)
客戶&案例
營(yíng)銷資訊
關(guān)于我們
客戶&案例
營(yíng)銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。