1、 集成开发环境:Microsoft Visual Studio 2010 二、流程图三、系统简介1.界面本软件使用DOS控制台界面,界面风格较为朴素。服务器:客户端:2. 软件功能 本软件实现了聊天室基本的功能,包括公开聊天,私聊,获取在线用户,更改昵称,获得帮助等。1) 公开聊天在光标处直接输入消息后按回车即为发送公开聊天,如下图所示。2) 私聊 使用命令【 /m 对方UID 消息 】即可发送私聊,私聊只有对方可以看到,如下图所示:客户端1,密聊UID为132的用户。发送后客户端2,UID为132的用户收到私聊消息。3) 获取在线用户列表 使用命令【/list】即可获得在线用户列表,用户列表
2、会议系统消息的方式返回,如下图所示。命令4) 更改昵称 使用命令【/name 你的新昵称】即可立即更改昵称,成功修改后服务器会以系统消息的方式返回成功修改的提示。命令发送后5) 帮助信息 使用命令【/help】即可查看服务器的欢迎信息,里面包含了该聊天室的使用帮助,如下图所示。3. 系统设计 开发本软件时,我使用了面向对象的思想,把服务器和客户端封装成对应的类,类设计将会在下一节做详细介绍。通行方面我在服务器接受客户端消息,和客户端接受服务器消息时使用了select模型,发送信息我使用的是普通的socket原语。基本原理为服务器与客户端建立TCP连接,然后服务器负责路由消息到各个客户端。4.
3、优点与缺点本软件对流程复杂的SELECT模型进行了细致的拆分与抽象,做到了逻辑流程清晰,每个函数简洁易懂,层次分明。例如服务器启动函数:void ChatServer:Start() InitListenSocket(); Bind(); Listen(); InitFDSocket(); while (true) DoSelect(); try charServer.Start();catch(SockException e) cout e.GetErrorInfo () endl; System Error Error Code: e.GetErrorCode () 0) for(int
4、i = 0;i m_fdRead.fd_count; i+) DoFDRead(m_fdRead.fd_arrayi); 它也只完成一个简单的流程,调用select,然后循环处理有读事件的socket。DoRead (SOCKET sRead) if (sRead = m_sListen) RecvNewConnect();else m_sNowClient = sRead;接下来的DoFDRead()函数完成的事情也非常直接,如果有事件的socket是监听socket的话,那么就是接收到了一个新的连接,否则是接收到了新的小。从上面这个简单的例子中可以看到,本软件最大的优点就是精心设计的类和函
5、数。避免了使用select模型常见的反复嵌套的循环和判断,每个函数清晰明了。本系统还存在以下不足,首先是没有对界面做更深入的优化,只是做了最基本的调整,让输入输出更加雅观,其次是底层原语的封装并没有考虑到泛用性。四、系统详细设计这部分的文档在编码之前已经基本完成,由于时间较为仓促,部分内容可能和实际有所出入。1. ChatServer类该类负责完成服务器所有操作。1) 类图2) 成员变量Map m_clients 聊天者的SOCKET与昵称的映射fd_set m_fdSocket 可用套接字集合fd_set m_fdRead 有事件发生的套接字集合SOCKET m_sListen 监听Sock
6、etSOCKET m_sNowClient 当前处理的客户套接字int m_nPort 监听端口3) 方法设计void Bind()void Listen() void Select()int Recv()SOCKET Accept() 封装底层原语,并加入异常机制,使得外部调用简约明了。构造函数 传入监听端口,初始化m_nPortStart() 1)初始化监听套接字:void InitListenSocket() 2)绑定套接字至本地机器: 3)进入监听模式(设置为非阻塞): 4)初始化可用套接字集合 void InitFDSocket() 5)死循环,调用select方法 DoSelect
7、() 6)结束DoSelect() 1)令m_fdRead = m_fdSocket 2)调用Select() 3)循环处理Select的结果 DoFdRead(Socket sRead) 4)结束DoFdRead(int iReadIndex) 1)判断是否为m_sListen 2)是m_sListen RecvNewConnect() 3)否则 令m_sNowClient = m_fdReadiReadIndex,调用RecvNewMessage()RecvNewConnect() 1)判断是否达到套接字上线 2)调用Accept(),接收连接sClient 3)添加sCilent 至 m
8、_fdSocket 4)添加套接字至m_clients AddClientToInfoMap(string name)AddClientToInfoMap(string name) 1)以SOKCET为键,name为值加入MAP RecvNewMessage() 1)调用Recv函数 2)是否为命令 IsCommand(string str) 3)是,则DoCommand(string cmd) 4)否,则DoMessage(string msg) 5)结束IsCommand(string str) 1)判断是否以 / 开头DoCommand(string cmd) 1)判断指令,并解析命令与
9、参数(argc, argv) 2)调用指令处理函数 3)假设只有SetName命令,那么则将对应的套接字的名称设置DoMessage(string msg) 1)拼接消息与名字 BuildMsg(string msg) 2)在服务器上输出 3)消息路由DispatchMessage(string msg)BuildMsg(string msg) 1)从m_clients 中取出用户昵称 2)拼接字符串,形成格式如下 超人君(127.0.0.1) 23:49:48 说: 大家好! 即为: 昵称(IP地址)时间 说: 消息正文 3)返回DispatchMessage(string msg) 1)构
10、造迭代器2) 遍历m_clients,若不是自身,则派送消息Send()2. ChatClient 类该类负责处理客户端的所有操作。2) 字段设计SOCKET m_sClient 客户端自身的socketSOCKET m_sServer 服务器socketstring m_name 昵称sockaddr_in m_ServerAddr; 服务器地址根据端口号和服务器IP初始化m_serverConnect()void Send()int Select()封装底层原语,加入异常处理,使得外部调用节约优雅void Start() 1)初始化套接字 InitClientSocket() 2)连接服务
11、器 Connect() 设置为非阻塞模式 3)获取名字并发送至服务器 InitName() 4)创建新线程并显示替他用户发言 线程函数RecvMsgThread() 5)循环 SendMsg() 6)关闭客户端 CloseClient()InitName() 1)提示输入昵称 2)获取昵称 3)合法性判断 判断重复 4)添加命令格式 5)发送至服务器SendMsg() 1)读取一行消息 2)判断是否为命令 IsCommand(string str) 3)命令:处理命令 DoCommand(string cmd) 4)消息:处理消息 DoMessage(string msg) 1)发送消息 Se
12、nd() 2)本地回显RecvMsgThread()1) 初始化fdSocket,将m_sClient加入 2)创建fdRead 3)死循环,将m_sClient拷贝至fdRead 4)调用Select 5)循环,并输出收到的消息 Recv()3. SocketException类该类负责记录SOKCET错误的代码以及错误信息。5. 命令协议命令格式为 /命令 参数1 参数21. 退出: /exit2. 获取在线用户列表:/getuser 3. 私聊: /m UID 信息4. 清屏:/clear5. 帮助:/help处理方式IsCommand(string str) 负责解析是否为命令 判断首
13、字母是否为斜杠 str.at(0) = /ResoveCommand(string cmd, int& argc, string argv) 若是命令将命令解析为argc,argvDoCommand(string cmd) 处理命令,调用具体的XXX命令处理函数DoCmdXXXX()。6. 消息格式1) 公共消息超人君(127.0.0.1) UID:100 说:大家好!李四(127.0.0.1) UID:101 说:你好!你悄悄地对 ABC UID:100 说: 你好CDF UID:101 悄悄地对你说:3) 服务器消息【系统消息】XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。
14、4) 程序内部提示System Infoxxxxxxxxxxxxxxxxxxxxxxxxx五、 系统测试1. 服务器使用错误2. 客户端使用错误3. 启动服务器4. 启动客户端客户端出现欢迎信息以及昵称输入提示。服务器出现连接提示5. 关闭客户端服务器出现断开连接提示6. 启动服务器错误提示给出错误提示信息和提示代码7. 公开聊天所有客户端以及服务器都会显示。8. 私聊只有私聊的二人才能看到聊天信息,其他用户和服务器无法看到。9. 错误的私聊私聊自己会得到一个错误提示私聊不存在的用户也会得到一个错误提示10. 更名11. 帮助12. 非法指令非法指令会给出错误提示。13. 非法的指令参数14.
15、 连接服务器失败六、心得体会这次实现我深入研究了select模型的使用,完成了一个简易的聊天室。这次试验也使我在编程技巧方面也有了很大的提高。七、完整代码Charserverd.cpp 服务器main函数文件#include ChatServer.hSockException.hInitSock.h#include using namespace std;InitSock initSock;int main(int argc, char* argv) if (argc 2) cout Usage: argv0 Port return 1; ChatServer charServer(atoi(
16、argv1); try charServer.Start(); catch (SockException& e) e.GetErrorInfo() System ErrorError Code: e.GetErrorCode() stringmapClientInfo.hclass ChatServerpublic: void Start(); void End(); ChatServer(int nPort); ChatServer(void);private: void InitFDSocket(); void DoSelect(); void DoFDRead(SOCKET sRead)
17、; void RecvNewConnect(); string IPAddrToString(sockaddr_in sin); void AddClientToInfoMap(ClientInfo info); void RecvNewMessage(); bool IsCommand(string str); void DoCommand(string cmd); void ResoveCommand(string cmd, int& argc, string argv); void DoCmdName(int argc, string argv); void DoCmdGetUsers(
18、int argc, string argv); void DoMessage(string msg); void DoCmdPrivateMsg(int argc, string argv); string BuildMessage(string str, bool bIsPublic); string BuildSystemMsg(string str); void DispatchMessage(string msg); void CloseConnect(); string IntToString(int nNum); /=简单封装底层原语= void InitListenSocket(
19、); void Bind(); void Listen() ; int Select(); int Recv(char msgBuff); void Send(string msg, SOCKET client); SOCKET Accept(sockaddr_in& sin); /= map m_clients; fd_set m_fdSocket; fd_set m_fdRead; SOCKET m_sListen; SOCKET m_sNowClient; int m_nPort;#endif CHAT_SERVER_HChatServer.cpp 服务器类#pragma comment
20、(lib, ws2_32.lib)#define MAX_BUFF_SIZE 500typedef mapm_nPort = nPort; DoSelect(); if (nRet for (int i = 0; i = FD_SETSIZE)System Info接受连接达到上限,拒绝连接 return; sockaddr_in clientAddr; m_sNowClient = Accept(clientAddr); ClientInfo clientInfo(clientAddr);System Info接受来自 clientInfo.GetIp() 的连接 FD_SET(m_sNow
21、Client, &m_fdSocket); AddClientToInfoMap(clientInfo);string ChatServer:IPAddrToString(sockaddr_in sin) string str = inet_ntoa(sin.sin_addr); str.append(); char szFormat20; str.append(ltoa( ntohs(sin.sin_port),szFormat,10); return str;AddClientToInfoMap(ClientInfo info) m_clientsm_sNowClient = info; char msgBuffMAX_BUFF_SIZE; int nRet = Recv(msgBuff); string msg(msgBuff); if (nRet = 0) return; if (IsCommand(msg) DoCommand(msg); DoMessage(msg);Do
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1