局域网实时聊天系统

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

局域网实时聊天系统1、图形界面的设置:使用MFC应用程序框架设计局域网聊天系统的图形界面(对话框),使用的Windows标准控件包括:按钮、静态文本、IP地址控件、分组框、编辑框、列表框等控件。本系统集服务器和客户端为一体,通过网络设置按键弹出具体设置页面,通过单选按钮进行单项设置:服务器端:本地监听端口,用于监听客户端的连接信息;开启按键和停止按键,用于强制服务器的开启和停止。客户端:服务器的IP设置和服务器的端口设置,用于连接服务器;连接服务器按键和停止按键,用于主动加入和退出聊天系统。聊天记录编辑框:默认只读,用于显示聊天系统中各客户端和服务器的聊天记录;聊天记录输入框:输入信息之后,可点击Enter或者发送信息按键发送信息;关闭窗口:点击按键,可关闭正在执行的对话框;更多功能:可用于聊天系统的其他功能扩展。2、初始化状态:CheckRadioButton:选择单选按钮中的一个;SetDlgItemText:设置编辑框中显示的字符串;EnableWindow:重载函数,设置控件的启用与关闭;ExtendDiaog:设置四个静态变量m_DlgRectLarge、m_GroupRectLarge:用于保存主对话框和分组框的临时变量;m_DlgRectSmall、m_GroupRectSmall:用于保存主对话框和分组框的改变变量。SetWindowPos:有ID获得主对话框和分组框的句柄,设置界面的伸缩。3、开启服务器:(1)创建监听线程:m_hListenThread=CreateThread(NULL,0,ListenThreadFunc,this,0,NULL);NULL:返回的句柄不能被继承;0:新线程堆栈的大小与进程主线程堆栈相同;ListenThreadFunc:线程开始运行的地址,一般为线程入口函数名;This:传递给线程启动函数的32位参数;0:线程创建后立即执行;若为CREAT_SUSPEND,则挂起不执行;NULL:存放返回的线程ID。(2)创建监听线程入口函数:DWORDWINAPIListenThreadFunc(LPVOIDpParam){CChatRoomDlg*pChatRoom=(CChatRoomDlg*)pParam;//创建监听套接字(IPv4网络协议、流式套接字、TCP协议)pChatRoom-m_ListenSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//创建sockaddr_in结构存储IP地址和端口sockaddr_inservice;//绑定IP地址和端口到监听套接字bind(pChatRoom-m_ListenSock,(sockaddr*)&service,sizeof(sockaddr_in));//监听申请的连接,等待客户端连接其中两个参数为:S:用于标识一个已捆绑未连接套接口的描述字。backlog:等待连接队列的最大长度。listen(pChatRoom-m_ListenSock,5);}(3)使用异步I/O模型防止阻塞connect、accept、recieve或recievefrom这些都是阻塞程序,所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。intselect(nfds,readfds,writefds,exceptfds,timeout);返回值:准备就绪的描述符数,若超时则返回0,若出错则返回-1。nfds:select监视的文件句柄数,视进程中打开的文件数而定,一般设为你要监视各文件中的最大文件号加一。(注:nfds并非一定表示监视的文件句柄数。)readfds:select监视的可读文件句柄集合。writefds:select监视的可写文件句柄集合。exceptfds:select监视的异常文件句柄集合。timeout:本次select()的超时结束时间。当readfds或writefds中映象的文件可读或可写或超时,本次select()就结束返回。程序员利用一组系统提供的宏在select()结束时便可判断哪一文件可读或可写,对Socket编程特别有用的就是readfds。几行相关的宏解释如下:FD_ZERO(fd_set*fdset):清空fdset与所有文件句柄的联系。FD_SET(intfd,fd_set*fdset):建立文件句柄fd与fdset的联系。FD_CLR(intfd,fd_set*fdset):清除文件句柄fd与fdset的联系。FD_ISSET(intfd,fd_set*fdset):检查fdset联系的文件句柄fd是否可读写,当0表示可读写。(4)非阻塞情况下,在一个套接口接受一个连接SOCKETaccept(SOCKETs,structsockaddrFAR*addr,intFAR*addrlen);accept函数主要用于服务器端,一般位于listen函数之后,默认会阻塞进程,直到有一个客户请求连接,建立好连接后,它返回的一个新的套接字socketfd_new,此后,服务器端即可使用这个新的套接字socketfd_new与该客户端进行通信,而sockfd则继续用于监听其他客户端的连接请求。第一个参数:用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字)第二个参数:用来保存客户端套接字对应的“地方”(包括客户端IP和端口信息等),第三个参数是“地方”的占地大小。返回值:对应客户端套接字标识。实际上是这样的:accept函数指定服务端去接受客户端的连接,接收后,返回了客户端套接字的标识,且获得了客户端套接字的“地方”(包括客户端IP和端口信息等)。这个新的套接字socketfd_new与监听套接字sockfd是什么关系?它所代表的socket对象包含了哪些信息?socketfd_new是否占用了新的端口与客户端通信?先简单分析一番,由于网站的服务器也是一种TCP服务器,使用的是80端口,并不会因客户端的连接而产生新的端口给客户端服务,该客户端依然是向服务器端的80端口发送数据,其他客户端依然向80端口申请连接。因此,可以判断,socketfd_new并没有占用新的端口与客户端通信,依然使用的是与监听套接字socketfd_new一样的端口号。那这么说,难道一个端口可以被两个socket对象绑定?当客户端发送数据过来的时候,究竟是与哪一个socket对象通信呢?首先,一个端口肯定只能绑定一个socket。我认为,服务器端的端口在bind的时候已经绑定到了监听套接字socetfd所描述的对象上,accept函数新创建的socket对象其实并没有进行端口的占有,而是复制了socetfd的本地IP和端口号,并且记录了连接过来的客户端的IP和端口号。那么,当客户端发送数据过来的时候,究竟是与哪一个socket对象通信呢?客户端发送过来的数据可以分为2种,一种是连接请求,一种是已经建立好连接后的数据传输。由于TCP/IP协议栈是维护着一个接收和发送缓冲区的。在接收到来自客户端的数据包后,服务器端的TCP/IP协议栈应该会做如下处理:如果收到的是请求连接的数据包,则传给监听着连接请求端口的socetfd套接字,进行accept处理;如果是已经建立过连接后的客户端数据包,则将数据放入接收缓冲区。这样,当服务器端需要读取指定客户端的数据时,则可以利用socketfd_new套接字通过recv或者read函数到缓冲区里面去取指定的数据(因为socketfd_new代表的socket对象记录了客户端IP和端口,因此可以鉴别)。(5)监听到的客户端连接加入消息队列在监听过程中,每接收一个客户端连接便产生一个通信套接字,为保存套接字、IP地址、端口号、套接字句柄等信息,此时设计一个类如下:classCClientItem{public:CStringm_strIp;SOCKETm_Socket;HANDLEhThread;CChatRoomDlg*m_pMainWnd;CClientItem(){m_pMainWnd=NULL;m_Socket=INVALID_SOCKET;hThread=NULL;}};CClientItemtItem;tItem.m_Socket=accSock;tItem.m_pMainWnd=pChatRoom;tItem.m_strIp=inet_ntoa(clientAddr.sin_addr);INT_PTRidx=pChatRoom-m_ClientArray.Add(tItem);//创建连接的客户端的工作线程,默认挂起状态tItem.hThread=CreateThread(NULL,0,ClientThreadProc,&(pChatRoom-m_ClientArray.GetAt(idx)),CREATE_SUSPENDED,NULL);pChatRoom-m_ClientArray.GetAt(idx).hThread=tItem.hThread;ResumeThread(tItem.hThread);4、开启客户端:(1)点击开启客户端,创建连接线程m_hConnectThred=CreateThread(NULL,0,ConnectThreadFunc,this,0,NULL);(2)创建连接线程入口函数DWORDWINAPIConnectThreadFunc(LPVOIDpParam){CChatRoomDlg*pChatRoom=(CChatRoomDlg*)pParam;//创建连接套接字pChatRoom-m_ConnectSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//获取对话框中的IP和端口信息CStringstrServIp;pChatRoom-GetDlgItemText(IDC_IP_ADDR,strServIp);intiPort=pChatRoom-GetDlgItemInt(IDC_CONNECT_PORT);//IP地址的转化charszIpAddr[16]={0};USES_CONVERSION;strcpy_s(szIpAddr,16,T2A(strServIp));//服务器相关IP和端口信息结构体sockaddr_inserver;server.sin_family=AF_INET;server.sin_port=htons(iPort);server.sin_addr.s_addr=inet_addr(szIpAddr);//连接函数connect(pChatRoom-m_ConnectSock,(structsockaddr*)&server,sizeof(structsockaddr))}5、发送消息://获取对话框消息,存到strMsgGetDlgItemText(IDC_INPUT_MSG,strMsg);//将消息在消息记录编辑框显示ShowMsg(strMsg);//如果是服务器端将消息发送给每一个客户端SendClientsMsg(strMsg);voidCChatRoomDlg::SendClientsMsg(CStringstrMsg,CClientItem*pNotSend){TCHARszBuf[MAX_BUF_SIZE]={0};//定义缓存器_tcscpy_s(szBuf,MAX_BUF_SIZE,strMsg);//把需要传

1 / 9
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功