这篇教程主要是告诉大家如何使用TCP和HTTP协议来完成网站的搭建。首先你需要有C/C++语言基础,且有服务器、客户端观点,如果你相识TCP或者HTTP协议的话,那么将会资助你更快的学会如何搭建小我私家网站。该服务器使用的Windows中的IOCP模式来举行,我将put代码中最为重要的几个部门1.准备好HTML文件,也就是你所要公布的网页,可以是静态网页也可以是动态网页,为了更好的资助大家入门,我准备了一个最为轻便的html代码如下:
如果你不关系如何去自己书写代码完成搭建小我私家网站的,请点击这里。2.开始正式的编程。
我假设你有TCP/IP协议的相关知识,故不做太多先容。a)首先我们设置开发情况,如果你使用的VS的话,那么首先你需要几步来完成网络通信的情况搭建1.) 建立项目并打开项目属性。转到 链接器中的输入选项之中:在附加依赖项之中添加 ws2_32.lib; 该库为网络通信库2.) 引入头文件 #include
注意:
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NULL)ErrorMsg("WSAStartup() error");///建立完后端口号。hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);GetSystemInfo(&sysInfo);//获取系统信息///建立多个线程来分散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);//建立重叠套接字///错误处置惩罚if (servSock == INVALID_SOCKET)ErrorMsg("WSASocket() Error");///初始化IP地址和端口号。
memset(&servAddr, 0, sizeof(servAddr));servAddr.sin_family = AF_INET;//IPV4协议servAddr.sin_port = htons(port);//小端序转位网路大端序servAddr.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取当地IP地址///绑定服务器地址信息if (bind(servSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)ErrorMsg("bind() error");///监听来自客户端的毗连请求。500表现最大接代500个客户端同时请求。if (listen(servSock, 500) == SOCKET_ERROR)ErrorMsg("listen() error");clntAddrSz = sizeof(clntAddr);//获取客户端地址结构的巨细std::cout << std::left;//设置左对齐处置惩罚来自客户端的毗连请求while (1){clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &clntAddrSz);wEvent = WSACreateEvent();//存放事件句柄if (clntSock == INVALID_SOCKET)//处置惩罚毗连请求堕落{ErrorMsg("accept() error");}///动态分配内存处置惩罚客户端的毗连pClntInfo = (LPCLNTINFO)malloc(sizeof(CLNTINFO));pClntInfo->hClntSock = clntSock;memcpy(&(pClntInfo->hClntAddr), &clntAddr, clntAddrSz);//复制客户端套接字的地址信息/// 建设套接字到完后端口的毗连CreateIoCompletionPort((HANDLE)clntSock, hComPort, (DWORD)pClntInfo, 0);//通报的四clntInfo整个结构的地址。之后可以提取出来。
/// 动态分配生存着数据传输信息的IO工具pIoData = (LP_IO_DATA)malloc(sizeof(IO_DATA));memset(&(pIoData->overlapped), 0, sizeof(OVERLAPPED));pIoData->wsaBuf.buf = pIoData->buf;pIoData->wsaBuf.len = BUF_SIZE;///吸收客户端信息//当客户端未向服务器发送数据的时候,此时可以判断,客户端需要请求服务器的数据if (WSARecv(clntSock, &(pIoData->wsaBuf), 1, (LPDWORD)&recvBytes, (LPDWORD)&flag, &(pIoData->overlapped), NULL) == SOCKET_ERROR) //注意此处通报的是ioData的整个结构地址{//if (WSAGetLastError() == WSA_IO_PENDING)//数据仍在吸收中//{//std::cout << "数据仍在吸收中..." << std::endl;//}}}closesocket(servSock);WSACleanup();return 0;}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899其中相关数据结构如下:/// 使用了却构指针的地址就是结构首成员地址该点来通报IO端口完成信息typedef struct{SOCKET hClntSock;//客户端套接字SOCKADDR_IN hClntAddr;//客户端IP地址}CLNTINFO, *LPCLNTINFO;//生存着客户端套接字属性typedef struct{OVERLAPPED overlapped;//重叠属性结构WSABUF wsaBuf;//存放缓冲数据的结构 主要存放了 待传输数据的巨细缓和冲地址值char buf[BUF_SIZE];//存放数据的缓冲区}IO_DATA, *LP_IO_DATA;//生存着IO相关的数据12345678910111213之前 提到的getPortNumber函数。使用的C++实现输入,如果你对C语言有相识可以换为C代码模式。
需要注意的是,我的代码为C/C++配合实现。该函数主要是保证用户输入流是正确的。
void getPortNumber(int & port){///输入端口号举行监听std::cout << "输入有效的监听端口号:";///确保输入了有效的换行符while (!(std::cin >> port)){std::cin.clear();//将cin中的错误位置位std::cin.ignore(224, '\n');//输入有误时忽略输入流中的缓冲信息.忽略最多224个字符,直到遇到了换行符为止std::cout << "输入的端口号有误,重新输入:";}}12345678910111213接下来需要划分解说在main函数之中挪用的各个函数:/// 建设套接字到完后端口的毗连CreateIoCompletionPort((HANDLE)clntSock, hComPort, (DWORD)pClntInfo, 0);12该函数是一个关键点,主要是将毗连进入的客户端与我们的输入输出完成端口举行绑定。之后该套接字存在事件时,将会转到我们的线程处置惩罚函数之中。开始吸收来自客户端的请求数据。使用的是重叠IO无阻塞模式。
WSARecv(clntSock, &(pIoData->wsaBuf), 1, (LPDWORD)&recvBytes, (LPDWORD)&flag, &(pIoData->overlapped), NULL);1之后每次当客户端发出请求数据,都市在以下函数中处置惩罚unsigned WINAPI ClntHandle(LPVOID cp){DWORD recvBytes = 0, flag = 0;LP_IO_DATA pIoData;//生存着IO工具的数据。挪用WSARecv通报的overlapped结构LPCLNTINFO pClntInfo;//生存客户端的相关信息。
挪用CreateIoCompletionPort通报的第三个参数SOCKET sock;//主要指的是客户端的套接字HANDLE hComPort = (HANDLE)cp;//CP工具while (1){///确定IO完成状态if (!GetQueuedCompletionStatus(hComPort, (LPDWORD)&recvBytes, (LPDWORD)&pClntInfo, (LPOVERLAPPED*)&pIoData, INFINITE)){///在客户端请求数据的历程中如果退出了请求,那么此时该函数则会泛起错误,那么我们需要从错误中恢复过来std::cout << GetLastError() << std::endl;closesocket(pClntInfo->hClntSock);//关闭该套接字。FreeData(&pIoData, &pClntInfo);//释放动态分配的内存。continue;}///插入毗连的客户端 IP地址信息connectCnt[inet_ntoa(pClntInfo->hClntAddr.sin_addr)]++;sock = pClntInfo->hClntSock;pIoData->wsaBuf.buf[recvBytes] = 0;if (recvBytes == 0)//吸收数据为0的时候,此时没有吸收到数据{closesocket(sock);FreeData(&pIoData, &pClntInfo);continue;}//std::cout << "开始吸收客户端信息:" << pIoData->wsaBuf.buf << std::endl;std::string fileName;//文件名std::string compleHead(pIoData->wsaBuf.buf, 100);//部门头信息。
最多获取100个字符char cntType[SMALL_SIZE];int mFind = compleHead.find('/');//查找请求方式int fFind = compleHead.find("HTTP/");//查找文件名///1. 当非GET请求,不剖析。2.当非HTTP/发出的请求不剖析 3.当请求花样错误,不剖析if (mFind == std::string::npos || fFind == std::string::npos|| std::string(compleHead, 0, mFind - 1) != "GET")//请求方式错误{closesocket(sock);ExcptionRequst(&(pClntInfo->hClntAddr), pIoData->wsaBuf.buf);//发生异常毗连 写入文件日志中FreeData(&pIoData, &pClntInfo);continue;}///获取完整文件名 在推断了多种可能的情况下,此种情况下获取文件名应该是宁静的fileName = std::string(compleHead.cbegin() + mFind + 1, compleHead.cbegin() + fFind - 1);if (fileName.empty())//当文件名为空,表现请求的是首页{fileName = "index.html";strcpy_s(cntType, "text/html");}else{char * cs = GetContentType(fileName);if (!cs)//返回的指针为空的时候表现文件不存在后缀{ErrorFile(sock);FreeData(&pIoData, &pClntInfo);continue;}strcpy_s(cntType, cs);//复制请求文件类型}///给客户端发送数据SendDataFile(sock, fileName.c_str(), cntType, pIoData);///释放内存closesocket(sock);FreeData(&pIoData, &pClntInfo);//std::cout << "网页数据发送完成..." << std::endl;}return 0;}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586其中该函数使用到一个很是重要的函数如下:该函数卖力向客户端发送请求文件int SendDataFile(SOCKET sock, const char * fileName, const char *contType, LP_IO_DATA &pIoData){std::ifstream inFile(fileName, std::fstream::binary);//以读的方式打开文件if (!inFile)//请求的文件不存在时{ErrorFile(sock);return -1;}///传输回应头信息//\r是回车(Carriage return) \n是换行 (New line) char protocol[] = "HTTP/1.1 200 OK\r\n";//状态char servName[] = "Server: TD Web server\r\n";//服务器名字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];//数据char end[] = "\r\n";//竣事符char cntLen[SMALL_SIZE];//内容长度WSABUF wsaBuf;//存放数据缓冲DWORD sendBytes = 0;//WSAEVENT wsaEvent = WSACreateEvent();sprintf_s(cntType, "Content-type:%s\r\n", contType);///向客户端发送回应头信息send(sock, protocol, strlen(protocol), 0);//通报状态行send(sock, servName, strlen(servName), 0);//通报消息头的服务端名send(sock, cntType, strlen(cntType), 0);//通报content-type/// 获取文件长度. 当第一次发送一个文件的时候,盘算出该文件的巨细值,之后每次只需要提取该文件对应的巨细即可。
if (fileSize.find(fileName) == fileSize.cend()){fileSize[fileName] = MyGetFileSize(fileName);}LARGE_INTEGER size = fileSize[fileName];///向客户端发送文件巨细长度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;///读取请求文件的数据并通报给客户端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();//关闭文件return 0;}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364该函数很是重要,注意视察该函数中的回应头信息,头信息必须根据HTTP协议的牢固花样举行发送。其中又牵扯到了其他几个函数。划分是获取文件的巨细信息ARGE_INTEGER MyGetFileSize(const char * fileName){///建立文件句柄并依此来获取到文件的巨细信息。
对于操作系统来讲,每次建立句柄和挪用系统函数将会存在分外的开销,绝对会影响性能HANDLE hFile = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);LARGE_INTEGER size;::GetFileSizeEx(hFile, &size);//获取文件巨细return size;}12345678910获取请求文件的花样char * GetContentType(const std::string& fileName){///通过string的反向查找函数首先查找.符号size_t index = fileName.find_last_of('.');if (index == std::string::npos || index == fileName.size())//请求的文件无后缀名的话 返回一个空指针return nullptr;std::string s(fileName, index + 1);//如果存在后缀的话,那么则检查后缀///通事后缀名来举行判断请求数据的类型std::string type = dataType[s];if (type.empty())return "text/plain";//如果在关联容器中没有找到相对应的类型///返回转换为char*的类型staticchar str[20];strcpy_s(str, type.c_str());return str;}12345678910111213141516171819202122固然其中也牵扯到其他的结构。这里并纷歧一举例,如果需要请发邮件给我。当你完成基本的结构的时候,则可以测试你的法式。
服务器输入 80 端口举行监听。此时打开浏览器。
输入 localhost并回车此时你的控制台将会打印出毗连的客户信息。固然这是在你实现了该项功效之后。
可是最起码你在浏览器中看到你的网页了。如果你希望能够让其他人也能够看到你网页,你可以实验如下方法:购置服务器并将法式和html文件放在服务器端执行,其他用户通过你的服务器外网IP地址举行会见。2.使用特定的软件举行内网穿透,将内网穿透为公网并设置端口号,其他用户通过你提供的公网IP和端口号举行毗连。你可以百度“免费frp”来找到心仪的软件,如 Sakura frp如果你不想编写自己的服务器代码,而只想要put自己的网站让其他人会见,你可以使用如下软件:Windows提供的 IIS云盾… 你可以百度搜索网站托管之后如果你想要其他人通过 域名来会见你的网站。
如 www.baidu.com就是一个域名。你就需要去购置域名,购置域名士程你可以百度。之后再域名认证存案一系列流程。
存案完成之后将域名与你的服务器公网IP地址举行绑定。如果想要网站响应速度变得更快,那么你可以再设置cdn缓存,同样该步骤可以通过百度查询获得。如果你域名存案乐成,那么则需要举行剖析。
这里给出我的域名剖析方案。详细你可以百度获得更详细的步骤。当你完成这些之后,那么恭喜你,你的网站应该可以正常被会见了! 那么富厚你的网站让更多人看到吧。
本文关键词:fb体育官方网站,fb体育官网
本文来源:fb体育官方网站-www.krmcxt.com