前记: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");
}