服务端实现步骤如下。
(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();
}