10.3.1 服務端

  服務端實現步驟如下。

1 打開VC6,單擊【File|New】命令,彈出【New】對話框。

2)在【Projects】選項卡中,選擇【MFC AppWizard(exe)】選項,創建一個MFC應用程序。在【Projectname】文本框中,輸入項目名稱“server”,如圖所示。

3)單擊“OK”,在創建MFC應用程序嚮導對話框選擇應用程序類型,這裏選擇“Dilag based”如圖所示。然後單擊“Finish”完成。

                                                     

(4)IDD_SERVER_DIALOG對話框窗體添加編輯框(Edit Box)控件。

(5) Ctrl+W 鍵或單擊【View|ClassWizard】命令,啓動“MFC ClassWizard”,對話框,選擇【Member Variables】選項。如圖所示。

6)選中“IDC_EDIT1”控件,單擊“Add Variable…”按鈕。添加CString變量m_str,如圖所示。

(7)IDD_SERVER_DIALOG對話框窗體添加列表框(List Box)控件。

(8) Ctrl+W 鍵或單擊【View|ClassWizard】命令,啓動“MFC ClassWizard”,對話框,選擇【Member Variables】選項。如圖所示。

9)選中“IDC_LIST”控件,單擊“Add Variable…”按鈕。添加控件變量m_list,如圖所示。

10)在IDD_SERVER_DIALOG對話框窗體,添加兩個命令按鈕(Button)控件。全部控件和佈局如圖所示。

11)添加自定義結構體,用戶保存客戶端SSL鏈接句柄。在serverDlg.h添加如下代碼

// CServerDlg dialog

typedef struct descriptor{

         int     fd;

         SSL  *ssl;

}descriptor_t;

12)添加服務器環境的宏定義以及保存客戶端鏈接信息的全局變量。在serverDlg.cpp文件添加如下代碼:

#define CERTF  "cert.cer"                           //服務器證書

#define KEYF   "key.pem"                          //服務器私鑰

#define ROOTCERTF "root.cer"                  //根證書

#define MAX_CLIENT 100                                     //允許鏈接的最大客戶端數

#define WM_CLIENT_MSG WM_USER+102    //自定義的消息

SSL_CTX* ctx;                                                 //SSL上下文句柄

descriptor_t Clients[MAX_CLIENT];  //保存客戶端鏈接句柄的數組

(13BOOL CServerDlg::OnInitDialog()函數中添加如下代碼初始化服務器環境。

         // TODO: Add extra initialization here

         //初始化OpenSSL環境

         SSL_load_error_strings();

         SSLeay_add_ssl_algorithms();

         SSL_METHOD *meth;

         meth = SSLv23_server_method();    //使用SSL V2V3協議

         ctx = SSL_CTX_new (meth);               //初始化SSL上下文

         if (!ctx)

         {

                   return FALSE;

         }

         //設置服務器證書

         if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0)

         {

                   return FALSE;

         }

         //設置服務器私鑰

         if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0)

         {

                   return FALSE;

         }

         //檢查服務器私鑰和證書是否匹配

         if (!SSL_CTX_check_private_key(ctx))

         {

                   return FALSE;

         }

         //初始化客戶端鏈接結構體數組

         for(int i=0;i<MAX_CLIENT;i++)

         {

                   Clients[i].fd=0;

                   Clients[i].ssl=NULL;

         }

         return TRUE;  // return TRUE  unless you set the focus to a control

(14void CServerDlg::OnStart()函數即“啓動”按鈕中添加如下代碼,創建線程,啓動服務。

//啓動服務

void CServerDlg::OnStart()

{

         unsigned long           idThread;

         int port=8443;

         //創建線程,啓動服務

         CreateThread(NULL,0,

                   (LPTHREAD_START_ROUTINE)StartServer,

                   (void *)port, 0 ,&idThread);

}

(15StartServer函數爲啓動線程處理函數,其代碼如下:

//啓動服務線程函數

void StartServer(void* void_parm)

{

        

         WSADATA              wsaData;

         int err;

         int listen_sd;

        

         unsigned long           idThread;

         struct sockaddr_in sa_serv;

         HANDLE            hd;

         int port = (int) void_parm;

         //初始化windows socket

         if (WSAStartup(MAKEWORD(1, 1), &wsaData))

         {

                   char *msg = (char *)malloc(128);

                   strcpy(msg,"初始化Socket失敗!");

                   SendMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLIENT_MSG,0,(long )msg);

                   return;

         }

         //新建socket句柄

         listen_sd = socket (AF_INET, SOCK_STREAM, 0);

         //初始化sockaddr_in結構體,設置TCP協議和端口

         memset (&sa_serv, '/0', sizeof(sa_serv));

         sa_serv.sin_family      = AF_INET;

         sa_serv.sin_addr.s_addr = INADDR_ANY;

         sa_serv.sin_port        = htons (port);        

         //綁定端口

         err = bind(listen_sd, (struct sockaddr*) &sa_serv,

                   sizeof (sa_serv));         

         if(err < 0)

         {

                   char *msg = (char *)malloc(128);

                   strcpy(msg,"綁定Socket失敗!");

                   SendMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLIENT_MSG,listen_sd,(long )msg);

                   return;

         }

         //偵聽,tcp連接

         err = listen (listen_sd, 5); 

         char *msg = (char *)malloc(128);

         strcpy(msg,"啓動服務成功!");

         SendMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLIENT_MSG,listen_sd,(long )msg);

         for(;;)

         {

                   //啓動接收連接線程

                   hd=CreateThread(NULL,0,

                            (LPTHREAD_START_ROUTINE)AcceptThreadProc,

                            (void *)listen_sd, 0 ,&idThread);

                   //等待該線程執行完畢,開啓下一個線程,繼續等待接收連接。

                   WaitForSingleObject(hd, INFINITE);

          }

        

}

