Socket实现两个服务之间通信以及SocketChannel(1.4升级版Socket)

Socket可以实现两个服务之间通信,数据传输.

SocketChannel的作用和Socekt一样,在Java1.4版本引入. 相当于是升级版Socekt,性能比Socket更好.

1.Socket与SocketChannel对比

Socket:服务端通过accept()为每个客户端Socket请求分配一个线程,而每个线程都有可能长时间阻塞,过多的线程会影响服务器的性能.ServerSocket是通过accept()来接受请求,如果不存在,会处于阻塞状态,保持长连接

SocketChannel:方便使用非阻塞通信,服务器端只需要一个线程就能处理客户端所有的Socket请求. ServerSocketChannel是通过注册一个selector选择处理器. selector.select()来查看是否有数据请求接入.

补充下阻塞非阻塞/同步异步的概念:

Socket实现两个服务之间通信以及SocketChannel(1.4升级版Socket)_第1张图片

下面为图形化表示,来体现多个用户端请求服务端时,Socekt和SocketChannel的处理方式

目前我只是理解了,多个请求时,又于Socket的accept阻塞和SocketChannel 的accept/读写方法的非阻塞带来的处理效率的差异,
并无法理解ServerSocket处理多个客户端Socket请求时针对每个请求会新创建1个线程来处理的线索和原因.

Socket实现两个服务之间通信以及SocketChannel(1.4升级版Socket)_第2张图片

2.Socket和SocketChannel案例

1)Socket示例

#服务器端

package com.glperry.demo.socket;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * Socket服务端
 * @Date: 2019-08-30
 */
public class SocketServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null; //开放的套接字
        Socket acceptSocket = null; //连接客户端的套接字
        Scanner scn = null; //键盘输入流
        Scanner fromClient = null; //获取从客户端读取的字节流
        PrintWriter toClient = null;

        //开放服务端套接字
        serverSocket = new ServerSocket(9999);
        System.out.println("服务器Socket已启动,等待客户端连接...");
        //连接客户端的套接字
        acceptSocket = serverSocket.accept();
        //监控是否有客户接入
        System.out.println("有客户端接入...");
        //实例化 从客户端读取的字节流
        fromClient = new Scanner(acceptSocket.getInputStream());
        //实例化 发送到客户端的字节流
        toClient = new PrintWriter(acceptSocket.getOutputStream());
        toClient.println("菠萝皓你好!欢迎接入服务器");
        toClient.flush();
        //实例化 键盘输入流
        scn = new Scanner(System.in);

        //阻塞等待客户端发送的数据
        while (fromClient.hasNext()){
            String fromClientData = fromClient.nextLine();
            System.out.println("小徐:"+fromClientData);
            //获取键盘输入流的数据
            String toClientData = scn.nextLine();
            toClient.println(toClientData);
            toClient.flush();
        }
    }
}

#客户端

package com.glperry.demo.socket;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * Socket客户端
 * @Date: 2019-08-30
 */
public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = null; //与服务器连接的 套接字
        Scanner scn = null; //键盘输入流
        Scanner fromServer = null; //获取到的服务器发送的键盘字节流
        PrintWriter toServer = null; //向服务器发送的字节流

        //连接服务器的套接字
        socket = new Socket("192.168.10.37",9999);

        //注意,服务器端和客户端不能都先获取输入流,那样会导致socket阻塞

        //实例化从服务器读的字节流
        fromServer = new Scanner(socket.getInputStream());
        //实例化向服务器读的字节流
        toServer = new PrintWriter(socket.getOutputStream());
        //实例化键盘输入流
        scn = new Scanner(System.in);

        while(fromServer.hasNext()){
            //阻塞等待服务器发送消息
            String fromServerData = fromServer.nextLine();
            System.out.println("服务器端:"+fromServerData);
            //获取输入的数据
            String toServerData = scn.nextLine();
            //发送输入的数据
            toServer.println(toServerData);
            toServer.flush();
        }
    }
}

2)SocketChannel示例

#服务器端

package com.glperry.demo.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 服务端,接收数据
 * @Date: 2019-08-29
 */
public class NioServer{

    public static void main(String[] args) throws IOException {
        //打开一个服务连接通道
        ServerSocketChannel channel = ServerSocketChannel.open();
        //创建服务连接地址
        InetSocketAddress socketAddress = new InetSocketAddress(8888);
        //通道绑定服务地址
        channel.bind(socketAddress);
        //设置服务端通道非阻塞
        channel.configureBlocking(false);
        //打开选择处理器
        Selector selector = Selector.open();
        //模拟多个用户,使用selector选择器进行响应
        //首先第一步进来的所有连接都是为了接入
        channel.register(selector,SelectionKey.OP_ACCEPT);
        //selector 处理接入
        while (selector.select()>0){ //阻塞体现在Selector上
            //可能客户到请求一次进来多个,进行判断
            Iterator iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                //获取其中的每个请求
                SelectionKey next = iterator.next();
                //判断请求是属于 连接过的还是未连接过的
                if(next.isAcceptable()){ //没连接过,直接跟踪,设置为读取状态
                    SocketChannel accept = channel.accept();//通道允许接入请求
                    accept.configureBlocking(false);//设置非阻塞
                    accept.register(selector,SelectionKey.OP_READ); //设置为读取数据
                }
                if(next.isReadable()){ //连接过,直接读取
                    SelectableChannel channel2 = next.channel();
                    channel2.configureBlocking(false);
                    //读取通道信息
                    readMsg(channel2);
                }
                iterator.remove();
            }
        }
    }

    private static void readMsg(SelectableChannel channel2) throws IOException {
        SocketChannel channel = (SocketChannel) channel2;
        SocketAddress localAddress = channel.getLocalAddress();
        //设置缓存区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //读取数据
        int len;
        byte[] b = new byte[1024];
        while ((len = channel.read(buffer))>0){
            buffer.flip();//刷新缓存区
            buffer.get(b, 0, len);
            System.out.println("服务端接受到数据为:"+new String(b,0,len));
        }
    }
}

#客户端

package com.glperry.demo.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

/**
 * 客户端 发送数据
 * @Date: 2019-08-29
 */
public class NioClient {

    public static void main(String[] args) throws IOException {
        //声明连接地址对象 nio框架使用tcp协议绑定ip和端口
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8888);
        //打开一个和服务端连接的通道
        SocketChannel channel = SocketChannel.open();

        channel.connect(socketAddress);
        //设置通道为非阻塞
        channel.configureBlocking(false);
        //设置缓存区大小
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //控制台的输入数据输出
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            //清除缓存区的数据
            buffer.clear();
            //获取控制台的数据
            String data = scanner.nextLine();
            buffer.put(data.getBytes());
            //刷新 缓存区的数据,与IO流的flush类似
            buffer.flip();
            channel.write(buffer);
        }
    }
}

 

你可能感兴趣的:(Java基础)