还是看一下MSDN里介绍的:
int WSAEventSelect(
__in SOCKET s, //使用的套接字
__in WSAEVENT hEventObject, //响应FD_**事件的句柄
__in long lNetworkEvents //FD_**事件
);
由于这次做的代码把网络通信的一些基本功能实现封装到一个类里,所以还是示例性的把代码提取出来吧。
在写代码之前有必要说一下什么是线程,按我使用过的理解,线程就是和程序并行运行的一种机制,与主程序分离独自地运行。通过一些机制可以在主程序里控制线程。这是现在理解的,不知道有没有什么不妥。现在要用线程解决的问题就是将阻塞主程序的一些网络通信相关内容放到线程里完成,这样就能实现很多功能了。
看一下线程函数的开启和关闭:
1.创建线程:
_beginthreadex(0,0,SocketThread,this,0,NULL) ,(还有一个是它的前期版本_beginthread)前两个参数一般不用(查找MSDN)第三个
就是你的线程函数了,第四个是你传到线程里使用的对象或者变量的指针,主要就是这两个参数了,其它按默认的就好。
线程函数一定要声明为静态的,像这样: static unsigned int _stdcall SocketThread(void* pParam),参数接收传入的指针。
MFC里使用AFXBeginThread来创建线程。
另外CreateThread和UIWorkThread不怎么使用。因为多线程一般使用的就不多,能用计时器实现的完成可以不用线程
2.结束:虽然有几个结束线程的函数但是一般是通过事件机制实现。
_endthread():在线程里使用,结束本线程。一般不使用。
TerminateThread(/*hThread*/):通过传入线程句柄结束想要结束的线程,在线程之外使用。
最后说一种我常用的,就是通过信号量机制控制。通过在线程里设定信号量,然后在线程外检测信号量的状态,当满足条件时就可以调用TerminateThread结束线程,或者可以这样,在线程之外设定信号量,线程里面检测信号有状态,当满足情况时就可以返回。一般线程里结束通过return是比较推荐的,线程运行需要做很多初始化,普通的终止有可能造成资源的不能释放。然而通过在线程里return就可以实现安全的退出。
再扩充一下:创建事件HANDLE hEvent = CreateEvent(/*MSDN*/),判断信号状态函数:nRet = WaitforSingleObject(/*hEvent*/),根据返回值来判断是否满足情况。
线程就先说这么些吧,现在开始总结事件选择机制:
- //1.创建线程并开启
- BOOL CEventSocket::StartSocket(int nPort)
- {
- m_nPort = nPort;
- if (m_hTread == NULL)
- {
- m_hTread = (HANDLE)_beginthreadex(0,0,
- SocketThread,this,0,NULL);
- return TRUE;
- }
- return TRUE;
- }
- //2.在线程里处理事件
- unsigned int _stdcall CEventSocket::SocketThread(void* pParam)
- {
- CEventSocket* pEventSocket = (CEventSocket*)pParam;//得到对象指针
- WSAEVENT hListenArray[WSA_MAXIMUM_WAIT_EVENTS]; //存储事件的数组
- //在头文件里定义:SOCKET m_lSockArray[WSA_MAXIMUM_WAIT_EVENTS];
- SOCKET* plSockArray = pEventSocket->m_lSockArray;
- //填充地址结构
- SOCKADDR_IN addrSer;
- memset(&addrSer,0,sizeof(addrSer));
- addrSer.sin_family = AF_INET;
- addrSer.sin_port = htons(8080);
- addrSer.sin_addr.s_addr = inet_addr("192.168.3.17");
- SOCKET m_Listen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
- if (m_Listen == SOCKET_ERROR)
- return -1;
- if (bind(m_Listen,(SOCKADDR*)&addrSer,sizeof(SOCKADDR)) != 0)
- return -2;
- if (listen(m_Listen,SOMAXCONN) != 0)
- return -3;
- //-------------------------
- //创建一个事件,并存储到数组里
- DWORD nEventNum = 0;
- plSockArray[nEventNum] = m_Listen;
- hListenArray[nEventNum] = WSACreateEvent();
- nEventNum++;
- //-------------------------
- // 将建立好的套接字和通信事件相关联
- if (WSAEventSelect(plSockArray[0], hListenArray[0], FD_ACCEPT) ==
- WSA_INVALID_HANDLE)
- return -4;
- //建立循环处理事件
- while (1)
- {
- memset(pEventSocket->szBuf,0,MAX_PATH*sizeof(TCHAR));
- //判断外部信号量,当m_hEvent有信号时就退出线程,结束线程
- DWORD nRet = WaitForSingleObject(pEventSocket->m_hEvent,0);
- if(nRet == WAIT_OBJECT_0)
- {
- ResetEvent(pEventSocket->m_hEvent);
- break;
- }
- //取得当前响应的事件在事件数组里的索引
- DWORD nIndex = WSAWaitForMultipleEvents(nEventNum,
- hListenArray,
- FALSE,
- WSA_INFINITE,
- FALSE);
- if ( nIndex == WSA_WAIT_FAILED)
- continue;
- //nIndex和WSA_WAIT_EVENT_0的差值即为事件索引
- nIndex = nIndex - WSA_WAIT_EVENT_0;
- //网络事件结构,传出参数用来接收存储事件信息
- WSANETWORKEVENTS netWorkEvent;
- if (WSAEnumNetworkEvents(plSockArray[nIndex],
- hListenArray[nIndex],
- &netWorkEvent) != 0)
- continue ;
- //判断是否是连接请求
- if ((netWorkEvent.lNetworkEvents & FD_ACCEPT) ==FD_ACCEPT)
- {
- //如果是连接请求则建立连接
- SOCKET clientSock = accept(plSockArray[nIndex],
- NULL,NULL);
- //将返回的套接字存入套接字数组
- plSockArray[nEventNum] = clientSock;
- hListenArray[nEventNum] = WSACreateEvent();
- if (WSAEventSelect(plSockArray[nEventNum],
- hListenArray[nEventNum],
- FD_READ) == WSA_INVALID_HANDLE)
- return -4;
- nEventNum++;
- if (nEventNum >= 64)
- {
- return -5;
- }
- }
- else if ((netWorkEvent.lNetworkEvents & FD_READ) ==FD_READ)
- {
- recv(plSockArray[nIndex],
- (char*)pEventSocket->szBuf,
- MAX_PATH*sizeof(TCHAR),0);
- if (pEventSocket)
- {
- //将接收到的内容显示到窗体上
- pEventSocket->m_pWnd->
- SetWindowText(pEventSocket->szBuf);
- }
- }
- }
- return 0;
- }
现在回顾一下整个处理过程:首先创建线程后建立一个基本的监听套接字,为之创建一个事件,并把它们两个分别存入相应的
数组里。当有事件到来时根据WSAWaitForMultipleEvents的返回值来判断当前事件在事件数组里的索引,然后判断是不是连接请求,如果是则进行accept操作,为返回的套接字创建事件,并将它们存入到对应的数组里;如果不是连接请示则判断是不是发送信息,如果是则开始接收住息,并将信息显示在窗体上。
大概过程就是这样了,我觉得还是有必要看一看这个函数,因为我在总结的时候才想起来这个函数没有说过。
DWORD nIndex = WSAWaitForMultipleEvents(nEventNum, //存储的事件的个数
hListenArray, //套接字端口
FALSE,
WSA_INFINITE, //等待时间
FALSE);
根据返回值nIndex和WSA_WAIT_EVENT_0的差值就能得到当前事件在事件数组里的索引。根据索引就可以向下进行一系列的操作。
/
这几天开学,一直也没有时间好好整理这些了,不过我还是想坚持把这些小的基础一点点的总结出来。可能其中一些总结的理解是有误差的,但学习就是一个从无知到所知的过程,慢慢总结吧。