使用IO完成端口实现简单回显服务器

news2025/1/22 19:04:28

说明

使用IO完成端口实现简单回显服务器,因为是测试用的,所以代码很粗糙。

  • 提醒
    使用的是ReadFile、WriteFile来实现Overlapped IO,正式场合应该用WSARecv、WSASend,原因:来自《Windows网络编程技术》 8.2.5节
    在这里插入图片描述

  • 技术点记录下
    io以同步方式立马完成时,系统也会将此通知投递到io完成端口通知列表中,这么做的原因是方便用户编码。
    SetFileCompletionNotificationModes传入FILE_SKIP_COMPLETION_PORT_ON_SUCCESS告诉系统,io以同步方式立马完成时,不要
    将此事件投递到IO完成端口列表中。

    参看《Windows核心编程》第10章 10.5.4
    在这里插入图片描述
    在这里插入图片描述

代码

#include <iostream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <set>
#include <memory>
#include <process.h>

#pragma comment(lib, "Ws2_32.lib")

class MyOverlapped
{
public:
	MyOverlapped()
		:m_bIsRead(false)
	{
		memset(&m_Overlapped, 0, sizeof(OVERLAPPED));
	}

	OVERLAPPED m_Overlapped;
	bool m_bIsRead;
};


struct ClientSocketItem
{
	ClientSocketItem()
	{
		hSocket = NULL;
		memset(szRecv, 0, sizeof(szRecv));
		nRecvSize = 0;
		bFinished = false;
		nWriteOffset = 0;

		readOverlapped.m_bIsRead = true;
		writeOverlapped.m_bIsRead = false;
	}

	SOCKET hSocket;
	std::string strIp;

	MyOverlapped readOverlapped;
	char szRecv[1024];
	unsigned int nRecvSize;
	
	MyOverlapped writeOverlapped;
	unsigned int nWriteOffset = 0;

	bool bFinished;
};
std::set<ClientSocketItem*> g_Clients;

HANDLE g_hIoCompletionPort = NULL;



bool do_read(ClientSocketItem* pClient)
{
	if (!pClient)
	{
		return false;
	}

	char c = 0; //测试用,每次只读一个字符
	DWORD dwReadBytes;
	if (::ReadFile((HANDLE)(pClient->hSocket), &(pClient->szRecv[pClient->nRecvSize]),
		1, &dwReadBytes, &(pClient->readOverlapped.m_Overlapped)))
	{
		return true;
	}

	DWORD dwError = ::GetLastError();
	if (ERROR_IO_PENDING == dwError)
	{
		return true;
	}

	std::cerr << "read failed with error " << dwError << std::endl;

	return false;
}


bool do_write(ClientSocketItem* pClient)
{
	if (!pClient)
	{
		return false;
	}

	//测试用,每次只发送一个字符
	DWORD dwWriteBytes = 0;
	if (::WriteFile((HANDLE)(pClient->hSocket), &(pClient->szRecv[pClient->nWriteOffset]),
		1, &dwWriteBytes, &(pClient->writeOverlapped.m_Overlapped)))
	{
		return true;
	}

	DWORD dwError = ::GetLastError();
	if (ERROR_IO_PENDING == dwError)
	{
		return true;
	}

	std::cerr << "write failed with error " << dwError << std::endl;

	return false;
}


bool do_accept(SOCKET hListenSocket)
{
	sockaddr_in mPeerAddr = { 0 };
	int nAddrLen = sizeof(sockaddr);
	SOCKET hClientSocket = accept(hListenSocket, (sockaddr*)(&mPeerAddr), &nAddrLen);
	if (INVALID_SOCKET == hClientSocket)
	{
		std::cout << "accept failed with error "
			<< WSAGetLastError() << std::endl;
		return false;
	}
	else
	{
		unsigned long nNoBlock = 0;
		ioctlsocket(hClientSocket, FIONBIO, &nNoBlock);

		std::string strIpAddr = inet_ntoa(mPeerAddr.sin_addr);
		std::cout << "accept success, peer ip is " << strIpAddr.c_str() << std::endl;

		auto pClient = new ClientSocketItem();
		pClient->hSocket = hClientSocket;
		pClient->strIp = strIpAddr;
		g_Clients.insert(pClient);

		//附加到IO完成端口上
		if (g_hIoCompletionPort != ::CreateIoCompletionPort(HANDLE(pClient->hSocket),
			g_hIoCompletionPort, ULONG_PTR(pClient), 0))
		{
			std::cerr << "attach socket to io completion port failed with error"
				<< ::GetLastError() << std::endl;
			closesocket(hClientSocket);
			return false;
		}

		//触发读取
		if (!do_read(pClient))
		{
			closesocket(pClient->hSocket);
			g_Clients.erase(pClient);
			std::cerr << "do_read failed, close client" << std::endl;
		}

		return true;
	}
}


