完整word版网络编程基于TCP的简易聊天室实验报告Word格式.docx
《完整word版网络编程基于TCP的简易聊天室实验报告Word格式.docx》由会员分享,可在线阅读,更多相关《完整word版网络编程基于TCP的简易聊天室实验报告Word格式.docx(52页珍藏版)》请在冰豆网上搜索。
集成开发环境:
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模型进行了细致的拆分与抽象,做到了逻辑流程清晰,每个函数简洁易懂,层次分明。
例如服务器启动函数:
voidChatServer:
:
Start()
{
InitListenSocket();
Bind();
Listen();
InitFDSocket();
while(true)
{
DoSelect();
}
}
try
charServer.Start();
catch(SockExceptione)
cout<
<
e.GetErrorInfo()<
endl;
"
[SystemError]ErrorCode:
"
<
e.GetErrorCode()<
它其实就完成了一个简单的流程,初始化socket,绑定,监听,初始化fd_socket集合,死循环调用select。
通过合理的封装底层原语和加入异常处理(异常交给顶层处理),使得代码专注于业务流程而不是繁杂的异常判断语句,在看下面这个函数DoSelect()。
DoSelect()
m_fdRead=m_fdSocket;
intnRet=Select();
if(nRet>
0)
for(inti=0;
i<
m_fdRead.fd_count;
i++)
DoFDRead(m_fdRead.fd_array[i]);
它也只完成一个简单的流程,调用select,然后循环处理有读事件的socket。
DoRead(SOCKETsRead)
if(sRead==m_sListen)
RecvNewConnect();
else
m_sNowClient=sRead;
接下来的DoFDRead()函数完成的事情也非常直接,如果有事件的socket是监听socket的话,那么就是接收到了一个新的连接,否则是接收到了新的小。
从上面这个简单的例子中可以看到,本软件最大的优点就是精心设计的类和函数。
避免了使用select模型常见的反复嵌套的循环和判断,每个函数清晰明了。
本系统还存在以下不足,首先是没有对界面做更深入的优化,只是做了最基本的调整,让输入输出更加雅观,其次是底层原语的封装并没有考虑到泛用性。
四、系统详细设计
这部分的文档在编码之前已经基本完成,由于时间较为仓促,部分内容可能和实际有所出入。
1.ChatServer类
该类负责完成服务器所有操作。
1)类图
2)成员变量
Map<
SOCKET,string>
m_clients聊天者的SOCKET与昵称的映射
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)绑定套接字至本地机器:
3)进入监听模式(设置为非阻塞):
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类
该类负责处理客户端的所有操作。
2)字段设计
SOCKETm_sClient客户端自身的socket
SOCKETm_sServer服务器socket
stringm_name昵称
sockaddr_inm_ServerAddr;
服务器地址
根据端口号和服务器IP初始化m_server
Connect()
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)
1)发送消息Send()
2)本地回显
RecvMsgThread()
1)初始化fdSocket,将m_sClient加入
2)创建fdRead
3)死循环,将m_sClient拷贝至fdRead
4)调用Select
5)循环,并输出收到的消息Recv()
3.SocketException类
该类负责记录SOKCET错误的代码以及错误信息。
5.命令协议
命令格式为/命令参数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()。
6.消息格式
1)公共消息
超人君(127.0.0.1)UID:
100说:
大家好!
李四(127.0.0.1)UID:
101说:
你好!
!
你悄悄地对ABCUID:
100说:
你好
CDFUID:
101悄悄地对你说:
3)服务器消息
【系统消息】XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX。
4)程序内部提示
[SystemInfo]xxxxxxxxxxxxxxxxxxxxxxxxx
五、系统测试
1.服务器使用错误
2.客户端使用错误
3.启动服务器
4.启动客户端
客户端出现欢迎信息以及昵称输入提示。
服务器出现连接提示
5.关闭客户端
服务器出现断开连接提示
6.启动服务器错误提示
给出错误提示信息和提示代码
7.公开聊天
所有客户端以及服务器都会显示。
8.私聊
只有私聊的二人才能看到聊天信息,其他用户和服务器无法看到。
9.错误的私聊
私聊自己会得到一个错误提示
私聊不存在的用户也会得到一个错误提示
10.更名
11.帮助
12.非法指令
非法指令会给出错误提示。
13.非法的指令参数
14.连接服务器失败
六、心得体会
这次实现我深入研究了select模型的使用,完成了一个简易的聊天室。
这次试验也使我在编程技巧方面也有了很大的提高。
七、完整代码
Charserverd.cpp服务器main函数文件
#include"
ChatServer.h"
SockException.h"
InitSock.h"
#include<
iostream>
usingnamespacestd;
InitSockinitSock;
intmain(intargc,char*argv[])
if(argc<
2)
cout<
Usage:
argv[0]<
Port"
return1;
ChatServercharServer(atoi(argv[1]));
try
charServer.Start();
catch(SockException&
e)
e.GetErrorInfo()<
[SystemError]ErrorCode:
e.GetErrorCode()<
ChatServer.h服务器类头文件
#ifndefCHAT_SERVER_H
#defineCHAT_SERVER_H
WinSock2.h>
string>
map>
ClientInfo.h"
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);
//==========================================
map<
SOCKET,ClientInfo>
m_clients;
fd_setm_fdSocket;
fd_setm_fdRead;
SOCKETm_sListen;
SOCKETm_sNowClient;
intm_nPort;
};
#endifCHAT_SERVER_H
ChatServer.cpp服务器类
#pragmacomment(lib,"
ws2_32.lib"
)
#defineMAX_BUFF_SIZE500
typedefmap<
iteratormap_it;
ChatServer:
ChatServer(intnPort)
this->
m_nPort=nPort;
DoSelect();
if(nRet>
for(inti=0;
i<
{
DoFDRead(m_fdRead.fd_array[i]);
}
DoFDRead(SOCKETsRead)
RecvNewConnect();
else
m_sNowClient=sRead;
RecvNewMessage();
if(m_fdSocket.fd_count>
=FD_SETSIZE)
[SystemInfo]接受连接达到上限,拒绝连接"
return;
sockaddr_inclientAddr;
m_sNowClient=Accept(clientAddr);
ClientInfoclientInfo(clientAddr);
[SystemInfo]接受来自"
clientInfo.GetIp()<
的连接"
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;
AddClientToInfoMap(ClientInfoinfo)
m_clients[m_sNowClient]=info;
charmsgBuff[MAX_BUFF_SIZE];
intnRet=Recv(msgBuff);
stringmsg(msgBuff);
if(nRet<
=0)return;
if(IsCommand(msg))
DoCommand(msg);
DoMessage(msg);
Do