IOCP windowsServer示例

1.环境

Qt5.9.6
vs2017

2.代码

  这段代码是一个简单的基于 Windows 平台的 C++ 程序,实现了一个基于 IOCP(Input/Output Completion Port)的简单的服务器,用于接受客户端的连接,并处理收发数据。

  • send_data_handle:处理接收到的数据,并准备要发送的数据。
  • PostAcceptEx:发起一个异步接受连接的操作。
  • workerThread:工作线程函数,处理完成端口上的 I/O 完成包。
  • InitServer:初始化服务器,包括启动 WinSock、创建监听套接字、绑定端口、创建完成端口等操作。
//main.cpp

#include "CodeServer.h"
#include <QtWidgets/QApplication>
#include <QJsonDocument>
#include <QJsonObject>

#pragma comment(lib,"ws2_32.lib")
#include <WinSock2.h>
#include <MSWSock.h>
#include <cstring>
#include <iostream>

#define BUFFER_SIZE 1024
#define THREAD_COUNT 2
#define START_POST_ACCEPTEX 2
#define PORT 8989


void send_data_handle(char* receive_buffer, char * buffer, ULONG &buffersize);

enum class IO_OP_TYPE {
	IO_ACCEPT,  // accept
	IO_SEND,
	IO_RECV,
	IO_CONNECT,
	IO_DISCONNECT,
};

struct ServerParams {
	SOCKET listenSocket;
	HANDLE completionPort;
};

struct ThreadParams {
	CodeServer* codeServer;
	ServerParams* serverParams;
};

typedef struct OverlappedPerIO {
	OVERLAPPED overlapped;
	SOCKET socket;
	WSABUF wsaBuf;
	IO_OP_TYPE type;
	char buffer[BUFFER_SIZE];
} *LPOverlappedPerIO;

void PostAcceptEx(SOCKET listenSocket) {
	SOCKET sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sock == INVALID_SOCKET) {
		return;
	}
	OverlappedPerIO* overlp = new OverlappedPerIO;
	if (overlp == nullptr) {
		closesocket(sock);
		return;
	}
	ZeroMemory(overlp, sizeof(OverlappedPerIO));
	overlp->socket = sock;
	overlp->wsaBuf.buf = overlp->buffer;
	overlp->wsaBuf.len = BUFFER_SIZE;
	overlp->type = IO_OP_TYPE::IO_ACCEPT;

	DWORD dwByteRecv = 0;
	while (false == AcceptEx(listenSocket,
		sock,
		overlp->wsaBuf.buf,
		0,
		sizeof(SOCKADDR_IN) + 16,
		sizeof(SOCKADDR_IN) + 16,
		&dwByteRecv,
		(LPOVERLAPPED)overlp)) {
		if (WSAGetLastError() == WSA_IO_PENDING) {
			break;
		}
		std::cout << WSAGetLastError() << std::endl;
	}
}

