Winsock 编程:200行 C++ 代码搭建一个完整的web服务器

1、环境

物理主机系统:macOS Catalina 10.15.4

虚拟机系统:Windows 10 专业版 x64

虚拟机软件:Parallels Desktop 15 for Mac Pro Edition, version 15.1.4 (47270)

浏览器:Edge

编程环境(IDE):Visual Studio 2019

 

2、服务器设计

  • 调用WSAStartup函数,初始化winsock。
  • 调用socket()函数创建一个Socket
  • 调用bind()函数和socket绑定
  • 调用listen()函数监听一个socket
  • 调用accept()函数接受由一个客户端发起的连接
  • 为该连接创建一个线程来处理与对应客户端有关的服务
  • 调用send()和recv()函数向客户端发送HTML消息或GIF、TXT格式的文件。
  • 客户端断开连接后,调用_endthread()函数关闭线程
  • 调用shutdown(),closesocket()函数断开与服务器的连接,关闭socket

 

3、效果展示

1. live2D页面

    先将 live2d.html(效果是可交互live2d)及其组件(js、css)放入服务器程序目录里,然后直接在浏览器访问。访问成功,网页左下角出现 live2d 动漫小人:

Winsock 编程:200行 C++ 代码搭建一个完整的web服务器_第1张图片

 

2. 进一步,完整的基于 bootstrap 的个人简历网页+live2D

    将 resume.html 及其组件放入服务器程序目录里,然后直接在浏览器访问,访问成功:

Winsock 编程:200行 C++ 代码搭建一个完整的web服务器_第2张图片

 

3. 多线访问

    多开浏览器页面同时访问服务器,均能访问成功:

Winsock 编程:200行 C++ 代码搭建一个完整的web服务器_第3张图片

 

4、C++ 代码

#include 
#include 
#include 
#include 
#include           // For binary handle options
#include        // For binary write()
#include              // Needed for open(), close(), write()
#include         // Needed for _beginthread() and _endthread()


#undef UNICODE

#define WIN32_LEAN_AND_MEAN

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")
//----- HTTP response messages ----------------------------------------------
#define OK_IMAGE  "HTTP/1.0 200 OK\r\nContent-Type:image/gif\r\n\r\n"
#define OK_TEXT   "HTTP/1.0 200 OK\r\nContent-Type:text/html\r\n\r\n" //目前只有两个文件种类,想支持更多的话可以另加
#define NOTOK_404 "HTTP/1.0 404 Not Found\r\nContent-Type:text/html\r\n\r\n"
#define MESS_404  "

FILE NOT FOUND

" //----- Defines ------------------------------------------------------------- #define BUF_SIZE 1024 // Buffer size (big enough for a GET) #define PORT_NUM 80 // Port number for a Web server #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT "80" int __cdecl main(void) { WSADATA wsaData; int iResult; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; struct addrinfo* result = NULL; struct addrinfo hints; int iSendResult; char recvbuf[DEFAULT_BUFLEN]; char outbuf[DEFAULT_BUFLEN]; int recvbuflen = DEFAULT_BUFLEN; //----- Function prototypes ------------------------------------------------- void handle_get(void* in_arg); // Thread function to handle GET //----Add these variables in main()---- struct sockaddr client_addr; int addr_len; SOCKET client_s = INVALID_SOCKET; // Initialize Winsock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; // Resolve the server address and port iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // Create a SOCKET for connecting to server ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } // Setup the TCP listening socket iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } freeaddrinfo(result); // Main loop to listen, accept, and then spin-off a thread to handle the GET while (1) { printf("main loop: linstening ... \n"); // Listen for connections and then accept listen(ListenSocket, 50); addr_len = sizeof(client_addr); client_s = accept(ListenSocket, (struct sockaddr*) & client_addr, &addr_len); if (client_s == -1) { printf("ERROR - Unable to create a socket \n"); exit(1); } printf("client socket accepted, %d... \n", client_s); // Spin-off a thread to handle this request (pass only client_s) if (_beginthread(handle_get, 4096, (void*)client_s) < 0) { printf("ERROR - Unable to create a thread to handle the GET \n"); exit(1); } } printf("main loop completed. close server socket... WSAcleanup \n"); // Close the server socket and clean-up winsock closesocket(ListenSocket); WSACleanup(); } //=========================================================================== //= This is the thread function to handle the GET = //=========================================================================== void handle_get(void* in_arg) { unsigned int client_s; // Client socket descriptor char in_buf[BUF_SIZE]; // Input buffer for GET request char out_buf[BUF_SIZE]; // Output buffer for HTML response int fh; // File handle int buf_len; // Buffer length for file reads char command[BUF_SIZE]; // Command buffer char file_name[BUF_SIZE]; // File name buffer int retcode; // Return code int j; // Set client_s to in_arg client_s = (unsigned int)in_arg; printf("thread %d... \n", client_s); // Receive the (presumed) GET request from the Web browser retcode = recv(client_s, in_buf, BUF_SIZE, 0); printf("thread %d...received web request: \n", client_s); for (j = 0; j < retcode; j++) printf("%c", in_buf[j]); // If the recv() return code is bad then bail-out (see note #3) if (retcode <= 0) { printf("ERROR - Receive failed --- probably due to dropped connection \n"); closesocket(client_s); _endthread(); } // Parse out the command from the (presumed) GET request and filename sscanf_s(in_buf, "%s %s \n", &command, BUF_SIZE, &file_name, BUF_SIZE); // Check if command really is a GET, if not then bail-out if (strcmp(command, "GET") != 0) { printf("ERROR - Not a GET --- received command = '%s' \n", command); closesocket(client_s); _endthread(); } // It must be a GET... open the requested file // - Start at 2nd char to get rid of leading "\" _sopen_s(&fh, &file_name[1], _O_RDONLY | _O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE); // If file does not exist, then return a 404 and bail-out if (fh == -1) { printf("File '%s' not found --- sending an HTTP 404 \n", &file_name[1]); strcpy_s(out_buf, NOTOK_404); send(client_s, out_buf, strlen(out_buf), 0); strcpy_s(out_buf, MESS_404); send(client_s, out_buf, strlen(out_buf), 0); closesocket(client_s); _endthread(); } // Check that filename does not start with a "..", "/", "\", or have a ":" in // the second position indicating a disk identifier (e.g., "c:"). // - This is a security check to prevent grabbing any file on the server if (((file_name[1] == '.') && (file_name[2] == '.')) || (file_name[1] == '/') || (file_name[1] == '\\') || (file_name[2] == ':')) { printf("SECURITY VIOLATION --- trying to read '%s' \n", &file_name[1]); _close(fh); closesocket(client_s); _endthread(); } // Generate and send the response printf("Thread %d, ...Sending file '%s' \n", in_arg, &file_name[1]); //search .gif in file_name if (strstr(file_name, ".gif") != NULL) strcpy_s(out_buf, OK_IMAGE); else strcpy_s(out_buf, OK_TEXT); send(client_s, out_buf, strlen(out_buf), 0); while (!_eof(fh)) { buf_len = _read(fh, out_buf, BUF_SIZE); send(client_s, out_buf, buf_len, 0); } // Close the file, close the client socket, and end the thread _close(fh); printf("Thread %d, ...comleted sending file '%s' \n", client_s, &file_name[1]); closesocket(client_s); printf("socket %d closed. \n", client_s); printf("thread %d ended. \n", client_s); _endthread(); }

 

HAPPY!

你可能感兴趣的:(c++,winsock,windows,server)