您好,欢迎访问三七文档
WWiinnddoowwss网网络络程程序序设设计计报报告告--------------聊聊天天工工具具的的设设计计专专业业::通通信信009922姓姓名名::一、设计需求在网络越来越发达的今天,人们对网络的依赖越来越多,越来越离不开网络,大家所熟知的腾讯QQ、微软的MSN、移动的Fetion等,都是做的比较成功的实时聊天工具。随着网络的日益普及,各种聊天工具也层出不穷,但当我们学习了《Windows网络程序设计》这门课程之后,我们便会觉得,其实要实现简单的网络通讯其实并不难。接下来的课程设计就是针对一个简单的网络聊天程序,利用C++为开发工具,实现基本的通讯功能。二、实现功能(1)登录功能。(2)客户可以通过服务器转发,实现一对一和多对多聊天。(3)实现呼叫功能。(4)客户端程序应该可以实时显示目前其它用户的状态。(5)应该具有易用、美观的图形界面。三、功能分析(1)多人会话。此程序分为服务器端和客户端,当客户端要进入聊天室的时候,就必须通过网络连接到服务端,以实现和其它客户端的通讯功能。其中最简单的一种通讯方式就是多人会话,运用多线程同时对多个用户的信息进行监听,服务器通过转发消息,让所有人都可以得到消息,实现多人会话。(2)一对一会话。此程序的服务器端除了能够提供多人会话的功能外,还提供了私人聊天功能,可以实现一对一的聊天。就是在消息转发的时候,私聊的消息只发给私聊的对象,而其他人看不到此消息,但是此消息会通过服务器端转发,然后再到达目的客户端。(3)服务器登陆。服务器端开启之后处于监听状态,多线程工作,接受每一个用户的连接请求。而客户端只需输入服务器端的IP地址即可,端口在服务器端自动生成。(4)使用Socket编程实现聊天工具客户端和服务端的设计。四、设计方案整个程序设计为两个部分:服务器(ChatRoomServer)和客户端(ChatRoom),该程序分输入IP地址和端口号后,能够连接到服务器,然后进行多人聊天。文件包括课程设计报告和程序代码。多人聊天的关键在于要将每个客户端发送过来的消息分发给所有其他客户端,为了解决这个问题,在服务器程序中建立一个套接口链表,用来保存所有与客户端建立了连接的服务端口。下面描述了多人聊天的实现原理:当客户端ClientN向对应的服务端口N发送了消息Message,服务端口N将Message复制给所有套接口列表(USERLIST)中的套接口缓冲区,然后向每个服务端口发送WRITE消息,使每个服务端口将Message发送给对应的客户端。这样,所有客户端就都获得了Message消息,实现了多人聊天功能。BOOLCClientSocketList::Sends(char*buff,intn){CClientSocket*curr=Head;while(curr){curr-Send(buff,n);curr=curr-Next;}returntrue;}USERLIST表时多人聊天程序的核心,它是一个动态变化的链表,为空表示没有客户端建立了连接,不为空时每个元素就代表有一个客户端与服务器建立了连接。(一)客户端的设计1.首先利用对话框建立聊天程序的界面,以下是客户端的界面:最上面是一个接受数据的编辑框,下面分别是一个Ip地址控件和发送数据的编辑框,左下角是触发发送数据的按钮。2.加载套接字库,在MFC中用AfxSocketInit()加载套接字库。需要注意的是这个函数要在程序的初始化函数中调用,即在Appliacation的InitInstance()中调用AfxSocketInit();3.初始化套接字。在对话框中添加一个成员函数SockInit(),用来初始化套接字,然后在对话框初始化函数OnInitDlg()中调用SockInit()。Tip:因为聊天程序既包含接收端又包含发送端,所以需要绑定Ip和端口。4.创建多线程。接受数据时,如没有数据到来,则会阻塞,导致程序暂停运行,所以把接受数据工作放到单独的线程中。在OnInitDlg()中创建多线程。Tip:因为需要给线程传递一个通信的套接字和响应消息的窗口句柄,所以需要额外定义一个结构体,用来存储这两个参数。structRECVPARAM{HWNDhWnd;//接收数据窗体的句柄SOCKETsock;//接收数据的套接字};5.实现线程过程函数。Tip:如果要求不能用全局函数的话,那线程过程函数就要定义为static(它属于类本身).在线程过程函数中循环接受消息,利用::PostMessgae()函数给接受数据编辑框发送消息,以实现显示信息的功能6.自定义消息的实现。因为在PostMessage中是自定义的消息(WM_RECVDATA),所以需要完成消息映射。(1)定义消息值#defineWM_RECVDATAWM_USER+1(2)声明消息处理函数:afx_msgvoidOnRecvData(WPARAMwParam,LPARAMlParam);(3)完成消息映射:ON_MESSAGE(WM_RECVDATA,OnRecvData)7.实现OnRecvData()函数。显示接收到的信息8.实现OnButtonSend()函数。发送消息编写一个基于MFC对话框的聊天程序:1.新建一个基于MFC项目工程Chat2.加载套接字库,有下面两种方法,在这里选择第二种方法,因为更方便。方法一、使用WSAStartup()加载套接字库参考MSDN中的范例代码,拷贝到自己的程序中修改即可。*注意不再使用套接字后调用WSACleanup()。WORDwVersionRequested;WSADATAwsaData;interr;wVersionRequested=MAKEWORD(2,2);err=WSAStartup(wVersionRequested,&wsaData);if(err!=0){return;}if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2){WSACleanup();return;}方法二、使用AfxSocketInit()加载套接字库(MFC)BOOLAfxSocketInit(WSADATA*lpwsaData=NULL);参数1:指向WSADATA结构体的指针。WSAData机构体如下功能是:存放windowssocket初始化信息.structWSAData{WORDwVersion;WORDwHighVersion;charszDescription[WSADESCRIPTION_LEN+1];charszSystemStatus[WSASYSSTATUS_LEN+1];unsignedshortiMaxSockets;unsignedshortiMaxUdpDg;charFAR*lpVendorInfo;};wVersion为你将使用的Winsock版本号,wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。最大数量的并发Sockets并不是什么神奇的数字,它是由可用的物理资源来决定的.lpVendorInfo是为Winsock实现而保留的制造商信息,这个在Windows平台上并没有什么用处.函数内部帮我们调用WSAStartup()加载1.1版本的套接字库。并且可以确保在应用程序终止之前调用WSACleanup()终止套接字库的使用。这个函数在CWinApp::InitInstance()中调用该函数。*调用该函数需要包含头文件:Afxsock.h在这个程序中我们将这个头文件放在stdafx.hstdafx.h是一个预编译头文件,在这个头文件中包含了MFC运行必要的头文件和标准组件注意ReturnValue所以设置一个IF语句判断字节库是否加载成功3.初始化套接字,在CChatDlg类中添加成员变量SOCKETm_socket;添加成员函数BOOLInitSocket();BOOLCChatDlg::InitSocket(){m_socket=socket(AF_INET,SOCK_DGRAM,0);if(INVALID_SOCKET==m_socket){MessageBox(套接字创建失败!);returnFALSE;}SOCKADDR_INaddrSock;addrSock.sin_family=AF_INET;addrSock.sin_port=htons(6000);addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//INADDR_ANY表示接受任意地址intretval;//用来接受bind返回值retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));if(SOCKET_ERROR==retval){closesocket(m_socket);MessageBox(绑定失败);returnFALSE;}}之后在BOOLCChatDlg::OnInitDialog()中调用BOOLCChatDlg::InitSocket(),这样初始化的工作才算完成。4.编写接收端程序:建立一个单独的线程用来接收数据,向这个线程传递两个参数:套接字和对话框句柄。但是CreateThread只能传递一个指针参数,于是我们可以定义一个结构体包含套接字和对话框句柄,然后将这样一个结构体的对象指针作为参数传递。结构体以定义如下:structRECVPARAM{SOCKETsock;HWNDhwnd;};接下来创建线程函数,:***一般来说,线程函数RecvProc应该是一个全局函数,而不能是一个对象的成员函数,否则会出错。因为要调用类的成员函数,必须先产生一个对象;而在创建线程时,不能去创建或者传递一个对象,所以不能直接调用一个对象的成员函数作为线程入口函数。但是如果将它设置成为静态函数,那么它不属于任何具体的对象,这时可以将它作为线程入口函数。RECVPARAM*pRecvParam=newRECVPARAM;//用来传递参数的结构体pRecvParam-sock=m_socket;pRecvParam-hwnd=m_hWnd;//所有与窗口有关的类都有一个数据成员m_hWnd,它保存了和这个类相关的窗口句柄HandlehThread=CreateThread(NULL,0,RecvProc/*线程函数*/,(LPVOID)pRecvParam,0,NULL);CloseHandle(hThread);DWORDWINAPICChatDlg::RecvProc(LPVOIDlpParameter){SOCKETsock=((RECVPARAM*)lpParameter)-sock;HWNDhwnd=((RECVPARAM*)lpParameter)-hwnd;SOCKADDR_INaddrFrom;intlen=sizeof(SOCKADDR);charrecvBuf[200];chartempBuf[300];intretval;while(TRUE){retval=recvfrom(sock,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len);if(SOCKET_ERROR==retval)break;sprintf(tempBuf,%s说:%s,inet_ntoa(addrFrom.sin_addr),recvBuf);::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tem
本文标题:网络程序设计报告
链接地址:https://www.777doc.com/doc-2142832 .html