//读写线程函数
unsigned __stdcall ReadWriteThreadFun(void* pParam)
{
	DWORD dwTransferBytes = 0;
	ULONG_PTR nCompleteKey = 0;
	LPOVERLAPPED lpOverlapped = NULL;
	while (::GetQueuedCompletionStatus(g_hIoCompletionPort, &dwTransferBytes,
		&nCompleteKey, &lpOverlapped, INFINITE))
	{
		if (nCompleteKey == UINT_MAX)
		{
			std::cout << "user require quit" << std::endl;
			break;
		}

		if (!lpOverlapped)
		{
			std::cerr << "lpOverlapped is null" << std::endl;
			break;
		}

		ClientSocketItem* pClient = (ClientSocketItem*)nCompleteKey;
		MyOverlapped* pMyOverlapped = CONTAINING_RECORD(lpOverlapped, MyOverlapped, m_Overlapped);
		if (!pMyOverlapped || !pClient)
		{
			std::cerr << "pMyOverlapped or pClient is null" << std::endl;
			break;
		}

		if (pMyOverlapped->m_bIsRead)//read finished notify
		{
			char c = pClient->szRecv[pClient->nRecvSize];
			pClient->nRecvSize += dwTransferBytes;
			std::cout << "read one char: " << c << std::endl;
			if (c == '\n')
			{
				std::cout << "read finished, start to write" << std::endl;
				if (!do_write(pClient))
				{
					std::cerr << "do_write failed, close socket" << std::endl;
					closesocket(pClient->hSocket);
					g_Clients.erase(pClient);
				}
			}
			else
			{
				std::cout << "next char read" << std::endl;
				if (!do_read(pClient))
				{
					std::cerr << "do_read failed, close socket" << std::endl;
					closesocket(pClient->hSocket);
					g_Clients.erase(pClient);
				}
			}
		}
		else //write finished notify
		{
			char c = pClient->szRecv[pClient->nWriteOffset];
			std::cout << "send one char: " << c << std::endl;
			pClient->nWriteOffset += dwTransferBytes;
			if (pClient->nWriteOffset == pClient->nRecvSize)
			{
				std::cout << "send finished, close client(" << pClient->strIp.c_str()
					<< ")" << std::endl;
				closesocket(pClient->hSocket);
				g_Clients.erase(pClient);
			}
			else
			{
				std::cout << "next send" << std::endl;
				if (!do_write(pClient))
				{
					std::cerr << "do_write failed, close socket" << std::endl;
					closesocket(pClient->hSocket);
					g_Clients.erase(pClient);
				}
			}
		}
	}

	std::cout << "thread quit" << std::endl;
	return 0;
}


int main(int argc, char* argv)
{
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData = { 0 };
	int err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return -1;
	}

	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2)
	{
		WSACleanup();
		return -1;
	}

	SOCKET hListenSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == hListenSocket)
	{
		std::cerr << "create socket failed with error " << WSAGetLastError()
			<< std::endl;
		return -1;
	}

	sockaddr_in mSockAddrIn = { 0 };
	mSockAddrIn.sin_family = AF_INET;
	mSockAddrIn.sin_port = htons((u_short)8878);
	mSockAddrIn.sin_addr.S_un.S_addr = inet_addr("0.0.0.0");
	if (SOCKET_ERROR == bind(hListenSocket, (sockaddr*)(&mSockAddrIn),
		sizeof(sockaddr)))
	{
		std::cerr << "bind failed with error " << WSAGetLastError() << std::endl;
		return -1;
	}

	if (SOCKET_ERROR == listen(hListenSocket, SOMAXCONN))
	{
		std::cerr << "listen failed with error " << WSAGetLastError() << std::endl;
		return -1;
	}

	//创建完成端口
	g_hIoCompletionPort = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (NULL == g_hIoCompletionPort)
	{
		std::cerr << "create io completion port failed with error " 
			<< ::GetLastError() << std::endl;
		return -1;
	}

	//创建一堆服务线程
	for (int i = 0; i < 4; ++i)
	{
		_beginthreadex(0, 0, ReadWriteThreadFun, 0, 0, nullptr);
	}

	while (true)
	{
		if (!do_accept(hListenSocket))
		{
			break;
		}
	}

	::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, UINT_MAX, nullptr);
	Sleep(2000); 


	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1165116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

