项目地址
1.WinSock套接字有阻塞模式和非阻塞模式两种,可用 ioctlsocket函数设置,cmd使用FIONBIO,0为阻塞。
阻塞socket,又被称为锁定状态的socket。非阻塞socket,又被称为非锁定状态的socket。当调用一个Windows API时, API的调用会耗费一定的CPU时间。当一个操作完成之后才返回到用户态,称其为阻塞,反之,则为非阻塞。
2.WinSock套接字IO模型有:
select(选择)
WSAAsyncSelect(异步选择)
WSAEventSelect(事件选择)
Overlapped I/O(重叠式I / O)
Completion port(完成端口)
3.本篇主要介绍select模型
注:这里使用WinSock2.h
fd_set结构体作为socket组,使用FD_ISSET函数可检查某个组是否准备好,FD_CLR从某个组中移除某个socket,FD_SET用于将某个socket加入某个组,FD_ZERO将某个组清空。
select函数在WinSock2.h头文件内,它有五个参数。
第一个int参数是为了兼容Berkeley socket,一般忽略填0。
第二个参数fd_set是指向一组要检查可读性的socket的指针
第三个参数fd_set是指向一组要检查可写性socket的指针。
第四个参数fd_set是指向一组要检查异常条件的socket的指针。
第五个参数timeval是超时时间,超过时间没有任何一个组可用,select将返回一个int值
返回值为0时代表无可用操作,为-1时是出错。其他情况下可用FD_ISSET函数检测读写socket组,这三个socket组至少要有一个不为nullptr。
当某一组的某一个socket可用时,即可进行读写或异常处理操作。
服务端监听socket应加入检查可读性socket组中,如监听socket可读时,说明有客户端需要连接,应使用accept函数接受连接请求。
接受请求的socket也应加入读写和异常检查组中,当其中某个socket可读时,说明客户端发来了数据,应使用recv函数接受数据。
数据发送情况自行编写,由一个bool值isSending控制,当组中某个socket可写并且这个连接的isSending为true时,使用send函数发送数据。
注:使用TCP协议
具体实现可点击查看,服务端和客户端均可使用。
socket初始化,头文件需要加入:#pragma comment(lib,"Ws2_32.lib")
void TService::StartUp(SOCKADDR_IN & sockAddr, IOType type)
{
this->error = WSAStartup(MAKEWORD(2, 2), &wsaData);//初始化WS2_32.dll
if (error == SOCKET_ERROR)
{
std::cout << "WSAStartup faild!error=" << error << std::endl;
delete this;
return;
}
this->listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
error = bind(listenSocket, (struct sockaddr *) &sockAddr, sizeof(SOCKADDR_IN));//绑定完成
if (error == SOCKET_ERROR)
{
std::cout << "bind faild!error=" << error << std::endl;
delete this;
return;
}
error = listen(listenSocket, maxListener);
if (error == SOCKET_ERROR)
{
std::cout << "listen faild!error=" << error << std::endl;
delete this;
return;
}
RegisterHandler();
std::cout << "server started successfully" << std::endl;
this->ioTyple = type;
switch (type)
{
case SelectMode:
InitSelect();
break;
case WSAAsyncSelectMode:
break;
case WSAEventSelectMode:
break;
case OverlappedMode:
break;
case CompletionPortMode:
break;
default:
break;
}
}
Select模型初始化和运行
void TService::InitSelect()
{
readfds = new fd_set();
writefds = new fd_set();
exceptfds = new fd_set();
timeout.tv_sec = 0; //秒
timeout.tv_usec = 30; //微秒
if (SetIOBlock(1) == SOCKET_ERROR) //非阻塞
{
std::cout << "Error setting io block" << std::endl;
return;
}
FD_SET(listenSocket,readfds); //监听socket加入读组
}
void TService::Select()
{
fd_set read = *readfds;
fd_set write = *writefds;
fd_set except = *exceptfds;
int res = select(0, &read, &write, &except, &timeout);
if (res == SOCKET_ERROR)
{
std::cout << "select error, code is " << WSAGetLastError() << std::endl;
return;
}
if (res == 0)
{
return;//表示当前无状态可控的socket
}
if (FD_ISSET(listenSocket, &read))
{
//listensocket可读,表示连接到达
SOCKET acp = accept(this->listenSocket, NULL, NULL);
if (acp == INVALID_SOCKET)
{
std::cout << "accept error,error code " << WSAGetLastError() << std::endl;
return;
}
//设置新到达socket为非阻塞模式,并加入socks以及fdset
if (ioctlsocket(acp, FIONBIO, &unblock) == SOCKET_ERROR)
{
std::cout << "ioctlsocket(acp) error,error code " << WSAGetLastError() << std::endl;
return;
}
FD_SET(acp, readfds); //新连接加入读写组
FD_SET(acp, writefds);
AddChannel(acp);
}
for (std::map::iterator iter = idChannels->begin(); iter != idChannels->end(); iter++)
{
TChannel *channel = iter->second;
SOCKET socket = channel->GetSocket();
if (FD_ISSET(socket, &read))
{
int error = channel->Receive();
if (error <= 0)
{
break;
}
}
if (FD_ISSET(socket, &write) && channel->isSending)
{
std::list* sendWrites = channel->sendWrites;
while (sendWrites->empty() == false)
{
NetDataWriter* sendWrite = sendWrites->front();
sendWrites->pop_front();
byte* sendData = &(sendWrite->GetData());
if (send(socket, (Byte*)sendData, sendWrite->GetWritePos(), 0) == SOCKET_ERROR)
{
channel->OnError("send data error");
}
delete sendWrite;
}
sendWrites->clear();
channel->isSending = false;
}
if (FD_ISSET(socket, &except))
{
std::cout << "except socket error";
}
}
}
数据发送,接收和分包处理
void TChannel::Send(const char* method, NetDataWriter* dataWrite)
{
int bodySize = dataWrite->GetWritePos(); //获取body写入大小
int packetSize = bodySize + NetPacket::GetHeadSize(); //加入head部分总长度
byte& bodyData = dataWrite->GetData(); //获取body部分数据
NetDataWriter *completeData = new NetDataWriter(packetSize);
byte* size = BitConvert::Int2Bytes(packetSize); //数据包总长度
byte* module = BitConvert::Short2Bytes(100); //获取module和cmd,两者组合为method,用于数据处理模块
byte* cmd = BitConvert::Short2Bytes(200);
completeData->Put(size, 0, 4);
completeData->Put(module, 0, 2);
completeData->Put(cmd, 0, 2);
completeData->Put(new byte[16], 0, 16); //IV,加密使用,占16字节,暂未使用
completeData->Put(&bodyData, 0, bodySize);
sendWrites->push_back(completeData); //加入待发送list,下一次轮询时发送
isSending = true;
delete dataWrite;
}
int TChannel::Receive()
{
isRecving = true;
int len=recv(m_socket, receRaw, MAX_RAWSIZE, 0); //每次接受8192字节
if (len == 0)
{
OnError("connection has been closed ");
service->RemoveChannel(id);
return 0;
}
else if (len == SOCKET_ERROR)
{
OnError("recv error");
service->RemoveChannel(id);
return SOCKET_ERROR;
}
else
{
std::cout << "rece data len=" << std::to_string(len) << std::endl;
}
Subpackage(len); //分包
isRecving = false;
return 1;
}
void TChannel::Subpackage(int len)
{
byte* receData = (byte*)receRaw;
if (totalSize == 0) //最新的数据包
{
totalSize = BitConvert::ToInt32(receData, offsetSize); //获取数据包大小
}
int size = totalSize - offsetSize >= len ? len : totalSize - offsetSize;
if (size == 0)
{
OnError("recv data length is zero");
return;
}
recvWrite->Put(receData, 0, size);//从缓存中写入数据包内
if (totalSize - offsetSize <= len)
{
byte* data = recvWrite->CopyData();
if (readCallBack)
{
readCallBack(this, data); //读取完毕调用回调函数
}
messageDispatcher->Dispatch(this, recvWrite->CopyData());//这个数据包已经接收完毕,分发处理
recvWrite->Reset(); //数据包分发完毕,重置偏移
int remainSize = len - totalSize - offsetSize; //剩余大小
totalSize = 0;
offsetSize = 0; //临时值清零
if (remainSize > 0) //下一个数据包数据已到达,这里可能是多个数据包
{
Subpackage(remainSize);
}
}
else
{
offsetSize += size; //这里需要继续接收数据,已接收数据包偏移值更新
Receive();
}
}