IOCP 简单服务器和客户端

<span style="font-family: Arial, Helvetica, sans-serif;">// iocp_client.cpp : 定义控制台应用程序的入口点。</span><span style="font-family: Arial, Helvetica, sans-serif;">//</span>

#include "stdafx.h"
#include <time.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <stdio.h>
void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD( 1, 1 );
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 1 ||
		HIBYTE( wsaData.wVersion ) != 1 ) {
			WSACleanup( );
			return; 
	}
	SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
	addrSrv.sin_family=AF_INET;
	addrSrv.sin_port=htons(6000);
	connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
	char recvBuf[100];
	int n=0;
	while (true)
	{
		memset(recvBuf,0,100);
		sprintf(recvBuf,"package %d\n",n);
		printf("send:%s",recvBuf);
		if(send(sockClient,recvBuf,strlen(recvBuf)+1,0)<=0)
		{
			printf("网络异常断开\n");
			break;
		}
		memset(recvBuf,0,100);
		recv(sockClient,recvBuf,100,0);
		printf("recv:%s\n",recvBuf);
		
		n++;
		if (n>=20000)
		{
			n=0;
		}
		
	}
	closesocket(sockClient);
	WSACleanup();
	system("pause");
}




// iocp_server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <iostream>
using namespace std;
// 单句柄数据
typedef struct tagPER_HANDLE_DATA
{
 SOCKET Socket;
 SOCKADDR_STORAGE ClientAddr;
 // 将和这个句柄关联的其他有用信息,尽管放在这里面吧
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
// 但I/O 操作数据
typedef struct tagPER_IO_DATA
{
 OVERLAPPED Overlapped;
 WSABUF DataBuf;
 char buffer[1024];
 int BufferLen;
 int OperationType; // 可以作为读写的标志,为简单,我忽略了
}PER_IO_DATA, *LPPER_IO_DATA;
//线程函数
DWORD WINAPI ServerWorkerThread(LPVOID lpParam);
DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
 HANDLE CompletionPort = (HANDLE)lpParam;
 DWORD BytesTransferred;
 LPOVERLAPPED lpOverlapped;
 LPPER_HANDLE_DATA PerHandleData = NULL;
 LPPER_IO_DATA PerIoData = NULL;
 DWORD SendBytes;
 DWORD RecvBytes;
 DWORD Flags;
 BOOL bRet = FALSE;
 while (TRUE) //无限循环
 {
  bRet = GetQueuedCompletionStatus(CompletionPort,&BytesTransferred,(PULONG_PTR)&PerHandleData,(LPOVERLAPPED*)&lpOverlapped,INFINITE);
  // 检查成功的返回,这儿要注意使用这个宏CONTAINING_RECORD
  PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped,PER_IO_DATA,Overlapped);
  // 先检查一下,看看是否在套接字上已有错误发生
  if (0 == BytesTransferred)
  {
   closesocket(PerHandleData->Socket);
   GlobalFree(PerHandleData);
   GlobalFree(PerIoData);
   continue;
  }
  // 数据处理
  char sendBuf[100];
  sprintf_s(sendBuf,"Welcome %s to  %d %d \n",PerIoData->DataBuf.buf,PerHandleData->Socket,::GetCurrentThreadId());
  send(PerHandleData->Socket,sendBuf,strlen(sendBuf)+1,0);
  //WSASend()
   /*DataBuf.len = DATA_BUFSIZE; 
    DataBuf.buf = buffer;   
    for(i=0; i < SEND_COUNT ;i++) {   
     WSASend(PerHandleData->Socket, &DataBuf, 1,   &SendBytes, 0, &SendOverlapped, NULL); */

  // 成功了!!!这儿就收到了来自客户端的数据
  cout << PerIoData->DataBuf.buf << ::GetCurrentThreadId() << endl;
  Flags = 0;
  // 为下一个重叠调用建立单I/O 操作数据
  ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
  PerIoData->DataBuf.len = 1024;
  PerIoData->DataBuf.buf = PerIoData->buffer;
  PerIoData->OperationType = 0; // read
  WSARecv(PerHandleData->Socket,&(PerIoData->DataBuf),1,&RecvBytes,&Flags,&(PerIoData->Overlapped),NULL);
 }
 return 0;
}
int main(int argc, _TCHAR* argv[])
{
 //头部申明
 HANDLE CompletionPort;
 WSADATA wsd;
 SYSTEM_INFO SystemInfo;
 SOCKADDR_IN InternetAddr;
 SOCKET Listen;
 // 加载WinSock2.2
 WSAStartup(MAKEWORD(2, 2), &wsd);
 // 1.创建一个I/O 完成端口
 CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
 // 2.确定系统中有多少个处理器
 GetSystemInfo(&SystemInfo);
 // 3.基于系统中可用的处理器数量创建工作器线程
 for (int i = 0; i < int(SystemInfo.dwNumberOfProcessors * 2); ++i)
 {
  HANDLE ThreadHandle;
  // 创建一个服务器的工作器线程,并将完成端口传递到该线程
  ThreadHandle = CreateThread(NULL,0,ServerWorkerThread,CompletionPort,0,NULL);
  CloseHandle(ThreadHandle);
 }
 // 4.创建一个监听套接字,以下的套路都是固定的。
 Listen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
 //绑定和监听
 InternetAddr.sin_family = PF_INET;
 InternetAddr.sin_port = htons(6000);
 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 bind(Listen, (SOCKADDR*)&InternetAddr, sizeof(InternetAddr));
 listen(Listen, 5);
 //无限循环
 BOOL b = TRUE;
 while (b)
 {
  PER_HANDLE_DATA * PerHandleData = NULL;
  SOCKADDR_IN saRemote;
  SOCKET Accept;
  int RemoteLen;
  // 5.接收连接,并分配完成端口,这儿可以用AcceptEx 来代替,以创
  // 建可伸缩的Winsock 应用程序。
  RemoteLen = sizeof(saRemote);
  Accept = accept(Listen, (SOCKADDR*)&saRemote, &RemoteLen);
  // 6.创建用来和套接字关联的单句柄数据信息结构
  PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR,sizeof(PER_HANDLE_DATA));
  //cout << "Socket number " << Accept << " connected" << endl;
  PerHandleData->Socket = Accept;
  memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen);
  // 7.将接受套接字和完成端口关联起来
  CreateIoCompletionPort((HANDLE)Accept,CompletionPort,(DWORD)PerHandleData,0);
  // 开始在接受套接字上处理I/O
  // 使用重叠I/O 机制,在新建的套接字上投递一个或多个异步
  // WSARecv 或 WSASend 请求。这些I/O 请求完成后,工作者线程
  // 会为I/O 请求提供服务,之后就可以坐享其成了
  static int const DATA_BUFSIZE = 4096; 
  DWORD RecvBytes = 0;
  DWORD Flags = 0;
  // 单I/O 操作数据
  LPPER_IO_DATA PerIoData = NULL;
  PerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
  ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
  PerIoData->DataBuf.len = 1024;
  PerIoData->DataBuf.buf = PerIoData->buffer;
  PerIoData->OperationType = 0; // read
  WSARecv(PerHandleData->Socket,&(PerIoData->DataBuf),1,&RecvBytes,&Flags,&(PerIoData->Overlapped),NULL);
 }
 return 0;
}
 


