网络编程 基于TCP的简易聊天室 实验报告.docx
- 文档编号:17702201
- 上传时间:2023-08-03
- 格式:DOCX
- 页数:50
- 大小:532.67KB
网络编程 基于TCP的简易聊天室 实验报告.docx
《网络编程 基于TCP的简易聊天室 实验报告.docx》由会员分享,可在线阅读,更多相关《网络编程 基于TCP的简易聊天室 实验报告.docx(50页珍藏版)》请在冰点文库上搜索。
网络编程基于TCP的简易聊天室实验报告
网络编程课程设计
-基于TCP的简易聊天室
一、实验基本信息概要
1.题目要求
熟悉异步网络编程的基本方法,掌握异步网络编程和网络协议设计的方法。
要求采用select模型、WSAAsyncSelect模型、WSAEventSelect模型、重叠模型或完成端口等模型完成编程任务。
2.上机要求
要求采用select模型、WSAAsyncSelect模型、WSAEventSelect模型、重叠模型或完成端口等模型完成下面的任务。
3.题目内容
内容概要:
实现一个聊天室,支持多人聊天。
也可以增加私聊等功能。
4.开发环境
操作系统:
Windows7
开发语言:
C++
集成开发环境:
MicrosoftVisualStudio2010
二、系统简介
1.界面
本软件使用DOS控制台界面,界面风格较为朴素,没用使用复杂的颜色。
但是对聊天时界面进行了一定的控制和修正使得界面较为美观,易读。
服务器:
客户端:
2.软件功能
本软件实现了聊天室基本的功能,包括公开聊天,私聊,获取在线用户,更改昵称,获得帮助等。
1)公开聊天
在光标处直接输入消息后按回车即为发送公开聊天,如下图所示。
2)私聊
使用命令【/m对方UID消息】即可发送私聊,私聊只有对方可以看到,如下图所示:
客户端1,密聊UID为132的用户。
发送后
客户端2,UID为132的用户收到私聊消息。
3)获取在线用户列表
使用命令【/list】即可获得在线用户列表,用户列表会议系统消息的方式返回,如下图所示。
命令
发送后
4)更改昵称
使用命令【/name你的新昵称】即可立即更改昵称,成功修改后服务器会以系统消息的方式返回成功修改的提示。
命令
命令发送后
5)帮助信息
使用命令【/help】即可查看服务器的欢迎信息,里面包含了该聊天室的使用帮助,如下图所示。
命令
命令发送后
3.系统设计
开发本软件时,我使用了面向对象的思想,把服务器和客户端封装成对应的类,类设计将会在下一节做详细介绍。
通行方面我在服务器接受客户端消息,和客户端接受服务器消息时使用了select模型,发送信息我使用的是普通的socket原语。
基本原理为服务器与客户端建立TCP连接,然后服务器负责路由消息到各个客户端。
4.优点与缺点
本软件对流程复杂的SELECT模型进行了细致的拆分与抽象,做到了逻辑流程清晰,每个函数简洁易懂,层次分明。
例如服务器启动函数:
它其实就完成了一个简单的流程,初始化socket,绑定,监听,初始化fd_socket集合,死循环调用select。
通过合理的封装底层原语和加入异常处理(异常交给顶层处理),使得代码专注于业务流程而不是繁杂的异常判断语句,在看下面这个函数DoSelect()。
它也只完成一个简单的流程,调用select,然后循环处理有读事件的socket。
接下来的DoFDRead()函数完成的事情也非常直接,如果有事件的socket是监听socket的话,那么就是接收到了一个新的连接,否则是接收到了新的小。
从上面这个简单的例子中可以看到,本软件最大的优点就是精心设计的类和函数。
避免了使用select模型常见的反复嵌套的循环和判断,每个函数清晰明了。
本系统还存在以下不足,首先是没有对界面做更深入的优化,只是做了最基本的调整,让输入输出更加雅观,其次是底层原语的封装并没有考虑到泛用性。
三、系统详细设计
这部分的文档在编码之前已经基本完成,由于时间较为仓促,部分内容可能和实际有所出入。
1.
ChatServer类
该类负责完成服务器所有操作。
1)类图
2)成员变量
Map
fd_setm_fdSocket可用套接字集合
fd_setm_fdRead有事件发生的套接字集合
SOCKETm_sListen监听Socket
SOCKETm_sNowClient当前处理的客户套接字
intm_nPort监听端口
3)方法设计
voidBind()
voidListen()
voidSelect()
intRecv()
SOCKETAccept()
封装底层原语,并加入异常机制,使得外部调用简约明了。
构造函数
传入监听端口,初始化m_nPort
Start()
1)初始化监听套接字:
voidInitListenSocket()
2)绑定套接字至本地机器:
voidBind()
3)进入监听模式(设置为非阻塞):
voidListen()
4)初始化可用套接字集合voidInitFDSocket()
5)死循环,调用select方法DoSelect()
6)结束
DoSelect()
1)令m_fdRead=m_fdSocket
2)调用Select()
3)循环处理Select的结果DoFdRead(SocketsRead)
4)结束
DoFdRead(intiReadIndex)
1)判断是否为m_sListen
2)是m_sListenRecvNewConnect()
3)否则令m_sNowClient=m_fdRead[iReadIndex],调用RecvNewMessage()
RecvNewConnect()
1)判断是否达到套接字上线
2)调用Accept(),接收连接sClient
3)添加sCilent至m_fdSocket
4)添加套接字至m_clientsAddClientToInfoMap(stringname)
AddClientToInfoMap(stringname)
1)以SOKCET为键,name为值加入MAP
RecvNewMessage()
1)调用Recv函数
2)是否为命令IsCommand(stringstr)
3)是,则DoCommand(stringcmd)
4)否,则DoMessage(stringmsg)
5)结束
IsCommand(stringstr)
1)判断是否以"/"开头
DoCommand(stringcmd)
1)判断指令,并解析命令与参数(argc,argv)
2)调用指令处理函数
3)假设只有SetName命令,那么则将对应的套接字的名称设置
DoMessage(stringmsg)
1)拼接消息与名字BuildMsg(stringmsg)
2)在服务器上输出
3)消息路由DispatchMessage(stringmsg)
BuildMsg(stringmsg)
1)从m_clients中取出用户昵称
2)拼接字符串,形成格式如下
超人君(127.0.0.1)23:
49:
48说:
大家好!
即为:
昵称(IP地址)时间说:
消息正文
3)返回
DispatchMessage(stringmsg)
1)构造迭代器
2)遍历m_clients,若不是自身,则派送消息Send()
2.ChatClient类
该类负责处理客户端的所有操作。
1)类图
2)字段设计
SOCKETm_sClient客户端自身的socket
SOCKETm_sServer服务器socket
stringm_name昵称
sockaddr_inm_ServerAddr;服务器地址
3)方法设计
构造函数
根据端口号和服务器IP初始化m_server
Connect()
voidSelect()
intRecv()
voidSend()
intSelect()
封装底层原语,加入异常处理,使得外部调用节约优雅。
voidStart()
1)初始化套接字InitClientSocket()
2)连接服务器Connect()设置为非阻塞模式
3)获取名字并发送至服务器InitName()
4)创建新线程并显示替他用户发言线程函数RecvMsgThread()
5)循环SendMsg()
6)关闭客户端CloseClient()
InitName()
1)提示输入昵称
2)获取昵称
3)合法性判断判断重复
4)添加命令格式
5)发送至服务器
SendMsg()
1)读取一行消息
2)判断是否为命令IsCommand(stringstr)
3)命令:
处理命令DoCommand(stringcmd)
4)消息:
处理消息DoMessage(stringmsg)
DoMessage(stringmsg)
1)发送消息Send()
2)本地回显
RecvMsgThread()
1)初始化fdSocket,将m_sClient加入
2)创建fdRead
3)死循环,将m_sClient拷贝至fdRead
4)调用Select
5)循环,并输出收到的消息Recv()
3.SocketException类
该类负责记录SOKCET错误的代码以及错误信息。
1)类图
4.命令协议
命令格式为/命令参数1参数2
1.退出:
/exit
2.获取在线用户列表:
/getuser
3.私聊:
/mUID信息
4.清屏:
/clear
5.帮助:
/help
处理方式
IsCommand(stringstr)负责解析是否为命令
判断首字母是否为斜杠"/"str.at(0)=='/'
ResoveCommand(stringcmd,int&argc,stringargv[])若是命令将命令解析为argc,argv
DoCommand(stringcmd)处理命令,调用具体的XXX命令处理函数DoCmdXXXX()。
5.消息格式
1)公共消息
超人君(127.0.0.1)UID:
100说:
大家好!
李四(127.0.0.1)UID:
101说:
你好!
!
2)私聊
你悄悄地对ABCUID:
100说:
你好
CDFUID:
101悄悄地对你说:
你好
3)服务器消息
【系统消息】XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。
4)程序内部提示
[SystemInfo]xxxxxxxxxxxxxxxxxxxxxxxxx
四、系统测试
1.服务器使用错误
2.客户端使用错误
3.启动服务器
5.启动客户端
客户端出现欢迎信息以及昵称输入提示。
服务器出现连接提示
6.关闭客户端
服务器出现断开连接提示
7.启动服务器错误提示
给出错误提示信息和提示代码
8.公开聊天
所有客户端以及服务器都会显示。
9.私聊
只有私聊的二人才能看到聊天信息,其他用户和服务器无法看到。
10.错误的私聊
私聊自己会得到一个错误提示
私聊不存在的用户也会得到一个错误提示
11.更名
12.获取在线用户
13.帮助
14.非法指令
非法指令会给出错误提示。
15.非法的指令参数
16.连接服务器失败
五、心得体会
这次实现我深入研究了select模型的使用,完成了一个简易的聊天室。
这次试验也使我在编程技巧方面也有了很大的提高。
六、完整代码
Charserverd.cpp服务器main函数文件
#include"ChatServer.h"
#include"SockException.h"
#include"InitSock.h"
#include
usingnamespacestd;
InitSockinitSock;
intmain(intargc,char*argv[])
{
if(argc<2)
{
cout<<"Usage:
"< return1; } ChatServercharServer(atoi(argv[1])); try { charServer.Start(); } catch(SockException&e) { cout< cout<<"[SystemError]ErrorCode: "< } } ChatServer.h服务器类头文件 #ifndefCHAT_SERVER_H #defineCHAT_SERVER_H #include #include #include #include"ClientInfo.h" usingnamespacestd; classChatServer { public: voidStart(); voidEnd(); ChatServer(intnPort); ~ChatServer(void); private: voidInitFDSocket(); voidDoSelect(); voidDoFDRead(SOCKETsRead); voidRecvNewConnect(); stringIPAddrToString(sockaddr_insin); voidAddClientToInfoMap(ClientInfoinfo); voidRecvNewMessage(); boolIsCommand(stringstr); voidDoCommand(stringcmd); voidResoveCommand(stringcmd,int&argc,stringargv[]); voidDoCmdName(intargc,stringargv[]); voidDoCmdGetUsers(intargc,stringargv[]); voidDoMessage(stringmsg); voidDoCmdPrivateMsg(intargc,stringargv[]); stringBuildMessage(stringstr,boolbIsPublic); stringBuildSystemMsg(stringstr); voidDispatchMessage(stringmsg); voidCloseConnect(); stringIntToString(intnNum); //============简单封装底层原语============= voidInitListenSocket(); voidBind(); voidListen(); intSelect(); intRecv(charmsgBuff[]); voidSend(stringmsg,SOCKETclient); SOCKETAccept(sockaddr_in&sin); //========================================== private: map fd_setm_fdSocket; fd_setm_fdRead; SOCKETm_sListen; SOCKETm_sNowClient; intm_nPort; }; #endifCHAT_SERVER_H ChatServer.cpp服务器类 #include #include #include"ChatServer.h" #include"SockException.h" #pragmacomment(lib,"ws2_32.lib") usingnamespacestd; #defineMAX_BUFF_SIZE500 typedefmap : iteratormap_it; ChatServer: : ChatServer(intnPort) { this->m_nPort=nPort; } voidChatServer: : Start() { InitListenSocket(); Bind(); Listen(); InitFDSocket(); while(true) { DoSelect(); } } voidChatServer: : DoSelect() { m_fdRead=m_fdSocket; intnRet=Select(); if(nRet>0) { for(inti=0;i { DoFDRead(m_fdRead.fd_array[i]); } } } voidChatServer: : DoFDRead(SOCKETsRead) { if(sRead==m_sListen) { RecvNewConnect(); } else { m_sNowClient=sRead; RecvNewMessage(); } } voidChatServer: : RecvNewConnect() { if(m_fdSocket.fd_count>=FD_SETSIZE) { cout<<"[SystemInfo]接受连接达到上限,拒绝连接"< return; } sockaddr_inclientAddr; m_sNowClient=Accept(clientAddr); ClientInfoclientInfo(clientAddr); cout<<"[SystemInfo]接受来自"< FD_SET(m_sNowClient,&m_fdSocket); AddClientToInfoMap(clientInfo); } stringChatServer: : IPAddrToString(sockaddr_insin) { stringstr=inet_ntoa(sin.sin_addr); str.append(": "); charszFormat[20]; str.append(ltoa(ntohs(sin.sin_port),szFormat,10)); returnstr; } voidChatServer: : AddClientToInfoMap(ClientInfoinfo) { m_clients[m_sNowClient]=info; } voidChatServer: : RecvNewMessage() { charmsgBuff[MAX_BUFF_SIZE]; intnRet=Recv(msgBuff); stringmsg(msgBuff); if(nRet<=0)return; if(IsCommand(msg)) { DoCommand(msg); } else { DoMessage(msg); } } voidChatServer: : DoCommand(stringcmd) { intargc; stringargv[100]; ResoveCommand(cmd,argc,argv); if(argv[0]=="name") { DoCmdName(argc,argv); } elseif(argv[0]=="list") { DoCmdGetUsers(argc,argv); } elseif(argv[0]=="m") { DoCmdPrivateMsg(argc,argv); } else { Send("【系统消息】命令不存在,请使用/help命令查看命令帮助",m_sNowClient); } } voidChatServer: : ResoveCommand(stringcmd,int&argc,stringargv[]) { intcount=0; for(inti=1;i { charc=cmd.at(i); if(c! ='') { argv[count]+=c; } else { count++; } } argc=++count; } voidChatServer: : DoCmdGetUsers(intargc,stringargv[]) { if(argc! =1) { return; } stringmsg; stringonline=IntToString(m_clients.size()); msg.append("【系统消息】在线人数共"+online+"人: "); msg.append("\n\r"); map_itbegin=m_clients.begin(); map_itend=m_clients.end(); for(;begin! =end;++begin) { msg.append("\t"+begin->second.GetName()+""+begin->second.GetIp()); msg.append("UID: "+IntToString((int)begin->first)); msg.append("\n\r"); } Send(msg,m_sNowClient); } voidChatServer: : DoCmdName(intargc,stringargv[]) { if(argc! =2) { Send("【系统消息】命令格式错误USAGE: /name你的昵称",m_sNowClient); return; } stringname
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 网络编程 基于TCP的简易聊天室 实验报告 网络 编程 基于 TCP 简易 聊天室 实验 报告