首页 > 其他分享 >网络IO模型之select基础

网络IO模型之select基础

时间:2022-11-07 19:07:50浏览次数:51  
标签:std int 模型 -------------------------------------------------------------------- 


思考:为什么线程开销会大
一、IO 有两种操作,同步 IO 和异步 IO 。 
    同步 IO 指的是,必须等待 IO 操作完成后,控制权
才返回给用户进程 。 
    异步 IO 指的是,无须等待 IO 操作完成,就将控制权返回给用户进程。
网络中的 IO ,由于不同的 IO 设备有着不同的特点,网络通信中往往需要等待 。 常见的
有以下 4 种情况 。
    (1)、输入操作
    (2)、输出操作
    (3)、服务器接收连接请求
    (4)、客户端发送连接请求
2、四种网络IO模型
    阻塞是指 IO 操作需要彻底完成后才返回到用户空间;
    而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,
不需要等到 IO 操作彻底完成
    (1)、阻塞IO模型
        只有系统调用获得结果或者超时出错才返回结果
    (2)、非阻塞IO模型
        从用户进程角度讲,它发起一个 read操作后,并不需要等待,而是马上就得到了
一个结果 。 当用户进程判断结果是一个错误时,它就知道数据还没有准备好,于是它可以
再次发送 read 操作,在非阻塞式 IO 中,用户进程其实需要不断地主动询问kernel 数据是
否准备好。非阻塞的接口相比于阻塞型接口的显著差异在于被调用之后立即返回 。 
使用如下的函数可以将某句柄归设为非阻塞状态 :fcntl( fd , F_SETFL , O_NONBLOCK );
    缺点;循环调用recv()会很消耗CPU
    (3)、多路复用IO
        基本原理:就是有个函数(如select)会不断地轮询所负责的所有 socket,当某个 
    socket 有数据到达了,就通知用户进程,
    (4)、异步IO模型(后面具体来说)
三、select
1、函数原型

#include <sys/select.h>   
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

   maxfdp 一个整数值,指的是集合中所有文件描述符的范围,即文件描述符数量+1

   readfds,writefds,errorfds 都应该包括文件描述符

   timeout 超时时间

2、重要的宏

void FD_ZERO(int fd, fd_set *fdset); // 关闭描述字集上的所有位  

void FD_SET(int fd, fd_set *fd_set);// 打开描述字集上一个描述符位
   
void FD_CLR(int fd, fd_set *fdset);  // 关闭描述字集上一个描述符位 

int FD_ISSET(int fd, fd_set *fdset); // 判断描述符位是否在描述字集上

(1) FD_ZERO宏,初始化,将一个 fd_set类型变量的所有位都设为 0

// 下面上代码

// 服务器部分

SelectClient.h

#pragma once
#include <winsock2.h>
#include <iostream>
#include <vector>
#include <thread>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll

typedef std::vector<int> VecClientFd; // 保存已经连接上的客户端套接字描述符

/*---------------------------------------------------------------------------
** 类名 : SubServer
**---------------------------------------------------------------------------
** 功能 : 子服务器
**---------------------------------------------------------------------------
** Date Name
** 2018.02.06 Mark
**---------------------------------------------------------------------------*/
class SelectClient
{
public:

// 初始化
SelectClient();

// 释放
~SelectClient();

// 初始化网络服务
bool InitNetService();

private:

// 初始化套接字
int InitSocket();

// 响应客户端连接
static bool th_DealClientConnect(int iServerfd);

// 处理广播
static void DealBroadCast(char* bufferSend);

// 获取新增客户端套接字
static int QuerySockClient();

// 设置新增客户端套接字
static void SetSocketClient(int iSock_client);
private:

static SelectClient * m_pSelectClient; // 轮询客户端连接模块

VecClientFd m_VecClientFd; // 已经连接上的客户端套接字描述符

std::thread *m_thSelectClient; // 轮询客户端请求线程

int m_iSock_client;// 客户端连接

};

SelectClient.cpp

#include "SelectClient.h"

SelectClient * SelectClient::m_pSelectClient = NULL;
/*--------------------------------------------------------------------
** 名称 : SelectClient
**--------------------------------------------------------------------
** 功能 : 初始化
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.10 Mark
**-------------------------------------------------------------------*/
SelectClient::SelectClient()
{
m_pSelectClient = this;
}

/*--------------------------------------------------------------------
** 名称 : ~SubServer
**--------------------------------------------------------------------
** 功能 : 释放
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.10 Mark
**-------------------------------------------------------------------*/
SelectClient::~SelectClient()
{
delete m_thSelectClient;

}