//一个简单的使用例子 //连接远程服务器成功 或 接收到一个远程连接时,本函数将会被ioc.dll回调.在本函数中,应该向客户端列表中添加节点,记得加锁 // //2.s :套接字句柄,标志着一个新的连接 //3.u_addr:对端的IP地址,网络字节序 //4.u_port:对端的端口号,网络字节序 //5.flag :如果是本地连接上了一个远程服务器,flag值为0,或者,是接收到一个远程客户端的连接,这时,flag值为1 //6.返回值:返回一个自定义的值,这个值将在其他回调函数中作为参数传递(注意:就如套接字句柄一样,这个值也最 //好能标志一个连接,比如,客户端列表的某个节点的指针,假设用STL中的MAP来维护客户端列表,那么用KEY作为返回值也不错。) long _stdcall ioc_call_connect(HIOC hIoc,HINT s,long u_addr,long u_port,long flag) { long res=0; printf("socket(%d) connected\n",s); return res; } //断开与远程服务器的连接 或 远程客户端断开,本函数将会被ioc.dll回调.在本函数中,你可以删除客户节点,记得加锁 //s :套接字句柄,标志着哪个连接断开了(在本回调函数返回之前,套接字句柄s不可能被重新利用,所以,用s作为关键字构建客户端列表也是没有问题的) //res:ioc_call_connect回调函数返回的那个值,如果它是客户端列表的某个节点的话,那么可以删除了。 void _stdcall ioc_call_disconnect(HIOC hIoc,HINT s,long res,long flag) { printf("socket(%d) disconnected\n",s); } //当ioc内部一收到数据就会回调本函数,所谓数据有可能是多个数据包,也有可能是一个不完整的数据包 //hIoc,s,flag都不再多做解释 //res :ioc_call_oprate_dat和ioc_call_disconnect一定是被线性回调的,不可能存在同时执行的情况,所以,res如果指向某个节点的话,在本函数中可以不加锁地尽情访问,在本函数返回之前,res不会被释放掉 //hArg:数据调度句柄 //data:数据 //len :数据大小 //返回值:返回剩余未处理完的字节数 long _stdcall ioc_call_oprate_dat(HIOC hIoc,HARG hArg,HINT s,long res,long flag,char *data,long len) { //假如数据包格式是这样: //struct DATAPACK //{ // long size; long cmd; ...... //}; //如果是这样的话,那么典型的处理方法如下: char *p=data; long size_res=len;//收到数据的总字节数 long size_per;//其中某一个数据包的字节数 while(1) { if(size_res<4) return size_res;// size_per=*(long*)(p+0x00);//取数据包的实际长度 if(size_per<0 || size_per>某个最大值) { ::iocCommon_CloseSocket(hIoc,s); return 0; } if(size_res<size_per) return size_res;//剩余数据不够一个完整的数据包,返回 //得到一个完整的数据包,可以就地处理,但如果处理这个数据包将会很耗时,那么为了不阻塞工作线程, //只好将其调度给数据处理线程,这里其实可以定义一个结构,除了将数据包调度出去,还可以附带一些其他信息 if(数据包处理起来比较简单) { //处理 } else//调度给数据处理线程 { char *msg=(char*)::malloc(len); ::memcpy(msg,p,size_per); ::iocCommon_DispatchMessage(hIoc,hArg,msg);//hArg就只有这么一个作用 } p+=size_per; size_res-=size_per; } } //阻塞数据处理线程回调函数 void _stdcall ioc_call_oprate_msg(HIOC hIoc,void *msg) { //处理数据包 ::free(msg);//根据谁分配谁释放的原则,释放msg } int main() { ::iocCommon_Startup(); long addr_con=(long)ioc_call_connect; long addr_dis=(long)ioc_call_disconnect; long addr_dat=(long)ioc_call_oprate_dat; long addr_msg=(long)ioc_call_oprate_msg;//这个参数是可选的,可以不要专门的阻塞数据处理线程 HIOC hIoc=::iocCommon_Create(3072,128,addr_con,0,addr_dis,0,addr_dat,0,addr_msg,0);//创建IOC ::iocCommon_SetOprateThread(hIoc);//增加一个工作线程 ::iocCommon_SetOprateThread(hIoc);//增加一个工作线程 //启动服务器,内部循环调用阻塞的accept函数,ioc不考虑客户端连接服务器有多困难,而只考虑如何高效地进行数据传输 //可以再创建几个线程,多调用几个iocServer_Start,各个iocServer_Start绑定不同端口也可以 ::iocServer_Start(hIoc,NULL,6800); ::iocCommon_Cleanup(); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值