5.4.1 主线程

  服务器主线程包括初始化、启动服务、停止服务和退出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++标准函数库(STLlist模版类的实例,其参数为CClient类的指针,在后面的正文中对该类进行详细介绍。

q        保护链表的临界区对象。使用该临界区对象确保各线程对该链表的互斥访问。

全局变量声明如下。

HANDLE  hThreadAccept;                                              //接受客户端连接线程句柄

HANDLE  hThreadHelp;                                                  //释放资源线程句柄

SOCKET  sServer;                                                            //监听套接字

BOOL        bServerRunning;                                            //服务器的工作状态

HANDLE  hServerEvent;                                                  //服务器退出事件对象

CLIENTLIST                        clientlist;                               //管理连接的链表

CRITICAL_SECTION         csClientList;                        //保护链表的临界区对象

全局变量的初始化由InitMember()函数完成。在该函数中,关键是初始化保护链表的临界区对象和创建服务器退出的事件对象。临界区和事件是线程同步的两种方法,在第一篇中介绍了此部分内容。

临界区对象在使用之前必须初始化,在使用完后删除。在程序中使用csClientList全局变量作为参数调用InitializeCriticalSection()函数完成对csClientList临界区对象的初始化。

在程序中,为了便于控制服务器退出事件对象的状态,将该事件对象设置为手动模式。以TRUEFALSE为第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()函数时,将等候连接队列的最大长度设置为5InitSocket()函数的程序清单如下。

#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

该函数将返回值分别赋予hThreadHelphThreadAccept变量。如果返回值有效,立即调用CloseHandle()函数,将线程句柄的引用计数减去1

HelperThread()AcceptThread()线程函数中,使用bServerRunning逻辑变量作为线程退出的条件。由于在调用CreateThread()函数成功后,这两个线程函数都立即运行,所以在调用CreateThread()函数之前将bServerRunning变量的值设置为TRUECreateHelperAndAcceptThread()函数的程序清单如下。

/**

 * 创建释放资源线程和接收客户端请求线程

 */

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

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值