服務端實現步驟如下。
(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]; //保存客戶端鏈接句柄的數組
(13)BOOL CServerDlg::OnInitDialog()函數中添加如下代碼初始化服務器環境。
// TODO: Add extra initialization here
//初始化OpenSSL環境
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
SSL_METHOD *meth;
meth = SSLv23_server_method(); //使用SSL V2或V3協議
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
(14)void CServerDlg::OnStart()函數即“啓動”按鈕中添加如下代碼,創建線程,啓動服務。
//啓動服務
void CServerDlg::OnStart()
{
unsigned long idThread;
int port=8443;
//創建線程,啓動服務
CreateThread(NULL,0,
(LPTHREAD_START_ROUTINE)StartServer,
(void *)port, 0 ,&idThread);
}
(15)StartServer函數爲啓動線程處理函數,其代碼如下:
//啓動服務線程函數
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);
}
}
(16)AcceptThreadProc函數爲接收鏈接線程處理函數,其代碼如下:
//接收連接線程函數
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);
}
(17)client函數爲接收客戶端消息線程處理函數,其代碼如下:
//處理客戶端事務線程函數,接收客戶端消息
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;
}
(19)void 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();
}