#include "stdafx.h"
#include "ServerSocketSelect.h"
bool SocketList::GetSocket( int i,SOCKET& socket )
{
bool bRet = false;
if (i >=0 && i < FD_SETSIZE)
{
socket = m_socketArray[i];
if(socket)
{
bRet = true;
}
}
return bRet;
}
SocketList::SocketList()
{
m_um=0;
for (int i=0;i<FD_SETSIZE;i++)
{
//因为socket的值是一个非负整数值,所以可以将socketArray初始化为0,让它来表示数组中的这一个元素有没有被使用
m_socketArray[i]=0;
}
}
bool SocketList::InsertSocket( SOCKET s )
{
bool bRet = false;
for (int i=0;i<FD_SETSIZE;i++)
{
//如果某一个socketArray[i]为0,表示哪一个位可以放入socket
if (m_socketArray[i]==0)
{
m_socketArray[i]=s;
m_um++;
bRet = true;
break;//这里一定要加上break,不然一个socket会放在socketArray的多个位置上
}
}
return bRet;
}
void SocketList::DeleteSocket( SOCKET s )
{
for (int i=0;i<FD_SETSIZE;i++)
{
if (m_socketArray[i]==s)
{
m_socketArray[i]=0;
m_um--;
break;
}
}
}
void SocketList::Makefd( fd_set * fd_list )
{
FD_ZERO(fd_list);//首先将fd_list清0
//将每一个socket加入fd_list中
for (int i=0;i<FD_SETSIZE;i++)
{
if (m_socketArray[i]>0)
{
FD_SET(m_socketArray[i],fd_list);
}
}
}
CServer_SocketSelect::CServer_SocketSelect(void)
:m_bIsRun(true)
,m_serverSocket(0)
{
}
CServer_SocketSelect::~CServer_SocketSelect(void)
{
m_bIsRun = false;
//关闭服务器SOCKET
if (m_serverSocket)
{
closesocket(m_serverSocket);
}
//清理Windows Socket库
WSACleanup();
}
int CServer_SocketSelect::StartServer( LPCTSTR lpAddr,int nPort )
{
int nRet = 0;
WSADATA wsaData;
int err;
//1.加载套接字库
err=WSAStartup(MAKEWORD(2,2),&wsaData);
if (err!=0)
{
cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;
nRet = -1;
}
else
{
//2.创建socket
//套接字描述符,SOCKET实际上是unsigned int
// 创建socket操作,建立流式套接字SOCK_STREAM,返回套接字号m_serverSocket
// 第一个参数,指定地址簇(TCP/IP只能是AF_INET,也可写成PF_INET)
// 第二个,选择套接字的类型(流式套接字),第三个,特定地址家族相关协议(0为自动)
m_serverSocket=socket(AF_INET,SOCK_STREAM,0);
if (m_serverSocket==INVALID_SOCKET)
{
cout<<"Create Socket Failed::"<<GetLastError()<<endl;
nRet = -2;
}
else
{
//服务器端的地址和端口号
struct sockaddr_in serverAddr;
serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS);
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(PORT);
//3.绑定Socket,将Socket与某个协议的某个地址绑定
// 第一个参数,指定需要绑定的套接字;
// 第二个参数,指定该套接字的本地地址信息,该地址结构会随所用的网络协议的不同而不同
// 第三个参数,指定该网络协议地址的长度
// PS: struct sockaddr{ u_short sa_family; char sa_data[14];};
// sa_family指定该地址家族, sa_data起到占位占用一块内存分配区的作用
// 在TCP/IP中,可使用sockaddr_in结构替换sockaddr,以方便填写地址信息
//
// struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8];};
// sin_family表示地址族,对于IP地址,sin_family成员将一直是AF_INET。
// sin_port指定将要分配给套接字的端口。
// sin_addr给出套接字的主机IP地址。
// sin_zero[8]给出填充数,让sockaddr_in与sockaddr结构的长度一样。
// 将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。
// 如果想只让套接字使用多个IP中的一个地址,可指定实际地址,用inet_addr()函数。
err=bind(m_serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if (err!=0)
{
cout<<"Bind Socket Failed::"<<GetLastError()<<endl;
nRet = -3;
}
else
{
//4.监听,将套接字由默认的主动套接字转换成被动套接字
// 第一个参数指定需要设置的套接字,第二个参数为(等待连接队列的最大长度)
err=listen(m_serverSocket,10);
if (err!=0)
{
cout<<"listen Socket Failed::"<<GetLastError()<<endl;
nRet = -4;
}
else
{
cout<<"服务器端已启动......启动工作线程"<<endl;
//SocketList socketList;
HANDLE hWorkThread=CreateThread(NULL,0,WorkThread,this,0,NULL);
HANDLE hAcceptThread=CreateThread(NULL,0,AcceptThread,this,0,NULL);
if (hWorkThread==NULL || hAcceptThread == NULL)
{
cout<<"Create Thread Failed!"<<endl;
nRet = -5;
}
else
{
if(!CloseHandle(hWorkThread) || !CloseHandle(hAcceptThread)) //创建线程成功,不需要对线程进行操作,直接关闭句柄
{
nRet = -6;
}
else
{
nRet = 1;
}
}
}
}
}
}
return nRet;
}
DWORD WINAPI CServer_SocketSelect::WorkThread( LPVOID lpParam )
{
CServer_SocketSelect* pThis = (CServer_SocketSelect*)lpParam;
int err=0;
fd_set fdread;//存在读文件的set,select会检测这个set中是否可以从某些socket中读入信息
struct timeval timeout;//设置select超时的时间
timeout.tv_sec=6; //6秒
timeout.tv_usec=0; //毫秒
//输入输出缓冲区
char receBuff[MAX_PATH];
char sendBuf[MAX_PATH];
SOCKET socket;
while(pThis->IsRun())
{
pThis->m_User.Makefd(&fdread);
//int select
// (
// int nfds, //Winsock中此参数无意义
// fd_set* readfds, //进行可读检测的Socket
// fd_set* writefds, //进行可写检测的Socket
// fd_set* exceptfds, //进行异常检测的Socket
// const struct timeval* timeout //非阻塞模式中设置最大等待时间
// )
/*struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
*/
// 只关心读取事件,6秒后无可读数据返回
err=select(0,&fdread,NULL,NULL,&timeout);
if (err==0)//select返回0表示超时
{
cout<<"select() is time-out!"<<endl;
continue;
}
//else if(err == -1)
else if(err < 0)//-1是没socket连接的时候返回的值,其它值一并在这里处理好了
{
//因为如果读集中没有任何套接字,select函数会立刻返回-1,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
//不过实际测试中发现,这里不写延时也没CPU %100,可能是select接口里面做了优化?还是我电脑太牛了???
//反正加上是最好的
cout<<"connect null, ret:"<<err<<endl;
Sleep(1);
continue;
}
else
{
//遍历socketList中的每一个socket,查看那些socket是可读的,处理可读的socket
//从中读取数据到缓冲区,并发送数据给客户端
for (int i=0;i<FD_SETSIZE;i++)
{
//取出有效的socket
if (!pThis->m_User.GetSocket(i,socket))
continue;
//通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作
//判断哪些socket是可读的,如果这个socket是可读的,从它里面读取数据
if (FD_ISSET(socket,&fdread))
{
err=recv(socket,receBuff,MAX_PATH,0);
//如果返回值表示要关闭这个连接,那么关闭它,并将它从sockeList中去掉
//if (err==0||err==SOCKET_ERROR)
//好像这个方法判断更为严谨,发生其它错误时还可以继续收包的
if (err == 0 || (err == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
closesocket(socket);
//FD_CLR(socket,fdread);
cout<<"断开连接!"<<endl;
pThis->m_User.DeleteSocket(socket);
}
else
{
if (err != 4)
{
::MessageBox(NULL,_T(""),_T(""),MB_OK);
}
//给客户端回发消息,测试用
cout<<"message from client:"<<receBuff<<" socket:"<<socket<<endl;
strcpy_s(sendBuf,_countof(sendBuf),"server receive a message:");
strcat_s(sendBuf,_countof(sendBuf),receBuff);