葡萄酒中的酒精含量是多少?

当你喝完一杯加利福尼亚赤霞珠和你的朋友在一个周五的晚上出游&#xff0c;你不禁会注意到&#xff0c;这只玻璃杯能让你感觉有点不同于你前几个周末喝的意大利灰皮诺葡萄酒。出于好奇&#xff0c;你查看了一下ABV&#xff0c;发现这个数字高达14.5%&#xff01;难怪&#xff0…

requires SDK version >=3.0.1 <4.0.0, version solving failed

这个很明显是FLUTTER SDK不匹配的问题&#xff0c;需要更新flutter SDK&#xff0c;最简单的办法&#xff0c;在flutter官网的页面直接下载最新的&#xff0c;然后替换之前旧版本的flutter 官网&#xff1a; 在 Windows 操作系统上安装和配置 Flutter 开发环境 - Flutter 中文…

C++并发编程实战——07.设计无锁的并发数据结构

文章目录 设计无锁的并发数据结构定义及意义无阻塞数据结构无锁数据结构无等待数据结构无锁结构的利弊 无锁数据结构的例子无锁线程安全栈使用风险指针检测不可回收的节点使用引用计数无锁栈上的内存模型实现一个无锁的线程安全队列 设计无锁数据结构的指导建议 设计无锁的并发…

windwos10搭建我的世界服务器,并通过内网穿透实现联机游戏Minecraft

文章目录 1. Java环境搭建2.安装我的世界Minecraft服务3. 启动我的世界服务4.局域网测试连接我的世界服务器5. 安装cpolar内网穿透6. 创建隧道映射内网端口7. 测试公网远程联机8. 配置固定TCP端口地址8.1 保留一个固定tcp地址8.2 配置固定tcp地址 9. 使用固定公网地址远程联机 …

mysql双主搭建

https://www.bilibili.com/video/BV1BK4y1t7MY/?spm_id_from333.880.my_history.page.click&vd_source297c866c71fa77b161812ad631ea2c25 要到用双主&#xff0c;或多主&#xff0c;主要是考虑到这么一个场景&#xff1a; 如果一个应用&#xff0c;全球用户都要用&#x…

API接口安全设计

简介 HTTP接口是互联网各系统之间对接的重要方式之一&#xff0c;使用HTTP接口开发和调用都很方便&#xff0c;也是被大量采用的方式&#xff0c;它可以让不同系统之间实现数据的交换和共享。 由于HTTP接口开放在互联网上&#xff0c;所以我们就需要有一定的安全措施来保证接口…

LuaHttp库写的一个简单的爬虫

LuaHttp库是一个基于Lua语言的HTTP客户端库&#xff0c;可以用于爬取网站数据。与Python的Scrapy框架类似&#xff0c;LuaHttp库也可以实现网站数据的抓取&#xff0c;并且可以将抓取到的数据保存到数据库中。不过需要注意的是&#xff0c;LuaHttp库并不像Scrapy框架那样具有完…

限制LitstBox控件显示指定行数的最新数据(3/3)

实例需求&#xff1a;由于数据行数累加增加&#xff0c;控件加载的数据越来越多&#xff0c;每次用户都需要使用右侧滚动条拖动才能查看最新数据。 因此希望ListBox只加载最后10行数据&#xff08;不含标题行&#xff09;&#xff0c;这样用户可以非常方便地选择数据&#xff…

基于python+django开发的电影链接搜索网站 - 毕业设计 - 课程设计

