电商系统系统笔记之Http,comet,WebSocket

Http协议的特点

1.单工通信,服务器无法推送信息给客户端

2.请求-应答模式

3 无状态

由于以上的http特点,传统模式的 Web 系统以客户端发出请求、服务器端响应的方式工作。这种方式并不能满足很多现实应用的需求,譬如:

  • 监控系统:后台硬件热插拔、LED、温度、电压发生变化;
  • 即时通信系统:其它用户登录、发送信息;
  • 即时报价系统:后台数据库内容发生变化;

这些应用都需要服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求。“服务器推”技术在现实应用中有一些解决方案,本文将这些解决方案分为两类:一类需要在浏览器端安装插件,基于套接口传送信息,或是使用 RMI、CORBA 进行远程调用;而另一类则无须浏览器安装任何插件、基于 HTTP 长连接。

将“服务器推”应用在 Web 程序中,首先考虑的是如何在功能有限的浏览器端接收、处理信息:

  1. 客户端如何接收、处理信息,是否需要使用套接口或是使用远程调用。客户端呈现给用户的是 HTML 页面还是 Java applet 或 Flash 窗口。如果使用套接口和远程调用,怎么和 JavaScript 结合修改 HTML 的显示。
  2. 客户与服务器端通信的信息格式,采取怎样的出错处理机制。
  3. 客户端是否需要支持不同类型的浏览器如 IE、Firefox,是否需要同时支持 Windows 和 Linux 平台。

Comet

一种就是控制客户端的页面不断的进行ajax请求,应该很好实现吧。js定时器就可以实现,每次请求如果服务器端有更新数据则响应到客户端。但是这会造成服务器的严重压力,如果在线用户数量过多的话,每隔个一两秒请求一次,哪个服务器能受得了,这种肯定不太现实,或者是最无奈的实现方法。

于是出现了 comet ,comet技术是服务器推技术的一个总称,但不是具体实现方式。下面我将会讲两种实现方式,是基于HTTP长连接的实现。

第一种叫做长轮询(long-polling)方式,它同样使用的ajax,简单说一下,就是客户端使用ajax发送一个请求,服务器端肯定会开启一个线程,这个线程会时时监测要请求的数据是否有变化,如果有变化,则向客户端输出最新消息,并关闭链接,客户端收到消息处理之后,再次向服务器端请求,如此循环,所以叫长轮询,这种实现方式比起上一种自然要好的多了,不需要客户端不断的ajax请求,减轻服务器端的一定压力,而且可以算得上是实时的。

轮询与长轮询之间的主要区别在于服务器花多长的时间作出响应。长轮询通常将连接保持一段较长的时间 — 通常是数秒钟,但是也可能是一分钟甚至更长。当服务器上发生某个事件时,响应被发送并随即关闭,轮询立即重新开始

长轮询相对于一般轮询的优点在于,数据一旦可用,便立即从服务器发送到客户机。请求可能等待较长的时间,期间没有任何数据返回,但是一旦有了新的数据,它将立即被发送到客户机。因此没有延时。如果您使用过基于 Web 的聊天程序,或者声称 “实时” 的任何程序,那么它很可能就是使用了这种技术

另外一种是流方式,这种和长轮询方式挺像,只有一点区别,就是流方式是在客户端请求服务端并建立链接之后,服务器端始终不会关闭链接(直到超时,断电或者其他特殊情况)每次有数据时,就向客户端进行输出,而不像长轮询每次向客户端输出之后,都要关闭链接

comet实现瓶颈解决——服务器servlet线程阻塞问题

到这里大家可能会想到另外一个问题,那就是客户端每来一个请求,都要在服务器端开一个线程来监测数据是否发生变化,即使数据很长时间内都不会发生改变,这条线程依然在这里阻塞着,资源不能得到释放,线程在这里又没其他事干,如果有过多的用户、过多的线程,自然会造成服务器的资源,内存不足的情况。这是个问题,不过既然有问题,自然有解决方法。

现在有很多 Web 服务器是用 Java 构建的。一个原因是 Java 有一个丰富的本地线程模型。因此实现典型的每个连接一个线程的模型便非常简单。该模型对于 Comet 不大适用,但是,Java 对此同样有解决的办法。为了有效地处理 Comet,需要非阻塞 IO,Java 通过它的 NIO 库提供非阻塞 IO。两种最流行的开源服务器 Apache Tomcat 和 Jetty 都利用 NIO 增加非阻塞 IO,从而支持 Comet。然而,这两种服务器中的实现却各不相同。我们来看看 Tomcat 和 Jetty 对 Comet 的支持

