【摘要】 时下腾讯QQ即时通讯软件的应用如火如荼,只要上网的人都知道QQ,几乎得上网的人都拥有自己的QQ号甚至是多个,然而腾讯的前身是QICQ,走到今天算是一个不平凡的历史,当年她就是模仿外国的ICQ来的,由于版权原因最终才改为腾讯QQ的。如今QQ成为中国网民最喜欢的通讯软件了,当年只有很简单的功能,经过几年的发展升级变成了今天超级强大的功能了,当然还有类似的国内通讯软件,如新浪的UC软件等。
90年代后期,互联网刚刚在中国开始普及的时候,国外的ICQ软件可算是全球的聊天软件的老大,比腾讯QQ要早,ICQ是英文“I seek you”的简称,中文意思是我找你。ICQ最大的功能就是即时信息交流,只要记得对方的号码,上网时可以呼他,无论他在哪里,只要他上网打开ICQ,人们就可以随时交流。ICQ源于以色列特拉维夫的Mirabils公司。改公司成立于1996年7月,也就是这个时候,互联网最出名,下载使用人数最多的免费软件ICQ诞生了。
但是这些公司都只是提供软件的客户端程序免费下载,而不提供其服务器程序,因此对于与互联网连接的私有网络或局域网,这些软件就用不上了。下面的软件是基于C++语言设计的聊天程序,功能只有和腾讯QQ早期最初的功能类似。
【关键词】 ICQ,腾讯QQ,通信软件,即时信息
一. 问题描述及设计思路 3
1.1问题描述 3
1.2实现功能 3
1.2.1服务器端功能 3
1.2.2客户端功能 3
1.3设计思路 4
1.3.1总体架构思路 4
1.3.2服务器端 4
1.3.3客户端 4
二. 详细设计过程 4
2.1程序总体架构图 4
2.2通信协议设计 5
2.2.1消息类型: 5
2.2.2消息结构体设计 7
2.2.3群留言信息结构设计 7
2.3通信模块软件设计 7
2.3.1注册模块 7
2.3.2登录模块 7
2.3.3下线模块 7
2.3.4添加好友模块 7
2.3.5好友聊天模块 7
2.3.6删除好友模块 7
2.3.7加入群模块 7
2.3.8创建群模块 7
2.3.9退出群模块 7
2.3.10群聊模块 8
2.3.11资源共享模块 8
2.3.12资源下载模块 8
2.4服务器界面模块设计 8
2.4.1运行信息显示模块 8
2.4.2群信息以及群成员显示模块 8
2.4.3用户信息显示模块 8
2.4.4其他功能模块 8
2.5客服端界面模块设计 8
2.5.1注册模块 8
2.5.2登录模块 8
2.5.3添加好友模块 8
2.5.4单聊模块 8
2.5.5群聊模块 8
2.5.6加入群模块 8
2.5.7创建群模块 8
2.5.8资源共享模块 8
2.5.9资源下载模块 8
三. 结论及体会 8
3.1程序运行主界面 8
3.1.1服务器 8
3.1.2客服端 9
3.2功能测试 9
3.2.1登录测试 9
3.2.2注册测试 9
3.2.3添加好友测试 10
3.2.4与好友聊天测试 10
3.2.5加入群测试 10
3.2.6创建群测试 10
3.2.7文件传输测试 11
3.2.8群聊测试 11
3.2.9资源共享测试 11
3.2.10文件收索及下载测试 11
3.2.11服务器测试 12
3.3体会 12
3.3.1程序设计 12
3.3.2文档编写 13
四. 附录 13
附录A:服务器处理通信协议的源代码 13
1.处理客户端连接消息 13
2. 处理接收到的各种消息: 14
附录B客户端处理通信程序: 32
1. 作为本地服务器 33
2.接受服务器端信息并处理程序: 33
1. 带颜色的ListBox: 47
2. 客服端通信封装的类(关键代码) 50
3. 客服端作为本地服务器封装的类(关键代码) 51
五. 参考文献 53
一.问题描述及设计思路
1.1问题描述
模拟QQ即时聊天工具设计一套网络聊天应用程序,按照C/S结构分别设计服务端程序和客户端程序。现在腾讯QQ的功能超级强大,要完全现实它的功能不是一两个人一两天能够完成的任务,基于时间和开发人员只有本人的情况下,所以选择一部分最常用的功能实现。设计的程序界面友好简单易用。
1.2实现功能
1.2.1服务器端功能
服务器端主要功能就是通过图形界面来维护服务器的信息,包括服务器运行信息的即时显示;手动启动和停止服务器的运行;查看现有群以及每个群有的成员;查看所有注册用户以及是否在线;清除服务器信息;服务器后台运行;保存和加载服务器信息等。
1.2.2客户端功能
客服端主要实现的功能有:用户注册;用户登陆;用户下线;程序运行的动态信息显示;添加好友;和好友聊天;删除好友;加入群;创建群;退出群;群聊;资源共享;下载共享资源;文件传输;实时监听好友是否传输文件等。
1.3设计思路
1.3.1总体架构思路
此程序要求设计客户端和服务器端,主要解决的问题就是客户端和服务器端的通信以及客户端和客户端的通信。因为涉及到通信的内容很多,例如登陆信息,注册信息,聊天信息等等。怎样识别不同的信息需要自己设计通信协议,及根据每次收到的信息的不同做相应的处理。所以自己定义了许多通信消息的类型,以便准确完成通信。
1.3.2服务器端
服务器运行开始等待客户端的登陆以及注册信息,当有客户端要求通信时,便对客户端信息做保存并更新服务器信息。对于每一个客服端都建立一个相应的通信套接字并实时异步监听客服端是否有动作。服务器端的主要作用就是维护客服端的信息以及中转必要的通信信息,例如对于客户添加好友,服务器端就需要转发客服端的求情信息到另外一个客户端。另外一点设计思路就是通过图形界面来操作服务器。
1.3.3客户端
客户端主要根据用户的使用信息即时相应消息,然后根据不同的信息发送不同的信息到服务器端,开始和服务器端通信。客户端在登录成功以后便建立自己的本地服务器,方便自己共享资源以及可以直接和好友聊天儿不必每次发送信息都需要通过服务器中转,一般情况下首次发送的信息由服务器中转,之后都是直接通信了。这样可以减少服务器端的负载,让服务器运行更稳定,以及支持更多的客服端数量。客服端需要实时监听好友是否上线的信息,如果好友上线了应该即时得到通知。同样客服端也实时显示程序运行的信息,这对于开发人员尤其关键和重要,因为可以根据这个来了解程序是否运行正常。
二.详细设计过程
2.1程序总体架构图
2.2通信协议设计
2.2.1消息类型:
//消息类型
enum MSGType
{
enroll,check_name,login,add_friend,single_chat,group_chat,invalid_name,
invalid_password,already_login,success,fail,establish_group,join_group,
secede_group,single_chat_server,single_chat_client,single_chat_transfer,
offline,client_list,group_list,member_list,server_address,full,leave_word,
addfriend_exist,addfriend_error,offline_error, friend_data, group_data,
addfriend_success, addfriend_fail, joingroup_success, joingroup_fail,
secedegroup_success, secedegroup_fail, establishgroup_success,
establishgroup_fail,delete_friend,search_file,search_return,search_null,
down_file,declare_file,declare_success
};
每种消息具体说明如下:
enroll: 用户注册
check_name: 检查用户名是否可用
login: 用户登陆
add_friend: 添加好友
single_chat: 单聊
group_chat: 群聊
invalid_name: 无效用户名
invalid_password: 无效的密码
already_login: 已近登陆
success: 成功
fail: 失败
establish_group: 建立群
join_group: 加入群
secede_group: 退出群
single_chat_server: 接收私聊时对方发送过来的信息
single_chat_client: 从服务器接收到好友的addr
single_chat_transfer: 从服务器接收到中转的信息
offline: 下线
client_list: 请求客户好友信息
group_list: 请求客户群信息
member_list: 请求群成员信息
server_address: 服务器地址
leave_word: 留言
addfriend_exist: 好友已经存在
addfriend_error: 添加好友出错
offline_error: 下线出错
friend_data: 好友数据
group_data: 群数据
addfriend_success,: 添加好友成功
addfriend_fail: 添加好友失败
joingroup_success: 加入群成功
joingroup_fail: 加入群失败
secedegroup_success: 退出群成功
secedegroup_fail: 退出群失败
establishgroup_success:建立群成功
establishgroup_fail: 建立群失败
delete_friend: 删除好友
search_file: 收索文件
search_return: 收索返回
search_null, 收索为空
down_file: 下载文件
declare_file: 共享文件
declare_success: 共享成功
服务器和客户端就是通过这些信息类型来通信的。
2.2.2消息结构体设计
struct message
{
MSGType type; //消息类型
char name[MaxNameContent + 1];//消息名称
char msg[MaxMsgContent + 1]; //消息具体内容
//重载复制操作符
void operator =( const message& b)
{
type = b.type;
strcpy(name, b.name);
strcpy(msg, b.msg);
}
};
每次客服端和服务器的通信都是发送这样的结构体数据信息。
2.2.3群留言信息结构设计
//群的留言1.群名,2.发言人名,3.发言内容,
struct word
{
char groupName[MaxNameContent + 1];
char clientName[MaxNameContent + 1];
char leaveWord [MaxMsgContent + 1];
//重载复制操作符
void operator = (const word& b)
{
strcpy(groupName, b.groupName);
strcpy(clientName, b.clientName);
strcpy(leaveWord , b.leaveWord);
}
};
2.3通信模块软件设计
2.3.1注册模块
客服端在填写好完整的注册信息以后就发送注册用户类型的消息到服务器端,服务器端接收到注册信息以后做相应的处理,如果成功就保存客户信息。如下图所示:
2.3.2登录模块
客服端发送登录信息到服务器端,服务器端根据用户名和密码检查是否成功登录,如果登录成功的话,就需要将客户的信息发送给客户端,具体有好友信息,用户加入的群信息等。如果登录成功客户端接收服务器发送给客服端的好友和群等信息并在界面中显示出来。如下图:
2.3.3下线模块
客服端发送下线请求的信息到服务器端,服务器端接到此信息以后把此用户状态改为离线。服务器端返回下线操作是否成功。如下图所示:
2.3.4添加好友模块
客户端可以请求服务器所用的用户信息并显示出来,客户端可以选择某一个用户向服务器端发送添加此用户为好友的信息。服务器接收到添加好友的信息以后查找被添加的用户,如果找到就发送一条有用户添加他为好友的信息,如果对方不在线服务器就直接返回一条此用户不在线的信息给添加好友的用户。如果被添加的用户在线并且接受请求,那么服务端就更新用户信息,将他们互相添加为好友信息。如下图:
2.3.5好友聊天模块
客服端选择一个好友向服务器发送聊天请求,服务器在他们没有互相建立以前中转他们的发送聊天信息。一旦他们建立连接以后服务器就不作用了。如果对方不存在就把信息存放起来,等到对方上线以后再转发给改用户就可以了。如下图:
2.3.6删除好友模块
客户端选择一个需要删除的好友,然后向服务器发送删除好友的信息,服务器接受到信息以后向对方发送删除好友的信息已通知对方,后然更新服务器信息,解除次二人的好友关系。通知双方好友关系已解除。
2.3.7加入群模块
此模块的设计同添加好友模块类似。
2.3.8创建群模块
这个模块的功能和用户注册模块类似,不详细叙述了。
2.3.9退出群模块
此模块的设计同删除好友模块类似。
2.3.10群聊模块
此模块的设计同聊天模块类似,只不过是以群为单位开始聊天,同样没有在线的用户就以后登录接收到此聊天信息。
2.3.11资源共享模块
客服端作为本地服务器可以共享资源,共享的资源在服务器端由记录。当其他好友收索到共享的资源以后就可以下载了。
2.3.12资源下载模块
每个用户可以登录以后可以收索有哪些共享资源,并且可以下载到本地。
2.4服务器界面模块设计
2.4.1运行信息显示模块
此功能在程序中的各个模块中都有涉及到,每次服务器的操作信息动态的显示到界面中来就可以了。此功能模块有助于程序员了解服务器程序是否运行正常。
2.4.2群信息以及群成员显示模块
将所有的群都显示到列表框中,在单击每一个群的时候在另一个列表框中显示此群有哪些成员用户。
2.4.3用户信息显示模块
根据用户是都在线分别显示所有用户的情况,在线的用户显示到在线的列表框,离线的显示在离线对话框。
2.4.4其他功能模块
其他的很多按钮功能,点击某一个按钮实现相应的功能。主要有启动和停止运行服务器;保存服务器现在的信息到数据文件,从数据文件加载服务器信息;以及清空服务器信息等。
2.5客服端界面模块设计
2.5.1注册模块
需要填写用户名和密码,可以发送注册信息。
2.5.2登录模块
根据填写的用户名和密码发送登录信息,可以调用注册模块注册。
2.5.3添加好友模块
从服务器端获取所有用户并显示用户信息到列表框中,选中某一用户发送请求。
2.5.4单聊模块
能够发送聊天信息并且查看聊天记录信息,还可以传输文件信息。
2.5.5群聊模块
此模块功能同单聊模块,界面上对一个参与聊天的用户列表,没有文件传送功能。
2.5.6加入群模块
同添加好友模块。
2.5.7创建群模块
同用户注册模块。
2.5.8资源共享模块
能够将本地文件共享并显示文件相关信息到列表框中。
2.5.9资源下载模块
能够收索已经共享的文件并且显示出每一个文件的相关信息。
三.结论及体会
3.1程序运行主界面
3.1.1服务器
3.1.2客服端
3.2功能测试
3.2.1登录测试
登录界面如下:
登录成功界面如下:
3.2.2注册测试
3.2.3添加好友测试
3.2.4与好友聊天测试
3.2.5加入群测试
3.2.6创建群测试
3.2.7文件传输测试
3.2.8群聊测试
3.2.9资源共享测试
3.2.10文件收索及下载测试
3.2.11服务器测试
通过上面那么多的测试,服务器端都记录了他们的信息,如下截图可知:
3.3体会
3.3.1程序设计
通过本软件的设计与开发使自己体会很多,现简单的总结如下:
首先在开始编写程序以前需要思考很多,比如考虑需要实现哪些功能,考虑使用哪一种IO模型。这些对于后面程序的设计以及开发都是相当重要的,如果很多需求前期没有做好,后面返工的可能性很大,而且更加麻烦。所以前期的分析暂用了很多时间,但是这些都是很必要的。
在实际程序的编写过程中遇到的问题更多,因为通信程序设计涉及到客服端和服务器端,不论从开发的模式和调试的难度都比一般的程序更具挑战度。因为你编程的时候不仅仅只需要考虑能不能正常运行,还必须考虑服务器和客服端是否能够真正的通信,而且保证是正确的。可能调试通信用的时间比本身写程序用的时间更多。因为有时间很难找到不能正确通信的原因。比如说,明明发送方正确发送了信息,客户端就是没有收到,或者是明明发送了4次信息,但是对方却只收到一条或2条信息,这个时候就必须认真考虑出现这种情况的可能信息,就是在连续发送的时候,对方是不是收到一条信息以后就处理信息去了,后面的信息就没有接受到。当再次接收的时候已经发送完毕了,所以接收信息不完整。这样的情况很多,遇到一次问题就是锻炼自己的一次机会,所以我就在遇到问题的时候静下心来慢慢分析,把解决问题当成不仅仅是锻炼自己的机会,更把这个当做自己最快乐的事情来对待。这是为什么了?那是因为解决问题以后的那种激动心情和成就感。每个人做自己已经会的事大家都觉得很平常,但是当你做一件你本来就不会的事,但是你却做成功了,这说明学到了东西并且完成了任务。所以困难是一定能够战胜的。
随着功能的慢慢实现,对程序的感觉越来越好,后面的功能就更快的实现了。这说明做什么事情都需要投入,只有万分的投入才能真正的找到感觉。
3.3.2文档编写
虽然很久以前自己就开始有写文档的意识,但是以前写的都是注重技术性问题的解决的文档,像本次这么完成的文档还是很少自己写到。其实很多人都认识到写设计文档的重要性,但是很少有人去真正做到。特别是对于我们还是在校生,因为这些任务都不是必须的。很多人做一个项目只注重项目功能的显示,至于具体怎么实现可能很多人都没有开始前仔细去想过。反正根据需要完成的功能去一步步做就是了。前期的设计根本无从谈起,但是基本上项目最后的功能都能够实现。这是因为在校生做的项目都是比较小,而且经不起检验和扩展的。作为大型的项目这样做基本上就是等于失败,在前一段时间我带领我的团队(都是在校大学生,我也是)一个相对大型一些的一个项目,由于缺乏前期数据库的设计和整体功能的软件架构设计。几乎做到还不到三分之一大家都做不下去了,因为大家都在不停的改动数据库。网络通信功能根本还没有谈起,当然这个项目最终就这样不了了之了。因为后面大家时间都很忙,就放弃这个项目了。从这一点可以看出,锻炼每一个人的分析分析和前期软件设计的能力是多么的重要,更重要的是体现单把具体的设计文档化吗,这样便于大家讨论后修改,这也是后期真正软件开发的基石和根据。以上就是对于此次写文档的体会吧。
四.附录
附录A:服务器处理通信协议的源代码
1.处理客户端连接消息
LRESULT CQQServerDlg::OnAccept(WPARAM wParam, LPARAM lParam)
{
if (WSAGETSELECTEVENT(lParam) == FD_ACCEPT)
{
int len = sizeof(sockaddr_in);
m_acceptedSocket = accept(m_localSocket, (sockaddr*)&m_tempAddr, &len);
if (m_acceptedSocket == INVALID_SOCKET)
{
MessageBox("接受套接字时出错", "错误");
return -1L;
}
WSAAsyncSelect(m_acceptedSocket, m_hWnd, WM_READ, FD_READ|FD_CLOSE);
}
return 0L;
}
2.处理接收到的各种消息:
LRESULT CQQServerDlg::OnRead(WPARAM wParam, LPARAM lParam)
{
int i, j, k; //辅助计数变量
CEdit* pEdit;
BOOL bExist = FALSE;
SOCKET sClient = (SOCKET)wParam;
int iTempNum;
switch (WSAGETSELECTEVENT(lParam))
{
case FD_READ:
if(recv(sClient, (char*)&m_recvData, sizeof(message), 0) == SOCKET_ERROR)
{
MessageBox("读取用户信息时出错", "错误");
return -1L;
}
switch (m_recvData.type)
{
case enroll: //用户注册
if (m_iClientNum < MaxClient) //数目尚未超过可以供注册的数目
{
bExist = FALSE;
for (i=0 ; i<m_iClientNum; i++) //搜索是否存在同名
{
if(strcmp(m_cArrClient[i], m_recvData.name) == 0 )
{
bExist = TRUE;
break;
}
}
if (!bExist) //没有同名时……
{
strcpy(m_cArrClient[m_iClientNum], m_recvData.name);
strncpy(m_cArrPassword[m_iClientNum], m_recvData.msg, sizeof(m_cArrPassword[m_iClientNum]));
m_sendData.type = success;
//将注册成功消息发给新注册的用户
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0); m_sendData.type = enroll;
strcpy(m_sendData.name, m_recvData.name);
for (k=0; m_bArrOnline[k] && k<m_iClientNum; k++) //将新用户注册的信息发给每一个用户
{
send(m_arrClientSocket[k], (char*)&m_sendData, sizeof(m_sendData), 0);
}
m_iClientNum++; //客户数目加1
//服务器信息更新
m_strInfo += "新注册了一个用户!/r/n";
m_strInfo += "该新用户名字是";
m_strInfo += m_recvData.name;
m_strInfo += "/r/n";
UpdateData(FALSE);
pEdit = (CEdit *)GetDlgItem(IDC_INFO);
pEdit->LineScroll(pEdit->GetLineCount());
update(); //更新一下资料
}
else
{
//当有同名时返回错误信息
m_sendData.type = invalid_name;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
}
else
{
//数目超过可供注册的数目
m_sendData.type = fail;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
closesocket(sClient);
break;
case check_name:
bExist = FALSE;
for (i=0 ; i<m_iClientNum; i++ ) //搜索是否存在同名
{
if (strcmp(m_cArrClient[i], m_recvData.name) == 0)
{
bExist = TRUE;
break;
}
}
if (!bExist) //没有同名时……
{
m_sendData.type = check_name;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0); //将注册成功消息发给新注册的用户
}
else
{
//当有同名时返回错误信息
m_sendData.type = invalid_name;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
break;
case login:
//搜索该账号是否存在
for (j=0; strcmp(m_cArrClient[j], m_recvData.name)!=0 && j<m_iClientNum; j++)
{
;
}
//没有该账号
if (j == m_iClientNum )
{
m_sendData.type = invalid_name;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
closesocket(sClient);
break;
}
//密码错误
if (strcmp(m_cArrPassword[j], m_recvData.msg) != 0)
{
m_sendData.type = invalid_password;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
closesocket(sClient);
break;
}
//已经登录
if (m_bArrOnline[j])
{
m_sendData.type = already_login;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
closesocket(sClient);
break;
}
//将登录成功信息发送回给该请求客户端
m_sendData.type = success;
itoa(m_iArrHFriend[j][0], m_sendData.name, 10);//name里包含有好友数
itoa(m_iArrHGroup[j][0], m_sendData.msg, 10);//msg里包含有群数
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
/* 编写将当前好友资料,拥有的群资料和留言发给该用户的代码 */
//一个好友一条发送消息地发给客户端,客户端关掉wsaasyncselect功能后
//用一个循环接收处理就行了
Sleep(1000);
for (k=1; k<=m_iArrHFriend[j][0]; k++)
{
m_sendData.type = friend_data;
strcpy(m_sendData.name, m_cArrClient[m_iArrHFriend[j][k]]);
//m_bArrOnline数组资料就合在msg里了
itoa(m_bArrOnline[m_iArrHFriend[j][k]], m_sendData.msg, 10);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
Sleep(10);
}
//这是将一个群名一条消息地发给客户端,同上理
for(k=1; k<=m_iArrHGroup[j][0]; k++)
{
m_sendData.type = group_data;
strcpy(m_sendData.name, m_cArrGroup[m_iArrHGroup[j][k]]);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
Sleep(10);
}
//接下来是留言操作……
m_sendData.type = leave_word;
while (!m_arrClientWord[j].empty())
{
m_sendData = m_arrClientWord[j].front();
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
Sleep(10);
m_arrClientWord[j].pop_front();
}
while (!m_arrGroupWord[j].empty())
{
m_sendData = m_arrGroupWord[j].front();
send(sClient, (char*)&m_sendData,sizeof(m_sendData), 0);
Sleep(10);
m_arrGroupWord[j].pop_front();
}
m_bArrOnline[j] = TRUE;
m_arrClientSocket[j] = sClient;
//发送新用户登录信息给所有在线的该用户的好友
m_sendData.type = login;
strcpy(m_sendData.name, m_recvData.name);
for (k=1 ; k<=m_iArrHFriend[j][0]; k++)
{
if(m_bArrOnline[m_iArrHFriend[j][k]])
{
send(m_arrClientSocket[m_iArrHFriend[j][k]], (char*)&m_sendData, sizeof(m_sendData), 0);
Sleep(10);
}
}
m_strInfo += "用户";
m_strInfo += m_recvData.name;
m_strInfo +="登录成功/r/n";
update(); //更新一下资料
UpdateData(FALSE);
pEdit = (CEdit *)GetDlgItem(IDC_INFO);
pEdit->LineScroll(pEdit->GetLineCount());
break;
case add_friend:
bExist = FALSE;
//根据用户名搜索对方序号
for (i=0; strcmp(m_recvData.name, m_cArrClient[i])!=0 && i<m_iClientNum; i++)
{
;
}
//根据socket来搜索主动方
for (j= 0; sClient!=m_arrClientSocket[j] && j<m_iClientNum; j++)
{
;
}
for(k=0; k<m_iArrHFriend[j][0]; k++)
{
//判断是否已经存在于对方的好友列表中
if (m_iArrHFriend[j][k] == i)
{
bExist = TRUE;
}
}
//如果已经存在或者就是申请者本身或者对方不在线无法答复,返回相应失败信息
if (bExist)
{
m_sendData.type = addfriend_exist;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
break;
}
if (i == j || i == m_iClientNum)
{
m_sendData.type = addfriend_error;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
break;
}
if (!m_bArrOnline[i])
{
m_sendData.type = offline_error;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
break;
}
//若上面情况没有发生,发送加好友请求给对方
m_sendData.type = add_friend;
strcpy(m_sendData.name, m_cArrClient[j]);
strcpy(m_sendData.msg, m_recvData.msg);
send(m_arrClientSocket[i], (char*)&m_sendData, sizeof(m_sendData), 0);
//等待对方答复,若成功则返回success,否则为fail
//注意,客户端在返回该类信息时,name应该为自己的名字
//msg为对方(请求加自己为好友的一方)的名字
break;
case addfriend_success:
//答应,记录好友信息,返回成功信息
//根据socket来搜索主动方
for (i=0; sClient!=m_arrClientSocket[i] && i<m_iClientNum; i++)
{
;
}
//根据用户名搜索请求方序号
for (j=0; strcmp(m_recvData.name, m_cArrClient[j])!=0 && j<m_iClientNum; j++)
{
;
}
//加好友操作
iTempNum = 0;
iTempNum = m_iArrHFriend[j][0];
iTempNum++;
m_iArrHFriend[j][0] = iTempNum;
m_iArrHFriend[j][iTempNum] = i;
iTempNum = 0;
iTempNum = m_iArrHFriend[i][0];
iTempNum++;
m_iArrHFriend[i][0] = iTempNum;
m_iArrHFriend[i][iTempNum] = j;
if (m_bArrOnline[j]) //若请求方还在线的话
{
m_sendData.type = addfriend_success;
strcpy(m_sendData.name, m_cArrClient[i]);
send(m_arrClientSocket[j], (char*)&m_sendData, sizeof(m_sendData), 0);
}
break;
case addfriend_fail:
//被拒绝,什么不做地返回失败信息给对方
//根据socket来搜索主动方
for (i=0; sClient!=m_arrClientSocket[i] && i<m_iClientNum; i ++)
{
;
}
//根据用户名搜索请求方序号
for (j=0; strcmp(m_recvData.name, m_cArrClient[j])!=0 && j<m_iClientNum; j++)
{
;
}
if (m_bArrOnline[j]) //若请求方还在线的话
{
m_sendData.type = addfriend_fail;
strcpy(m_sendData.name, m_cArrClient[i]);
send(m_arrClientSocket[j], (char*)&m_sendData, sizeof(m_sendData), 0);
}
break;
case delete_friend://删除好友操作
//根据Socket来搜索申请人名称
for (i =0; sClient!=m_arrClientSocket[i] && i<m_iClientNum; i++)
{
;
}
for (j=0; strcmp(m_recvData.name, m_cArrClient[j])!=0 && j<m_iClientNum; j++)
{
;
}
//将i中的好友j删除
for (k=1; k<=m_iArrHFriend[i][0]; k++)
{
if (m_iArrHFriend[i][k] == j)
{
break;
}
}
for(; k<m_iArrHFriend[i][0]; k++)
{
m_iArrHFriend[i][k] = m_iArrHFriend[i][k+1];
}
m_iArrHFriend[i][0]--;
// 将删除好友的信息返回给用户
m_sendData.type = delete_friend;
strcpy(m_sendData.name, m_cArrClient[j]);
send(m_arrClientSocket[i], (char*)&m_sendData,sizeof(m_sendData), 0);
//如果j在线则通知j将好友i删除
if (m_bArrOnline[j])
{
m_sendData.type = delete_friend;
strcpy(m_sendData.name, m_cArrClient[i]);
send(m_arrClientSocket[j], (char*)&m_sendData, sizeof(m_sendData), 0);
}
//将j中的好友i删除
for (k=1; k<=m_iArrHFriend[j][0]; k++)
{
if(m_iArrHFriend[j][k] == i)
{
break;
}
}
for (; k<m_iArrHFriend[j][0]; k++)
{
m_iArrHFriend[j][k] = m_iArrHFriend[j][k+1];
}
m_iArrHFriend[j][0]--;
break;
case server_address://客户端发送过来他作为服务器的sockaddr_in结构
//根据用户名搜索对方序号
for( i = 0; strcmp(m_recvData.name, m_cArrClient[i])!=0 && i<m_iClientNum; i++)
{
;
}
memcpy(&m_arrClientAddr[i], m_recvData.msg, sizeof(sockaddr_in));
break;
case single_chat:
//搜索被叫用户的序号
for (j=0; strcmp(m_cArrClient[j], m_recvData.name)!=0 && j<m_iClientNum; j++)
{
;
}
//搜索主叫用户的序号
for (k=0; m_arrClientSocket[k]!=sClient; k++)
{
;
}
if (m_bArrOnline[j])
{
//被叫用户在线操作
//主动方操作
m_sendData.type = single_chat_client;
strcpy(m_sendData.name, m_recvData.name); // 被叫用户的名字
memcpy(m_sendData.msg, &m_arrClientAddr[j], sizeof(sockaddr_in));// 被叫用户的addr
send(sClient,(char*)&m_sendData, sizeof(m_sendData), 0);
//被动方操作
m_sendData.type = single_chat_client;
strcpy(m_sendData.name, m_cArrClient[k]);// 主叫用户的名字
memcpy(m_sendData.msg, &m_arrClientAddr[k], sizeof(sockaddr_in)); // 主叫用户的addr
send(m_arrClientSocket[j], (char*)&m_sendData, sizeof(m_sendData), 0);
}
else
{
//被叫用户离线操作
m_sendData.type = single_chat_transfer;
strcpy(m_sendData.name, m_recvData.name);
strcpy(m_sendData.msg, "对方不在线,信息将通过服务器中转!/r/n/r/n");
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
break;
case single_chat_transfer:
//搜索被叫用户的序号
for (j=0; strcmp(m_cArrClient[j], m_recvData.name)!=0 && j<m_iClientNum; j++)
{
;
}
//搜索主叫用户的序号
for (k=0; m_arrClientSocket[k]!=sClient; k++)
{
;
}
strcpy(m_recvData.name, m_cArrClient[k]);
m_arrClientWord[j].push_back(m_recvData);//保存被叫用户的信息,名字就是主叫用户
break;
case group_chat:
//搜索发言人
for (i=0; m_arrClientSocket[i]!=sClient && i<m_iClientNum; i++)
{
;
}
//搜索群序号
for (j=0; strcmp(m_cArrGroup[j], m_recvData.name)!=0 && j<m_iClientNum; j++)
{
;
}
//对每个用户发信息
for (k=1; k<=m_iArrMember[j][0]; k++ )
{
//如果用户在线
if(m_bArrOnline[m_iArrMember[j][k]])
{
if (m_arrClientSocket[m_iArrMember[j][k]] != sClient)
{
send(m_arrClientSocket[m_iArrMember[j][k]], (char*)&m_recvData,sizeof(m_recvData), 0);
}
}
else
{
//否则,用户离线,需要将该信息保存起来
m_arrGroupWord[m_iArrMember[j][k]].push_back(m_recvData);
}
}
break;
case client_list://来自客户端的用户列表请求
//首先发送client_list消息过去,让客户端确认人数
m_sendData.type = client_list;
for (i=0; i<m_iClientNum; i++)
{
strcpy(m_sendData.name, m_cArrClient[i]);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0 );
Sleep(10);
}
break;
case group_list://来自客户端的群列表请求
//首先发送group_list过去,让客户端确认人数,人数包含在name里
m_sendData.type = group_list;
//然后发送group的名字过去
for (i=0; i<m_iGroupNum; i++)
{
strcpy(m_sendData.name, m_cArrGroup[i]);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
Sleep(10);
}
break;
case member_list://来自客户端的群内成员列表请求
//搜索该群序号
for (i=0; strcmp(m_recvData.name, m_cArrGroup[i])!=0 && i<m_iGroupNum; i++)
{
;
}
m_sendData.type = member_list;
for (j=1; j<=m_iArrMember[i][0]; j++)
{
//一个成员一条消息地发给客户端
strcpy(m_sendData.name, m_recvData.name);
strcpy(m_sendData.msg, m_cArrClient[m_iArrMember[i][j]]);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
Sleep(10);
}
break;
case establish_group:
if (m_iGroupNum < MaxGroup )
{
//判断是否存在同名群
bExist = FALSE;
for (i=0; i<m_iGroupNum; i++)
{
if (strcmp(m_cArrGroup[i], m_recvData.name) == 0)
{
bExist = TRUE;
m_sendData.type = establishgroup_fail;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
break;
}
}
if (!bExist)
{
//若没有同名群,刚成功
//搜索申请人
for (i=0; m_arrClientSocket[i]!=sClient && i<m_iClientNum; i++)
{
;
}
strcpy(m_cArrGroup[m_iGroupNum], m_recvData.name);
m_iArrMember[m_iGroupNum][0] = 1;
m_iArrMember[m_iGroupNum][1] = i; //新群内已有1人,就是该申请人
//该申请人参加的群增加1个,群号是m_iGroupNum
m_iArrHGroup[i][++m_iArrHGroup[i][0]] = m_iGroupNum;
m_sendData.type = establishgroup_success;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
//服务器信息更新
m_strInfo += "用户";
m_strInfo += m_cArrClient[i];
m_strInfo += "建立了一个新群";
m_strInfo += m_cArrGroup[m_iGroupNum];
m_strInfo += "!/r/n";
m_iGroupNum++;
UpdateData(FALSE);
pEdit = (CEdit *)GetDlgItem(IDC_INFO);
pEdit->LineScroll(pEdit->GetLineCount());
}
}
update();//更新一下资料
break;
case join_group:
//搜索该群序号
for (i=0; strcmp(m_cArrGroup[i], m_recvData.name)!=0 && i<m_iGroupNum; i++)
{
;
}
if (i < m_iGroupNum)
{
if (m_iArrMember[i][0] < MaxMember )
{
//群内人数未满
bExist = FALSE;
//搜索申请人
for (j=0; m_arrClientSocket[j]!=wParam && j<m_iClientNum; j++)
{
;
}
for (k=1; k<=m_iArrHGroup[j][0]; k++)
{
if (m_iArrHGroup[j][k] == i)
{
bExist = TRUE;
break;
}
}
if (!bExist)
{
//用户尚未加入该群
//群更新资料发送到每位群内在线用户
m_sendData.type = join_group;
strcpy(m_sendData.name, m_cArrGroup[i]);
strncpy(m_sendData.msg, m_cArrClient[j], sizeof(m_cArrClient[j]));
for (k=1; k<=m_iArrMember[i][0]; k++)
{
if (m_bArrOnline[m_iArrMember[i][k]] && m_iArrMember[i][k] != j)
{
send(m_arrClientSocket[m_iArrMember[i][k]], (char*)&m_sendData, sizeof(m_sendData), 0);
}
}
m_iArrMember[i][0]++;
m_iArrMember[i][m_iArrMember[i][0]] = j;
m_iArrHGroup[j][++m_iArrHGroup[j][0]] = i;
//服务器信息更新
m_strInfo += "用户";
m_strInfo += m_cArrClient[j];
m_strInfo += "加入了群";
m_strInfo += m_cArrGroup[i];
m_strInfo += "/r/n";
UpdateData(FALSE);
pEdit = (CEdit *)GetDlgItem(IDC_INFO);
pEdit->LineScroll(pEdit->GetLineCount());
m_sendData.type = joingroup_success;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
else
{
//用户已经加入该群
m_sendData.type = joingroup_fail;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
}
else
{
//人数满了
m_sendData.type = joingroup_fail;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
update();//更新一下资料
}
else
{
m_sendData.type = joingroup_fail;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
break;
case secede_group:
//搜索该群序号
for (i=0; strcmp(m_cArrGroup[i], m_recvData.name)!=0 && i<m_iGroupNum; i++)
{
;
}
//搜索申请人
for (j=0; m_arrClientSocket[j]!=sClient && j<m_iClientNum; j++)
{
;
}
//搜索申请人拥有的群中该群所在位置
for (k=1; i!=m_iArrHGroup[j][k] && k<=m_iArrHGroup[j][0]; k++)
{
;
}
// 该群组不存在或用户不属于该群组
if (i==m_iGroupNum || i!= m_iArrHGroup[j][k])
{
m_sendData.type = secedegroup_fail;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
else
{
for(; k<m_iArrHGroup[j][0]; k ++ )
{
m_iArrHGroup[j][k] = m_iArrHGroup[j][k+1];
}
m_iArrHGroup[j][0]--;
//搜索该申请人在群内的序号
for (k=1; k<=m_iArrMember[i][0] && m_iArrMember[i][k]!=j; k++)
{
;
}
m_iArrMember[i][0]--;
//服务器信息更新
m_strInfo += "用户";
m_strInfo += m_cArrClient[j];
m_strInfo += "退出了群";
m_strInfo += m_cArrGroup[i];
m_strInfo += "/r/n";
//当群还有用户存在的时候
if (m_iArrMember[i] != 0)
{
for(; k<=m_iArrMember[i][0]; k++)
{
m_iArrMember[i][k] = m_iArrMember[i][k+1];
}
//将用户退群信息发给所有该群的用户
m_sendData.type = secede_group;
strcpy(m_sendData.name, m_cArrGroup[i]);
strcpy(m_sendData.msg, m_cArrClient[j]);
for(k=1; k<=m_iArrMember[i][0]; k++)
{
send(m_arrClientSocket[m_iArrMember[i][k]],(char*)&m_sendData, sizeof(m_sendData), 0);
}
}
else
{
//没有用户则删除该群
m_iGroupNum--;
for (k=i; k<m_iGroupNum; k++)
{
strcpy(m_cArrGroup[k], m_cArrGroup[ k+1]);
}
int temp[MaxGroup][MaxMember+1];//暂存成员数据
memcpy(temp, m_iArrMember, sizeof(int)*(MaxGroup-k-1) * MaxMember);
memcpy(&m_iArrMember[k][0] ,temp, sizeof(int)*(MaxGroup-k-1) * MaxMember);
//服务器信息更新
m_strInfo += "群";
m_strInfo += m_cArrGroup[i];
m_strInfo += "由于没有用户存在而被取消了!/r/n";
}
////将退群成功信息发送给该用户
m_sendData.type = secedegroup_success;
strcpy(m_sendData.name, m_recvData.name);
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
//ps:这里貌似也不需要了……
UpdateData(FALSE);
pEdit = (CEdit *)GetDlgItem(IDC_INFO);
pEdit->LineScroll(pEdit->GetLineCount());
update();//更新一下资料
}
break;
case search_file:
{
int count = 0;
//搜索文件
for (i=0; i<m_iClientNum; i++)
{
CString strFile(m_cArrFileShare[i]);
CString strSearch(m_recvData.name);
int nPos = strFile.Find(strSearch);
if (nPos != -1)
{
m_sendData.type = search_return;
strcpy(m_sendData.name, m_cArrClient[i]); // 拥有资源的客户端
//CString s=L"offline";
//memcpy(state,s,s.GetLength()*2);
//memset(sendData.msg,0,sizeof(sendData.msg));//复制其名字
if(m_bArrOnline[i]) //如果在线则发送IP地址过去,否则发送"offline"
{
memcpy(m_sendData.msg, &m_arrClientAddr[i], sizeof(sockaddr_in));//
}
else
{
strcpy(m_sendData.msg, "offline");
}
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
count++;
}
}
if (count == 0)
{
m_sendData.type = search_null;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
}
}
break;
case declare_file:
//根据Socket来搜索声明人
for( i = 0; wParam != m_arrClientSocket[i] && i<m_iClientNum; i++)
{
;
}
strcpy(m_cArrFileShare[i], m_recvData.msg);
m_sendData.type = declare_success;
send(sClient, (char*)&m_sendData, sizeof(m_sendData), 0);
break;
default:
break;
}
break;
//客户端下线操作
case FD_CLOSE:
//搜索下线用户的序号
for (i=0; sClient!=m_arrClientSocket[i] && i<m_iClientNum; i++)
{
;
}
if (i < m_iClientNum)
{
m_bArrOnline[i] = FALSE;
closesocket(m_arrClientSocket[i]);
m_sendData.type = offline;
strcpy(m_sendData.name, m_cArrClient[i]);
for (j=1; j<=m_iArrHFriend[i][0]; j++)
{
if(m_bArrOnline[m_iArrHFriend[i][j]])
{
if (send(m_arrClientSocket[m_iArrHFriend[i][j]],
(char*)&m_sendData, sizeof(m_sendData), 0) == SOCKET_ERROR)
{
AfxMessageBox("发送离线信息失败!");
}
}
}
m_strInfo += "用户";
m_strInfo += m_cArrClient[i];
m_strInfo += "下线了!/r/n";
update();
UpdateData(FALSE);
pEdit = (CEdit *)GetDlgItem(IDC_INFO);
pEdit->LineScroll(pEdit->GetLineCount());
break;
}
}
return 0L;
}
附录B客户端处理通信程序:
1.作为本地服务器
LRESULT CQQClientDlg::OnServerMessage(WPARAM wParam, LPARAM lParam)
{
// 此函数用于接收私聊时对方发送过来的信息
SOCKET socket = (SOCKET)wParam;
CEdit *output = NULL;
int len;
switch(lParam)
{
case FD_ACCEPT:
socket = accept(m_server.m_hSocket, NULL, NULL);
return 0;
case FD_READ:
len = recv(socket, (char*)&m_recvData, sizeof(m_recvData), 0);
switch(m_recvData.type)
{
case single_chat_server:
CString strname;
strname = m_recvData.name ;
m_singlechat_client = TRUE;
m_singlechat_server = TRUE;
m_singlechat_transfer = FALSE;
m_singlechat_update = TRUE;
// 判断是否弹出对话框
OnFriendDlg(strname);
}
return 0;
case FD_WRITE:
return 0;
case FD_CLOSE:
return 0;
default:
return 0;
}
}
2.接受服务器端信息并处理程序:
LRESULT CQQClientDlg::OnClientMessage(WPARAM wParam, LPARAM lParam)
{
// 此函数用于接收从服务器发送过来的消息
CEdit *pEdit = NULL;
int len;
CString strName;
switch(lParam)
{
case FD_CONNECT:
len = GetLastError();
if(len != 0)
{
AfxMessageBox("Error in Connecting");
}
else
{
m_bInit = TRUE;
m_bClient = TRUE;
m_strShowText += "已连接到好友的服务器!/r/n";
}
return 0;
case FD_READ:
len = recv(m_client.m_hSocket, (char*)&m_recvData, MaxBuf, 0);
// 获取发送方的名称
strName = m_recvData.name ;
switch(m_recvData.type)
{
// 从服务其接收到新用户注册的消息
case enroll:
{
CString str = m_recvData.name;
m_strShowText += "/r/n";
m_strShowText += str;
m_strShowText+="-注册了!/r/n";
// 随时跟踪滚动条位置
pEdit = (CEdit *)GetDlgItem(IDC_SHOWTEXT);
pEdit->SetWindowText(m_strShowText);
pEdit->LineScroll(pEdit->GetLineCount());
}
return 0;
// 从服务器接收到好友上线的消息
case login:
{
// 在客户端状态编辑框里显示
m_strShowText += strName;
m_strShowText += "-上线了!/r/n";
// 随时跟踪滚动条位置
pEdit = (CEdit *)GetDlgItem(IDC_SHOWTEXT);
pEdit->SetWindowText(m_strShowText);
pEdit->LineScroll(pEdit->GetLineCount());
// 在listbox中修改好友状态
int index = m_friendList.FindString(0, strName);
m_friendList.DeleteString(index);
m_friendList.AddString(strName, RGB(255, 0, 0));
map<CString, BOOL>::iterator it = m_friendMap.find(strName);
if (it != m_friendMap.end())
{
it->second = TRUE;
}
// 当正在和好友聊天(通过服务中转)时, 修改状态
// 在pFriendDlgMap中查找私聊的窗口是否打开
map<CString, CFriendDlg*>::iterator itdlg = m_pFriendDlgMap.find(strName);
if (itdlg != m_pFriendDlgMap.end())
{ // 私聊模式转换成非中转模式
itdlg->second->m_bClient = FALSE;
itdlg->second->m_bTransfer = FALSE;
// 修改标题
CString str;
str = "与 聊天中";
str.Insert(2, strName);
str += " (在线)";
itdlg->second->SetWindowText(str);
// 提示好友上线了
itdlg->second->m_strFriendrecv += "对方上线了, 自动建立连接!/r/n/r/n";
itdlg->second->GetDlgItem(IDC_FRIENDRECV)->SetWindowText(itdlg->second->m_strFriendrecv);
pEdit = (CEdit *)itdlg->second->GetDlgItem(IDC_FRIENDRECV);
// 随时跟踪滚动条的位置
pEdit->LineScroll(pEdit->GetLineCount());
// 向服务器发送私聊的请求(即与对方建立连接)
m_sendData.type = single_chat;
strcpy(m_sendData.name, strName.GetBuffer(strName.GetLength()));
strName.ReleaseBuffer();
send(m_client.m_hSocket, (char*)&m_sendData, sizeof(m_sendData), 0);
} // end if
}
return 0;
// 从服务器接收到好友的数据
case friend_data:
{
// 若好友在线则字体颜色为红色
BOOL bOnline;
bOnline = atoi(m_recvData.msg);
if(bOnline)
{
m_friendList.AddString(strName, RGB(255, 0, 0));
}
else
{
m_friendList.AddString(strName);
}
// 将好友的资料添加到FriendMap
m_friendMap.insert(make_pair(strName, bOnline));
}
return 0;
// 从服务器接收到好友的数据
case group_data:
{
CString strData;
strData = "默认";
m_groupMap.insert(make_pair(strName, strData));
m_groupList.AddString(strName);
}
return 0;
// 从服务器接收到添加好友后的返回值
case addfriend_exist:
{
AfxMessageBox("该好友已存在!");
}
return 0;
case addfriend_error:
{
AfxMessageBox("你加自己为好友了?或者无此用户");
}
return 0;
case offline_error:
{
AfxMessageBox("该用户不在线");
}
return 0;
// 从服务器接收到被要求添加好友的信息(被请求方)
case add_friend:
{
CAddFriendAnwserDlg dlg;
dlg.m_strAddFriendprName = strName;
dlg.m_client = m_client;
dlg.m_strAddFriendprContent = m_recvData.msg;
dlg.DoModal();
if (dlg.m_bAccept)
{
m_friendList.AddString(strName, RGB(255, 0, 0));
m_friendMap.insert(make_pair<CString,BOOL>(strName, TRUE)); // 添加好友时,假定好友在线
}
}
return 0;
// 从服务器接收到添加好友成功的信息(请求方)
case addfriend_success:
{
CString str;
str = strName;
str += " 接受了你的请求!";
m_friendList.AddString(strName, RGB(255, 0, 0));
// 加入到好友map
m_friendMap.insert(make_pair<CString,BOOL>(strName, TRUE));
AfxMessageBox(str);
}
return 0;
// 从服务器接收到对方拒绝添加好友的信息
case addfriend_fail:
{
CString str;
str = strName;
str += " 拒绝添加你为好友!";
AfxMessageBox(str);
}
return 0;
// 从服务器接收到删除好友的信息
case delete_friend:
{
CString str;
str = strName;
str += "- 好友删除(你执行了该操作或对方执行了)!";
AfxMessageBox(str);
m_friendMap.erase(strName);
int index;
index = m_friendList.FindString(0, strName);
m_friendList.DeleteString(index);
}
return 0;
// 从服务器接收到新用户加入群组
case join_group:
{
CString str;
str = m_recvData.msg;
str += " 加入了群 - ";
str += strName;
str += "/r/n";
CString strMemberName;
strMemberName = m_recvData.msg;
// 寻找对应的群, 并在打开的对话框中显示新用户加入的信息
CGroupDlg *pGroupDlg = NULL;
map<CString, CGroupDlg*>::iterator it_dlg = m_pGroupDlgMap.find(strName);
if (it_dlg != m_pGroupDlgMap.end())
{
pGroupDlg = it_dlg->second;
}
if (pGroupDlg != NULL)
{
pGroupDlg->m_strGroupRecv += str;
pGroupDlg->GetDlgItem(IDC_GROUPRECV)->SetWindowText(pGroupDlg->m_strGroupRecv);
pEdit = (CEdit *)pGroupDlg->GetDlgItem(IDC_GROUPRECV);
// 随时跟踪滚动条的位置
pEdit->LineScroll(pEdit->GetLineCount());
pGroupDlg->m_groupMemberList.AddString(strMemberName);
}
}
return 0;
//从服务器接收到加入群组请求后的返回信息
case joingroup_success:
{
CString str;
str = "群 - ";
str += strName;
str += " 加入成功!";
m_groupList.AddString(strName);
// 在GroupMap中加入
m_groupMap.insert(make_pair(strName, CString("默认")));
AfxMessageBox(str);
}
return 0;
case joingroup_fail:
{
CString str;
str = "群 - ";
str += strName;
str += " 加入失败!";
str += m_recvData.msg;
AfxMessageBox(str);
}
return 0;
// 从服务器接收到新建群组后的返回信息
case establishgroup_success:
{
CString str;
str = "群 - ";
str += strName;
str += " 创建成功!";
m_groupList.AddString(strName);
// 在GroupMap中加入
m_groupMap.insert(make_pair(strName, CString("默认")));
AfxMessageBox(str);
}
return 0;
case establishgroup_fail:
{
CString str;
str = "群 - ";
str += strName;
str += " 创建失败! ";
str += m_recvData.msg;
AfxMessageBox(str);
}
return 0;
// 从服务器接收到某用户退出群组
case secede_group:
{
CString str;
str = m_recvData.msg;
str += " 退出了群 - ";
str += strName;
str += "/r/n";
CString strMemberName;
strMemberName = m_recvData.msg;
// 寻找对应的群, 并在打开的对话框中显示用户退出的信息
CGroupDlg *pGroupDlg = NULL;
map<CString, CGroupDlg*>::iterator it_dlg = m_pGroupDlgMap.find(strName);
if (it_dlg != m_pGroupDlgMap.end())
{
pGroupDlg = it_dlg->second;
}
if (pGroupDlg != NULL)
{
pGroupDlg->m_strGroupRecv += str;
pGroupDlg->GetDlgItem(IDC_GROUPRECV)->SetWindowText(pGroupDlg->m_strGroupRecv);
pEdit = (CEdit *)pGroupDlg->GetDlgItem(IDC_GROUPRECV);
// 随时跟踪滚动条的位置
pEdit->LineScroll(pEdit->GetLineCount());
int index = pGroupDlg->m_groupMemberList.FindString(0, strMemberName);
pGroupDlg->m_groupMemberList.DeleteString(index);
}
}
return 0;
// 从服务器接收到退出群组后的返回信息
case secedegroup_success:
{
CString str;
str = "群 - ";
str += strName;
str += " 退出成功!";
// 从listbox中删除相应的字符串
int index;
index = m_groupList.FindString(0, strName);
m_groupList.DeleteString(index);
// 寻找对应的群, 关闭窗口
CGroupDlg *pGroupDlg = NULL;
map<CString, CGroupDlg*>::iterator it_dlg = m_pGroupDlgMap.find(strName);
if (it_dlg != m_pGroupDlgMap.end())
{
pGroupDlg = it_dlg->second;
}
if (pGroupDlg != NULL)
{
pGroupDlg->PostMessage(WM_CLOSE, 0, 0);
}
// 在GroupMap中删除
m_groupMap.erase(strName);
AfxMessageBox(str);
}
return 0;
case secedegroup_fail:
{
CString str;
str = "群 - ";
str += strName;
str += " 退出失败!";
AfxMessageBox(str);
}
return 0;
// 从服务器接收到群的成员资料
case member_list:
{
// 在存储对话框搜索是否存在strname对应的对话框, 即判断对话框是否已经创建
CGroupDlg* pGroupDlg = NULL;
CString strMemberName;
strMemberName = m_recvData.msg;
map<CString, CGroupDlg*>::iterator it_dlg = m_pGroupDlgMap.find(strName);
if (it_dlg != m_pGroupDlgMap.end())
{
pGroupDlg = it_dlg->second;
}
if (pGroupDlg != NULL)
{
pGroupDlg->m_groupMemberList.AddString(strMemberName);
}
}
return 0;
// 从服务器接收到成员列表
case client_list:
{
m_pAddFriendDlg->m_clientList.AddString(strName);
}
return 0;
// 从服务器接收到群组列表
case group_list:
{
m_pJoinGroupDlg->m_groupList.AddString(strName);
}
return 0;
// 从服务器接收到好友的addr
case single_chat_client:
{
m_singlechat_client = TRUE;
m_singlechat_server = FALSE;
m_singlechat_transfer = FALSE;
m_singlechat_update = FALSE;
// 获取好友服务器的addr
sockaddr_in addr;
memcpy(&addr, m_recvData.msg, sizeof(sockaddr_in));
// 将好友的addr添加到FriendAddrMap
map<CString, sockaddr_in>::iterator it = m_friendAddrMap.find(strName);
if (it == m_friendAddrMap.end())
{
m_friendAddrMap.insert(make_pair(strName, addr));
}
else
{
it->second = addr;
}
OnFriendDlg(strName);
}
return 0;
// 从服务器接收到中转的信息
case single_chat_transfer:
{
m_singlechat_client = FALSE;
m_singlechat_server = FALSE;
// 判断好友是否在线
map<CString, BOOL>::iterator it = m_friendMap.find(strName);
if (it != m_friendMap.end())
{
if (it->second) // 好友在线
{
m_singlechat_transfer = FALSE;
}
else
{
m_singlechat_transfer = TRUE;
}
m_singlechat_update = TRUE;
OnFriendDlg(strName);
}
}
return 0;
// 从私聊对方接收到私聊的信息
case single_chat:
m_singlechat_update = TRUE;
OnFriendDlg(strName);
return 0;
// 接收到的为群聊的信息
case group_chat:
{
m_groupchat_update = TRUE;
OnGroupDlg(strName);
}
return 0;
//搜索返回
case search_return:
{
//AfxMessageBox(L"Yeah~I have");
CString strMsg = m_recvData.msg;//msg里存IP啊
if (strcmp(m_recvData.msg, "offline") == 0)
{
m_pFileSearchDlg->m_fileList.AddString(strName);
m_pFileSearchDlg->push(m_recvData.msg);
}
else
{
// AfxMessageBox(L"OnLine!");
sockaddr_in Saddr;
memcpy(&Saddr, m_recvData.msg, sizeof(sockaddr_in));
//m_MyFriends.AddString(strname, RGB(255, 0, 0));
m_pFileSearchDlg->m_fileList.AddString(strName,RGB(255, 0, 0));
CString strIP = inet_ntoa(Saddr.sin_addr);
//AfxMessageBox(strIP);
char wchIP[15];
memset(wchIP, 0, sizeof(wchIP));
memcpy(wchIP, strIP, strIP.GetLength());
m_pFileSearchDlg->push(wchIP);
}
//m_FileSearchDlg
}
return 0;
case search_null:
{
AfxMessageBox("Oh~~No such file");
}
return 0;
// 从服务器接收到好友的addr
case declare_success:
AfxMessageBox("声明成功!");
return 0;
// 从服务器接收到好友下线的信息
case offline:
{
// 在主窗口的控件中显示相应信息
CString str = m_recvData.name;
m_strShowText += str;
m_strShowText += "-下线了!/r/n";
// 随时跟踪滚动条位置
pEdit = (CEdit *)GetDlgItem(IDC_SHOWTEXT);
pEdit->SetWindowText(m_strShowText);
pEdit->LineScroll(pEdit->GetLineCount());
// 在listbox中修改好友状态为离线
int index = m_friendList.FindString(0, strName);
m_friendList.DeleteString(index);
m_friendList.AddString(strName);
// 在FriendMap中修改好友状态
map<CString, BOOL>::iterator itdata = m_friendMap.find(strName);
if (itdata != m_friendMap.end())
{
itdata->second = FALSE;
}
// 在pFriendDlgMap中查找私聊的窗口是否打开
map<CString, CFriendDlg*>::iterator it = m_pFriendDlgMap.find(strName);
if (it != m_pFriendDlgMap.end())
{
// 私聊模式转换成服务器中转模式
it->second->m_bClient = FALSE;
it->second->m_bTransfer = TRUE;
it->second->m_client = m_client;
// 修改标题
CString strCaption;
strCaption = "与 聊天中";
strCaption.Insert(2, strName);
strCaption += " (离线)";
it->second->SetWindowText(str);
// 修改窗口的ip和port
CString strIP("IP: ");
strIP += m_strServerIP;
strIP += " Port: ";
CString strPort;
strPort.Format("%d", m_uPort);
strIP += strPort;
it->second->GetDlgItem(IDC_SHOWIP)->SetWindowText(strIP);
// 显示下线信息
it->second->m_strFriendrecv += "对方下线了, 信息通过服务器中转!/r/n/r/n";
it->second->GetDlgItem(IDC_FRIENDRECV)->SetWindowText(it->second->m_strFriendrecv);
pEdit = (CEdit *)it->second->GetDlgItem(IDC_FRIENDRECV);
// 随时跟踪滚动条的位置
pEdit->LineScroll(pEdit->GetLineCount());
} // end if
// 在FriendAddrMap中查找对方的addr
map<CString, sockaddr_in>::iterator iter = m_friendAddrMap.find(strName);
if (iter != m_friendAddrMap.end()) // 删除保存好友的addr
{
m_friendAddrMap.erase(strName);
}
} // end case
return 0;
} // end switch
return 0;
case FD_WRITE:
return 0;
case FD_CLOSE:
if (wParam == m_client.m_hSocket) // 服务器下线
{
OnOffline();
m_strShowText += "服务器关闭了!/r/n";
// 随时跟踪滚动条位置
pEdit = (CEdit *)GetDlgItem(IDC_SHOWTEXT);
pEdit->SetWindowText(m_strShowText);
pEdit->LineScroll(pEdit->GetLineCount());
AfxMessageBox("服务器关闭了!");
}
return 0;
default:
if (wParam == m_client.m_hSocket) // 服务器下线
{
OnOffline();
m_strShowText+="服务器关闭了!/r/n";
// 随时跟踪滚动条位置
pEdit = (CEdit *)GetDlgItem(IDC_SHOWTEXT);
pEdit->SetWindowText(m_strShowText);
pEdit->LineScroll(pEdit->GetLineCount());
AfxMessageBox("服务器关闭了!");
}
pEdit = (CEdit *)GetDlgItem(IDC_SHOWTEXT);
pEdit->SetWindowText("An network error has occured, the connection is dropped!/r/n");
m_bInit = FALSE;
return 0;
}
}
附录C其他辅助类代码
1.带颜色的ListBox:
void CColorListBox::DrawItem(LPDRAWITEMSTRUCT lpdis)
{
if (GetCount() <= 0)
{
return;
}
if (lpdis->itemID < 0)
{
return;
}
COLORREF cvText;
COLORREF cvBack;
CString itemString;
if ((lpdis->itemState & ODS_SELECTED) && // if item has been selected
(lpdis->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
DrawFocusRect(lpdis->hDC, &lpdis->rcItem);
if (!(lpdis->itemState & ODS_SELECTED) && // if item has been deselected
(lpdis->itemAction & ODA_SELECT))
DrawFocusRect(lpdis->hDC, &lpdis->rcItem);
// get and display item text
GetText(lpdis->itemID, itemString );
if(lpdis->itemData) // if color information is present
{
cvText = SetTextColor(lpdis->hDC, lpdis->itemData);
itemString += " (在线)";
}
else // if no color information, use default system colors
{
cvText = SetTextColor(lpdis->hDC, GetSysColor((lpdis->itemState & ODS_SELECTED)
? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
itemString += " (离线)";
}
// always use system colors for background
cvBack = SetBkColor(lpdis->hDC, GetSysColor((lpdis->itemState & ODS_SELECTED)
? COLOR_HIGHLIGHT : COLOR_WINDOW));
DrawText(lpdis->hDC, itemString, -1, &lpdis->rcItem, DT_LEFT | DT_SINGLELINE);
// restore DC colors
SetTextColor(lpdis->hDC, cvText);
SetBkColor(lpdis->hDC, cvBack);
}
int CColorListBox::AddString( /*LPCTSTR*/CString lpszItem)
{
return ((CListBox*)this)->AddString(lpszItem);
}
int CColorListBox::AddString(CString lpszItem,COLORREF rgb )
{
int item = AddString(lpszItem);
if(item >=0)
{
SetItemData(item,rgb);
}
return item;
}
int CColorListBox::InsertString( int nIndex, /*LPCTSTR*/CString lpszItem, COLORREF rgb)
{
int item = ((CListBox*)this)->InsertString(nIndex,lpszItem);
if(item >=0)
{
SetItemData(item,rgb);
}
return item;
}
2.客服端通信封装的类(关键代码)
#define SER_MESSAGE WM_USER + 100
#define CLI_MESSAGE WM_USER + 101
BOOL CClient::InitAndConnect(HWND hwnd, UINT port, CString strserver)
{
m_hWnd = hwnd;
m_uPort = port;
m_strServer = strserver;
if (m_hSocket != NULL)
{
// 如果原来打开这个套接字, 则先将其关闭
//closesocket(m_hSocket);
m_hSocket = NULL;
}
if (m_hSocket == NULL)
{
// 创建新的流套接字
m_hSocket = socket(AF_INET, SOCK_STREAM, 0);
ASSERT(m_hSocket != NULL);
//ClientInit();
if(WSAAsyncSelect(m_hSocket, hwnd, CLI_MESSAGE, FD_READ| FD_WRITE| FD_CLOSE| FD_CONNECT)>0)
{
AfxMessageBox("Error in select");
}
}
// 准备服务器的信息, 这里需要指定服务器的地址
in_addr.sin_family = AF_INET;
char *pAnsi = m_strServer.GetBuffer(m_strServer.GetLength());
m_strServer.ReleaseBuffer();
in_addr.sin_addr.S_un.S_addr = inet_addr(pAnsi);
in_addr.sin_port = htons(m_uPort); // 改变端口号的数据格式
// 这里主动连接服务器
int ret = 0;
int error = 0;
ret = connect(m_hSocket, (LPSOCKADDR)&in_addr, sizeof(in_addr));
if(ret == SOCKET_ERROR)
{
// 连接失败
if(GetLastError()!=WSAEWOULDBLOCK)
{
AfxMessageBox("请确认服务器已经打开并工作在同一端口!");
return FALSE;
}
}
return TRUE;
}
void CClient::SendString(CString a)
{
char *pAnsi = m_strServer.GetBuffer(m_strServer.GetLength());
m_strServer.ReleaseBuffer();
if(send(m_hSocket, pAnsi, strlen(pAnsi), 0) == SOCKET_ERROR)
{
AfxMessageBox("Client send data error");
}
}
void CClient::GetString(CString &str)
{
char *pAnsi = new char[1024] ;
recv(m_hSocket, pAnsi, 1024, MSG_DONTROUTE);
str = pAnsi;
delete []pAnsi;
}
void CClient::ClientInit()
{
if(WSAAsyncSelect(m_hSocket, m_hWnd, CLI_MESSAGE, FD_READ| FD_WRITE| FD_CLOSE| FD_CONNECT)>0)
{
AfxMessageBox("Error in select");
}
}
3.客服端作为本地服务器封装的类(关键代码)
#define SER_MESSAGE WM_USER + 100
#define CLI_MESSAGE WM_USER + 101
CServer::~CServer(void)
{
WSAAsyncSelect(m_hSocket, m_hWnd, 0, 0);
}
BOOL CServer::InitAndListen(HWND hwnd, UINT &port)
{
m_hWnd = hwnd;
if (m_hSocket != NULL)
{
// 如果已经创建了套接字, 则先关闭原来的
closesocket(m_hSocket);
m_hSocket = NULL;
}
if (m_hSocket == NULL)
{
// 创建新的套接字, 这里创建的是流类型的套接字
m_hSocket = socket(AF_INET, SOCK_STREAM, 0);
ASSERT(m_hSocket != NULL);
WSAAsyncSelect(m_hSocket, hwnd, SER_MESSAGE, FD_ACCEPT| FD_READ| FD_WRITE| FD_CLOSE);
}
int ret = SOCKET_ERROR;
int error = 0;
// 为新建的服务器寻找一个端口
while(ret == SOCKET_ERROR)
{
in_addr.sin_family = AF_INET;
in_addr.sin_addr.S_un.S_addr = INADDR_ANY;
in_addr.sin_port = htons(port);
// 绑定一个套接字到本机的地址
ret = bind(m_hSocket, (LPSOCKADDR)&in_addr, sizeof(in_addr));
if (ret != SOCKET_ERROR)
{
break;
}
port++;
}
if (ret == SOCKET_ERROR)
{
// 绑定错误
AfxMessageBox("Binding Error");
return FALSE;
}
// 开始监听, 等待客户连接
ret = listen(m_hSocket, 100); // 第二个参数表示最多支持的客户连接数
if (ret == SOCKET_ERROR)
{
// 监听失败
AfxMessageBox("Listen Error");
return FALSE;
}
return TRUE;
}
void CServer::ServerInit()
{
if(WSAAsyncSelect(m_hSocket, m_hWnd, SER_MESSAGE, FD_ACCEPT| FD_READ| FD_WRITE| FD_CLOSE) > 0)
{
AfxMessageBox("Select Error");
}
}
五.参考文献