/*--------------------------------------------------------------------
** 名称 : InitNetService
**--------------------------------------------------------------------
** 功能 : 初始化网络服务
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.10 Mark
**-------------------------------------------------------------------*/
bool SelectClient::InitNetService()
{
int iServerfd = InitSocket();

// 处理客户端连接
m_thSelectClient = new std::thread(th_DealClientConnect,iServerfd);
return true;
}

/*--------------------------------------------------------------------
** 名称 : InitSocket
**--------------------------------------------------------------------
** 功能 : 初始化网络
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : serverfd
**--------------------------------------------------------------------
** Date: Name
** 19.02.10 Mark
**-------------------------------------------------------------------*/
int SelectClient::InitSocket()
{
//初始化DLL
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if(NO_ERROR != iResult)
{
return 0;
}

//创建套接字
SOCKET iListenSocket = INVALID_SOCKET;
iListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if(INVALID_SOCKET == iListenSocket)
{
std::cout << "Create Socket Error!" << std::endl;

WSACleanup();

return 0;
}
else
{
std::cout << "Create Socket Success" << std::endl;
}

sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(9999);
//service.sin_zero = 0;
//----------------------
// Bind the socket.
iResult = bind(iListenSocket, (SOCKADDR *)&service, sizeof(service));
if(INVALID_SOCKET == iResult)
{
WSACleanup();

return 0;
}
else
{
std::cout << "Bind Socket Success" << std::endl;
}

int iRet = listen(iListenSocket, SOMAXCONN);
if(0 != iRet)
{
return 0;
}
else
{
std::cout << "Listen Socket Success" << std::endl;
}

return iListenSocket;
}
/*--------------------------------------------------------------------
** 名称 : th_DealClientConnect
**--------------------------------------------------------------------
** 功能 : 处理网络请求
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : serverfd
**--------------------------------------------------------------------
** Date: Name
** 19.02.10 Mark
**-------------------------------------------------------------------*/
bool SelectClient::th_DealClientConnect(int iServerfd)
{
if(0 == iServerfd)
{
return false;
}

fd_set client_fdset; /*监控文件描述符集合*/
struct timeval tv; /*超时返回时间*/

tv.tv_sec = 10;
tv.tv_usec = 0;

int conn_amount = 0; //用来记录描述符数量
SOCKET client_sockfd[5] = {0}; //存放活动的sockfd
int iMaxsock; //监控文件描述符中最大的文件号
iMaxsock = iServerfd;

char buffer[256];
memset(buffer, '\0', sizeof(buffer));

while(1)
{
/*初始化文件描述符号到集合*/
FD_ZERO(&client_fdset);

/*加入服务器描述符*/
FD_SET(iServerfd, &client_fdset);

//把活动的句柄加入到文件描述符中
for (int i = 0; i < 5; ++i)
{
//程序中Listen中参数设为5,故i必须小于5
if (client_sockfd[i] != 0)
{
FD_SET(client_sockfd[i], &client_fdset);
}
}
int ret = select(iMaxsock + 1, &client_fdset, NULL, NULL, &tv);
if(ret < 0) // 出错 -1
{
perror("select error!\n");

int i = WSAGetLastError();

break;

}
else if(ret == 0) // 超时 0
{
//printf("timeout!\n");

continue;
}


// 检测是否有网络IO请求
for (int i = 0; i < conn_amount; i++)
{
if (FD_ISSET(client_sockfd[i], &client_fdset))
{
int iClientFd = client_sockfd[i];
ret = recv(iClientFd, buffer, 1024, 0);
if (ret < 0)
{
//return -1;
FD_CLR(client_sockfd[i], &client_fdset);
client_sockfd[i] = 0;
}
else
{
std::cout << "buffer = " << buffer << "strlen(buffer)"<< strlen(buffer) <<std::endl;
DealBroadCast(buffer);
memset(buffer, '\0', sizeof(buffer));

}

}
}

// 检测是否有新的连接
if(FD_ISSET(iServerfd, &client_fdset))
{
struct sockaddr_in client_addr;
size_t size = sizeof(struct sockaddr_in);

int iSock_client = accept(iServerfd, (struct sockaddr*)(&client_addr), (int*)(&size));
if(iSock_client < 0)
{
perror("accept error!\n");
continue;
}

// 把连接加入到新的文件描述符集合中
if(conn_amount < 5)
{
client_sockfd[conn_amount++] = iSock_client;

std::cout << "sock_client " << iSock_client << "Accept" << std::endl;
m_pSelectClient->m_VecClientFd.push_back(iSock_client);
}

}


}
return true;
}

