服务器主线程包括初始化、启动服务、停止服务和退出4个模块。其流程图如图所示。
服务器的主线程函数清单如下。
q InitSever()函数完成服务器的初始化。如果初始化服务器失败,则释放申请的资源,并返回SERVER_SETUP_FAIL错误代码。
q 由StartService()函数启动服务,创建清理资源线程和接受客户端请求线程。如果启动服务失败,则返回SERVER_SETUP_FAI错误代码,释放资源。成功启动服务后,在服务器中存在3个线程:主线程、清理资源线程和接受客户端请求线程。此时服务器的套接字以非阻塞模式等待客户端的连接请求。
q StopService()函数用于等待用户退出服务器的输入操作,停止服务器工作。当用户确认退出服务器时,由清理资源线程停止客户端的服务,所有子线程退出。
q ExitServer()函数释放主线程申请的资源。
#define SERVER_SETUP_FAIL 1//启动服务器失败
int main(int argc, char* argv[])
{
//初始化服务器
if (!InitSever())
{
ExitServer();
return SERVER_SETUP_FAIL;
}
//启动服务
if (!StartService())
{
ShowServerStartMsg(FALSE);
ExitServer();
return SERVER_SETUP_FAIL;
}
//停止服务
StopService();
//服务器退出
ExitServer();
return 0;
}
1.初始化
InitSever()函数实现服务器的初始化。包括全局变量的初始化和Windows Sockets的初始化。该服务器程序是Win32 Console Application程序。在程序中使用了一些全局变量,其中包括以下几个方面。
q 接受客户端请求线程句柄
q 清理资源线程句柄
q 监听客户端连接套接字
q 服务器的当前工作状态变量
q 服务器退出事件句柄。
q 管理客户端连接的链表。把每个接受的客户端作为链表的一个节点。使用该链表对客户端的接入和退出进行管理。该链表声明为C++标准函数库(STL)list模版类的实例,其参数为CClient类的指针,在后面的正文中对该类进行详细介绍。
q 保护链表的临界区对象。使用该临界区对象确保各线程对该链表的互斥访问。
全局变量声明如下。
HANDLE hThreadAccept; //接受客户端连接线程句柄
HANDLE hThreadHelp; //释放资源线程句柄
SOCKET sServer; //监听套接字
BOOL bServerRunning; //服务器的工作状态
HANDLE hServerEvent; //服务器退出事件对象
CLIENTLIST clientlist; //管理连接的链表
CRITICAL_SECTION csClientList; //保护链表的临界区对象
全局变量的初始化由InitMember()函数完成。在该函数中,关键是初始化保护链表的临界区对象和创建服务器退出的事件对象。临界区和事件是线程同步的两种方法,在第一篇中介绍了此部分内容。
临界区对象在使用之前必须初始化,在使用完后删除。在程序中使用csClientList全局变量作为参数调用InitializeCriticalSection()函数完成对csClientList临界区对象的初始化。
在程序中,为了便于控制服务器退出事件对象的状态,将该事件对象设置为手动模式。以TRUE和FALSE为第2个和第3个参数,调用CreateEvent()函数。创建hServerEvent服务器退出事件对象为手动模式,初始为无信号状态。InitMember()函数清单如下。
/**
* 初始化全局变量
*/
void InitMember(void)
{
InitializeCriticalSection(&csClientList); //初始化临界区
hServerEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //手动设置事件,初始化为无信息号状态
hThreadAccept = NULL; //设置为NULL
hThreadHelp = NULL; //设置为NULL
sServer = INVALID_SOCKET; //设置为无效的套接字
bServerRunning = FALSE; //服务器为没有运行状态
clientlist.clear(); //清空链表
}
初始化套接字的过程由InitSocket()函数实现。首先初始化Windows Sockets DLL和创建套接字,然后调用ioctlsocket()函数,将该套接字设置为非阻塞模式。该函数的使用方法在第一节中已经介绍。在后面的Windows Sockets API函数的调用中,都是以该非阻塞套接字作为参数进行调用。调用的API都会立即返回,并返回调用成功或者失败代码。
在InitSocket()中,使用该非阻塞套接字调用bind()和listen()函数,完成套接字的绑定和监听。如果参数使用正确,该函数会立即成功返回。需要注意的是这两个函数在操作失败时,不会返回WSAEWOULDBLOCK错误指示。在该函数中没有对这两个API的错误返回值做太多的处理。在调用listen()函数时,将等候连接队列的最大长度设置为5。InitSocket()函数的程序清单如下。
#define SERVERPORT 5556 //服务器TCP端口
/**
* 初始化SOCKET
*/
BOOL InitSocket(void)
{
int reVal; //返回值
//初始化Windows Sockets DLL
WSADATA wsData;
reVal = WSAStartup(MAKEWORD(2,2),&wsData);
//创建套接字
sServer = socket(AF_INET, SOCK_STREAM, 0);
if(INVALID_SOCKET== sServer)
return FALSE;
//设置套接字非阻塞模式
unsigned long ul = 1;
reVal = ioctlsocket(sServer, FIONBIO, (unsigned long*)&ul);
if (SOCKET_ERROR == reVal)
return FALSE;
//绑定套接字
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(SERVERPORT);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
reVal = bind(sServer, (struct sockaddr*)&serAddr, sizeof(serAddr));
if(SOCKET_ERROR == reVal )
return FALSE;
//监听
reVal = listen(sServer, SOMAXCONN);
if(SOCKET_ERROR == reVal)
return FALSE;
return TRUE;
}
2.启动服务
StartService()函数实现启动服务器。调用ShowTipMsg()函数,提示用户输入“s”或“S”字符。在do while循环体内,反复调用该函数直到用户输入正确的字符为止。
在用户正确输入字符后,调用CreateHelperAndAcceptThread()函数创建清理资源线程和等待客户端请求线程。如果创建线程成功,则在界面显示成功的信息。启动服务程序清单如下。
/**
* 启动服务
*/
BOOL StartService(void)
{
BOOL reVal = TRUE; //返回值
ShowTipMsg(TRUE); //提示用户输入
char cInput; //输入字符
do
{
cin >> cInput;
if ('s' == cInput || 'S' == cInput)
{
if (CreateHelperAndAcceptThread()) //创建清理资源和接受客户端请求的线程
{
ShowServerStartMsg(TRUE); //创建线程成功信息
}else{
reVal = FALSE;
}
break; //跳出循环体
}else{
ShowTipMsg(TRUE);
}
} while(cInput != 's' && //必须输入's'或者'S'字符
cInput != 'S');
return reVal;
}
在CreateHelperAndAcceptThread()函数中,调用CreateThread()函数分别创建清理资源线程和接受客户端请求线程。该函数的参数设置如下。
q 第1个参数设置为NULL。新创建的线程使用缺省安全属性。
q 第2个参数为设置0。新线程使用缺省大小的堆栈。
q 第3个参数分别为HelperThread()和AcceptThread函数的地址。
q 第4个参数设置为NULL。没有给新的线程函数传递任何参数。
q 第5个参数设置为0。新创建的线程将立即运行各自的线程函数。
q 第6个参数为unsigned long类型变量的指针。在该函数返回时,返回该线程的ID。
该函数将返回值分别赋予hThreadHelp和hThreadAccept变量。如果返回值有效,立即调用CloseHandle()函数,将线程句柄的引用计数减去1。
在HelperThread()和AcceptThread()线程函数中,使用bServerRunning逻辑变量作为线程退出的条件。由于在调用CreateThread()函数成功后,这两个线程函数都立即运行,所以在调用CreateThread()函数之前将bServerRunning变量的值设置为TRUE。CreateHelperAndAcceptThread()函数的程序清单如下。
/**
* 创建释放资源线程和接收客户端请求线程
*/
BOOL CreateHelperAndAcceptThread(void)
{
bServerRunning = TRUE; //设置服务器为运行状态
//创建释放资源线程
unsigned long ulThreadId;
hThreadHelp = CreateThread(NULL, 0, HelperThread, NULL, 0, &ulThreadId);
if( NULL == hThreadHelp)
{
bServerRunning = FALSE;
return FALSE;
}else{
CloseHandle(hThreadHelp);
}
//创建接收客户端请求线程
hThreadAccept = CreateThread(NULL, 0, AcceptThread, NULL, 0, &ulThreadId);
if( NULL == hThreadAccept)
{
bServerRunning = FALSE;
return FALSE;
}else{
CloseHandle(hThreadAccept);
}
return TRUE;
}
3.停止服务
StopService()函数完成停止服务的功能。停止服务程序与启动服务程序有相似之处。调用ShowTipMsg()函数在服务器界面上显示提示信息,要求用户输入“e”或者“E”字符。当用户输入该字符后,程序弹出消息框,等待用户的确认。
当用户取消退出服务器操作时,调用Sleep()函数使主线程睡眠,以使其他线程获得运行的机会。此时,服务器至少还有2个子线程正在运行。服务器工作重点在于为客户端提供的服务上,所以将Sleep()函数的参数设置较大。
当用户确认退出服务器时,该函数执行下面步骤。
(1)修改bServerRunning变量值为FALSE,以便清理资源线程和接受客户端请求线程退出。
(2)调用ShowServerExitMsg()函数显示服务器正在退出的信息。
(3)调用Sleep()函数,给其他线程运行机会,以便退出。
(4)以hServerEvent服务器退出事件和INFINITE无限期时间为参数,调用WaitForSingleObject()函数。该函数直到收到清理资源线程发送的服务器退出事件后才会返回。停止服务函数的程序清单如下。
#define TIMEFOR_THREAD_EXIT 5000 //主线程睡眠时间
/**
* 停止服务
*/
void StopService(void)
{
BOOL reVal = TRUE; //返回值
ShowTipMsg(FALSE); //提示用户输入
char cInput; //输入的操作字符
for (;bServerRunning;)
{
cin >> cInput;
if (cInput == 'E' || cInput == 'e')
{
if (IDOK == MessageBox(NULL, "Are you sure?", //等待用户确认退出的消息框
"Server", MB_OKCANCEL))
{
break; //跳出循环体
}else{
Sleep(TIMEFOR_THREAD_EXIT); //线程睡眠
}
}else{
Sleep(TIMEFOR_THREAD_EXIT); //线程睡眠
}
}
bServerRunning = FALSE; //服务器退出
ShowServerExitMsg(); //显示服务器退出信息
Sleep(TIMEFOR_THREAD_EXIT); //给其他线程时间退出
WaitForSingleObject(hServerEvent, INFINITE); //等待清理资源线程发送的事件
return;
}
4.退出
ExitServer()函数完成服务器退出时的最后清理工作。清理在启动主线程时申请的资源。包括释放保护链表的临界区对象,释放关闭服务器事件对象,关闭套接字和释放Window Sockets DLL。该函数程序清单如下。
/**
* 释放资源
*/
void ExitServer(void)
{
DeleteCriticalSection(&csClientList); //释放临界区对象
CloseHandle(hServerEvent); //释放事件对象句柄
closesocket(sServer); //关闭SOCKET
WSACleanup(); //卸载Windows Sockets DLL
}