DWORD WINAPI workerThread(LPVOID lpParam) {
	ServerParams* pms = (ServerParams*)((ThreadParams*)lpParam)->serverParams;
	HANDLE completionPort = pms->completionPort;
	SOCKET listenSocket = pms->listenSocket;

	DWORD bytesTrans;
	ULONG_PTR comletionKey;
	LPOverlappedPerIO overlp;
	int ret;
	while (true) {
		BOOL result = GetQueuedCompletionStatus(
			completionPort,
			&bytesTrans,
			&comletionKey,
			(LPOVERLAPPED*)&overlp,
			INFINITE);
		if (!result) {
			if ((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED)) {
				std::cout << "socket disconnection:" << overlp->socket << std::endl;
				((ThreadParams*)lpParam)->codeServer->appendText("Client disconnection:" + QString::number(overlp->socket));
				closesocket(overlp->socket);
				delete overlp;
				continue;
			}
			std::cout << "GetQueuedCompletionStatus failed" << std::endl;
			return 0;
		}
		switch (overlp->type) {
		case IO_OP_TYPE::IO_ACCEPT:
		{
			PostAcceptEx(listenSocket);
			std::cout << "happed IO_ACCEPT:" << bytesTrans << std::endl;
			((ThreadParams*)lpParam)->codeServer->appendText(u8"Client establishes connection:"+ QString::number(overlp->socket));
			setsockopt(overlp->socket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&(listenSocket), sizeof(SOCKET));

			ZeroMemory(overlp->buffer, BUFFER_SIZE);
			overlp->type = IO_OP_TYPE::IO_RECV;
			overlp->wsaBuf.buf = overlp->buffer;
			overlp->wsaBuf.len = BUFFER_SIZE;
			CreateIoCompletionPort((HANDLE)overlp->socket, completionPort, NULL, 0);

			DWORD dwRecv = 0, dwFlag = 0;
			ret = WSARecv(overlp->socket, &overlp->wsaBuf, 1, &dwRecv, &dwFlag, &(overlp->overlapped), 0);
			if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
				std::cout << "WSARecv failed:" << WSAGetLastError() << std::endl;
			}
		}
		break;
		case IO_OP_TYPE::IO_RECV:
		{
			std::cout << "happed IO_RECV:" << bytesTrans << std::endl;
			if (bytesTrans == 0) {
				std::cout << "socket disconnection:" << overlp->socket << std::endl;
				((ThreadParams*)lpParam)->codeServer->appendText(u8"Client disconnection:" + QString::number(overlp->socket));
				closesocket(overlp->socket);
				delete overlp;
				continue;
			}

			((ThreadParams*)lpParam)->codeServer->appendText(u8" Recved data(Client "+ QString::number(overlp->socket) + "):\n" + QString::fromUtf8(overlp->buffer));
			std::cout << "recved data:" << overlp->buffer << std::endl;

			ZeroMemory(&overlp->overlapped, sizeof(OVERLAPPED));
			overlp->type = IO_OP_TYPE::IO_SEND;
			//overlp->wsaBuf.buf = (char *)"response from server\n";
			//overlp->wsaBuf.len = strlen("response from server\n");
			send_data_handle(overlp->buffer, overlp->wsaBuf.buf, overlp->wsaBuf.len);

			DWORD dwSend = 0;
			ret = WSASend(overlp->socket, &overlp->wsaBuf, 1, &dwSend, 0, &(overlp->overlapped), 0);
			if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
				std::cout << "WSARecv failed:" << WSAGetLastError() << std::endl;
			}

		}
		break;
		case IO_OP_TYPE::IO_SEND:
		{
			std::cout << "happed IO_SEND:" << bytesTrans << std::endl;

			if (bytesTrans == 0) {
				std::cout << "socket disconnection:" << overlp->socket << std::endl;
				((ThreadParams*)lpParam)->codeServer->appendText(u8"Client disconnection:" + QString::number(overlp->socket));
				closesocket(overlp->socket);
				delete overlp;
				continue;
			}
			ZeroMemory(overlp->buffer, BUFFER_SIZE);
			overlp->type = IO_OP_TYPE::IO_RECV;
			overlp->wsaBuf.buf = overlp->buffer;
			overlp->wsaBuf.len = BUFFER_SIZE;

			DWORD dwRecv = 0, dwFlag = 0;
			ret = WSARecv(overlp->socket, &overlp->wsaBuf, 1, &dwRecv, &dwFlag, &(overlp->overlapped), 0);
			if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
				std::cout << "WSARecv failed:" << WSAGetLastError() << std::endl;
			}
		}
		break;
		}
	}
	return 0;
}

void send_data_handle(char* receive_buffer,char * buffer,ULONG &buffersize) {

	QByteArray receive_byteArray(receive_buffer);
	QJsonDocument receive_jsonDoc = QJsonDocument::fromJson(receive_byteArray);

	// 检查JSON是否有效
	if (receive_jsonDoc.isNull()) {
		// JSON无效
		std::cout << "Failed to parse JSON."<<std::endl;
	}
	QJsonObject receive_jsonObject = receive_jsonDoc.object();

	QJsonObject jsonObject;

	if (receive_jsonObject["cmd"].toString() == "add")
	{
		jsonObject["cmd"] = "addreply";
		jsonObject["status"] = "true";
	}

	// 将QJsonObject转换为QJsonDocument
	QJsonDocument jsonDoc(jsonObject);

	// 将QJsonDocument转换为字节流
	QByteArray byteArray = jsonDoc.toJson();
	buffersize = byteArray.size();

	// 创建一个char数组来存储字节流,并复制数据
	//char* buffer = new char[bufferSize];
	memcpy(buffer, byteArray.constData(), buffersize);
	 
}