/*--------------------------------------------------------------------
** 名称 : DealBroadCast
**--------------------------------------------------------------------
** 功能 : 处理广播
**--------------------------------------------------------------------
** 参数 : sock_client 客户端连接
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.10 Mark
**-------------------------------------------------------------------*/
void SelectClient::DealBroadCast(char* bufferSend) // 设计成类似群聊
{
if(0 == strlen(bufferSend))
{
return;
}

VecClientFd::iterator itClientFd = m_pSelectClient->m_VecClientFd.begin();
for (itClientFd; itClientFd != m_pSelectClient->m_VecClientFd.end(); itClientFd++)
{
int iClientFd = *itClientFd;
send(iClientFd, bufferSend, strlen(bufferSend), 0);

std::cout << "BroadCast size = "<< strlen(bufferSend) <<"Info = " << bufferSend<<std::endl;

}
memset(bufferSend, '\0', sizeof(bufferSend));
}
/*--------------------------------------------------------------------
** 名称 : QuerySockClient
**--------------------------------------------------------------------
** 功能 : 查询新增客户端套接字
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : 新增客户端套接字
**--------------------------------------------------------------------
** Date: Name
** 19.02.18 Mark
**-------------------------------------------------------------------*/
int SelectClient::QuerySockClient()
{
int iSocket_client = m_pSelectClient->m_iSock_client;

return iSocket_client;
}
/*--------------------------------------------------------------------
** 名称 : SetSocketClient
**--------------------------------------------------------------------
** 功能 : 设置新增客户端套接字
**--------------------------------------------------------------------
** 参数 : iSock_client 新增客户端套接字
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.18 Mark
**-------------------------------------------------------------------*/
void SelectClient::SetSocketClient(int iSock_client)
{
if(iSock_client >0)
{
m_pSelectClient->m_iSock_client = iSock_client;
}

}

// 服务器入口

#include <iostream>
//#include "ConnectLoginServer.h"
#include "SelectClient.h"
#include <windows.h>

int main()
{
// ConnectLoginServer m_SubServer;
SelectClient m_SelectClient;
//
// bool bResult = m_SubServer.StartNetService();

bool bResult = true;
if(bResult)
{
m_SelectClient.InitNetService();
}


system("pause");

return 0;
}

// 客户端部分

// 头文件

#pragma once
#include <WinSock2.h>
#include <WS2tcpip.h>
#include<iostream>
#include<thread>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll

//网络地址
struct STNetAddress
{
const char* pszIp;
int iPort;
int iSocket;
};

// 邮件结构体
struct STMailInfo
{
//bool bSpecial;
const char* strTitle; // 邮件标题
const char* strContent; // 邮件内容
// STMailInfo()
// {
// //bSpecial = false;
// }

};
/*---------------------------------------------------------------------------
** 类名 : Client
**---------------------------------------------------------------------------
** 功能 : 客户端
**---------------------------------------------------------------------------
** Date Name
** 2019.02.11 Mark
**---------------------------------------------------------------------------*/
class Client
{
public:

// 初始化
Client();

// 释放
~Client();

// 开启网络服务
bool StartNetService();

// 网络测试
bool StartNetServiceTest();

// 开始广播
static void SendBroadCast();
private:

// 连接功能服务器
int LinkFunctionServer(const char*pszIP, int iPort);

// 等待广播消息
static void BroadCast(int iServerFd);

// 获取服务器id
static int QueryServerSocket();

private:

static Client* m_pClient; //客户端指针

std::thread* th_BroadCast; //广播

int m_iSubServerSocket; // 服务器套接字

static STMailInfo m_MailInfo;
static STNetAddress m_stNetAddr;
};

// 实现文件

#include "Client.h"
#define DEFAULT_BUFLEN 256
#define D_BROADCAST_SEND 64

Client* Client::m_pClient = NULL;

STMailInfo Client::m_MailInfo = {"",""};
STNetAddress Client::m_stNetAddr = { "",0,0 };

/*--------------------------------------------------------------------
** 名称 : Client
**--------------------------------------------------------------------
** 功能 : 初始化
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.11 Mark
**-------------------------------------------------------------------*/
Client::Client()
{
m_pClient = this;
}

/*--------------------------------------------------------------------
** 名称 : ~Client
**--------------------------------------------------------------------
** 功能 : 释放
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.11 Mark
**-------------------------------------------------------------------*/
Client::~Client()
{
delete th_BroadCast;
}

