select
void TestModel::doSelect(int _sock){
/*
principle:
user transport fd_vec to kernel ,kernel check fd signal state ,and return have signal state fd_vec to user
interface:
具体的操作:
(1)四个操作宏
FD_ZERO() 将列表清零
FD_SET() 将fd添加到列表中
FD_CLR() 将fd从列表清除
FD_ISSET() 判断fd是否存在列表
(2)检测fd信号状态的函数
int select (int __nfds, fd_set *__restrict __readfds,
fd_set *__restrict __writefds,
fd_set *__restrict __exceptfds,
struct timeval *__restrict __timeout);
param:
_nfds: 检测的最大的fd的大小,例如5,则大于或者等于5的fd将不检测
fd_set: fd的数组,有大小限制(一般1024),select 可以检测可读,可写,错误
_time_out: 超时设置
*/
fd_set client_set;
FD_ZERO(&client_set);
FD_SET(_sock, &client_set);
const int recv_buf_stander_len = (1 << 10) * 16;
char recv_buf[recv_buf_stander_len]{ 0 };
int fd_max = _sock;
timeval time_out{ 1,0 };
while (true) {
fd_set read_set = client_set;//because fd_set will change ,so we must copy it
int signal_count = select(fd_max+1, &read_set, NULL, NULL, &time_out);
if (signal_count == 0)continue;
if (FD_ISSET(_sock, &read_set) == true) {//listen sock have client enter
sockaddr_in addr{ 0 };
unsigned int addr_len = sizeof(addr);
auto client_sock = accept(_sock, (sockaddr*)&addr, &addr_len);
if (client_sock == -1) {
LOGXF(errno, VAR_DATA(strerror(errno)));
sleep(-1);
exit(1);
}
FD_SET(client_sock, &client_set);
addClient(&addr, client_sock);
fd_max = std::max(client_sock, fd_max);
LOGXD(VAR_DATA(client_sock));
}
else {//client recv
vector<int> need_delete_sock_vec{};
for(auto it:m_imp->m_clients){
int client_sock = it->c_sock;
if (FD_ISSET(client_sock, &read_set) == false)continue;
int recv_len = recv(client_sock, recv_buf, recv_buf_stander_len, 0);
if (recv_len <= 0) { need_delete_sock_vec.emplace_back(client_sock); continue; }
//LOGXD("recv data", string(recv_buf, recv_len), VAR_DATA(client_sock));
}
for(auto it:need_delete_sock_vec){
FD_CLR(it, &client_set);
delClient(it);
}
}
}
}
poll
void TestModel::doPoll(int _sock){
/*
principle:
user transport poll_fd_vec to kernel ,kernel check fd signal state ,and will change poll_fd_revents,vec no change
poll检车所需的信号状态,然后改变的是revents,所以,使用的时候还是遍历数组,和select 区别不同的是fd_set传递进去和传递出来
的是不一样的(client sock个数不一样了,只是返回有信号的sock),所以在fd_set那里,我们得复制一份出来,但是pool_fd,client的sock
并没有改变,so. 还有就是poll个数不限制了
(1)data struct
struct pollfd{
int fd; /* File descriptor to poll.
short int events; /* Types of events poller cares about.
short int revents; /* Types of events that actually occurred.
};
poll (
struct pollfd *__fds,//poll_fd_vec
nfds_t __nfds, //check max_fd
int __timeout)//unit is ms
*/
const int fd_max_count = (200);//理论上可以存在fd_max_count-1个client,1是listen,但是如果经常fd_max_count-1,程序会出错,一般fd_max_count-4
pollfd client_fds[fd_max_count]{-1};
client_fds[0].fd = _sock;
client_fds[0].events = POLLIN;
int max_fd = _sock;
timeval time_out{ 1,0 };
const int recv_buf_stander_len = (1 << 10) * 16;
char recv_buf[recv_buf_stander_len]{ 0 };
while (true) {
int signal_count = poll(client_fds, max_fd + 1,1);
if(signal_count==0)continue;
LOGXD(VAR_DATA(signal_count));
int signal_number = 0;//increase effective
//1.deal listen
if (client_fds[0].revents & POLLIN) {//listen sock,accept
sockaddr_in addr{ 0 };
unsigned int addr_len = sizeof(addr);
auto client_sock = accept(_sock, (sockaddr*)&addr, &addr_len);
if (client_sock == -1) {
LOGXF(VAR_DATA(strerror(errno)));
sleep(-1);
exit(1);
}
addClient(&addr, client_sock);
max_fd = std::max(max_fd, client_sock);
for(int i=1;i< fd_max_count;i++){
if (client_fds[i].fd <= 0) {
client_fds[i].fd = client_sock;
client_fds[i].events = POLLIN;
break;
}
}
signal_number += 1;
}
//2.deal client
for(int i=1;i<fd_max_count;i++){
//LOGXT(VAR_DATA(i));
if (signal_number >= signal_count)break;
if (client_fds[i].fd <= 0)continue;
if (client_fds[i].revents & (POLLIN | POLLERR)) {
++signal_number;
int client_sock = client_fds[i].fd;
int recv_len = recv(client_sock, recv_buf, recv_buf_stander_len, 0);
if (recv_len <= 0) {
delClient(client_sock);
client_fds[i].fd = -1;
continue;
}
LOGXD("recv data", string(recv_buf, recv_len), VAR_DATA(client_sock));
}
}
LOGXD(VAR_DATA(signal_number));
}
}
epoll
void TestModel::doEpoll(int _sock){
/*
epoll 不用从用户态传递fd_vec给内核检测相关的信号,用户直接调用相关函数操作存储在内核的fd_vec
相关的函数:
int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event) __THROW;//用于将fd加入\删除\x修改内核态的epoll_fd_vec
int signal_count = epoll_wait(epoll_fd, client_events, max_event_count, 20);//用于检测相关的信号,
返回值client_events数组,大小是signal_count,20超时毫秒
max_event_count是返回数据最大的数组大小,与epoll_create的大小值无关
*/
int epoll_fd = epoll_create((1<<20)*10);//内核数组的大小,可接受client 连接的近似数量
{
epoll_event temp;
temp.events = EPOLLIN;
temp.data.fd = _sock;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, _sock, &temp);
}
const int max_event_count = 200;
epoll_event client_events[max_event_count]{ 0 };//
const int recv_buf_stander_len = (1 << 10) * 16;
char recv_buf[recv_buf_stander_len]{ 0 };
while (true) {
int signal_count = epoll_wait(epoll_fd, client_events, max_event_count, 20);
if (signal_count == 0)continue;
for(int i=0;i<signal_count;i++){
if (client_events[i].data.fd == _sock) {//listen sock
sockaddr_in addr{ 0 };
unsigned int addr_len = sizeof(addr);
auto client_sock = accept(_sock, (sockaddr*)&addr, &addr_len);
if(client_sock==-1){
LOGXF(VAR_DATA(strerror(errno)));
sleep(-1);
exit(1);
}
addClient(&addr, client_sock);
{
epoll_event temp;
temp.events = EPOLLIN;
temp.data.fd = client_sock;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_sock, &temp);
}
}
else {//client sock
int client_sock = client_events[i].data.fd;
int recv_len = recv(client_sock, recv_buf, recv_buf_stander_len, 0);
if (recv_len < 0) {
delClient(client_sock);
{
epoll_event temp;
temp.events = EPOLLIN;
temp.data.fd = client_sock;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_sock, &temp);
}
continue;
}
LOGXD("epoll recv data", string(recv_buf, recv_len), VAR_DATA(client_sock));
}
}
}
close(epoll_fd);
}
IOCP
void TestModel::doIOCP(int _scok) {
/*
原理:
将client的sock(实质就是fd)和同一个fd相关联,这样子只需要检测fd的状态,获取的client_fd的所需的信号的状态
HANDLE WINAPI CreateIoCompletionPort(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE ExistingCompletionPort,
_In_ ULONG_PTR CompletionKey,
_In_ DWORD NumberOfConcurrentThreads
);
官方文档是这样的:
在创建新的完成端口时,
FileHandle==INVALID_HANDLE_VALUE
ExistingCompletionPort==NULL
并且此时忽略CompletionKey,然后就会返回新的完成端口的handle
将handle 和完成端口绑定的时候
CreateIoCompletionPort((HANDLE)acceptSocket, completionPort, (DWORD)acceptSocket, NULL);
if completionPort!=NULL NumberOfConcurrentThreads will ingnore
CompletionKey这东西是自己设置的,会一直跟随
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, //指定的IOCP
LPDWORD lpNumberOfBytes, //一次完成后的I/O操作所传送数据的字节数
PULONG_PTR lpCompletionKey, //当文件I/O操作完成后,用于存放与之关联的CK
LPOVERLAPPED *lpOverlapped, //为调用IOCP机制所引用的OVERLAPPED结构
DWORD dwMilliseconds); //用于指定调用者等待CP的时间
int WSAAPI WSARecv (
SOCKET s, //socket
LPWSABUF lpBuffers, //存放数据的数组的指针
DWORD dwBufferCount, //数组的数量
LPDWORD lpNumberOfBytesRecvd, //接收字节数的指针
LPINT lpFlags, //指向标志位的指针
LPWSAOVERLAPPED lpOverlapped, //指向重叠结构的指针
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);//调用例程的指针
如果需要什么样子的信号,得提前给client 的socket设置,就比如需要recv的,则
WSARecv(client_sock, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
这其中lpBuffers(PerIoData->DataBuf)得存在内存空间,不要为空,
检测状态:
GetQueuedCompletionStatus(completionPort, &BytesTransferred,&completion_key, (LPOVERLAPPED*)&PerIoData, INFINITE)
这个时候BytesTransferred接收的数据的大小,
completion_key就是与client相对应的key,我们可以直接设置为sock,也可存相应的结构体的指针,
然后可以从指针找到结构体就可以找到sock,只要想办法和sock建立起联系就好
PerIoData从这里直接获取到接收的数据
最后因为传递进去的信号已经使用过了,相当于没有了,我们得继续传递相应的信号进去,为下一次的信号检测做准备
WSARecv(client_sock, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
*/
/// 结构体定义
const int recv_stander_size = (1 << 10) * 8;
typedef struct {
OVERLAPPED Overlapped;
WSABUF DataBuf;
CHAR Buffer[recv_stander_size];
}PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
do {
//start
HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (completionPort == NULL) {
LOGXF("create iocp error", VAR_DATA(GetLastError()));
break;
}
//2.open thread of check client_sock state
SYSTEM_INFO mySysInfo;
GetSystemInfo(&mySysInfo);
for (DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2 + 2); ++i) {
thread t([completionPort,this] {
DWORD BytesTransferred;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD completion_key = 0;
while (true){
if (false == GetQueuedCompletionStatus(completionPort, &BytesTransferred,
&completion_key, (LPOVERLAPPED*)&PerIoData, INFINITE)){
LOGXW("GetQueuedCompletionStatus false");
if ((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED)){
int client_sock = completion_key;
delClient(client_sock);
delete PerIoData;//this delete wsarecv new
continue;
}
else{
LOGXF("GetQueuedCompletionStatus failed!");
break;
}
}
int client_sock = completion_key;
if (BytesTransferred == 0){
// 说明客户端已经退出
int client_sock = completion_key;
delClient(client_sock);
delete PerIoData;//this delete wsarecv new
continue;
}
// 取得数据并处理
LOGXD("recv data", string((char*)(PerIoData->DataBuf.buf), BytesTransferred), VAR_DATA(client_sock));
// 继续向 socket 投递WSARecv操作
DWORD Flags = 0;
DWORD dwRecv = 0;
ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
PerIoData->DataBuf.buf = PerIoData->Buffer;
PerIoData->DataBuf.len =sizeof(PerIoData->Buffer);
WSARecv(client_sock, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
}
});
t.detach();
}
//3.start accept client
std::thread acceptThread([this, _scok,completionPort] {
do {
SOCKADDR_IN saRemote;
int RemoteLen;
SOCKET acceptSocket;
RemoteLen = sizeof(saRemote);
// 3.1 接收连接,并分配完成端,这儿可以用AcceptEx()
acceptSocket = accept(_scok, (SOCKADDR*)&saRemote, &RemoteLen);
if (SOCKET_ERROR == acceptSocket) { // 接收客户端失败
LOGXF("accpet client fail:", VAR_DATA(GetLastError()));
break;
}
//3.2 与completionPort绑定
CreateIoCompletionPort((HANDLE)acceptSocket, completionPort, (DWORD)acceptSocket, NULL);
//3.3
LPPER_IO_OPERATION_DATA PerIoData = new PER_IO_OPERATION_DATA();//here new ,no delete
ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
PerIoData->DataBuf.buf = PerIoData->Buffer;
PerIoData->DataBuf.len = sizeof(PerIoData->Buffer);
DWORD RecvBytes;
DWORD Flags = 0;
WSARecv(acceptSocket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &PerIoData->Overlapped, NULL);
//
addClient(&saRemote, acceptSocket);
} while (true);
});
acceptThread.detach();
} while (false);
}
good luck to you