// 初始化 回滚式
int InitServer(ServerParams& pms) {
	WSADATA wsaData;
	int ret;

	ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (ret == 0) {
		pms.listenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
		if (pms.listenSocket != INVALID_SOCKET) {
			// 绑定地址和端口
			sockaddr_in address;
			address.sin_family = AF_INET;
			address.sin_addr.s_addr = INADDR_ANY;
			address.sin_port = htons(PORT);
			ret = bind(pms.listenSocket, (const sockaddr*)& address, sizeof(address));
			if (ret == 0) {
				ret = listen(pms.listenSocket, SOMAXCONN);
				if (ret == 0) {
					pms.completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
					if (pms.completionPort != NULL) {
						if (NULL != CreateIoCompletionPort((HANDLE)pms.listenSocket,
							pms.completionPort,
							NULL,
							0)) {
							return 0;
						}
						CloseHandle(pms.completionPort);
					}
				}
			}
		}
		closesocket(pms.listenSocket);
	}
	WSACleanup();
	if (ret == 0) ret = -1;
	return ret;
}


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
	CodeServer w;

	w.setPortLabelText(PORT);

	ServerParams pms;
	int ret;
	ret = InitServer(pms);
	if (ret != 0) {
		std::cout << "InitServer Error" << std::endl;
		w.appendText("InitServer Error");
		//return 1;
	}
	else
	{
		w.appendText("InitServer Finish");
	}

	ThreadParams threadParams;
	threadParams.codeServer = &w;
	threadParams.serverParams = &pms;

	for (int i = 0; i < THREAD_COUNT; i++) {
		CreateThread(NULL, 0, workerThread, &threadParams, 0, NULL);
	}

	for (int i = 0; i < START_POST_ACCEPTEX; i++) {
		PostAcceptEx(pms.listenSocket);
	}
    w.show();
    return a.exec();
}

//CodeServer.h
#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_CodeServer.h"

#include <QScrollBar>
#include <QDateTime>


namespace Ui {
	class CodeServerClass;
}

class CodeServer : public QMainWindow
{
    Q_OBJECT

public:
    CodeServer(QWidget *parent = nullptr);
    ~CodeServer();

	void appendText(QString str);

	void setPortLabelText(int port);
signals:
	void outputDataReceived(const QString& data);
private:
    Ui::CodeServerClass *ui;
};

//CodeServer.cpp
#include "CodeServer.h"

CodeServer::CodeServer(QWidget *parent)
    : QMainWindow(parent),
	ui(new Ui::CodeServerClass)
{
    ui->setupUi(this);

	ui->textEdit->setReadOnly(true);
	QScrollBar* verticalScrollBar = ui->textEdit->verticalScrollBar();
	verticalScrollBar->setValue(verticalScrollBar->maximum());

	// 监听文本内容变化的信号,保持滚动到最后
	QObject::connect(ui->textEdit, &QTextEdit::textChanged, [=]() {
		verticalScrollBar->setValue(verticalScrollBar->maximum());
	});
}

CodeServer::~CodeServer()
{}

void CodeServer::appendText(QString str)
{
	QDateTime currentDateTime = QDateTime::currentDateTime();
	QString dateTimeString = currentDateTime.toString("yyyy-MM-dd hh:mm:ss");

	ui->textEdit->append(dateTimeString + ":" + str);
}

void CodeServer::setPortLabelText(int port)
{
	ui->label_server_port->setText(QString::number(port));
}

3.主要流程

  • 在 main 函数中,首先初始化服务器,然后创建多个工作线程,每个工作线程都会在完成端口上等待 I/O 完成包的到来。
  • 当有新连接到来时,会调用 PostAcceptEx 发起异步接受连接的操作,然后在 workerThread 中处理连接的接收和发送数据的操作。
    CodeServer

