介绍

C++ Windows实现EchoServer非阻塞服务端(TPC协议)
Windows IDE:Visual Studio 2022
我们将采用分文件编写方式

支持库

#include <winsock2.h>
#include <string>

代码

main.h

#define _WINSOCK_DEPRECATED_NO_WARNINGS
 
#include <winsock2.h>
#include <string>
 
#pragma comment(lib,"ws2_32.lib")
 
class EchoServer
{
private:
	int port = 0;
	WORD sockVersion;
	WSADATA data;
	int InitCode;
 
	SOCKET socketFD = 0;
	SOCKET clientFD = 0;
 
	sockaddr_in sin{};
	sockaddr_in clientAddr{};
 
	timeval timeout{};
 
	fd_set wait{};
 
	bool serverIsOpen = false;
 
	int bufferSize = 1024;
 
	bool isBlock = false;
public:
	EchoServer();
	~EchoServer();

	int initServer(int port, bool noBlock = true);

	void openServre();

	std::string getClientIP();

	int sendClientDate(std::string date);

	std::string getClientDate(bool fastRead = false);

	void CloseServer();

	void setTimeout(int s = 3, int us = 0);
 
protected:
	virtual void handleConnect() = 0;
 };

这里使用了虚函数handleConnect作为接口

main.cpp

#include "main.h"

EchoServer::EchoServer()
{
	sockVersion = MAKEWORD(2, 2);
	InitCode = WSAStartup(sockVersion, &data);
	setTimeout();
}

EchoServer::~EchoServer()
{
	WSACleanup();
}

int EchoServer::initServer(int port, bool noBlock)
{
	if (InitCode != 0)
		return -3;
	socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketFD == INVALID_SOCKET)
		return -2;

	if (noBlock) {
		u_long on = 1;
		if (ioctlsocket(socketFD, FIONBIO, &on) < 0) {
			return -4;
		}
	}

	isBlock = !noBlock;

	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;

	if (bind(socketFD, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
		return -5;
	if (listen(socketFD, 5) == SOCKET_ERROR)
		return -1;
	
	return 0;
}

void EchoServer::openServre()
{
	serverIsOpen = true;

	DWORD start, end, elapsedTime;

	while (serverIsOpen)
	{
		if (!isBlock)
			start = GetTickCount64();

		int clientAddrSize = sizeof(clientAddr);
		
		clientFD = accept(socketFD, (SOCKADDR*)&clientAddr, &clientAddrSize);

		if (clientFD != INVALID_SOCKET) {
			handleConnect();
			closesocket(clientFD);
			continue;
		}

		if (!isBlock)
			end = GetTickCount64();

		if (!isBlock)
			elapsedTime = end - start;

		if (!isBlock)
			Sleep(elapsedTime);
	}
}

std::string EchoServer::getClientIP()
{
	std::string result = "";
	result.append(inet_ntoa(clientAddr.sin_addr));
	return result;
}

int EchoServer::sendClientDate(std::string date)
{
	timeval tv = timeout;
	FD_ZERO(&wait);
	FD_SET(clientFD, &wait);
	if (select(clientFD + 1, NULL, &wait, NULL, &tv) > 0) {
		return send(clientFD, date.c_str(), date.size(), 0);
	}
	return -1;
}

std::string EchoServer::getClientDate(bool fastRead)
{
	std::string result = "";
	char* buffer = new char[bufferSize + 1];
	int len = 0,ready;
	buffer[len] = '\0';

	timeval tv = timeout;
	FD_ZERO(&wait);
	FD_SET(clientFD, &wait);

	while (true)
	{
		if (isBlock)
			goto getMessage;
		ready = select(clientFD + 1, &wait, NULL, NULL, &tv);
		if (ready > 0) {
			if (FD_ISSET(clientFD, &wait)) {
			getMessage:
				len = recv(clientFD, buffer, bufferSize, 0);
				if (len > 0) {
					buffer[len] = '\0';
					result.append(buffer);
					if (len < bufferSize && fastRead) {
						break;
					}
					if (isBlock)
						continue;
				}
				else
					break;
			}
		}
		else
			break;
	}
	delete[] buffer;
	return result;
}

void EchoServer::CloseServer()
{
	if (socketFD)
		closesocket(socketFD);
	serverIsOpen = false;
}

void EchoServer::setTimeout(int s, int us)
{
	timeout.tv_sec = s;
	timeout.tv_usec = us;
}

示例

#include <iostream>
#include "main.h"
 
 
class EchoServerRewirte : public EchoServer {
    void handleConnect() {
        std::string result = getClientDate();
        std::string ip = getClientIP();
 
        std::cout << result << "来自IP:" << ip << "\n";
 
        sendClientDate("close");
        CloseServer();
    }
};
 
int main()
{
    EchoServerRewirte echoServer;
    echoServer.initServer(1001);
    echoServer.openServre();
    return 0;
}