MFC基于select模型的套接字类之服务器(6)

来源:互联网 发布:企业网络规划方案 编辑:程序博客网 时间:2024/05/16 14:53

(2)套接字的Select模型

Select模型是套接字中最常见的模型。它的核心是利用select()函数实现套接字的输入输出管理。利用select()函数,应用程序可以判断指定套接字上是否存在数据,如果套接字上存在数据,则调用recv()函数进行接收;还可以通过该函数判断能否向指定套接字上发送数据,即指定套接字是否已经准备好接收数据,如果已经准备好,则调用send()函数发送数据。

在使用Select模型时,需要用到fd_set结构以及FD_ZEROFD_SET等宏。

fd_set是一个管理多个套接字的结构体。该结构体的定义为

typedef struct fd_set{   u_int fd_count;   SOCKET fd_array[FD_SETSIZE];}fd_set;
其中,fd_count表示管理的套接字数量;fd_arraySOCKET类型的数组,其元素是管理的套接字,FD_SETSIZE的值是64,也就是说最多可以管理64个套接字。在前文提到的select()函数就是对fd_set类型的变量进行管理。

FD_ZERO宏:该宏的作用是初始化fd_set结构的对象;

FD_SET宏:该宏的作用是将指定的套接字加入到fd_set结构的对象中;

(2)Select模型的实现

ThreadFunc_RecvData()函数中,首先定义fd_set结构的对象,用来保存要管理的套接字:

fd_set socket_fs;

接下来通过while()语句,循环接收来自客户端的数据。

int socketindex = pServer->m_client_currentindex;while (pServer->m_clientconnectflag_array[socketindex]){          ........}
其中,pServerThreadFunc_RecvData()函数的参数,使用方法在“2.3.3定义线程函数”中已介绍。通过pServer可以在CTCPSocket_Server类的静态函数中调用该类的普通成员。m_client_currentindex是套接字在“套接字池”中的索引,该索引值在“2.3.2指定回调函数”提到的代码中获取。将m_client_currentindex保存在变量socketindex中的原因是,在接受客户端连接的线程ThreadFunc_StartServer中,如果有新的客户端连入服务端,m_client_currentindex值会发生改变。如果不将该值保存在临时变量中,在接收客户端数据时可能会发生错误。

while()的循环语句是数组pServer->m_clientconnectflag_array中的元素。在“2.3.3定义线程函数”中提到,该数组中包含的是“套接字池”中的套接字是否可用的标志。也就是说,当指定的套接字可用时,while()循环的语句才被执行。当将相应的元素值设置为false时,将结束接收数据的线程。

while()循环中,首先调用FD_ZERO宏清空套接字集合socket_fs,之后将与客户端通信的套接字添加到该集合中。

FD_ZERO(&socket_fs);FD_SET(pServer->m_clientsocket_array[socketindex], &socket_fs);
FD_SET()宏的第一个参数是要加入集合的套接字,第二个参数是套接字集合的指针。之后,利用select()函数判断套接字集合中的套接字是否可读、可写。select()函数的格式为

int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout);

其中,参数nfds为保留参数,可以将其设置为0readfds是具有可读性套接字集合的指针,如果要判断套接字集合中的套接字中是否有数据,则要设置该参数;writefds是具有可写性套接字集合的指针,如果要判断套接字集合中的套接字是否已经准备好接收数据,则要设置该参数;exceptfds是检查错误套接字集合的指针,如果要检查套接字集合中的套接字是否发生错误,则要设置该参数;timeout用于设置调用select()函数时的等待时间,其类型是timeval结构,该结构主要指定时间,其格式为

typedef struct timeval{long tv_sec;long tv_usec;}timeval;
tv_sec表示秒,tv_usec表示毫秒,在使用时指定两个成员变量中的一个即可。select()函数的返回值是套接字集合中发生可读、可写或者异常的套接字数量,如果是0则表示已经过了等待时间还没有发生可读、可写或者异常。

if (select(0, &socket_fs, NULL, NULL, &time_selectwait) == 1){value_recvretrun = recv(pServer->m_clientsocket_array[socketindex], buf, LENGTH_RECVDATA, 0);if (pServer->m_receiveclientdata_proc != NULL){pServer->m_receiveclientdata_proc(buf, LENGTH_RECVDATA, socketindex);}}
其中,time_selectwaittimeval结构的对象,其定义为

timeval time_selectwait = { 0, 5000 };
select()函数等待5秒钟后,如果在套接字集socket_fs中没有套接字变为可读,则函数返回0,此时通过while()语句,继续调用select()函数等待套接字变为可读,即select()函数返回值是1。当套接字变为可读,则说明套接字中有数据等待读取,此时调用recv()函数接收数据。

(3)接收数据

服务端通过recv()函数,通过套接字接收来自客户端的数据。该函数的格式是

int recv(SOCKET s, char* buf, int len, int flags);
其中,参数s表示指定的套接字;buf指定了保存数据的缓冲区;len表示缓冲区buf的长度;flags参数会影响recv()函数的行为,如果将该参数设置为0,则表示没有特殊行为。如果成功接收到了来自客户端的数据,则recv()函数的返回值为接收到数据的大小。

在接收到了客户端数据之后,调用2.3.2指定回调函数”中提到的回调函数m_receiveclientdata_proc,将接收到的数据交给主窗口来处理。

(4)更新“套接字池”

当客户端退出时,需要对服务端的“套接字池”进行更新。客户端的退出分为两种情况:“优雅”退出和“强行”退出。“优雅”退出指的是客户端调用了closesocket()函数关闭套接字,“强行”退出指的是直接关闭了客户端程序。recv()函数通过不同的返回值来反映这两种退出。当客户端“优雅”退出时,recv()函数的返回值是0;当客户端“强行”退出时,recv()函数的返回值是-1,即SOCKET_ERROR。所以,在“(3Select模型的实现”中提到的while()循环的执行代码中,还需添加对recv()函数返回值的判断:

if (SOCKET_ERROR == value_recvretrun || 0 == value_recvretrun){closesocket(pServer->m_clientsocket_array[socketindex]);pServer->m_clientconnectflag_array[socketindex] = false;}
当客户端退出时,服务端通过closesocket()函数关闭与客户端通信的套接字,并且把“套接字池”的客户端套机字是否合法的数组进行更新,将该数组的相应元素设置为false。此时,while()循环会退出。在循环退出之后,对“套接字池”的客户端当前索引值进行更新

pServer->m_client_currentindex--;


原创粉丝点击