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

这篇具有很好参考价值的文章主要介绍了使用IO完成端口实现简单回显服务器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

说明

使用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
    使用IO完成端口实现简单回显服务器,Win32/MFC,C/C++,完成端口
    使用IO完成端口实现简单回显服务器,Win32/MFC,C/C++,完成端口文章来源地址https://www.toymoban.com/news/detail-741314.html

代码

#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;
}

到了这里,关于使用IO完成端口实现简单回显服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 教你如何使用云服务器搭建我的世界Minecraft服务器(超级简单-10分钟完成)

    一个人玩游戏没啥意思,和朋友一块联机呢,距离太远,家庭局域网宽带又没有公网ip,你的朋友没办法与你联机,然而你只需要一台服务器即可搞定了;但是很多用户没没接触过相关的内容,具体的该怎么操作呢?下面我将吧详细的教程分享给大家,适合完全零基础,跟着

    2024年02月07日
    浏览(54)
  • 【TCP服务器的演变过程】使用IO多路复用器epoll实现TCP服务器

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 本节,在上一章节的基础上,将IO多路复用机制select改为更高效的IO多路复用机制epoll,使用epoll管理每

    2024年01月17日
    浏览(70)
  • Navicat使用HTTP通道服务器进行连接mysql数据库(超简单三分钟完成),centos安装nginx和php,docker安装nginx+php合并版

    因为数据库服务器在外网是不能直接连接访问的,但是可以访问网站,网站后台就能访问数据库,所以在此之前,访问数据库的数据是一件非常麻烦的事情,在平时和运维的交流中发现,他们会使用ssh通道进行连接访问数据库,之前并没在意这个东西,直到运维人员一直连不

    2024年02月10日
    浏览(80)
  • Java使用Netty实现端口转发&Http代理&Sock5代理服务器

    这里总结整理了之前使用Java写的端口转发、Http代理、Sock5代理程序,放在同一个工程中,方便使用。 开发语言:Java 开发框架:Netty 端口转发: HTTP代理服务器,支持账号密码认证 Sock5代理服务器,支持账号密码认证 支持连接后端时直接连接或采用代理连接,也后端代理连接认

    2024年01月25日
    浏览(55)
  • linux服务器上使用frp实现tcp端口转发--以访问内网mysql为例

    前言 最近在部署测试环境 部署服务器上没有公网地址和端口 无法使用navicat等工具对数据库操作 因此需要内网穿透或tcp端口转发来实现 公网服务器作为服务器端frps 内网服务器作为客户端frpc 服务端和客户端均下载相应的包 没开端口的自行开启相应端口 开启7000端口如下 1

    2024年02月16日
    浏览(54)
  • 【网络编程】——基于TCP协议实现回显服务器及客户端

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得,欢迎大家在评论区交流讨论💌 TCP提供的API主要有两个类 Socket ( 既会给服务器使用也会给客

    2024年02月03日
    浏览(65)
  • Linux环境下SVN服务器的搭建与公网访问:使用cpolar端口映射的实现方法

    由于文档资料越来越多,将所有资料都存放在自己的电脑上容易混淆,并且也不利于分享。这种情况下,考虑将资料上传SVN统一管理,这样一来其他人也能很方便的查略各种资料。 当SVN安装在局域网内的话,想要远程访问资料库或者代码将会受到限制,为了能从公共网络访问内

    2024年02月11日
    浏览(64)
  • 【网络编程】基于UDP数据报实现回显服务器/客户端程序

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得,欢迎大家在评论区交流讨论💌 前言 我们如果想让应用程序进行网络通信的话,就需要调用传

    2024年02月04日
    浏览(54)
  • 【网络编程】TCP流套接字编程(TCP实现回显服务器)

    Socket(既能给客户端使用,也能给服务器使用) 构造方法 基本方法: ServerSocket(只能给服务器使用) 构造方法: 基本方法: 客户端代码示例: 服务器代码示例: 运行结果: 代码执行流程: 服务器启动,阻塞在accept,等待客户端建立连接. 客户端启动.这里的new操作会触发和服务器之间建立连

    2024年04月25日
    浏览(66)
  • 简单版本视频播放服务器V3-前端优化-播放器在左,列表在右侧【推荐】【完成】

    做个家用版本的家庭影院,通过这个服务器可以给电脑,平板,手机等设备提供直接播放电影的作用,通过离线下载电影放入目录里就是就可以给全家提供电影播放了,通过浏览器就是可以访问电脑里面的视频,实现简单的家庭版本服务了。 1.0最终代码:【前端以这个为主;

    2024年02月16日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包