(16AcceptThreadProc函數爲接收鏈接線程處理函數,其代碼如下:

//接收連接線程函數

void AcceptThreadProc( void* void_parm )

{

         int sockFd = (int) void_parm;

         HANDLE hd;

         unsigned long idThread;

         int clientFd;

         //接收連接

         clientFd = accept(sockFd, NULL, NULL);

         if(clientFd < 0){

                   if(errno == EINTR)    /* interrupted system call */

                            return;

                   ExitThread(0);

         }

         //接收客戶端連接後,開啓線程處理客戶端事務即接收客戶端消息

         hd=CreateThread(NULL,0,

                   (LPTHREAD_START_ROUTINE)client,

                   (void *)clientFd, 0 ,&idThread);

}

(17client函數爲接收客戶端消息線程處理函數,其代碼如下:

//處理客戶端事務線程函數,接收客戶端消息

void  client( void* void_parmint )

{

         int clientFd = (int) void_parmint;

         int     maxFd;

         int len;

         int flag=0;

         fd_set       writeFds, readFds, excFds;

         char buffer[8192];

         descriptor_t clientDesc;

         clientDesc.fd = clientFd;

         clientDesc.ssl = NULL;

         SSL           *ssl;

         //新建SSL連接句柄

         if((ssl = SSL_new(ctx)) == NULL)

         {

                   return;

         }

         //設置SSL連接Socket句柄

         SSL_set_fd(ssl, clientFd);

         //接收SSL連接

         if(SSL_accept(ssl) <= 0)

         {

                   return;

         }

         clientDesc.ssl = ssl;

         //把客戶端鏈接句柄保存到全局變量中。

         for(int i=0;i<MAX_CLIENT;i++)

         {

                   if(Clients[i].fd==0)

                   {

                            Clients[i].fd=clientFd;

                            Clients[i].ssl=ssl;

                            flag = 1;

                            break;

                   }

         }

         if(!flag)

         {

                   //已經達到鏈接最大限制

                   SSL_write(ssl,"服務器已滿!",strlen("服務器已滿!"));

                   SSL_shutdown(ssl);

                   SSL_free(ssl);

                   return;

         }

         maxFd = clientFd;

         for(;;)

         {

                   FD_ZERO(&writeFds);

                   FD_ZERO(&readFds);

                   FD_ZERO(&excFds);

                   FD_SET(clientFd, &readFds);

                   //異步的方式等待客戶端數據

                   int nfd = select(maxFd + 1, &readFds, &writeFds, &excFds, NULL);

                   if(nfd <= 0)

                   {

                            if(errno == EINTR)    /* interrupted system call */

                                     continue;

                            return;

                   }

                   if(FD_ISSET(clientFd, &readFds))//有客戶端數據需要讀取

                   {

                            //接收客戶端數據

                            len = SSL_read(clientDesc.ssl,buffer,sizeof(buffer));

                            if(len <=0)

                            {

                                     char *msg = (char *)malloc(128);

                                     strcpy(msg,"客戶端退出!");

                                     SendMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLIENT_MSG,clientFd,(long )msg);

                                     return;

                            }

                            buffer[len]='/0';

                            char *msg = (char *)malloc(len +1);

                            strcpy(msg,buffer);

                            //發送WM_CLIENT_MSG消息到主窗體,把接收到的消息顯示到列表框

                            SendMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLIENT_MSG,clientFd,(long )msg);

                   }

         }

         return;

}

(18)添加處理WM_CLIENT_MSG消息的函數OnClientMsg()。其代碼如下:

//處理線程WM_CLIENT_MSG消息,顯示到列表框中

LRESULT CServerDlg::OnClientMsg(WPARAM wParam,LPARAM lParam)

{

         CString msg;

         msg.Format("Socket:%d  %s",wParam,(char *)lParam);

         m_list.InsertString(0,msg);

         free((void*)lParam);

         return 0L;

}

(19void CServerDlg:: OnSend ()函數即“Send”按鈕中添加如下代碼,向所有已經鏈接的客戶端發送消息。

//向客戶端發送消息

void CServerDlg::OnSend()

{

         UpdateData();

         //遍續所有已經鏈接客戶端,發送消息

         for(int i=0;i<MAX_CLIENT;i++)

         {

                   if(Clients[i].fd != 0)

                   {

                            //發送消息

                            SSL_write(Clients[i].ssl,m_str.GetBuffer(0),m_str.GetLength());

                            m_str.ReleaseBuffer();

                   }

         }

         m_list.InsertString(0,m_str);

         m_str.Empty();

         UpdateData(FALSE);

         ((CEdit *)GetDlgItem(IDC_EDIT1))->SetActiveWindow();

}

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章