TCP之 Event select模型

前记:select模型主要用于解决传统tcp通信线程过多的问题,而EventSelect模型则用于解决select模型的效率问题,因为select模型的内部是使用Sleep函数来阻塞线程,然后消耗系统时间片,从而降低了效率,而event select模型则使用WSAEVENT的通知机制。

服务端:

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

#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>//必须放在windows.h前面
#include <Windows.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")

SOCKET ArrSocket[1024]={0};
WSAEVENT ArrEvent[1024]={0};
DWORD dwTotal = 0;
DWORD dwIndex = 0;
int clientNum = 0;

BOOL WinsockInit()
{
	WSADATA data={0};
	if (WSAStartup(MAKEWORD(2,2), &data))
	{
		return FALSE;
	}
	if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2)
	{
		return FALSE;
	}
	return TRUE;
}


//用于解决select模型没有充分利用时间片的问题,同时将线程数再减少一个
DWORD WINAPI ListenThreadProc(LPARAM lparam)
{

	char buf[1024]={0};
	SOCKET sockClient = INVALID_SOCKET;//用于accept临时的SOCKET
	WSANETWORKEVENTS NetWorkEvent = {0};
	while (TRUE)
	{
		dwIndex = WSAWaitForMultipleEvents(dwTotal, ArrEvent , FALSE, 100, FALSE);
		if (dwIndex == WSA_WAIT_TIMEOUT)
		{
			continue;
		}

		WSAEnumNetworkEvents(ArrSocket[dwIndex-WSA_WAIT_EVENT_0], ArrEvent[dwIndex-WSA_WAIT_EVENT_0], &NetWorkEvent);

		//当有客户端连接时,会监听到客户端连接事件
		if (NetWorkEvent.lNetworkEvents & FD_ACCEPT)
		{
			if (NetWorkEvent.iErrorCode[FD_ACCEPT_BIT] != 0)
			{
				continue;
			}
			sockClient = accept(ArrSocket[dwIndex-WSA_WAIT_EVENT_0], NULL, NULL);//这儿也可以把客户端的地址信息接收回来
			if (sockClient == INVALID_SOCKET)
			{
				continue;
			}
			//和客户端连接完成后就保存相应数据
			WSAEVENT newEvent = WSACreateEvent();
			WSAEventSelect(sockClient, newEvent, FD_READ | FD_WRITE | FD_CLOSE);//客户端的socket我们关注3个事件
			ArrSocket[dwTotal]=sockClient;
			ArrEvent[dwTotal]=newEvent;
			++dwTotal;
			++clientNum;
		}

		//当客户端向服务端发送数据时,服务端会接收到这个消息
		if (NetWorkEvent.lNetworkEvents & FD_READ)
		{
			if (NetWorkEvent.iErrorCode[FD_READ_BIT] != 0)
			{
				continue;
			}
			recv(ArrSocket[dwIndex - WSA_WAIT_EVENT_0], buf, sizeof(buf), 0);
			printf("服务端接收:%s\n", buf);
			send(ArrSocket[dwIndex - WSA_WAIT_EVENT_0], buf, strlen(buf), 0);
		}

		//服务端准备发送数据给客户端,当服务端操作send函数时,会接收到这个消息
		if (NetWorkEvent.lNetworkEvents & FD_WRITE)
		{
			if (NetWorkEvent.iErrorCode[FD_WRITE_BIT] !=0)
			{
				continue;
			}
			printf("发送一些数据。。\n");
		}

		//当客户端closesocket关闭socket时,会接收到这个消息
		if (NetWorkEvent.lNetworkEvents & FD_CLOSE)
		{
			if (NetWorkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
			{
				continue;
			}
			closesocket(ArrSocket[dwIndex-WSA_WAIT_EVENT_0]);
		}
	}
	return 0;
}

void main()
{
	//初始化网络环境
	if (!WinsockInit())
	{
		return;
	}

	//创建用于监听的套接字  AF_INET:IPV4版本
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (sockSrv == INVALID_SOCKET)
	{
		return;
	}

	//地址绑定-告诉操作系统是在哪一个地址及端口
	int port = 5099;
	SOCKADDR_IN addrSrv;
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port); //1024以上的端口号,htons本地转换为网络数据
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//电脑上所有的网络ip


	//绑定服务端地址
	int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
	if(retVal == SOCKET_ERROR)
	{
		printf("绑定bind失败:%d\n", WSAGetLastError());
		return;
	}

	//进行EventSelect特有的操作
	WSAEVENT ListenEvent = WSACreateEvent();
	WSAEventSelect(sockSrv, ListenEvent, FD_ACCEPT | FD_CLOSE);//仅关注两个时事件,

	if(listen(sockSrv,5/*SOMAXCONN*/) ==SOCKET_ERROR)
	{
		printf("监听listen失败:%d\n", WSAGetLastError());
		return;
	}

	//使用一个子线程用来处理事件,也可以在主线程中处理
	CreateThread(NULL,NULL, (LPTHREAD_START_ROUTINE)ListenThreadProc, NULL, NULL, NULL);

	//初始只是装入一个用于监听客户端连接的socket
	ArrSocket[dwTotal]=sockSrv;
	ArrEvent[dwTotal]=ListenEvent;
	++dwTotal;


	system("pause");
	if (sockSrv != INVALID_SOCKET)
	{
		closesocket(sockSrv);
	}
	WSACleanup();
}

客户端:

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

#include "stdafx.h"
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
	//加载套接字
	WSADATA wsaData;
	char buff[1024];
	memset(buff, 0, sizeof(buff));

	if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		printf("Failed to load Winsock");
		return;
	}

	SOCKADDR_IN addrSrv;
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(5099);//http默认端口
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	//创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
	if(SOCKET_ERROR == sockClient){
		printf("Socket() error:%d", WSAGetLastError());
		return;
	}

	//向服务器发出连接请求
	if(connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){
		printf("Connect failed:%d", WSAGetLastError());
		return;
	}

	int iRecvLen = 0;

	//发送数据
	char* buffSend = "hello, this is a Client....";
	iRecvLen = send(sockClient, buffSend, strlen(buffSend), 0);
	
	
	//接收数据
	iRecvLen = recv(sockClient, buff, sizeof(buff), 0);
	printf("%s\n", buff);

	//关闭套接字
	closesocket(sockClient);
	WSACleanup();
	system("pause");
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值