版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、局域网实时聊天系统1、图形界面的设置:,使用的Windows标准控件包括:使用MFC应用程序框架设计局域网聊天系统的图形界面(对话框) 按钮、静态文本、IP地址控件、分组框、编辑框、列表框等控件。确盘5关闭育口 I菩冷崑选屢:掇竇器IPL0-0那必3購:示帝I垢iZI回I亘停止幵启孵器傅止本系统集服务器和客户端为一体,通过网络设置按键弹出具体设置页面,服务器端:本地监听端口,用于监听客户端的连接信息; 开启按键和停止按键,用于强制服务器的开启和停止。客户端:服务器的IP设置和服务器的端口设置,用于连接服务器; 连接服务器按键和停止按键,用于主动加入和退出聊天系统。聊天记录编辑框:默认只读,用于
2、显示聊天系统中各客户端和服务器的聊天记录; 聊天记录输入框:输入信息之后,可点击En ter或者发送信息按键发送信息;关闭窗口:点击按键,可关闭正在执行的对话框; 更多功能:可用于聊天系统的其他功能扩展。通过单选按钮进行单项设置:2、初始化状态:CheckRadioButt on SetDIgItemText EnableWindow Exte ndDiaog:选择单选按钮中的一个;:设置编辑框中显示的字符串;:重载函数,设置控件的启用与关闭;:设置四个静态变量m_DlgRectLarge 、m_GroupRectLarge :用于保存主对话框和分组框的临时变量; m DIgRectSmall
3、 、m GroupRectSmall :用于保存主对话框和分组框的改变变量。、m GroupRectSmallSetWindowPos :有ID获得主对话框和分组框的句柄,设置界面的伸缩。3、开启服务器:(1)创建监听线程:m_hListe nThread = CreateThread(NULL :返回的句柄不能被继承;0 :新线程堆栈的大小与进程主线程堆栈相同;NULL, 0, Liste nThreadFu nc.this , 0, NULL);Liste nThreadFu nc:线程开始运行的地址,一般为线程入口函数名;This :传递给线程启动函数的 32位参数;0 :线程创建后立即
4、执行;若为CREAT_SUSPEND,则挂起不执行;' NULL :存放返回的线程ID。(2)创建监听线程入口函数:DWORD WINAPI ListenThreadFunc( fLPVOID pParam )CChatRoomDIg *pChatRoom = ( CChatRoomDlg *) pParam ;/创建监听套接字(IPv4网络协议、流式套接字、 TCP协议)ChatRoom->m_ListenSock = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP );/创建sockaddr_in结构存储IP地址和端口5ockaddr i
5、n service;/绑定IP地址和端口到监听套接字bind(p ChatRoom->m_Liste nSock,(sockaddr *)& service,sizeof (sockaddr_ in);/监听申请的连接,等待客户端连接其中两个参数为: S:用于标识一个已捆绑未连接套接口的描述字。backlog :等待连接队列的最大长度。)isten(p ChatRoom->m Liste nSock, 5); 使用异步I/O模型防止阻塞connect 、accept 、recieve 或recievefrom这些都是阻塞程序, 所谓阻塞方式 block ,顾名思义,就是进程
6、或是线程执行到这些函数时必须等待某个事件的发生, 函数不能立即返回。如果事件没有发生,进程或线程就被阻塞,可是使用Select 就可以完成非阻塞(所谓非阻塞方式 non-block ,就是进程或线程执行此函数时不 必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与 阻塞方式相同,若事件没有发生,则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率 较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况一读写或是异常。int select (n fds, readfds, writefds, exce ptfds, timeout)
7、;返回值:准备就绪的描述符数,若超时则返回 0,若出错则返回-1。nfds : select监视的文件句柄数,视进程中打开的文件数而定中的最大文件号加一。readfds : select般设为你要监视各文件(注:nfds并非一定表示监视的文件句柄数。)监视的可读文件句柄集合。writefds: select监视的可写文件句柄集合。exceptfds: select监视的异常文件句柄集合。timeout :本次select()的超时结束时间。当 readfds 或 writefds用一组系统提供的宏在Selecto中映象的文件可读或可写或超时,本次结束时便可判断哪一文件可读或可写,对select
8、()就结束返回。程序员利Socket编程特别有用的就是readfds 。几行相关的宏解释如下:FD_ZERO(fd_set *fdset):清空fdset 与所有文件句柄的联系。FD_SET(i ntfd, fd_set *fdset):建立文件句柄fd 与 fdset的联系。FD_CLR(i ntfd, fd_set *fdset):清除文件句柄fd 与 fdset的联系。FD_ISSET(i ntfd, fd_set *fdset)可读写。(4)非阻塞情况下,在一个套接口接受一个连接:检查fdset联系的文件句柄fd是否可读写,当0表示liste n函数之后,默认会阻塞进程,直到有一个客户
9、请socketfd_new,此后,服务器端即可使用这个新的套sockfd则继续用于监听其他客户端的连接请求。SOCKET acce pt(SOCKET s, structsockaddr FAR *addr, i nt FAR *addrle n); accept函数主要用于服务器端,一般位于求连接,建立好连接后,它返回的一个新的套接字 接字socketfd_new 与该客户端进行通信,而第一个参数:用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字)第二个参数:用来保存客户端套接字对应的地方”(包括客户端IP和端口信息等),第三个参数是 地 方”的占地大小。返回值:对应客户
10、端套接字标识。实际上是这样的:accept函数指定服务端去接受客户端的连接,接收后,返回了客户端套接字的标识,且获得了客户端套接字的地方”(包括客户端IP和端口信息等)。这个新的套接字socketfd new与监听套接字sockfd是什么关系?它所代表的 socket 对象包含了哪些信息? socketfd new是否占用了新的端口与客户端通信?先简单分析一番,由于网站的服务器也是一种TCP服务器,使用的是80端口,并不会因客户端的连80端口发送数据,其他客户端依然向80接而产生新的端口给客户端服务,该客户端依然是向服务器端的端口申请连接。因此,可以判断,socketfd_ new并没有占用新
11、的端口与客户端通信,依然使用的是与监听套接字socketfd new一样的端口号。那这么说,难道一个端口可以被两个socket对象绑定?当客户端发送数据过来的时候,究竟是与哪一个socket 对象通信呢?首先,一个端口肯定只能绑定一个socket 。我认为,服务器端的端口在bind的时候已经绑定到了监听套接字socetfd所描述的对象上,accept函数新创建的socket对象其实并没有进行端口的占有,而是复制了socetfd 的本地IP和端口号,并且记录了连接过来的客户端的IP和端口号。那么,当客户端发送数据过来的时候,究竟是与哪一个socket 对象通信呢?客户端发送过来的数据可以分为 2
12、种,一种是连接请求,一种是已经建立好连接后的数据传输。由于TCP/IP协议栈是维护着一个接收和发送缓冲区的。在接收到来自客户端的数据包后,服务器端 的TCP/IP协议栈应该会做如下处理:如果收到的是请求连接的数据包,则传给监听着连接请求端口的socetfd 套接字,进行accept 处理;如果是已经建立过连接后的客户端数据包,则将数据放入接收缓冲区。这样,当服务器端需要读取指定客户端的数据时,则可以利用代表的socket对象记录了客户端 IPsocketfd_new套接字通过 recv 或者read函数到缓冲区里面去取指定的数据(因为socketfd_new和端口,因此可以鉴别)。(5)监听到
13、的客户端连接加入消息队列在监听过程中,每接收一个客户端连接便产生-个通信套接字,为保存套接字、IP地址、端口号、套接字句柄等信息,此时设计一个类如下:classCClie ntitem public:CStri ngm_strl p;SOCKET m Socket;HANDLE hThread;CChatRoomDlg *m_p Mai nWnd;CClie ntltem()m_pM ai nWnd = NULL;m Socket = INVALID SOCKET; hThread = NULL;r;CClie ntitemtitem;:ltem.m Socket = accSock;:lte
14、m.m pMain Wnd = p ChatRoom;ltem.m_strlp = inet_n toa(clie ntAddr.sin_addr);INT PTR idx = pChatRoom->m ClientArray.Add(tItem);/创建连接的客户端的工作线程,默认挂起状态tItem.hThread = CreateThread(NULL, 0, Clie ntThread Proc,&(p ChatRoom->m_Clie ntArray.GetAt(idx), CREATE_SUS PENDED, NULL); p ChatRoom->m_Cli
15、e ntArray.GetAt(idx).hThread = tItem.hThread;ResumeThread(tltem.hThread);4、开启客户端:(1 )点击开启客户端,创建连接线程 m_hC onn ectThred = CreateThread(2 )创建连接线程入口函数DWORWINAPI ConnectThreadFunc(NULL, 0, Co nn ectThreadFu nc.LPVOID pParam )this , 0, NULL);CChatRoomDlg *pChatRoom = (CChatRoomDlg *) pParam ;/创建连接套接字)Chat
16、Room->m ConnectSock = socket(AF INET , SOCK STREAM ,IPPROTO TCP);/获取对话框中的IP和端口信息CStri ngstrServI p;pChatRoom- GetDIgItemText ( IDC IP ADDR , strServIp);nt iPort = pChatRoom->GetDlgltemlnt(IDC_CONNECT_PORT );/IP 地址的转化charszl pAddr16 = 0;USES CONVERSION;strc py_s(szl pAddr, 16, T2A(strServI p);/
17、服务器相关IP和端口信息结构体5ockaddr i n server;server.sin_family = AF_INET; server.s in port = hton s(i Port);server.s in addr.s addr = in et addr(sz Ip Addr);/连接函数conn ect (p ChatRoom->m C onn ectSock, (structsockaddr *)&server,Sizeof(structsockaddr)5、发送消息:/获取对话框消息,存到strMsgGetDlgItemText( IDC_INPUT_MSG
18、, strMsg);/将消息在消息记录编辑框显示 ShowMsg(strMsg);/如果是服务器端将消息发送给每一个客户端Sen dClie ntsMsg(strMsg);void CChatRoomDlg :SendClientsMsg( fTCHARszBuf MAX_BUF_SIZE = 0;/ 定义缓存器_tcscpy_s (szBuf, MAX_BUF_SIZE , strMsg );/ 把需要传送的信息拷贝到缓存器/遍历消息队列中每一个客户端for ( INT PTR idx = 0; idx<m ClientArray.GetCount(); idx+ )CString s
19、trMsg , CClientitem * pNotSend )f ( !pN otSe nd |pN otSe nd ->m Socket !=m Clie ntArray.GetAt(idx).m Socket |pNotSend ->hThread != m ClientArray.GetAt(idx).hThread |pN otSe nd ->m_strl p != m_Clie ntArray.GetAt(idx).m_strl p)/将缓存器中的内容传给客户端第一个参数为客户端对应的套接字标识由accept()函数生成se nd(m_Clie ntArray.G
20、etAt(idx).m_Socket,(tcslen(szBuf)* sizeof (TCHAR), 0);/char *)szBuf, /如果是客户端将消息发送给服务器m_ConnectSock为客户端套接字sen d(m Conn ectSock, (char *)strMsg.GetBuffer(),strMsg.GetLe ngth()*sizeof (TCHAR), 0);send 函数int send( SOCKET s, const char FAR *buf, i nt len, int flags );不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据
21、。客户程序一般用send函数向服务器发送请求,而服务器则通常用send该函数的第一个参数指定发送端套接字描述符; 第二个参数指明一个存放应用程序要发送数据的缓冲区; 第三个参数指明实际要发送的数据的字节数; 第四个参数一般置 0。这里只描述同步Socket的send函数的执行流程。(1) send先比较待发送数据的长度len和套接字冲区的长度,该函数返回SOCKET_ERROR;(2)如果len小于或者等于s的发送缓冲区的长度,函数来向客户程序发送应答。当调用该函数时,s的发送缓冲的长度,如果len大于s的发送缓那么 send先检查协议是否正在发送s的发送缓s的发送缓冲中的数据或者s的len冲
22、中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送 发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和(3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完(4)如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际 copy的字节数,如果send在copy数据时出现错误, 那么send就返回SOCKET_ERROR如果send
23、在等待协议传送数据时网络断开的话,那么send函数也返回 SOCKET_ERRORo要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROFO (每一个除send外的Socket函数在执行的最开始总要先等待 套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该 就返回 SOCKET_ERROR )注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用sendSIG PI
24、PE信号,进程对该信号的默认处理是进程终止。通过测试发现,异步socket的send函数在网络刚刚断开时还能发送返回相应的字节数,Socket 函数的进程会接收到一个同时使用select检测也是可写的,但是过几秒钟之后,再send就会出错了,返回-1 o select也不能检测出可写了。6、接收消息:/in tiRet = recv( pChatRoom->m_Co nn ectSock, (char *)szBuf, MAX_BUF_SIZE, 0);/in tiRet = recv(m_Clie ntltem.m_Socket, (char *)szBuf, MAX_BUF_SIZE
25、, 0);recv 函数int recv( SOCKET s, char FAR *buf, i nt len, i nt flags);不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放第三个参数指明buf的长度;第四个参数一般置 0。recv函数接收到的数据;这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送出现网络错误,那么 recv函数返回SOCKET_ERROR(2)如果s的发送缓冲中
26、没有数据或者数据被协议成功发送完毕后,冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据函数时,s的发送缓冲中的数据时recv就一直等待,直到协议把数据接 copy到buf中(注意协议接收 的接收缓冲中的数据先检查套接字s的接收缓到的数据可能大于 buf的长度,所以 在这种情况下要调用几次recv函数才能把scopy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回如果recv函数在等待协议接收数据时网络中断了,那么
27、它返回0。注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用 到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。SOCKET_ERROR;recv的进程会接收7、显示消息:VoidCChatRoomDlg:ShowMsg(CStringstrMsg)任何当前选定内容被去掉选定状态m_MsgEdit.SetSel(-1, -1);m_MsgEdit.Re placeSel(strMsg+_T(”rn");void SetSel( int n StartChar, i nt nEn dChar, BOOL bNoScroll = FALSE );功
28、能:调用此函数能在 EDIT Co ntrol中选择某范围的字符串。NoScroll指示是否显示脱字符是滚动可见的。如果值为FALSE,则显示,TRUE不显示。nStartChar指出当前选中部分的开始位置。如果nStartChar=0 且nEndChar=-1,则编辑控件的文本被全选;如果 nStartChar=-1,则任何当前选定内容被去掉选定状态。nEn dChar 指出结束位置。8、客户端退出:CChatRoomDIg :RemoveClie ntFromArray(CClie ntitemin_Item )voidfor (int idx = 0; idx<m ClientAr
29、ray.GetCount(); idx+ ) CClie ntitemtitem = m Clie ntArray.GetAt(idx);if ( tltem.m_Socket =in_Item.m_Socket&&tItem.hThread =in_Item.hThread&&tltem.m_strl p =in_Item .m_strI p ) m ClientArray.RemoveAt(idx);9、扩展知识:(1)UD P套接口函数SOCK RAW JUTI1DE10或者IPPROTO_*原始套接字,程序自己填写协议的首部和数据btrulSocket 函数:SOCKET WSAA PI socket( intaf,intt yp e,int p rotocolAf: AF INET4pv4 AF_INETipv6Type 和 protocolSOCK STREAM0 或者 IPPROTO TCPTC
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论