一、IO多路复用模型
IO多路复用模型是一种同步IO模型,其原理在于使用一个线程完成在集合内的多个文件句柄的监视,若在集合中发现句柄,则进行读写操作,反之这一直处于阻塞状态,直至在集合中发现句柄。
二、Select函数
(1) FD_ZERO(fd_set* fdset)
: 将fd_set变量的所有位初始化为0。(2) FD_SET(int fd, fd_set* fdset)
:在参数fd_set指向的变量中注册文件描述符fd的信息。 简单来说就是将socket(fd)加入集合(fdset
)中(3) FD_CLR(int fd, fd_set* fdset)
:参数fd_set指向的变量中清除文件描述符fd的信息(4) FD_ISSET(int fd, fd_set* fdset)
:若参数fd_set指向的变量中包含文件描述符fd的信息,则返回真。
三、过程
将需要查看的套接字加入一个集合内,将集合交给select在内核查看,一段时间后,校验谁还在集合内,在集合内的socket代表发生了网络事件。
请求IO响应机制
同步:请求IO操作之后,等待结果,当结果返回之后,在处理其他事情。
异步:请求IO操作之后,处理其他事情,如果IO操作发生,会发送通知。
代码流程
1.定义一个集合fd_set;
2.清空集合 FD_ZERO();
3.将需要查看的socket 加入集合内 FD_SET();
4.将集合交给select 查看;
5.校验集合中的socket FD_ISSET();
四、Select优点
1.单线程处理多个套接字;
2.默认套接字数量限制64位,最多1024;
3.集合的底层数据结构是数组;
4.可跨平台、简单、方便;
5.阻塞采用同步状态;
五、服务端全部代码
1.tcpservice.h
#ifndef TCPSERVICE_H
#define TCPSERVICE_H
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <list>
#include <map>
//socket对应的--包大小,缓冲区,偏移量
struct stru_pack{
int m_nPackSize;
char *m_pszbuf;
int m_noffset;
};
using namespace std;
class TCPService
{
public:
TCPService();
~TCPService();
public:
bool initNetWork(const char* szip = "127.0.0.1",short nport = 1234);//初始化函数
void unInitNetWork(const char* szerr="");//删除函数
bool sendData(SOCKET sock ,const char*szbuf,int nlen);
void recvData();
public:
//线程函数
static DWORD WINAPI ThreadAccept(LPVOID lpvoid);
static DWORD WINAPI ThreadRecv(LPVOID lpvoid);
public:
bool b_flagQuit;//标志
SOCKET m_socklisten;//套接字
std::list<HANDLE> m_listthread;//线程链表
fd_set m_fd_set;//集合
std::map<DWORD,stru_pack*> m_mapSocketToPack;//映射socket对应的结构体(stru_pack)
};
#endif // TCPSERVICE_H
2.tcpservice.cpp
#include "tcpservice.h"
TCPService::TCPService(){
m_socklisten = 0;
b_flagQuit = true;
FD_ZERO(&m_fd_set);//清空集合
}
TCPService::~TCPService(){}
bool TCPService::initNetWork(const char *szip, short nport)
{
//1
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return false;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return false;
}
else
printf("The Winsock 2.2 dll was found okay\n");
//2
m_socklisten = socket(AF_INET,SOCK_STREAM,0);
if(INVALID_SOCKET == m_socklisten){
unInitNetWork("sock erro\n");
return false;
}
//3
sockaddr_in service;
service.sin_addr.S_un.S_addr = inet_addr(szip);
service.sin_family = AF_INET;
service.sin_port = htons(nport);
if (bind(m_socklisten, (sockaddr *) &service, sizeof (service)) == SOCKET_ERROR) {
unInitNetWork("bind erro\n");
return false;
}
//4
if (listen(m_socklisten, 10) == SOCKET_ERROR){
unInitNetWork("listen erro\n");
return false;
}
//1-4都为普通TCP服务端基本流程
//创建线程
HANDLE hThread = CreateThread(0,0,&ThreadAccept,this,0,0);
if(hThread){
m_listthread.push_back(hThread);
}
//利用单线程轮换接收所有客户端
hThread = CreateThread(0,0,&ThreadRecv,this,0,0);
if(hThread){
m_listthread.push_back(hThread);
}
return true;
}
DWORD TCPService::ThreadAccept(LPVOID lpvoid)
{
TCPService* pthis = (TCPService*)lpvoid;
while(pthis->b_flagQuit){
/****************接收客户端连接ip、nport信息*************/
sockaddr_in addrclient;
int nsize =sizeof(addrclient);
SOCKET sockWaiter = accept(pthis->m_socklisten,(struct sockaddr*)&addrclient,&nsize);
cout<<"cilent Ip: "<<inet_ntoa(addrclient.sin_addr)<<" client nport: "<<addrclient.sin_port<<endl;
FD_SET(sockWaiter,&pthis->m_fd_set);//将sockWaiter加入集合中
}
return 0;
}
DWORD TCPService::ThreadRecv(LPVOID lpvoid)
{
TCPService* pthis = (TCPService *)lpvoid;
while(pthis->b_flagQuit){
/********************接收客户端的数据******************/
pthis->recvData();
}
return 0;
}
void TCPService::unInitNetWork(const char *szerr)
{
b_flagQuit = false;
for(auto &ite:m_listthread){
if(WAIT_TIMEOUT == WaitForSingleObject(ite,100)){
TerminateThread(ite,-1);
}
if(ite){
CloseHandle(ite);
ite =NULL;
}
m_listthread.clear();
}
printf(szerr);
if(m_socklisten){
closesocket(m_socklisten);
m_socklisten = 0;
}
WSACleanup();
}
void TCPService::recvData()
{
SOCKET sockWaiter;//此时的socket
int nReadNum;//接收recv的返回值
int PackSize;//包大小
fd_set fdst;//集合
TIMEVAL tv;//检测时间
tv.tv_sec=0;
tv.tv_usec=100;
while(b_flagQuit){
//接收包大小
fdst = m_fd_set;
//将集合交给select
select(0,&fdst,0,0,&tv);
//校验是否发生网络事件
for(int i =0 ;i < m_fd_set.fd_count;i++){
if(FD_ISSET(m_fd_set.fd_array[i],&fdst)){
//用sockWaiter变量接一下此时集合中的socket
sockWaiter = m_fd_set.fd_array[i];
//创建一个空的结构体指针
//映射socketwaiter对应的一个stru_pack的结构体,在后续接收中使用
//m_mapSocketToPack[sockWaiter]创建键为socketWaiter的映射
stru_pack *p = m_mapSocketToPack[sockWaiter];
if(!p){
//第一次接收包大小
nReadNum = recv(sockWaiter,(char*)&PackSize,sizeof(int),0);
//判断读取错误
if(nReadNum <= 0){
if(GetLastError() == 10054){
closesocket(sockWaiter);
//移除socketwaiter
FD_CLR(sockWaiter,&m_fd_set);
}
continue;
}
//对p开辟空间,并赋值
p = new stru_pack;
p->m_nPackSize=PackSize;
p->m_noffset=0;
p->m_pszbuf = new char[PackSize];
m_mapSocketToPack[sockWaiter] = p;//对键为socketWaiter的值写入
}
else{
//第2-n次接收包内容
//nReadNum 为每次接收的大小
nReadNum = recv(sockWaiter,p->m_pszbuf+p->m_noffset,p->m_nPackSize,0);
if(nReadNum > 0){
p->m_noffset+=nReadNum;//将偏移量的值改为累计接收的大小
p->m_nPackSize-=nReadNum;//m_nPackSize为此刻接收多少大小
}
if(p->m_nPackSize == 0){
//当包全部传输完成后输出其内容
printf("%s\n",p->m_pszbuf);
delete p;
m_mapSocketToPack[sockWaiter] = NULL;
}
}
}
}
}
}
3.main.cpp
测试时客户端需要先发包大小,再发包内容,该服务端才能接收到数据。
#include <QCoreApplication>
#include "network/tcpservice.h"//引入头文件
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TCPService tp;
if(tp.initNetWork()){
cout<<" server is running!!! "<<endl;
}
else{
cout<<"server err"<<endl;
}
return a.exec();
}