C++聊天室系统源代码 第2页

C++聊天室系统源代码 第2页
二、系统结构
为了保证聊天信息的实时性和系统的效率,聊天室系统使用C/S结构,由服务器端程序和客户端程序两部分组成。服务器端和客户端通过TCP协议进行通信。其中,客户端用于显示来自服务器端的聊天信息,接收用户的输入信息并发送给服务器端,服务器端用于接受来自客户端的信息,处理之后转发给聊天室中的其他用户。
三、网络通信
1)Winsock I/O模型
在Windows环境中,可以使用Windows 提供Winsock API来实现客户端与服务器端直接的通信。
Winsock提供的 Recv和Send 函数是堵塞的,在程序调用这些函数时,如果网络IO未结束,线程将被堵塞起来,从而影响程序其它部分的正常工作。在服务器端,如果要实现同时处理来自多个客户端的连接,就必须为每一个连接到服务器端的客户端创建独立的线程来处理网络通信,从而不会互相影响。但如果这样设计,当有大量用户并发连接到服务器时,服务器会创建大量的线程,这时操作系统在调度这些线程的时候会占用大量的CPU资源,导致系统的效率非常低下。可见,并发量高的情况下,使用这种方来是非常不合适的。
为了提高网络IO效率,Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型
① select 模型:
  select模型是WinSock中应用最广泛的模型之一,核心就是select函数,它可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据。这个函数可以有效地防止应用程序在套接字处于阻塞模式中时,send或recv进入阻塞状态;同时也可以防止产生大量的WSAEWOULDBLOCK错误select的优势是能够从单个线程的多个套接字上进行多重连接及I/O。这就避免了伴随阻塞套接字和多重连接的线程剧增。

② WSAAsyncSelect 模型:
   因为它是以消息为基础的,关键就是WSAAsyncSelect函数,将socket消息发送到hWnd窗口上,然后在那里处理相应的FD_READ、FD_WRITE等等消息。优点:WSAAsyncSelect和WSAEventSelect模型提供了读写数据能力的异步通知,但他们不提供异步数据传送,而重叠及完成端口提供异步数据的传送。而且它可以在系统开销不大的情况下同时处理很多连接,而select模型还需要建立fd_set结构。 缺点:必须要使用一个窗口接收消息,如果处理成千上万的套接字就力不从心了。
③ WSAEventSelect 模型:
  这个也是以时间为基础的网络事件通知,但是与WSAAsyncSelect不同的是,它主要是由事件对象句柄完成的,而不是通过窗口。优点:不需要窗口。缺点:每次只能等待64个事件,所以处理多个套接字时有必要组织一个线程池;所以伸缩性就不如后面的完成端口了。
④ 重叠模型:
  这个模型可以使程序能达到更佳的系统性能。基本设计原理就是让应用程序使用重叠的数据结构,一次投递一个或多个I/O请求。针对这些提交的请求,在他们完成之后,应用程序可为他们提供服务。它又分为两种实现方法:在事件中使用,还有就是完成例程。
⑤ 完成端口:
  完成端口提供了最好的伸缩性,往往可以使系统达到最好的性能,是处理成千上万的套接字的首选。从本质上说,完成端口模型要求创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。

在上面五个网络IO模型中,完成端口的效率最高,可同时处理数万个并发连接,要利用“完成端口”实现一个高效,稳定的网络通信模块,需要解决四个基本问题:
1、 粘包。
2、 多线程导致接受和发送包时乱序。
3、 缓存区满。
4、 客户端断开连接时指针异常
解决上面4个问题需要编写一定量的代码,由于时间匆忙,本次编写的聊天室程序使用较为简单Select模型来实现网络通信。

2)Select模型通信的使用
int select(  int nfds,fb_set FAR * readfds, fb_set FAR * writefds, fb_set FAR * exceptfds, const struct timeval FAR * timeout);
其中,第一个参数n f d s会被忽略。之所以仍然要提供这个参数,只是为了保持与早期的B e r k e l e y套接字应用程序的兼容。三个f d _ s e t参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。从根本上说,fb_set数据类型代表着一系列特定套接字的集合。其中, readfds集合包括符合下述任何一个条件的套接字:
■ 有数据可以读入。
■ 连接已经关闭、重设或中止。
■ 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
■ 有数据可以发出。
■ 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
最后,exceptfds集合包括符合下述任何一个条件的套接字:
■ 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
■ 有带外(out-of-band,OOB)数据可供读取。
例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合,再等待select函数完成。s e l e c t完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。在三个参数中(readfds、writefds和exceptfds),任何两个都可以是空值( N U L L);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;否则, select函数便没有任何东西可以等待。最后一个参数timeout对应的是一个指针,它指向一个timeval结构,用于决定select最多等待I / O操作完成多久的时间。如timeout是一个空指针,那么select调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。对timeval结构的定义如下:
Struct timeval
{
Long tv_sec;
Long tv_usec;
}
其中,tv_sec字段以秒为单位指定等待时间; tv_usec字段则以毫秒为单位指定等待时间。若将超时值设置为( 0 , 0),表明select会立即返回,允许应用程序对select操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。select成功完成后,会在fd_set结构中,返回刚好有未完成的I / O操作的所有套接字句柄的总量。若超过timeval设定的时间,便会返回0。不管由于什么原因,假如select调用失败,都会返回socket_error。用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外FD_SET结构.
主要涉及到几个宏操作:
■ FD_CLR(s, *set):从s e t中删除套接字s。
■ FD_ISSET(s, *set):检查s是否s e t集合的一名成员;如答案是肯定的是,则返回T R U E。
■ FD_SET(s, *set):将套接字s加入集合s e t。
■ FD_ZERO ( * set ):将s e t初始化成空集合。

上一页  [1] [2] [3] [4] [5] [6] 下一页

Copyright © 2007-2012 www.chuibin.com 六维论文网 版权所有