推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家:
零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核)
https://xxetb.xet.tech/s/3Zqhgt

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Qt IOCP(Input/Output Completion Port)服务器是一个基于Qt框架和IOCP技术实现的服务器。 首先,IOCP是Windows提供的一种高性能的异步IO模型。与传统的同步阻塞IO模型相比,IOCP采用了事件驱动的方式,在进行IO操作后不需要等待IO完成,而是通过在IO完成时触发事件的方式进行通知。这种方式可以提高服务器的并发处理能力和响应速度。 Qt是一套跨平台的C++开发框架,提供了丰富的工具和类库,可以用于开发各种类型的应用程序,包括服务器。Qt提供了QIODevice和QAbstractSocket等类来封装底层IO操作,使开发者可以方便地进行网络编程。 Qt IOCP服务器的实现过程大致如下: 1. 创建一个QTcpServer对象,用于监听并接收客户端的连接请求。 2. 当有客户端连接请求到达时,QTcpServer会触发newConnection()信号,我们可以在之前连接好的槽函数中编写处理客户端连接的逻辑。 3. 在处理连接的槽函数中,可以通过调用QTcpServer的nextPendingConnection()函数获取与客户端之间通信的QTcpSocket对象。 4. 使用QSocketNotifier和QAbstractSocket提供的信号和槽机制,可以实现对客户端的各种操作,如接收数据、发送数据等。 5. 在同步IO模型中,可以通过调用QTcpSocket的waitForReadyRead()和waitForBytesWritten()等函数来进行阻塞操作。而在IOCP模型中,我们可以通过调用QTcpSocket的setSocketOption()和waitForConnected()等函数来设置非阻塞模式。 6. 当有数据到达或发送完毕时,QTcpSocket会相应地触发相应的信号,我们可以在相应的槽函数中编写数据处理的逻辑。 总而言之,Qt IOCP服务器结合了Qt框架和IOCP技术的优势,提供了一种高效的方式来实现高并发、高性能的网络服务器。 ### 回答2: Qt是一个跨平台的应用程序开发框架,提供了丰富的工具和库来快速开发高质量的应用程序。而IOCP(Input/Output Completion Port)是一种用于高性能网络通信的技术。 Qt提供了QAbstractSocket类来进行网络编程,该类封装了操作系统提供的底层网络接口,可以方便地进行TCP或UDP通信。对于IOCP服务器,我们可以使用Qt的IOCP模块来实现。 Qt IOCP模块是在Windows平台上使用Windows API的IOCP功能来处理并发网络操作的一种方法。IOCP提供了一种高级的异步I/O机制,可以通过将I/O操作请求提交给IOCP内核对象,从而实现对多个I/O操作的集中管理和同时处理。 在Qt中实现IOCP服务器,我们可以创建一个QTcpServer对象,并使用它的listen()函数来监听指定的IP地址和端口。当有新的客户端连接请求时,QTcpServer将会发出newConnection()信号,我们可以通过连接这个信号的槽函数来处理新的连接。 对于IOCP功能,我们可以使用QTcpSocket::setSocketOption()函数来启用IOCP模式,并使用QTcpSocket::socketDescriptor()函数获得底层套接字描述符,然后使用QAbstractSocket::socketHandle()函数获得底层套接字句柄。通过使用这些底层接口,我们可以实现IOCP服务器的事件循环,监听和处理客户端连接和数据的到达。 总的来说,Qt IOCP服务器通过结合Qt的网络编程功能和WindowsIOCP机制,提供了一种高效、可靠的方式来开发高性能的网络服务器。通过合理的设计和编码,我们可以利用Qt的IOCP模块实现稳定、高并发和可扩展的服务器应用程序。 ### 回答3: Qt是一种跨平台的C++应用程序开发框架,它的IOCP(Input/Output Completion Port)服务器是一种基于IOCP技术实现的服务器模型。 IOCP是一种高效的异步IO模型,通过将IO操作处理和应用程序逻辑分离,使得服务器可以同时处理多个客户端请求。在Qt中,使用IOCP来实现服务器可以提高系统的响应速度和并发性能。 Qt的IOCP服务器使用Qt网络模块中的QThreadPool和QThread来管理并发处理多个客户端请求。服务器首先会创建一个QThreadPool对象,用于管理处理客户端请求的线程池。然后,服务器将创建一个QTcpServer对象,监听指定的网络端口。当有客户端请求连接时,服务器会将该连接分配给线程池中的一个空闲线程进行处理。 在处理客户端请求的线程中,服务器可以使用Qt的信号与槽机制来处理数据的接收和发送。服务器可以通过信号与槽机制将接收到的数据发送到应用程序的其他部分进行处理,同时也可以将处理完的数据发送给客户端。这种通过信号与槽机制实现的异步IO操作使得服务器能够并发处理多个客户端请求,提高了系统的性能和稳定性。 总之,Qt的IOCP服务器是一种基于IOCP技术实现的高效异步IO服务器模型。它利用Qt的信号与槽机制和线程池来实现并发处理多个客户端请求,提高了系统的性能和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值