// SelectModeWinSock.cpp : 定義控制台應用程式的進入點。
//
/************************************
作者: wangweixing2000
Revision By: 0.01
Revised on 2005-6-29 11:52:03
Comments: SelectModeWinSock 該程式中存在bug,沒有處理多線程資料訪問的衝突和判斷用戶端斷開的有效處理!哪位有興趣可以加上!
************************************/
//select(選擇)模型,是利用select函數實現對i/o的管理。select函數可以用於
//判斷通訊端上是否存在資料,或者能否向一個通訊端寫入資料。
/*
int select(
int nfds, //被忽略參數,為了保持和早期Berkeley通訊端應用程式相容而保留的這個參數
fd_set* readfds, //檢查可讀性fd_set*參數
fd_set* writefds, //檢查可寫性fd_set*參數
fd_set* exceptfds, //帶外資料
const struct timeval* timeout //指定select等待i/o操作完成時,最多等待多長時間。
);
typedef struct timeval
{
long tv_sec; //以秒為單位指定等待的時間
long tv_usec; //以毫秒為單位指定的等待時間
} timeval;
用select對通訊端進行監聽前,應用程式必須將通訊端控制代碼分配給一個集合,設定好一個或所有的讀、寫以及例外的
fd_set結構。將一個通訊端分配給任何一個集合後,再來調用select,便可知道某個通訊端上是否正在發生i/o活動。
Winsock提供了幾個宏用來處理fd_set:
FD_ZERO(*set) //清空fd_set*
FD_CLR(s,*set) //從set中刪除s通訊端
FD_ISSET(s,*set) //檢查s是否是set集合的一名成員,如果是返回TRUE
FD_SET(s,*set) //設定通訊端s加入集合set中
*/
#include "stdafx.h"
#include <iostream>
#include <Winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"WS2_32.lib")
using namespace std;
struct SocketObj
{
SOCKET socket; //當前對象的socket
BOOL listening; //該通訊端是否已經
SocketObj *next, //向後
*prev; //向前
};
SocketObj *g_pSocketList = NULL; //Socket連表
SocketObj *g_pSocketEnd = NULL; //連表的尾部
int g_nSocketCount = 0;
HANDLE g_hSelect;
//建立SocketObj
SocketObj* GetSocketObj(SOCKET s,BOOL listening)
{
SocketObj *newSocketObj = NULL;
newSocketObj = (SocketObj*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(SocketObj));
if(newSocketObj == NULL)
{
cout<<"GetSocketObj: HeapAlloc failed: "<< GetLastError()<<endl;
ExitProcess(-1); //結束進程
}
newSocketObj->socket = s;
newSocketObj->listening = listening;
return newSocketObj;
}
//插入一個SocketObj
void InserSocketObj(SocketObj *obj)
{
obj->next = obj->prev = NULL;
if(g_pSocketList == NULL)
{
g_pSocketList = g_pSocketEnd = obj;
}
else
{
obj->prev = g_pSocketEnd;
g_pSocketEnd->next = obj;
g_pSocketEnd = obj;
}
g_nSocketCount++;
}
//刪除
void RemoveSocketObj(SocketObj *obj)
{
if(obj->prev)
{
obj->prev->next = obj->next;
}
if(obj->next)
{
obj->next->prev = obj->prev;
}
if(obj == g_pSocketList)
{
g_pSocketList = obj->next;
}
if(obj == g_pSocketEnd)
{
g_pSocketEnd = obj->prev;
}
g_nSocketCount--;
HeapFree(GetProcessHeap(),0,obj);
}
//監聽線程
DWORD WINAPI ListenThread(void *pVoid)
{
cout<<"server start listening!"<<endl;
SOCKADDR_IN ClientAddr; // 定義一個用戶端得地址結構作為參數
int addr_length=sizeof(ClientAddr);
SOCKET *listen = (SOCKET*)pVoid;
SocketObj *pSocketObj = NULL;
while(1)
{
SOCKET Client = accept(*listen,(sockaddr*)&ClientAddr,&addr_length);
if(Client == INVALID_SOCKET)
{
cout<<"accept failed!"<<endl;
continue;
}
// 這裡可以取得用戶端的IP和連接埠,但是我們只取其中的SOCKET編號
LPCTSTR lpIP = inet_ntoa(ClientAddr.sin_addr);
UINT nPort = ClientAddr.sin_port;
cout<<"一個用戶端已經串連!IP:"<<lpIP<<"SOCKET 連接埠號碼:"<<nPort<<endl;
//建立SocketObj並添加如list
pSocketObj = GetSocketObj(Client,TRUE);
InserSocketObj(pSocketObj);
if(g_nSocketCount == 1) //如果有一個用戶端就喚起select線程
{
ResumeThread((HANDLE)g_hSelect);
}
}
return 0;
}
//select線程函數
DWORD WINAPI SelectThread(void *pVoid)
{
SocketObj *sptr = NULL;
fd_set readfds,
writefds,
exceptfds;
timeval timeout;
char Buffer[4096];
while(TRUE)
{
//清空fd_set
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
//設定timeout
timeout.tv_sec = 5;
timeout.tv_usec = 0;
sptr = g_pSocketList;
//設定fd_set
while(sptr)
{
FD_SET(sptr->socket,&readfds);
FD_SET(sptr->socket,&writefds);
FD_SET(sptr->socket,&exceptfds);
sptr = sptr->next;
}
//開始select
int ret = select(0,&readfds,&writefds,NULL,&timeout);
if(ret == SOCKET_ERROR)
{
cout<<"select failed!"<<endl;
WSACleanup();
return -1;
}
else if(ret == 0)
{
//逾時
cout<<"time out!"<<endl;
continue;
}
else
{
sptr = g_pSocketList;
while(sptr)
{
if(FD_ISSET(sptr->socket,&readfds))
{
ZeroMemory(Buffer,4096);
int re = recv(sptr->socket,Buffer,4096,0);
if(re == SOCKET_ERROR)
{
return -1;
}
else if(re == 0)
{
//該用戶端已經斷開
closesocket(sptr->socket);
SocketObj *tmp = sptr;
sptr = sptr->next;
RemoveSocketObj(tmp);
continue;
}
else
{
cout<<"recv buffer:"<<Buffer<<endl;
}
}
if(FD_ISSET(sptr->socket,&writefds))
{
//發送資料給當前用戶端
char *sendBuffer = "wwx send msg!";
int re = send(sptr->socket,sendBuffer,16,0);
if(re == SOCKET_ERROR)
{
return -1;
}
else if(re == 0)
{
continue;
}
else
{
//這裡可以顯示發送是否成功
//cout<<"send successed!"<<endl;
}
}
sptr = sptr->next;
}
}
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
WSAData wsaData;
SOCKET Listen;
SocketObj *pSockobj = NULL;
SOCKET Accept = INVALID_SOCKET;
SOCKADDR_IN ServerAddr;
//初始化Winsock庫
int ret = WSAStartup(MAKEWORD(2,2),&wsaData);
if(ret != 0)
{
cout<<"WSAStartup error!"<<endl;
WSACleanup();
return -1;
}
Listen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(Listen == INVALID_SOCKET)
{
cout<<"Listen create failed!"<<endl;
WSACleanup();
return -1;
}
//綁定
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
ServerAddr.sin_port = htons(10012);
ret = bind(Listen,(sockaddr*)&ServerAddr,sizeof(SOCKADDR_IN));
if(ret == SOCKET_ERROR)
{
cout<<"bind failed!"<<endl;
closesocket(Listen);
WSACleanup();
return -1;
}
//監聽
listen(Listen,200);
//開啟監聽線程
HANDLE hListen = CreateThread(NULL,0,ListenThread,&Listen,0,NULL);
if(hListen == NULL)
{
cout<<"Create ListenThread failed!"<<endl;
}
//創價掛起的select線程,因為剛開始沒有串連的用戶端
g_hSelect = CreateThread(NULL,0,SelectThread,NULL,CREATE_SUSPENDED,NULL);
if(g_hSelect == NULL)
{
cout<<"Create SelectThread failed!"<<endl;
}
system("PAUSE");
return 0;
}
如果引用該文,請註明出處!謝謝!