/*--------------------------------------------------------------------
** 名称 : StartNetService
**--------------------------------------------------------------------
** 功能 : 开启网络服务
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.11 Mark
**-------------------------------------------------------------------*/
bool Client::StartNetService()
{
//初始化DLL
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (NO_ERROR != iResult)
{
return 0;
}

//创建套接字
SOCKET iListenSocket = INVALID_SOCKET;
iListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if(INVALID_SOCKET == iListenSocket)
{
std::cout << "Create Socket Error!" << std::endl;

WSACleanup();

return false;
}
else
{
std::cout << "Create Socket Success" << std::endl;
}

sockaddr_in service;

service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(6666);

//----------------------
// Bind the socket.
iResult = connect(iListenSocket, (SOCKADDR *)&service, sizeof(service));
if (0 != iResult)
{
std::cout << "ConnectMainServer Failed " << iResult << std::endl;
}
else
{
std::cout << "ConnectMainServer Success!" << std::endl;
//const char *buf = "OK";
//send(iListenSocket, buf, strlen(buf), 0);
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

iResult = recv(iListenSocket, recvbuf, recvbuflen, 0);
if(iResult > 0)
{
std::cout << " recvbuf= " << recvbuf << std::endl;
printf("recvbuf = %s\n", recvbuf);

m_iSubServerSocket = LinkFunctionServer("127.0.0.1",6666);

if(m_iSubServerSocket > 0)
{
th_BroadCast = new std::thread(BroadCast,static_cast<int>(m_iSubServerSocket)); //收听广播

}
}
else if(0 == iResult)
{
std::cout <<"Connection Closed!" << std::endl;
}
else
{
std::cout << "Received Error"<< WSAGetLastError()<< std::endl;
}
}

return true;
}
/*--------------------------------------------------------------------
** 名称 : StartNetServiceTest
**--------------------------------------------------------------------
** 功能 : 网络测试
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.18 Mark
**-------------------------------------------------------------------*/
bool Client::StartNetServiceTest()
{
m_iSubServerSocket = LinkFunctionServer("127.0.0.1", 9999);

if(m_iSubServerSocket > 0)
{
th_BroadCast = new std::thread(BroadCast, static_cast<int>(m_iSubServerSocket)); //收听广播

}
return true;
}

/*--------------------------------------------------------------------
** 名称 : SendBroadCast
**--------------------------------------------------------------------
** 功能 : 开始广播
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.18 Mark
**-------------------------------------------------------------------*/
void Client::SendBroadCast()
{
char chSend[D_BROADCAST_SEND];

while(true)
{
memset(chSend, 0, strlen(chSend));

gets_s(chSend);
int iServerSocket = QueryServerSocket();

send(iServerSocket, chSend, sizeof(chSend), 0);
}

}

/*--------------------------------------------------------------------
** 名称 : LinkFunctionServer
**--------------------------------------------------------------------
** 功能 : 连接功能服务器
**--------------------------------------------------------------------
** 参数 : pszIP IP
** 参数 : iPort 端口
** 参数 : iSocket 套接字
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.15 Mark
**-------------------------------------------------------------------*/
int Client::LinkFunctionServer(const char* pszIP, int iPort)
{
if(iPort <= 0)
{
return 0;
}

//初始化DLL
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if(NO_ERROR != iResult)
{
return 0;
}

//创建套接字
SOCKET iListenSocket = INVALID_SOCKET;
iListenSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if(INVALID_SOCKET == iListenSocket)
{
std::cout << "Create Socket Error!" << std::endl;

WSACleanup();

return 0;
}
else
{
std::cout << "Create Socket Success" << std::endl;
}

sockaddr_in service;

service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr(pszIP);
service.sin_port = htons(iPort);

//----------------------
// Bind the socket.
iResult = connect(iListenSocket, (SOCKADDR *)&service, sizeof(service));
if(0 != iResult)
{
std::cout << "ConnectFunctionServer Failed " << iResult << std::endl;

return 0;
}
else
{
std::cout << "ConnectFunctionServer Success " << iResult << std::endl;

char buffer[256] = "OK I Came";
//strcmp(buffer, "OK I Came");
//sprintf(buffer, "OK I Came");
send(iListenSocket, buffer, strlen(buffer), 0);
std::cout <<"send buffer" << buffer << "strlen(buffer)"<< strlen(buffer) << std::endl;
memset(buffer, '\0', sizeof(buffer));
}

return iListenSocket;
}
/*--------------------------------------------------------------------
** 名称 : BroadCast
**--------------------------------------------------------------------
** 功能 : 广播
**--------------------------------------------------------------------
** 参数 : iServerFd 服务器套接字
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.15 Mark
**-------------------------------------------------------------------*/
void Client::BroadCast(int iServerFd)
{
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

while(1)
{
memset(recvbuf, '\0', sizeof(recvbuf));
int iResult = recv(iServerFd, recvbuf, 1024, 0);
if(iResult > 0)
{
std::cout << "Reveived BroadCast "<< recvbuf << std::endl;
}
else if (0 == iResult)
{
std::cout << "Connection Closed!" << std::endl;
}
memset(recvbuf, '\0', sizeof(recvbuf));
// else
// {
// std::cout << "Received Error" << WSAGetLastError() << std::endl;
// }

//Sleep(5000);
}
}
/*--------------------------------------------------------------------
** 名称 : QueryServerSocket
**--------------------------------------------------------------------
** 功能 : 获取服务器套接字
**--------------------------------------------------------------------
** 参数 : NULL
** 返值 : NULL
**--------------------------------------------------------------------
** Date: Name
** 19.02.18 Mark
**-------------------------------------------------------------------*/
int Client::QueryServerSocket()
{
int iServerSocket = m_pClient->m_iSubServerSocket;

return iServerSocket;
}

