一、课程设计要求
1.1 项目介绍
首先,服务器聊天程序要在特定的端口上等待来自聊天客户的连接请求,并且需要维护一个客户连接表,以记录所有成功的连接。
其次,服务器聊天程序要及时接收从各个聊天客户发送过来的信息,然后把这些信息转发到一个或多个客户连接。对于公共聊天室,服务器将把收到的信息向除源端外的所有客户发送过去。
最后,服务器还要监控这些连接的状态,在客户主动离开或发生故障时从列表中删除相应表项,并及时更新连接表。
这些要求可以通过CSocket类提供的功能来实现。从CSocket派生出两个类:ClistenSocket和CclientSocket,它们分别用来侦听客户的连接请求和建立与客户的连接。服务器只需要——个侦听套接字ClistenSocket,然后根据客户的连接请求动态创建客户套接字CclientSocket。客户套接字的数量是不可预知的,因此需要一个列表来记录。MFC的CptrList类就能够实现这种功能。
1.2 实现要求
本聊天服务器端聊天程序要求能够检测和显示所有聊天用户的聊天内容,以及各个聊天客户的网络地址和连接端口。
监听本机IP地址中的一个指定的端口
当有用户端向该端口发送请求时,服务器程序里克建立一个与该客户端的连接并启动一个新的线程来处理该客户端的所有请求
根据客户端发送来的各种不同的请求,执行相应地操作,并将处理结果返回给该客户端。服务器能够识别3种请求命令:CONN(建立新连接)、CHAT(聊天)、和EXIT(离开)
二、设计流程:
首先需要运行服务器端,在服务器端创建服务器端的SOCKET并且开启一个特定的端口等待客户端的访问,并且监听到达该端口的访问;客户端运行后通过服务器端的IP地址和端口号与服务器端建立TCP链接,各个客户端的聊天内容先发送到客户端然后再由客户端发向各个客户端。客户端或者服务器端终端链接后释放连接。
2.1 服务器端流程图
2.2 客户端流程图
2.3 主要用到的类和函数说明
2.3.1 Socket类publicSocket(InetAddressaddress,intport)throwsIOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
如果应用程序已指定套接字,则调用该套接字的 createSocketImpl 方法来创建实际套接字实现。否则创建“普通”套接字。
参数: address - IP 地址 port - 端口号
侦听套接字类:CListeningSocket
抛出:
IOException - 如果创建套接字时发生 I/O 错误
SecurityException - 如果安全管理器存在并且其 checkConnect 方法不允许进行该操作
2.3.2 BufferedReader类publicBufferedReader(Readerin,intsz)
创建一个使用指定大小输入缓冲区的缓冲字符输入流。
参数: in - 一个 Reader sz - 输入缓冲区的大小
2.3.3 InputStream类publicabstractclassInputStreamextendsObjectimplementsCloseable
此抽象类是表示字节输入流的所有类的超类。
需要定义 InputStream 的子类的应用程序必须始终提供返回下一个输入字节的方法。
2.3.4 JFrame类publicclassJFrameextendsFrame
implementsWindowConstants,Accessible,RootPaneContainer
2.3.5 ActionListener类publicinterfaceActionListener
ActionListener 接口是一个有用扩展,以便若干控件访问相同的功能。 ActionListener 接口定义的 actionPerformed 方法用来进行对于事件的监听,然后进行处理。描述该方法的一个或多个文本字符串。描述该方法的一个或多个图标。
2.4 主要部分系统代码的实现
2.4.1 服务器端监听套接字的实现CListeningSocket::CListeningSocket(CServerDlg*pDlg)
{
//对成员变量赋值
m_pDlg=pDlg;
}
CListeningSocket::~CListeningSocket()
{
}
voidCListeningSocket::OnAccept(intnErrorCode)
{
CSocket::OnAccept(nErrorCode);
//调用主对话框类中的函数
m_pDlg->OnAccept();
}
/
// CListeningSocket member functions
#ifdef_DEBUG
voidCListeningSocket::AssertValid()const
{
CSocket::AssertValid();
}
voidCListeningSocket::Dump(CDumpContext&dc)const
{
CSocket::Dump(dc);
}
#endif
IMPLEMENT_DYNAMIC(CListeningSocket,CSocket)
2.4.2 客户端套接字的实现//构造函数
CClientSocket::CClientSocket(CServerDlg*pDlg)
{
m_pDlg=pDlg;
m_nMsgCount=0;
m_pFile=NULL;
m_pArchiveIn=NULL;
m_pArchiveOut=NULL;
}
//初始化
voidCClientSocket::Initialize()
{
//构造相应的CSocketFile对象
m_pFile=newCSocketFile(this);
//构造相应的CArchive对象
m_pArchiveIn=newCArchive(m_pFile,CArchive::load);
m_pArchiveOut=newCArchive(m_pFile,CArchive::store);
//放弃传送
voidCClientSocket::Abort()
{
if(m_pArchiveOut!=NULL)
{
m_pArchiveOut->Abort();
deletem_pArchiveOut;
m_pArchiveOut=NULL;
}
}
//发送消息
voidCClientSocket::SendMessage(CMsg*pMsg)
{
if(m_pArchiveOut!=NULL)
{
//对消息进行序列化
pMsg->Serialize(*m_pArchiveOut);
//将CArchive对象中的数据强制性写入文件中
m_pArchiveOut->Flush();
}
}
//接收消息
voidCClientSocket::ReceiveMessage(CMsg*pMsg)
{
//对消息进行序列化
pMsg->Serialize(*m_pArchiveIn);
}
//OnReceive事件处理函数
voidCClientSocket::OnReceive(intnErrorCode)
{
CSocket::OnReceive(nErrorCode);
//调用主对话框类中的相应函数处理
m_pDlg->OnReceive(this);
}
//析构函数
CClientSocket::~CClientSocket()
{
if(m_pArchiveOut!=NULL)
deletem_pArchiveOut;
if(m_pArchiveIn!=NULL)
deletem_pArchiveIn;
if(m_pFile!=NULL)
deletem_pFile;
}
#ifdef_DEBUG
voidCClientSocket::AssertValid()const
{
CSocket::AssertValid();
}
voidCClientSocket::Dump(CDumpContext&dc)const
{
CSocket::Dump(dc);
}
#endif
IMPLEMENT_DYNAMIC(CClientSocket,CSocket)
2.5 程序运行结果
服务器端运行界面
客户端运行结果