Socket,ServerSocket,WebSocket

最近在看webSocket,忽然想到以前学的Socket和ServerSocket,那么他们之间有什么不同呢?还有来回忆下Socket,和学习下webSocket
(天真的我以为写一个ServerSocket,再写一个webSocket就能实现通信了)

一 区别

首先来说下区别吧,
Socket和ServerSocket 指传输层网络接口协议,是基于套接字的服务端和客户端实现。
而WebScoket是应用层协议,是客户端-服务器的异步通信方法,用于双向推送消息。

二 Socket和ServerSocket

ServerSocket简单实现

ServerSocket server = null;
    Socket client = null;
    try {
        server = new ServerSocket(8888);
         client = server.accept();
          BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream()));
          boolean flag = true;
          while (flag) {
              String line = input.readLine();
              System.out.println("客户端:" + line);
              if (line.equals("exit")) {
                  flag = false;
              } 
          }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            if(client!=null){
            client.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Socket简单实现

 Socket client =null;
    try {
        client = new Socket("127.0.0.1", 8888);
         PrintWriter output =
                 new PrintWriter(client.getOutputStream(), true);
         Scanner cin = new Scanner(System.in);
         String words;

         while (cin.hasNext()) {
             words = cin.nextLine();

             output.println(words);

             System.out.println("写出了数据:"  + words);
         }

         cin.close();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally{
        try {
            if(client!=null){
                client.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

NIO回顾

Java NIO 由以下几个核心部分组成:

Channels
Buffers
Selectors
Channel 和 Buffer

基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:
Socket,ServerSocket,WebSocket_第1张图片
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
Socket,ServerSocket,WebSocket_第2张图片

SocketChannel

    try {

        Selector selector = Selector.open(); 
        // 创建一个套接字通道,注意这里必须使用无参形式  
        SocketChannel channel = SocketChannel.open();
        // 设置为非阻塞模式,这个方法必须在实际连接之前调用(所以open的时候不能提供服务器地址,否则会自动连接)  
        channel.configureBlocking(false);  
        channel.connect(new InetSocketAddress("127.0.0.1", 8888));
        channel.register(selector, SelectionKey.OP_CONNECT);  
        while (true) {
             // 测试等待事件发生,分为直接返回的selectNow()和阻塞等待的select(),另外也可加一个参数表示阻塞超时  
            // 停止阻塞的方法有两种: 中断线程和selector.wakeup(),有事件发生时,会自动的wakeup()  
            // 方法返回为select出的事件数(参见后面的注释有说明这个值为什么可能为0).  
            // 另外务必注意一个问题是,当selector被select()阻塞时,其他的线程调用同一个selector的register也会被阻塞到select返回为止  
            // select操作会把发生关注事件的Key加入到selectionKeys中(只管加不管减)  
            if (selector.select() == 0) { //  
                System.out.println("跳过");
                continue;  
            }  
         // 获取发生了关注时间的Key集合,每个SelectionKey对应了注册的一个通道  
            Set keys = selector.selectedKeys();  
            Iterator it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                 // OP_CONNECT 两种情况,链接成功或失败这个方法都会返回true  
                if (key.isConnectable()) {  
                    // 由于非阻塞模式,connect只管发起连接请求,finishConnect()方法会阻塞到链接结束并返回是否成功  
                    // 另外还有一个isConnectionPending()返回的是是否处于正在连接状态(还在三次握手中)  

                    if (channel.finishConnect()) {  
                        System.out.println("完成连接");
                        // 链接成功了可以做一些自己的处理,略  
                        Scanner cin = new Scanner(System.in);
                        SocketChannel serverchannel = (SocketChannel) key.channel();  
                           String words = "";
                            ByteBuffer buffer = null;
                            while (cin.hasNext()) {
                                words = cin.nextLine();
                                 buffer = ByteBuffer.wrap(words.getBytes("UTF8"));
                                 System.out.println(serverchannel.equals(channel));//同一对象
                                 channel.write(buffer); 
                                System.out.println("写出了数据:"  + words);
                            }
                        // 处理完后必须吧OP_CONNECT关注去掉,改为关注OP_READ  
                        key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);    
                    }  
                    if(channel.isConnectionPending()){
                        System.out.println("正在连接状态");
                    }
                } 
                // 后略 和服务器端的类似  
                // ...
                if (key.isReadable()) {  
                    System.out.println("读事件");
                    key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);  
                }  
                if (key.isWritable()) { 
                    System.out.println("写事件");
                key.interestOps(SelectionKey.OP_READ);  
                }  
                if(key.isConnectable()){
                    System.out.println("连接状态");
                     key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);  
                }
                it.remove();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

ServerSocketChannel

    try {

        // 创建一个选择器,可用close()关闭,isOpen()表示是否处于打开状态,他不隶属于当前线程  
        Selector selector = Selector.open();  
        // 创建ServerSocketChannel,并把它绑定到指定端口上  
        ServerSocketChannel server = ServerSocketChannel.open();  
        server.bind(new InetSocketAddress( 8888));  
        // 设置为非阻塞模式, 这个非常重要  
        server.configureBlocking(false);  
        // 在选择器里面注册关注这个服务器套接字通道的accept事件  
        // ServerSocketChannel只有OP_ACCEPT可用,OP_CONNECT,OP_READ,OP_WRITE用于SocketChannel  
        server.register(selector, SelectionKey.OP_ACCEPT);  
        while(true){
             // 测试等待事件发生,分为直接返回的selectNow()和阻塞等待的select(),另外也可加一个参数表示阻塞超时  
            // 停止阻塞的方法有两种: 中断线程和selector.wakeup(),有事件发生时,会自动的wakeup()  
            // 方法返回为select出的事件数(参见后面的注释有说明这个值为什么可能为0).  
            // 另外务必注意一个问题是,当selector被select()阻塞时,其他的线程调用同一个selector的register也会被阻塞到select返回为止  
            // select操作会把发生关注事件的Key加入到selectionKeys中(只管加不管减)  
            if (selector.select() == 0) { //  
                continue;  
            }  
         // 获取发生了关注时间的Key集合,每个SelectionKey对应了注册的一个通道  
            Set keys = selector.selectedKeys();  
            // 多说一句selector.keys()返回所有的SelectionKey(包括没有发生事件的)  
            Iterator it =  keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                 // OP_ACCEPT 这个只有ServerSocketChannel才有可能触发  
                if (key.isAcceptable()) {  
                    System.out.println("套接字通道 ");
                    // 得到与客户端的套接字通道  
                    SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();  
                    // 同样设置为非阻塞模式  
                    channel.configureBlocking(false);  
                    // 同样将于客户端的通道在selector上注册,OP_READ对应可读事件(对方有写入数据),可以通过key获取关联的选择器 
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    channel.register(key.selector(), SelectionKey.OP_READ, buffer);  
                }  
                // OP_READ 有数据可读  
                if (key.isReadable()) {
                    System.out.println("正在读");
                    SocketChannel channel = (SocketChannel) key.channel();  
                    // 得到附件,就是上面SocketChannel进行register的时候的第三个参数,可为随意Object  
                    ByteBuffer buffer = (ByteBuffer) key.attachment();  
                    // 读数据 
                    channel.read(buffer);  
                    buffer.flip();
                   byte[] arr = new byte[buffer.limit()];
                    while (buffer.hasRemaining()) {
                        int i = 0;
                        arr[i]=buffer.get();
                        System.out.println("B:"+arr[i]);
                        i++;
                    }
                    System.out.println("C:"+new String(arr,"utf-8"));
                    buffer.clear();
                 // 改变自身关注事件,可以用位或操作|组合时间  
                    key.interestOps( SelectionKey.OP_WRITE|SelectionKey.OP_READ);  
                }  
                // OP_WRITE 可写状态 这个状态通常总是触发的,所以只在需要写操作时才进行关注  
                if (key.isWritable()) {  
                    System.out.println("正在写");
                    // 写数据掠过,可以自建buffer,也可用附件对象(看情况),注意buffer写入后需要flip  
                    // ......  
                    // 写完就吧写状态关注去掉,否则会一直触发写事件  
                    key.interestOps(SelectionKey.OP_READ);  
                }  
                // 由于select操作只管对selectedKeys进行添加,所以key处理后我们需要从里面把key去掉  
                it.remove();
            }

            //for (SelectionKey key : keys) {  
           // }
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

webSocket

再来看看webSocket,spring集成实现

1.添加jar包

        <dependency>  
   <groupId>org.springframeworkgroupId>  
   <artifactId>spring-websocketartifactId>  
   <version>${spring.version}version>  
dependency> 
<dependency>
    <groupId>javax.websocketgroupId>
    <artifactId>javax.websocket-apiartifactId>
    <version>1.0version>
    <scope>providedscope>
dependency>

其他核心jar自行添加,另spring需4.0以上版本

2.实现WebSocketHandler,用于处理信息

package com.fp.controller.websocket;

import java.util.ArrayList;
import java.util.List;

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler{
    private List sessions = new ArrayList();

    //建立
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("afterConnectionEstablished");
        sessions.add(session);

    }
    //发送消息
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception {
        System.out.println("handleMessage");
        for(WebSocketSession se : sessions){
            se.sendMessage(message);
        }
        System.out.println(message);
        session.sendMessage(message);

    }
    //异常
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        System.out.println("handleTransportError");

    }
    //关闭
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        System.out.println("afterConnectionClosed");

    }

    @Override
    public boolean supportsPartialMessages() {
        System.out.println("supportsPartialMessages");
        return false;
    }

}

3.实现拦截器HttpSessionHandshakeInterceptor

package com.fp.controller.websocket;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpMessage;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;


public class MyHttpSessionHandshakeInterceptor extends HttpSessionHandshakeInterceptor{
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map attributes) throws Exception {

        // 解决The extension [x-webkit-deflate-frame] is not supported问题
        if (request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {
            request.getHeaders().set("Sec-WebSocket-Extensions",
                    "permessage-deflate");
        }

        System.out.println("Before Handshake");
       /* String domain = request.getHeaders().getOrigin();
        System.out.println(domain);
        HttpHeaders headers = response.getHeaders();
        headers.setAccessControlAllowOrigin(domain);
        List list = new ArrayList();
        list.add(HttpMethod.GET);
        list.add(HttpMethod.POST);
        list.add(HttpMethod.DELETE);
        list.add(HttpMethod.POST);
        list.add(HttpMethod.OPTIONS);
        headers.setAccessControlAllowMethods(list);
        headers.setAccessControlAllowCredentials(true);*/
        /*headers
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Control-Expose-Headers");
        response.setHeader("Access-Control-Expose-Headers", "Content-Type");    */
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) {
        System.out.println("After Handshake");
        /*String domain = request.getHeaders().getOrigin();
        HttpHeaders headers = response.getHeaders();
        headers.setAccessControlAllowOrigin(domain);
        List list = new ArrayList();
        list.add(HttpMethod.GET);
        list.add(HttpMethod.POST);
        list.add(HttpMethod.DELETE);
        list.add(HttpMethod.POST);
        list.add(HttpMethod.OPTIONS);
        headers.setAccessControlAllowMethods(list);
        headers.setAccessControlAllowCredentials(true);*/
        ex.printStackTrace();
        super.afterHandshake(request, response, wsHandler, ex);
    }
}

4.spring配置

<bean id="websocket" class="com.fp.controller.websocket.MyWebSocketHandler"/>  

    <websocket:handlers allowed-origins="*">  
     
    <websocket:mapping path="/websocket" handler="websocket"/>  
    
    <websocket:handshake-interceptors>  
    <bean class="com.fp.controller.websocket.MyHttpSessionHandshakeInterceptor"/>  
    websocket:handshake-interceptors>  
websocket:handlers>  

如果前端用sockjs,websocket:handlers需加节点
(这个还没有试过,只是别处的方法)

 <websocket:sockjs/>

5.前端页面


<html>
    <head>
        <meta charset="UTF-8">
        
        <script src="js/jquery-1.7.2.min.js" type="text/javascript" charset="utf-8">script>
        <title>title>
    head>
    <body>
        <input type="" name="" id="sendtext" value="" />
        <input type="button" id="send" value="发送" />
        <div id="return">

        div>
    body>

    <script type="text/javascript">     
        var url = 'ws://localhost:8080/ws/websocket';
        //var url = 'ws://echo.websocket.org/';
        var socket = new WebSocket(url);
            socket.onopen = function( ){
                console.log("open ");
                $("#return").text("open");
            }

            socket.onclose = function(  ){
                console.log("close  "); 
                $("#return").text("close");
            }
            socket.onmessage = function(e){
                console.log("onmessage  ");
                console.log(e.data);
                $("#return").text(e.data);
            }
            $("#send").click(function(){
                var text = $("#sendtext").val();
                socket.send(text);
            });

    script>
html>

如果报403错误,是跨域没有配置allowed-origins="*"属性

https://www.cnblogs.com/zhjh256/p/6052102.html
http://ifeve.com/java-nio-all/

你可能感兴趣的:(学习)