文章目录 源码下载地址项目介绍界面预览项目备注毕设定制&#xff0c;咨询 源码下载地址 点击这里下载代码 项目介绍 该项目是基于python的web类库django开发的一套web网站&#xff0c;给同学做的课程作业。 本人的研究方向是一项关于搜索的研究项目。在该项目中&#xff0c…

WoShop跨境电商源码:解放你的双手,批量发货轻松搞定

随着跨境电商的快速发展&#xff0c;越来越多的企业开始涉足这一领域。在这个过程中&#xff0c;如何高效地处理批量发货成为了亟待解决的问题。本文将探讨跨境电商源码支持批量发货的优势、需求分析、实现方案、技术实现、测试与维护以及总结与建议。 一、引言 在跨境电商领域…

Linux进程概念(2)

Linux进程概念(2) &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了进程的概念&#xff0c;PCB&am…

Python用RoboBrowser库写一个通用爬虫模版

以下是一个使下载lianjia内容的Python程序&#xff0c;爬虫IP服务器为duoip的8000端口。 from robobrowser import RoboBrowser# 创建一个RoboBrowser对象 browser RoboBrowser(user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) …

人工智能与无人驾驶:未来驾驶体验的革命性变革

人工智能与无人驾驶&#xff1a;未来驾驶体验的革命性变革 人工智能&#xff08;AI&#xff09;和无人驾驶技术的迅速发展正在改变我们的交通方式和出行体验。它们结合了先进的感知技术、智能算法和高性能计算能力&#xff0c;为实现自动驾驶提供了可能性。本文将探讨人工智能和…

悟道云端,探索测试新境

“探寻新技术的前沿&#xff0c;分享测试经验的心得”&#xff0c;这是我参加云栖大会的初衷。回顾第一次参加云栖大会的情景&#xff0c;仿佛还历历在目。那是2017年&#xff0c;我刚刚步入职场&#xff0c;对云计算领域充满了好奇和憧憬。云栖大会给了我一个难得的机会&#…

JS利用时间戳倒计时案例

我们在逛某宝&#xff0c;或者逛某东时&#xff0c;我们时常看到一个倒计时&#xff0c;时间一到就开抢&#xff0c;这个倒计时是如何做的呢&#xff1f;让我为大家介绍一下。 理性分析一下&#xff1a; 1.用将来时间减去现在时间就是剩余的时间 2.核心&#xff1a;使用将来的时…

基于 golang 从零到一实现时间轮算法 (一)

前言 时间轮是用来解决海量百万级定时器&#xff08;或延时&#xff09;任务的最佳方案&#xff0c;linux 的内核定时器就是采用该数据结构实现。 应用场景 自动删除缓存中过期的 Key&#xff1a;缓存中设置了 TTL 的 kv&#xff0c;通过把该 key 对应的 TTL 以及回调方法注册…

云安全-云原生k8s攻击点(8080,6443,10250未授权攻击点)

0x00 k8s简介 k8s&#xff08;Kubernetes&#xff09; 是容器管理平台&#xff0c;用来管理容器化的应用&#xff0c;提供快速的容器调度、弹性伸缩等诸多功能&#xff0c;可以理解为容器云&#xff0c;不涉及到业务层面的开发。只要你的应用可以实现容器化&#xff0c;就可以部…

css——半圆实心

案例 代码 <view class"circleBox"></view>.circleBox {width: 50px;height: 100px;background: red;border-radius: 100px 0 0 100px; }

史上最详细注释,用flask写一个博客系统

文本用flask写个博客系统&#xff0c;源码带有详细注释&#xff0c;通俗易懂&#xff0c;拿去就能用。博客效果如下&#xff0c;博客首页&#xff1a; 这个博客麻雀虽小&#xff0c;但五脏俱全。有如下功能&#xff1a; 博客文章浏览用户注册用户登录/登出发文章/修改文章/删除…

Linux设置ssh免密登录

ssh连接其他服务器 基本语法 ssh 另一台机器的ip地址 连接后输入连接主机用户的密码&#xff0c;即可成功连接。 输入exit 可以登出&#xff1b; 由于我配置了主机映射所以可以不写ip直接写映射的主机名即可&#xff0c;Linux配置主机映射的操作为 vim /etc/hosts # 我自己…