// 客户端入口

#include <iostream>
#include "Client.h"
#include <windows.h>

int main()
{
Client m_client;
//m_client.StartNetService();
m_client.StartNetServiceTest();

m_client.SendBroadCast();
system("pause");
return 0;
}

 

标签:std,int,模型,--------------------------------------------------------------------,
From: https://blog.51cto.com/u_11320078/5830850

相关文章

  • Springboot 用session监听器统计在线用户数量
    今天给大家分享这个吧。利用Springboot中的session监听器去实现统计在线用户数量的需求(当然其实用shiro或者security是框架自己带有会话管理的,用起来更加方便)。但是,接下来......
  • .net集成微信退款的错误,System.Net.WebException:请求被中止:未能创建SSL/TLS安全通道
    集成微信退款的时候,出现了这样的一个错误。  需要修改IIS的应用程序池的高级配置中的一个值。这个地方要改成True,好像不改是读不到支付的证书。 ......
  • 当tomcat启动的时候出现EOFException错误
    当tomcat启动的时候出现下面错误:[ERROR]org.apache.catalina.session.ManagerBase-IOExceptionwhileloadingpersistedsessions:java.io.EOFException或者严重:IO......
  • 回归模型评价指标总结
    回归评价指标总结1.MeanAbsoluteError,MAE平均绝对误差(MeanAbsoluteError,MAE),也称为L1损失。它是通过取预测值和实际值之间的绝对差值并在整个数据集中取平均值来计......
  • 2022最全Hbuilder打包成苹果IOS-App的详解
      本文相关主要记录一下使用Hbuilder打包成苹果IOS-App的详细步骤。介绍一下个人开发者账号:再说下什么是免费的苹果开发者账号,就是你没交688年费的就是免费账号,如果......
  • 用username.github.io在github创建一个个人空间
    对于技术人员来说,分享技术新的、学习经验都有许多平台,比如我们常用的CSDN,很多技术人员都会在上面创建自己的博客。但是以这种方式来分享,可能没有那么个性化,因此我们可以在gi......
  • Springboot Condition 实用讲解,只看一遍包学会
    前言该篇文章,还是一贯的风格,源码+示例+自言自语的分析,目的只有一个:就是想让大家都会玩 Condition、Conditional。正文先看看Condition是被放在包springcontext(上下文/......
  • Visual Studio创建自己的快捷代码片段
    功能说明:我们平常经常写一些重复的代码,我们可以把这些代码模板化,VisualStudio给我们提供是代码片段的功能可以满足我们的需求。具体操作如下:一、打开vs→工具→代码片段......
  • Git推送报错:remote: Support for password authentication was removed on August 13,
    根据账号密码校验时,推送失败,报错如图官方日志:从2021年8月13日开始,我们将在对Git操作进行身份验证时不再接受帐户密码,并将要求使用基于令牌(token)的身份验证官方......
  • Istio代理级指标
    代理级指标Envoy会生成其资源级别(例如Listener、Cluster等)的指标获取Envoy统计信息的常用方式有两程AdminInterface的/stats或/stats/prometheus用于接收统计......