// 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;
LPCTSTR lpIP;
UINT nPort;
//创建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编号
lpIP = inet_ntoa(ClientAddr.sin_addr); 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 * sendbuf = "I am a proxy!";
int re = send(sptr->socket,lpIP,strlen(lpIP)+1,0);
int re1=send(sptr->socket,(char *)&nPort,sizeof(int),0);
if(re == SOCKET_ERROR)
{
return -1;
}
else if(re == 0)
{
continue;
}
else
{
//这里可以显示发送是否成功
//cout<<"send successed!"<<endl;
}
}
sptr = sptr->next;
}
}
}
return 0;
}
int main(int argc, CHAR* 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;
}