目前有两种解决方法,第一种是利用Tomcat 和 Jetty这两种开源服务器对NIO的支持 代码实现和添加服务器支持可以参考http://www.ibm.com/developerworks/cn/web/wa-cometjava/,第二种则是Java 1.6 出来的Servlet3.0,Servlet3.0可以实现真正的异步处理,就是新开一个线程用于处理复杂业务,而servlet线程本身则继续往下执行直到结束之后,再返回servlet容器,待到另一条线程业务处理完之后,再向客户端输出结果。但是使用servlet3.0,需要tomcat7和以上才支持,servlet3.0的实现百度就有好多,就不在此多赘述了。只谈理论

NIO原理

IO

NIO

Core differences: Core differences:
Stream oriented processing Uses buffers
Blocking in processing Non blocking in processing
Good for: Good for:
High data volume with low simultaneous open file descriptor counts

(eg: less client connections with more data chunks per connection)

Less data volume with high simultaneous open file descriptor counts

(eg: More connections with smaller / infrequent “chunks” of data)

A quick glance at the summary of the Java NIO API reveals to us the core abstractions one should be familiar with when working with Java NIO. These are:

  • Buffers       : A container to hold data for the purposes of reading and or writing.
  • Channels   : An abstraction for dealing with an open connection to some component that is performing some kind of IO operation at a hardware level.
  • Charsets    : Contains charsets, decoders and encoders for translating between bytes and unicode.
  • Selectors   : A means to work with multiple channels via one abstraction.

Streams versus blocks

The most important distinction between the original I/O library (found in java.io.* ) and NIO has to do with how data is packaged and transmitted. As previously mentioned, original I/O deals with data in streams, whereas NIO deals with data in blocks.

stream-oriented I/O system deals with data one byte at a time. An input stream produces one byte of data, and an output stream consumes one byte of data. It is very easy to create filters for streamed data. It is also relatively simply to chain several filters together so that each one does its part in what amounts to a single, sophisticated processing mechanism. On the flip side, stream-oriented I/O is often rather slow.

block-oriented I/O system deals with data in blocks. Each operation produces or consumes a block of data in one step. Processing data by the block can be much faster than processing it by the (streamed) byte. But block-oriented I/O lacks some of the elegance and simplicity of stream-oriented I/O.

Three easy steps

Our first step is to get a channel. We get the channel from the FileInputStream:

1
2
FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();------- IO是单向的,input或者output,在建立连接后,通过创建channel获取一个双向的通道,channel是双向的

The next step is to create a buffer:

1
ByteBuffer buffer = ByteBuffer.allocate( 1024 );

And, finally, we need to read from the channel into the buffer, as shown here:

1
fc.read( buffer );

Writing to a file

Writing to a file in NIO is similar to reading from one. We start by getting a channel from a FileOutputStream:

1
2
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();

Our next step is to create a buffer and put some data in it -- in this case, the data will be taken from an array called messagewhich contains the ASCII bytes for the string "Some bytes." (The buffer.flip() and buffer.put() calls will be explained later in the tutorial.)

1
2
3
4
5
6
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
 
for (int i=0; i<message.length; ++i) {
      buffer.put( message[i] );
}
buffer.flip();

Our final step is to write to the buffer:

1
fc.write( buffer );

如果IO是系统的瓶颈,那么可以利用多线程充分使用cpu资源。

{
 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池

 ServerSocket serverSocket = new ServerSocket();
 serverSocket.bind(8088);
 while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
 Socket socket = serverSocket.accept();
 executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}

class ConnectIOnHandler extends Thread{
    private Socket socket;
    public ConnectIOnHandler(Socket socket){
       this.socket = socket;
    }
    public void run(){
      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件
          String someThing = socket.read()....//读取数据
          if(someThing!=null){
             ......//处理数据
             socket.write()....//写数据
          }

      }
    }
}

上面的代码是经典的单连接单线程模型。其实这也是所有使用多线程的本质:利用多核,当IO阻塞系统的时候,可以充分利用CPU的资源。这个模型如果单机线程数1000,其实ok的。不过这个模型主要的问题是严重依赖线程,线程是非常昂贵的资源,因为一个线程大约占用内容512k-1M,而且线程数过大,创建和销毁成本也高,线程之间切换也有一定的成本。

如果面对十万级,百万级连接,传统的BIO模型无能为力

NIO:利用一个线程管理selector,selector监听多个channel。减少了线程的数据,监听了更多的socket。理论上可以用一个线程处理所有的socket请求

NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)



猜你喜欢

转载自blog.csdn.net/hanruikai/article/details/80105464
今日推荐