从本节开始,从IM的功能出发,以及代码展示来实现IM客户端:
从第一个用例开始:注册
注册按钮是放置在登陆界面上的,笔者的登陆界面展示如下:
单击注册按钮后,出现如下界面:
如图所示,登陆界面是程序的主对话框,客户端的注册框的任务是:
1)建立客户端与服务端链接//这一步在CRegister::OnInitDialog()中执行
2)填写注册信息
3)检查信息格式//此步以及后面的步骤在CRegister::OnOK()中执行
4)发送注册包
5)接受服务端的反馈包
8)断开与服务端的链接
根据上面的流程,当填好注册信息后,主要执行代码如下:
BOOL CRegister::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
//设置默认性别
((CButton *)GetDlgItem(IDC_RADIO3))->SetCheck(TRUE);//选上
sex=0x04;
//添加代码--〉向服务器申请登陆
BYTE *pIP;//获取IP控件Address
CString strIP;
DWORD dwIP;
CWnd *pWnd=GetParent();
((CLoggingDlg *)pWnd)->m_Address.GetAddress(dwIP);
pIP = (unsigned char*)&dwIP;
strIP.Format( "%u.%u.%u.%u ",*(pIP+3), *(pIP+2), *(pIP+1), *pIP);
//链接
extern CTCPSocket sock;
if(sock.SetupTCPClientSocket(strIP, ((CLoggingDlg *)pWnd)->m_strPort)<0)
{
::MessageBox(NULL,"socket() or connect() failed","system error",MB_OK|MB_ICONERROR);
}
else//链接成功
{
sock.rio_readinitb(sock.rio, sock.sock);
}
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CRegister::OnOK()
{
// 本地数据检验与处理
UpdateData(TRUE);
if(m_ByteAge>130)
::MessageBox(NULL,"年龄范围在0-130之间","错误信息",MB_OK|MB_ICONERROR);
if(m_strPass!=m_strRePass)
::MessageBox(NULL,"两次密码输入不一致","错误信息",MB_OK|MB_ICONERROR);
m_strName.TrimLeft();
m_strName.TrimRight();
//发送数据
extern CTCPSocket sock;
cln_register reg;
reg.magic=0x54;
reg.flags=0x02;
sprintf(reg.name,"%s",m_strName);
sprintf(reg.pass,"%s",m_strPass);
sprintf(reg.city,"%s",m_strCity);
reg.age=m_ByteAge;
reg.sex=sex;
sprintf(reg.info,"%s",m_strInfo);
int num;
if((num=sock.rio_writeline(sock.sock, ®, sizeof(reg), 0))<0)
::MessageBox(NULL,"发送注册消息失败","system error",MB_OK|MB_ICONERROR);
//接受数据
char buf[88];
int size=sock.rio_readlineb(sock.rio, buf, sizeof(buf));
serv_register *serv_reg=(serv_register *)buf;
if(serv_reg->magic==0x55 && serv_reg->flags==0x02)
{
CString str="注册成功,id:";
str += serv_reg->id;
str += "密码:";
str += serv_reg->pass;
::MessageBox(NULL,str,"system error",MB_OK);
closesocket(sock);//关闭TCP套接字
CDialog::OnOK();
}
else
{
::MessageBox(NULL,"注册失败,请重新注册","system error",MB_OK|MB_ICONERROR);
}
}
以上就是注册用例了,可以看出明显的缺陷是:每次注册都会链接一次服务器,注册结束后直接回到登陆界面,关于注册失败也没有采取保留措施,导致用户友好性不是很好,不过这些都可以改善,读者有兴趣可以试一试;
-----------------------------------------------------------------------------------------------------------------------------------------
既然讲了注册,下面就将登陆一并讲了吧:
客户端的登陆任务步骤如下:
1)建立客户端与服务端链接
2)输入登录信息
3)本地校验输入信息格式错误
4)发送登录请求
5)接受服务端的反馈包
6)分析包:登陆成功或失败(成功则进入好友界面,接受好友信息)
根据上面的流程,当填好登陆信息后,主要执行代码如下:
BOOL CLoggingDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
m_ButtonCheckPort.SetCheck(BST_CHECKED);//默认自动获取port
if(m_ButtonCheckPort.GetCheck())
{
m_EditPort.EnableWindow(FALSE);
}
m_ButtonIp.SetCheck(BST_CHECKED);//使用默认IP
if(m_ButtonIp.GetCheck())
{
m_Address.EnableWindow(FALSE);
}
//设置IP默认值
CString strIP = "127.0.0.1 ";
DWORD dwIP = inet_addr(strIP);
BYTE *pIP = (BYTE*)&dwIP;
m_Address.SetAddress(*pIP, *(pIP+1), *(pIP+2), *(pIP+3));
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CLoggingDlg::OnCheckPort()
{
// TODO: Add your control notification handler code here
if(m_ButtonCheckPort.GetCheck())
{
m_EditPort.EnableWindow(FALSE);
}
else
m_EditPort.EnableWindow(TRUE);
}
void CLoggingDlg::OnCheckIp()
{
// TODO: Add your control notification handler code here
if(m_ButtonIp.GetCheck())
{
m_Address.EnableWindow(FALSE);
}
else
m_Address.EnableWindow(TRUE);
}
void CLoggingDlg::OnOK()
{
// TODO: 本地校验
this->UpdateData(TRUE);
if((strcmp(this->m_strUserId,"")==0) || (strcmp(this->m_strPassword ,"")==0))
{
::MessageBox(NULL,"用户名或口令不能为空!","错误信息",MB_OK|MB_ICONERROR);
}
else
{
this->UpdateData(TRUE);
//添加代码--〉向服务器申请登陆
BYTE *pIP;//获取IP控件Address
CString strIP;
DWORD dwIP;
m_Address.GetAddress(dwIP);
pIP = (unsigned char*)&dwIP;
strIP.Format( "%u.%u.%u.%u ",*(pIP+3), *(pIP+2), *(pIP+1), *pIP);
//生成链接socket
extern CTCPSocket sock;
//extern rio_t rio;
if(sock.SetupTCPClientSocket(strIP, m_strPort)<0)
{
::MessageBox(NULL,"socket() or connect() failed","system error",MB_OK|MB_ICONERROR);
}
else//链接成功
{
sock.rio_readinitb(sock.rio, sock.sock);
cln_log log;
log.magic=0x54;
log.flags=0x01;
sprintf(log.userid,"%s",m_strUserId);
sprintf(log.password,"%s",m_strPassword);
char buf[10];
memset(buf,'\0',sizeof(buf));
int num;
if((num=sock.rio_writeline(sock.sock, &log, sizeof(log), 0))<=0)
::MessageBox(NULL,"rio_writen() failed","system error",MB_OK|MB_ICONERROR);
int size=sock.rio_readlineb(sock.rio, buf, sizeof(buf));
if(size>0)
{
header *servlog =(header *)buf;
if((servlog->magic==0x55) && (servlog->flags==0x01))
//登录成功
CDialog::OnOK();//打开好友聊天界面对话框
else
if((servlog->magic==0x54) && (servlog->flags==0x01))
::MessageBox(NULL,"登录失败","system error",MB_OK|MB_ICONERROR);
else
::MessageBox(NULL,"数据包不在服务范围内","system error",MB_OK|MB_ICONERROR);
}
}
}
}