340 likes | 485 Views
教学内容. 第 9 章 网络编程. 聊天室程序 演示 CSocket 程序设计基础 基于 Csocket 的网络编程 聊天室客户端应用程序 的开发 聊天室服务器端应用程序 扩充练习. 返 回. §9.1 公众聊天室程序. 服务器端程序 ( 演示程序 ) 客户端程序 ( 演示程序 ). 返 回. §9.2 CSocket 程序设计基础. 计算机名、 IP 地址和端口 WinSock 的工作原理 服务器端的编程步骤 客户端编程步骤. 要点提示:. 返回目录. 计算机名、 IP 地址和端口. 1. IP 地址
E N D
教学内容 • 第9章 网络编程 • 聊天室程序演示 • CSocket程序设计基础 • 基于Csocket的网络编程 • 聊天室客户端应用程序的开发 • 聊天室服务器端应用程序 • 扩充练习 返 回
§9.1 公众聊天室程序 • 服务器端程序 (演示程序) • 客户端程序 (演示程序) 返 回
§9.2 CSocket程序设计基础 • 计算机名、IP地址和端口 • WinSock的工作原理 • 服务器端的编程步骤 • 客户端编程步骤 要点提示: 返回目录
计算机名、IP地址和端口 1.IP地址 IP地址是一个32位的数字,用于唯一标识位于网络中的计算机。IP地址由两部分组成:网络标识和主机标识。IP地址的格式有两类:二进制和十进制格式。32位二进制的IP地址以8为单位进行分隔,换算成十进制,每个十进制数之间用“.”隔开。 例如: 10100010,1110010,1011111,10000001转化为十进制的IP地址是: 162.114.95.129 为了便于对IP地址进行管理,将IP地址分为5类,如图9-3所示。 2.计算机名 避免输入计算机的IP地址带来的麻烦,我们可以通过计算机名来代替IP地址。
计算机名、IP地址和端口(续) 3.端口 在利用计算机网络进行通讯时,除利用IP地址找到指定的计算机外,还要通过端口(Port)来标识进行通讯的进程,TCP/UDP协议为每个端口都分配一个端口号,当进程通过系统调用,同一个或多个端口建立联系后,就可已通过这些端口进行数据传输了。 TCP/IP端口号有16位,因此可以有216个端口。在这216个端口中,有一些是保留端口,由权威机构分配,用于特殊目的。例如,端口80被用作超文本协议和WWW服务。保留端口外的端口为自由端口,以本地方式进行分配。 返回目录
利用WinSock进行有连接的通信 使用TCP传输协议,可以与指定IP地址的主机建立连接,采用流的方式进行数据传输,优点是保证网络数据传输的可靠性,对没有到达的数据进行重传,通过效验确定数据是否正确。工作方式如图9-4所示。 返回目录
利用WinSock进行无连接的通信 使用UDP数据报传输协议,可以与指定IP地址的主机发送数据,也可以从指定IP地址的主机接收数据,发送和接收方处于相同的地位,没有主次之分如图9-5所示。 返回目录
§9.3基于CSocket的网络编程 服务器端的编程步骤: • 从CSocket类派生一个新类 • 创建一个CSocket派生类对象 • 调用CSocket::Create()创建一个套接口,并指定端口 • 调用CSocket::Listen()函数侦听端口 • 在CSocket派生类中添加虚函数OnAccept()调用CSocket::Accept()接受客户的连接请求 • CSocket派生类中添加虚函数OnReceive()调用CSocket::Receive()接受数据 • 调用Close()函数关闭为各个客户分配的读套接口。 返回目录
基于CSocket的网络编程(续) 客户端的编程步骤: • 创建CSocket类的派生类对象,用于连接和读写 • 调用CSocket::Create( ) 创建一个套接口 • 调用CSocket::Connect( )连接服务器的指定端口 • 调用CSocket::Send( ) 发送数据 • 在CSocket::OnReceive( )函数中进行读写操作 • 结束通讯时,调用CSocket::Close( ) 关闭套接口 返回目录
§9.4 聊天室客户端应用程序 要点提示: • 9.4.1 创建工程MyWc(设置“Windows Sockets”支持) • 9.4.2 可视化设计用户界面(参考表9.2) • 9.4.3 创建一个新类CWCSock,用于与服务器通信 • 9.4.4 接受服务器发来的数据 • 9.4.6 建立与服务器的连接 • 9.4.6 向服务器发送数据 • 9.4.7 建立CMyWcDlg类与CWCSock类的关联 • 9.4.8 处理自定义消息 • 9.4.9 处理控件的状态 返回目录
返回目录 创建与服务器的通信类CWSock 1. 选择“Insert”菜单的“New Class”菜单项,在New Class对话框中,Bass class列表框中选取CSocket作为基类 2. 单击“OK”按钮,VC++就为MyWc工程创建了一个新类CWCSocket,相应地为工程创建了两个文件WCSocket.h和WCSocket.cpp
建立与服务器的连接 void CMyWcDlg::OnConnect() { UpdateData(true); //更新关联变量,如服务器名、端口号 if(!sockClient.Create()) //1创建套接口 { AfxMessageBox("Create WC socket failed"); PostQuitMessage(0); } if(!sockClient.Connect(m_server_name,m_nPort))//2连接服务器 { MessageBox("连接失败!"); return; } m_Dat.m_bOnline=true;//标记连接成功 } 返回目录
向服务器发送数据 void CMyWcDlg::OnSend() { UpdateData(true); //更新关联变量,如要发送的信息 m_Dat.m_bOnline=true; memset(m_Dat.m_dbData,0,255); memcpy(m_Dat.m_dbData,m_send_info,m_send_info.GetLength()); strcpy(m_Dat.m_strName,m_client_name); int iSent = sockClient.Send(&m_Dat,sizeof(m_Dat)); //发送的信息 //如果发送成功,将其发送的信息添加并显示在列表编辑框 if(iSent != SOCKET_ERROR) { m_ctlSent.AddString(m_send_info); UpdateData(false); // 显示更新 } } 返回目录
接受服务器发来的数据 1. 添加虚拟函数 用MFC ClassWizard在CWCSock类中添加虚函数OnReceive() 2. 读取服务器传来的数据,通知主窗口进行处理 void CWCSock::OnReceive(int nErrorCode) { if(Receive(&m_Dat,sizeof(m_Dat))==sizeof(m_Dat)) ::PostMessage(::AfxGetApp()->m_pMainWnd->m_hWnd, RE_RECEIVED, (WPARAM)&m_Dat,0); CSocket::OnReceive(nErrorCode); } 返回目录
建立CMyWcDlg类与CWCSock类的关联 1. 在CMyWcDlg类中添加CWCSock类的对象,使它们相互关联。方法是在MyWcDlg.h文件中,在CMyWcDlg类定义前面添加如下语句: #include "WCSock.h" 2. 在CMyWcDlg类定义中添加如下语句: CWCSock sockClient; 返回目录
处理自定义消息 1. 在WCSock.h中,定义自定义消息,以使CWCSock能够同应用程序主窗口通信。 #define RE_RECEIVED WM_USER+1 2.在CMyWcDlg.cpp文件中,添加消息映射宏,响应CWCSock类对象发送过来的自定义消息RE_RECEIVED BEGIN_MESSAGE_MAP(CMyWcDlg, CDialog) //{{AFX_MSG_MAP(CMyWcDlg) …… ON_BN_CLICKED(IDC_SEND, OnSend) ON_MESSAGE(RE_RECEIVED,OnReceive); //}}AFX_MSG_MAP END_MESSAGE_MAP() 返回目录
处理自定义消息(续一) 3.在CMyWcDlg类添加自定义函数OnMyReceive class CMyWcDlg : public CDialog { public: CMyWcDlg(CWnd* pParent = NULL); CWCSock sockClient; …… //自定义函数说明 LRESULT OnMyReceive(WPARAM wparam,LPARAM lParam); …… } 返回目录
处理自定义消息(续二) 4. 在CMyWcDlg类的实现文件中添加函数体 LRESULT CMyWcDlg::OnMyReceive(WPARAM wparam,LPARAM lParam) { _DATA *dat; dat = new _DATA; memcpy(dat,(_DATA *)wparam,sizeof(_DATA)); CString str = dat->m_strName; str += ": "; str += dat->m_dbData; m_ctlRecvd.AddString(str);//将信息添加并显示在列表框中 UpdateData(false); //显示列表编辑框内容 delete dat; return 1; } 返回目录
处理控件的状态 1. 在OnClose()和OnInitDialog()函数中 GetDlgItem(IDC_CONNECT)->EnableWindow(true); GetDlgItem(IDC_SEND)->EnableWindow(false); GetDlgItem(IDC_CLOSE)->EnableWindow(false); GetDlgItem(IDC_SERVER_PORT)->EnableWindow(true); GetDlgItem(IDC_CLIENT_NAME)->EnableWindow(true); GetDlgItem(IDC_SERVER_NAME)->EnableWindow(true); 返回目录
处理控件的状态(续) 2. 在OnConnect()函数中 GetDlgItem(IDC_CONNECT)->EnableWindow(false); GetDlgItem(IDC_SEND)->EnableWindow(true); GetDlgItem(IDC_CLOSE)->EnableWindow(true); GetDlgItem(IDC_SERVER_PORT)->EnableWindow(false); GetDlgItem(IDC_CLIENT_NAME)->EnableWindow(false); GetDlgItem(IDC_SERVER_NAME)->EnableWindow(false); 返回目录
§9.5 聊天室服务器端应用程序 要点提示: • 9.5.1 创建工程 • 9.5.2 设计用户界面 • 9.5.3 创建侦听类CLSock • 9.5.4 增加读/写类CRWSock • 9.5.8 处理接受客户的信息 • 9.5.9 处理客户的连接请求 • 9.5.10 启动、关闭服务器 • 9.5.11 控件的状态更新 返回目录
创建侦听类CLSock (1) 选择“Insert”菜单的“New Class”菜单项,弹出New Class对话框 (2) 在New Class对话框中,Bass class下拉式列表框中选取CSocket作为CLSocket的基类。如下图所示。 (3) 单击“OK”按钮,VC++就为MyWs工程创建了一个新类CLSocket,相应地为工程创建了两个文件LSocket.h和LSocket.cpp 返回目录
返回目录 增加一个读写类CRWSock 创建CSocket类的派生类CRWSock,与侦听类CLSock位于相同的LSock.h,LSock.cpp文件中。 ① 选择“Insert”菜单的“New Class”菜单项,弹出New Class对话框 ② 在New Class对话框中,Bass class下拉式列表框中选取CSocket作为CRWSock的基类 ③ 单击“Change…”按钮,弹出“Change Files”对话框。将Header file编辑框修改为LSock.h,Implementation file编辑框框修改为LSock.cpp,如图所示。
处理接受客户的信息 1.在CRWSock类中,添加虚拟函数OnReceive() 2. 在虚拟函数OnReceive()中接收客户传来的数据,首先添加到列表编辑框并显示,然后再分两种情况处理: 当收到客户离线信息时,就从套接口链表中删除该结点; 否则,将收到的信息发送给在线的客户。 void CRWSock::OnReceive(int nErrorCode) { m_p->ReadMessage(this); CSocket::OnReceive(nErrorCode); } 返回目录 转下页
返 回 读取客户的信息(续一) void CMyWsDlg::ReadMessage(CRWSock* sock) { int len = sock->Receive(&m_Dat,sizeof(m_Dat)); _DATA *dat = &m_Dat; CString str = "收到客户机(";str += dat->m_strName; str += “)的信息:”; str += dat->m_dbData; m_ctlRecvd.AddString(str); if(m_Dat.m_bOnline)SendMessage(sock,&m_Dat); else { sock->Close(); POSITION pos = m_list.Find(sock); m_list.RemoveAt(pos);delete sock; } }
向在线的客户发送信息(续二) void CMyWsDlg::SendMessage(CRWSock* socket,_DATA *buf) { POSITION pos = m_list.GetHeadPosition( ); while ( pos != NULL) { CRWSock *client =(CRWSock*)m_list.GetNext(pos); if(client == socket)continue; client->Send(buf,sizeof(m_Dat)); } } 返 回
处理客户的连接请求 返回目录 1.在CmyWsDlg类中,添加一个CPtrList类的对象,用于管理各个客户的套接口 CPtrList m_list; 2.在CLSock类中添加虚拟函数OnAccept(),用于处理客户的连接请求 void CLSock::OnAccept(int nErrorCode) { ASSERT(m_p); m_p->AcceptClient(); CSocket::OnAccept(nErrorCode); } 转下页
处理客户的连接请求(续) 创建套接口,添加到链表中 void CMyWsDlg::AcceptClient() { m_clientSocket = new CRWSock(this); if(!m_listenSocket->Accept(*m_clientSocket)) { AfxMessageBox("请求连接失败"); delete m_clientSocket; m_clientSocket=NULL; return; } m_list.AddTail(m_clientSocket); } 返 回
启动服务器 返回目录 void CMyWsDlg::OnStart() { UpdateData(true); //①创建套接口 m_listenSocket = new CLSock(this); if(!m_listenSocket->Create(m_server_port)) {delete m_listenSocket; m_listenSocket=NULL;return;} //②进入侦听状态 if(!m_listenSocket->Listen()) { delete m_listenSocket;m_listenSocket=NULL;return;} }
关闭服务器 返 回 void CMyWsDlg::OnClose() { POSITION pos = m_list.GetHeadPosition(); while( pos != NULL )//①从套接口链表中删除所有结点 { CRWSock* client=(CRWSock*)m_list.GetNext(pos); delete client; } if(m_listenSocket != NULL)//②关闭读套接口 { m_listenSocket->Close(); delete m_listenSocket; } }
控件的状态更新 返回目录 1.在OnStart()函数中末添加如下控件的状态更新语句: GetDlgItem(IDC_START)->EnableWindow(false); GetDlgItem(IDC_CLOSE)->EnableWindow(true); GetDlgItem(IDC_SERVER_PORT)->EnableWindow(false); 2.在OnClose()和OnInitDialog()函数中末添加如下控件的状态更新语句: GetDlgItem(IDC_START)->EnableWindow(true); GetDlgItem(IDC_CLOSE)->EnableWindow(false); GetDlgItem(IDC_SERVER_PORT)->EnableWindow(true);
扩充练习 1.修改聊天室程序,使客户断聊天室信息中,也包含自己发送的文字信息; 2.修改聊天室程序,在客户端,增加客户登入界面。 3.完善聊天室程序,添加客户可选的聊天对象,即私人聊天活多人聊天。 请批评指导,谢谢! 返回目录