還是看一下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的差值就能得到當前事件在事件數組裏的索引。根據索引就可以向下進行一系列的操作。
/////////////////////////
這幾天開學,一直也沒有時間好好整理這些了,不過我還是想堅持把這些小的基礎一點點的總結出來。可能其中一些總結的理解是有誤差的,但學習就是一個從無知到所知的過程,慢慢總結吧。