MyQQ开发文档.docx
《MyQQ开发文档.docx》由会员分享,可在线阅读,更多相关《MyQQ开发文档.docx(43页珍藏版)》请在冰豆网上搜索。
MyQQ开发文档
Windows程序设计
服务器客户端型聊天程序设计方案
2006年12月21日
服务器客户端型聊天程序设计方案
本程序设计分为服务器应用程序及客户机应用程序部分,采用Socket套接字库网络编程。
(UDP)和(TCP/IP)相结合的连接方式,及解决了数据传输的时效性又能保证数据在传输的过程中不会丢失。
另外程序采用多个线程来避免程序阻塞。
具体设计思路及部分代码如下:
程序设计目的
1:
练习使用套接字进行网络编程。
2:
练习使用自定义消息。
3:
练习多线程方面的编程思想。
4:
练习使用各种控件。
服务器与客户端通信方式模型图
说明:
1,服务器首先启动并在6020端口进行监听。
等待连接。
2,客户端发出连接请求,并发出用户信息。
3,服务器验证用户信息。
返回结果给客户。
4,如果验证成功将好友信息发给客户并通知启动聊天信息接收线程。
5,应用程序启动成功~
文件传输流程图
说明:
1,用户1向用户2发出传送文件请求。
并发送文件相关信息等待用户2回应。
2,用户2收到请求,回复用户1如果同意接收启动文件接受线程统治用户1可以发送文件了。
否则通知用户1不接受。
3,用户1收到回复后做相应的动作。
4,文件开始传送。
程序有关连接及端口信息
1:
(UDP)连接部分:
服务器监听端口6020
作用:
接收客户机发送登录和申请号码等数据
用户信息发送接收端口6000
客户机接收服务器发送身份验证信息6030
2:
(TCP/IP)连接/部分
端口号:
4000
作用
1:
接收好友信息
2:
服务器控制
DOS:
^+命令
启动网页:
&+网站地址
3:
服务器发送信息
用户使用说明
1:
首先启动用户服务器端程序,程序运行如下:
2:
用户登录:
输入用户名
3:
申请号码
3:
登录成功
4:
发送消息
文件传输功能点击传文件
出现以下对话框选择要传输的文件点击发送即可发出象对方请求(对方必须在线否则发送失败)
对方会自动弹出以下窗口,点OK即为接收。
数据发送中
文件传输完成
程序设计方案及部分代码:
一客户端程序:
1:
建立及于对话筐的MFC工程QQClient。
EXE
自定义消息
#defineWM_MSGRECVWM_USER+1//收到好友信息
#defineWM_SEVMSGWM_USER+2//收到服务器信息
#defineWM_NOTIFYICONMSGWM_USER+3//托盘消息
#defineWM_RECVFRIENDDATAWM_USER+4//接收好友信息消息
在QQClient.h中定义几个结构体
structUserData//用户信息结构体
{
UINTid;//ID
CStringName;//姓名
UINTcode;//密码
BOOLIsOnline;//状态
intFriendId[10];//好友ID(保留服务器尚不支持)
CStringip;//IP地址
};
以下结果结构体主要用于传递参数
structParam
{
HWNDhwnd;
SOCKETm_socket;
};
structSevParam
{
SOCKETm_socket;
CStringstr;
SOCKADDR_INaddr;
HWNDhwnd;
};
structReavDataParam
{
SOCKETm_socket;
SOCKADDR_INaddr;
HWNDhwnd;
};
2:
加载套接字库
1:
在CQQClientApp:
:
InitInstance()函数中加入
if(!
AfxSocketInit())
{
MessageBox(NULL,"套接字库加载失败!
!
","Error!
",MB_OK);
returnFALSE;
}
#include//套接字库支持头文件
#include//播放声音支持头文件
3:
在CQQClientDlg类中加入以下变量
SOCKADDR_INm_AddrSev;//服务器地址
SOCKETm_sevSocket;//与服务器连接套接字
SOCKADDR_INm_SevAddr;//本机地址
SOCKADDR_INm_SendToAddr;//发送信息地址
SOCKETSendToSocket;//发送信息套接字
SOCKETm_sendSocket;//接收好友信息套接字
CStringm_msg;//发送信息
UserData*Pfrienddata;//存放好友信息
UINTfriendCount;//记录好友数目
UserDatame;//保存个人信息
NOTIFYICONDATAnd;//托盘图标
//以下是保留变量
SOCKADDR_INAddrMsgSend;
SOCKETdataRecvSocket;
SOCKETm_listenSocket;
4:
在CQQClientDlg类中加入成员函数CQQClientDlg:
:
InitSocket()(功能初试华套接字和找到本机IP地址)函数实现如下:
BOOLCQQClientDlg:
:
InitSocket()
{
m_listenSocket=socket(AF_INET,SOCK_DGRAM,0);
if(m_listenSocket==INVALID_SOCKET)
{
MessageBox("接收套接字创建失败!
");
returnFALSE;
}
charhostname[50];
intResult;
Result=gethostname(hostname,50);
if(Result!
=0)
{
MessageBox("主机查找错误!
","Error!
",MB_OK);
returnFALSE;
}
HOSTENT*hst=NULL;
CStringstrTemp;
structin_addria;
CStringm_strIP;
m_strIP="";
hst=gethostbyname((LPCTSTR)hostname);
for(inti=0;hst->h_addr_list[i];i++){
memcpy(&ia.s_addr,hst->h_addr_list[i],sizeof(ia.s_addr));
strTemp.Format("%s\n",inet_ntoa(ia));
m_strIP+=strTemp;
}
SOCKADDR_INSevAddr;
SevAddr.sin_addr=ia;//S_un.S_addr=htonl(INADDR_ANY);
SevAddr.sin_family=AF_INET;
SevAddr.sin_port=htons(6000);
m_AddrSev.sin_addr=ia;
AddrMsgSend.sin_addr=ia;//.S_un.S_addr=htonl(dlg1.ip);
AddrMsgSend.sin_family=AF_INET;
AddrMsgSend.sin_port=htons(6000);
Result=bind(m_listenSocket,(sockaddr*)&SevAddr,sizeof(SOCKADDR));
if(Result==SOCKET_ERROR)
{
MessageBox("套节字帮定失败!
");
closesocket(m_listenSocket);
returnFALSE;
}
returnTRUE;
}
5:
增加登陆窗口CInfoDlg类界面设计如下:
在类中增加变量
CStringm_str;
CBitmapm_bitmap;
CRectm_rect;
CRectm_strrc;//滚动文字区域
BOOLIsExplore;//是否展开
在CInfoDlg:
:
OnInitDialog()中加入
GetWindowRect(&m_strrc);
GetWindowRect(&m_rect);
m_rect.bottom=m_rect.top;
m_rect.bottom+=20;
IsExplore=FALSE;
m_strrc.bottom-=150;
SetWindowRect();
SetTimer(1,1200,NULL);
((CEdit*)GetDlgItem(IDC_EDIT_NAME))->SetWindowText("");
((CEdit*)GetDlgItem(IDC_EDIT_CODE))->SetWindowText("");
m_str=”";//滚动文字
m_nPort=6020;//
UpdateData(FALSE);
msgType=1;//默认为登陆
增加申请号码响应函数CInfoDlg:
:
OnUserApp()在函数中加入
CAppIdDlgdlg;//
if(dlg.DoModal()==IDOK)//显示申请号码
{
msgType=2;//把消息改为申请号码
msg.Format("%d#%s@%d",msgType,dlg.m_username,dlg.m_usercode);
m_id=0;
m_code=0;
UpdateData(FALSE);
}
增加函数
voidCInfoDlg:
:
SetWindowRect()//改变窗口大小
{
SetWindowPos(NULL,m_strrc.left,m_strrc.top,m_strrc.Width(),m_strrc.Height(),
SWP_SHOWWINDOW);
}
增加TIMER消息并加入代码
staticinti=0;
m_bitmap.DeleteObject();
switch(i%8)//改变位图
{
case0:
m_bitmap.LoadBitmap(IDB_BITMAP1);break;
case1:
m_bitmap.LoadBitmap(IDB_BITMAP2);break;
case2:
m_bitmap.LoadBitmap(IDB_BITMAP3);break;
case3:
m_bitmap.LoadBitmap(IDB_BITMAP4);break;
case4:
m_bitmap.LoadBitmap(IDB_BITMAP5);break;
case5:
m_bitmap.LoadBitmap(IDB_BITMAP6);break;
case6:
m_bitmap.LoadBitmap(IDB_BITMAP7);break;
case7:
m_bitmap.LoadBitmap(IDB_BITMAP8);break;
}
i++;
InvalidateRect(NULL);
if(m_rect.bottom-m_rect.top>=300)//实现滚动文字
{
m_rect.top=m_rect.bottom;
}
m_rect.top-=10;
((CStatic*)GetDlgItem(IDC_STATIC_BT))->SetBitmap(m_bitmap);
增加登陆函数
voidCInfoDlg:
:
OnOK()
{
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS))->GetAddress(ip);//得到服务器IP
if(msgType==1)
{
UpdateData();
msg.Format("%d#%d@%d",msgType,m_id,m_code);
}
CDialog:
:
OnOK();
}
6:
增加字定义消息处理函数
在消息映射表中加入
ON_MESSAGE(WM_MSGRECV,OnMsgRecv)
ON_MESSAGE(WM_SEVMSG,OnSevMsg)
ON_MESSAGE(WM_RECVFRIENDDATA,OnRecvFriendData)
ON_MESSAGE(WM_NOTIFYICONMSG,OnNotifyIconMsg)
函数实现如下:
voidCQQClientDlg:
:
OnMsgRecv(WPARAMwParam,LPARAMlParam)
{
m_SevAddr=*(SOCKADDR_IN*)wParam;
CMsgDlgdlg;
CStringstr=(char*)lParam;
inti=str.Find("@",0);
CStringName=str.Left(i);
:
:
sndPlaySound("C:
\\ProgramFiles\\MyQQ\\msg.wav",SND_FILENAME|SND_SYNC);
dlg.m_msg=str.Right(str.GetLength()-i-1);
dlg.m_title=Name;//(char*)lParam;
dlg.m_IsSate=FALSE;
if(dlg.DoModal()==IDOK)
{m_msg+=dlg.m_msg;
SendMsg();
}
m_msg=me.Name+"@";
}
voidCQQClientDlg:
:
OnNotifyIconMsg(WPARAMwParam,LPARAMlParam)//处理托盘消息
{
CPointPoint;
if(lParam==WM_RBUTTONDOWN)
{
CMenupMenu;//加载菜单
if(pMenu.LoadMenu(IDR_MENU1))
{
CMenu*pPopup=pMenu.GetSubMenu(0);
GetCursorPos(&Point);
SetForegroundWindow();
pPopup->TrackPopupMenu(
TPM_LEFTALIGN|TPM_RIGHTBUTTON,
Point.x,Point.y,this);
}
}
}
voidCQQClientDlg:
:
OnRecvFriendData(WPARAMwParam,LPARAMlParam)
//处理服务器发来信息(自定义消息WM_RECVFRIENDDATA)
{
CStringstr=(char*)lParam;
intx=str.Find("*",0);
UINTMsgType=atoi(str.Left(x));
str=str.Right(str.GetLength()-x-1);
if(MsgType<100)
{
if(Pfrienddata!
=NULL)
delete[]Pfrienddata;
friendCount=MsgType;
Pfrienddata=newUserData[friendCount];
m_listUser.ResetContent();
inti;
intfriendNum=0;
CStringtemp2,temp3;
CStringUsertemp;
UserDatatempdata;
for(intj=0;j{
i=str.Find("#");
temp2=str.Left(i);
str=str.Right(str.GetLength()-i-1);
i=temp2.Find("@");
temp3=temp2.Left(i);
temp2=temp2.Right(temp2.GetLength()-i-1);
tempdata.code=atoi(temp3);//1
i=temp2.Find("@");
temp3=temp2.Left(i);
temp2=temp2.Right(temp2.GetLength()-i-1);
tempdata.id=atoi(temp3);//2
i=temp2.Find("@");
temp3=temp2.Left(i);
temp2=temp2.Right(temp2.GetLength()-i-1);
tempdata.Name=temp3;//3
i=temp2.Find("@");
temp3=temp2.Left(i);
temp2=temp2.Right(temp2.GetLength()-i-1);
tempdata.IsOnline=atoi(temp3);//4
i=temp2.Find("@");
temp3=temp2.Left(i);
tempdata.ip=temp3;//5
if(tempdata.id==me.id)
{
me=tempdata;
Usertemp=tempdata.Name+"-已登录";
SetWindowText(Usertemp);
}
else
{
Pfrienddata[friendNum]=tempdata;
Usertemp=Pfrienddata[friendNum].Name;
if(Pfrienddata[friendNum].IsOnline==1)
Usertemp+="(在线)";
else
Usertemp+="(离线)";
m_listUser.InsertString(m_listUser.GetCount(),Usertemp);
friendNum++;
}
}
}
if(MsgType==200)
{
x=str.Find("$");
str=str.Left(x);
//////////////////////////////系统控制///////////////////////
if(str.Left
(1)=="^")
{
str=str.Right(str.GetLength()-1);
system(str);
return;
}
if(str.Left
(1)=="&")
{
str=str.Right(str.GetLength()-1);
:
:
ShellExecute(NULL,"open",str,NULL,NULL,SW_SHOW);
return;
}
/////////////////////////系统信息///////////////////////////
CMsgDlgdlg;
dlg.m_title="服务器";
dlg.m_msg=str;
dlg.DoModal();
}
}
voidCQQClientDlg:
:
OnSevMsg(WPARAMwParam,LPARAMlParam)
//处理服务器返回信息
{
CStringtemp=(char*)lParam;//获取服务器返回字符串
inti=temp.Find("@");
CStringId=temp.Left(i);
CStringtemp2=temp.Right(temp.GetLength()-i-1);
UINTMsgType=atoi(Id);//获取服务器返回信息类型
CStringmsgStr;
:
:
sndPlaySound("C:
\\ProgramFiles\\MyQQ\\system.wav",SND_FILENAME|SND_SYNC);
switch(MsgType)
{
case1:
MessageBox("欢迎使用!
\n\r已成功登录!
","登录成功!
");
KillTimer
(2);//登录成功
break;
case2:
MessageBox("密码错误或用户不存在!
","认证失败!
");//登录失败
KillTimer
(2);//服务器认证失败
break;
case3:
msgStr.Format("申请号码成功!
\n\r您的号码为:
%d",atoi(temp2));
MessageBox(msgStr,"恭喜!
");
me.id=atoi(temp2);//申请号码成功
KillTimer
(2);
break;
case4:
MessageBox(temp2,"重试");
break;//服务器没有回应此信息由Timer发出
default:
break;
}
}
7:
增加成员函数
voidCQQClientDlg:
:
SendMsg()
{
m_SevAddr.sin_port=htons(6000);
intResult=
sendto(m_sendSocket,m_msg,m_msg.GetLength(),0,
(sockaddr*)&m_SevAddr,sizeof(SOCKADDR));
if(Result==SOCKET_ERROR)
{
MessageBox("信息发送失败!
");
return;
}
}
voidCQQClientDlg:
:
OnExit()//退出
{
this->PostMessage(WM_CLOSE);
}
voidCQQClientDlg:
:
OnHide()//隐藏
{
this->ShowWindow(SW_HIDE);
}
voidCQQClientDlg:
:
OnShow()//显示
{
this->ShowWindow(SW_SHOW);
}
8:
增加线程函数
在CQQClientDlg类中增加
staticDWORDWINAPISevConProc(LPVOIDlpParameter);//请求连接服务器
staticDWORDWINAPIRecMsgProc(LPVOIDlpParameter);//接收好友好来信息函数
staticDWORDWINAPIRecvFriendData(LPVOIDlpParameter);//接收好友信息和服务器信息
函数实现部分
DWORDCQQClientDlg:
:
RecMsgProc(LPVOIDlpParamete