从本节开始,从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); } } } }