Socket 阻塞/非阻塞 同步IO

参考资料:https://www.cnblogs.com/goodboy-heyang/p/6372058.html

 Socket通信实现步骤:1、创建ServerSocket和Socket(client)。2、打开连接到Socket的输入/输出流。3、按照协议对Socket进行读/写操作。4、关闭输入输出流、关闭Socket。

一定有两个不同的Socket(要么ip不同,要么port不同)才能通信!!!

1 创建服务端

    1、创建ServerSocket对象,绑定监听端口。     new ServerSocket(8888)
    2、通过accept()方法监听客户端请求。   Socket socket =   serverSocket.accept()    
    3、链接建立后,通过输入流读取客户端发送的请求信息。     socket.getInputStream();
    4、通过输出流向客户端发送响应信息。   socket.getOutputStream()
    5、关闭相关资源。

public class Server {
    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(8888);

        while (true)
        {
               // 阻塞  直到有socket连接serverSocket
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();// 得到一个输入流,接收客户端传递的信息
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);// 提高效率,将自己字节流转为字符流
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);// 加入缓冲区
            CharBuffer charBuffer = CharBuffer.allocate(1024);
            while (bufferedReader.read(charBuffer) != -1) {
                charBuffer.flip();
                char[] inc = new char[charBuffer.limit()];
                System.out.println("已接收到客户端连接");

                charBuffer.get(inc);

                System.out.println(Thread.currentThread().getName() + "服务端接收到客户端信息:" + new String(inc)
                        + ",当前客户端ip为:" + socket.getInetAddress().getHostAddress());
                charBuffer.clear();
            }
        }
    }

}

2 创建客户端

    1、创建Socket对象,指明需要连接的服务器的地址和端口号。    new Socket(“localhost”,8888)     相当于connect  localhost:8888    但自身的socket对应端口肯定不是8888,由socket.getLocalPort()获取,随机产生(如62572)
    2、连接建立后,通过输出流向服务器端发送请求信息。   socket.getOutputStream()
    3、通过输入流获取服务器响应的信息。      socket.getInputStream()
    4、关闭相关资源。

public class Client {
    public static void main(String[] args) {
        // 1、创建客户端Socket,指定服务器地址和端口
        try {
            Socket socket = new Socket("localhost", 8888);
            // 2、获取输出流,向服务器端发送信息
            OutputStream os = socket.getOutputStream();// 获取字节输出流
            // 将输出流包装为打印流
            PrintWriter pw = new PrintWriter(os);
            pw.write("用户名:admin 密码:123");
            pw.flush();
            while(true){
                InputStream is =  socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                CharBuffer cb = CharBuffer.allocate(1024);

                int rs = br.read(cb);

                if(rs>0){
                    cb.flip();
                    char[] c = new char[cb.limit()];
                    cb.get(c);
                    System.out.println("接收来自服务端的消息:"+new String(c));
                }

            }


           

        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

3 深入分析

参考资料:https://blog.csdn.net/yinwenjie/article/details/48472237

阻塞模型

                    è¿éåå¾çæè¿°

一定要认识到,上述socket通信是标准的阻塞模型(BIO),具体反映在serverSocket.accept(),serverSocket接收到新连接前应用程序一直阻塞,当操作系统接到客户端A的连接请求后,才会建立socket连接,服务端接收传递数据,且在处理数据过程中,客户端B的请求连接是一直阻塞的。               

进程中只有一个线程,只能同时处理一个socket!!!

让我们来看下socket.accept()源码,位于DualStackPlainSocketImpl中。默认情况下,是没有设置timeout的,所以timeout=0,直接进入connect0方法。这样就会无限期阻塞等待,除非有连接进入。

 void socketConnect(InetAddress address, int port, int timeout)
        throws IOException {
        int nativefd = checkAndReturnNativeFD();

        if (address == null)
            throw new NullPointerException("inet address argument is null.");

        int connectResult;
        // 默认情况下timeout并未设置
        if (timeout <= 0) {
            connectResult = connect0(nativefd, address, port);
        } else {
            configureBlocking(nativefd, false);
            try {
                connectResult = connect0(nativefd, address, port);
                if (connectResult == WOULDBLOCK) {
                    waitForConnect(nativefd, timeout);
                }
            } finally {
                configureBlocking(nativefd, true);
            }
        }
        /*
         * We need to set the local port field. If bind was called
         * previous to the connect (by the client) then localport field
         * will already be set.
         */
        if (localport == 0)
            localport = localPort0(nativefd);
    }

非阻塞模式

为了避免serverSocket.accept()没有设置timeout而造成的无限期阻塞,可以认为设置timeout值,这样如果在时间内仍没有新连接产生,则抛出SocketTimeoutException。这样应用程序就可以在阻塞间隔做其他事,过段时间再来访问。

通过serverSocket.setSoTimeout()来设置accept阻塞时间,

通过socket.setSoTimeout()来设置read超时时间。

其实这种隔段时间再来访问操作系统的模式也可以称之为轮询

 

                        è¿éåå¾çæè¿°

调用serverSocket.setSoTimeout()方法设置accept失效时间

 while (true)
        {
            serverSocket.setSoTimeout(1000);
            Socket socket = null;
            try{
                 socket = serverSocket.accept();

            }catch(Exception e){
                //===========================================================
                //      执行到这里,说明本次accept没有接收到任何TCP连接
                //      主线程在这里就可以做一些事情,记为X
                //===========================================================
                synchronized (Server.xWait) {
                    System.out.println("socket 还未接收到消息");
                  
                }
                continue;
            }
        ...
       }

调用socket.setSoTimeout()设置read失效时间

 //下面我们收取信息(设置成非阻塞方式,这样read信息的时候,又可以做一些其他事情)
                socket.setSoTimeout(10);
                BIORead:while(true) {
                    try {
                        while((realLen = in.read(contextBytes, 0, maxLen)) != -1) {
                            message.append(new String(contextBytes , 0 , realLen));
                            /*
                             * 我们假设读取到“over”关键字,
                             * 表示客户端的所有信息在经过若干次传送后,完成
                             * */
                            if(message.indexOf("over") != -1) {
                                break BIORead;
                            }
                        }
                    } catch(SocketTimeoutException e2) {
                        //===========================================================
                        //      执行到这里,说明本次read没有接收到任何数据流
                        //      主线程在这里又可以做一些事情,记为Y
                        //===========================================================
                        SocketServer3.LOGGER.info("这次没有从底层接收到任务数据报文,等待10毫秒,模拟事件Y的处理时间");
                        continue;
                    }
                }

添加多线程

当有多个客户端同时连接服务端时,上述的非阻塞方法也无法解决高并发问题,因为socket在处理通信数据过程中,客户端是阻塞的。为了处理多个客户端请求,服务端可采用多线程方法,在操作系统监听到新的连接后,新建一个线程去处理这个连接的通信,这样就不会造成客户端A连接服务端时,客户端B却一直阻塞没法与服务端通信。

现在一个进程有多个线程,对应多个socket

while(true) {
                Socket socket = null;
                try {
                    socket = serverSocket.accept();
                } catch(SocketTimeoutException e1) {
                    //===========================================================
                    //      执行到这里,说明本次accept没有接收到任何TCP连接
                    //      主线程在这里就可以做一些事情,记为X
                    //===========================================================
                    synchronized (SocketServer4.xWait) {
                        SocketServer4.LOGGER.info("这次没有从底层接收到任何TCP连接,等待10毫秒,模拟事件X的处理时间");
                        SocketServer4.xWait.wait(10);
                    }
                    continue;
                }
                //当然业务处理过程可以交给一个线程(这里可以使用线程池),并且线程的创建是很耗资源的。
                //最终改变不了.accept()只能一个一个接受socket连接的情况
                SocketServerThread socketServerThread = new SocketServerThread(socket);
                new Thread(socketServerThread).start();
            }

但要知道最大线程数是和cpu操作系统息息相关的,且每创建一个线程都得需要serverSocket.accept()获取socket,accept()只能一个个接收socket连接,相当于你又一个很大的水管,连接的水龙头却是在慢慢滴水。更重要的是需要明白,accept()阻塞的原因是调用的操作系统模型是同步IO,应用程序可以是阻塞/非阻塞

总结

这次介绍的socket io 采用的模型是  阻塞同步  和  非阻塞同步

阻塞/非阻塞是对于应用程序而言,应用程序在等待操作系统接收新连接的时候,可以无限期阻塞,也可以设定timeout时间,超时就做其他事,过会再来查看是否有新连接。

同步/异步是对于操作系统而言,同步是指应用程序和操作系统必须一问一答,类似于http请求,操作系统是不会主动告知应用程序是否有新的连接进入;异步是操作系统接收到应用程序请求后,不会立刻回答结果,等有连接产生了才会通知应用程序,类似于ajax请求和订阅通知。

至于java nio框架,采用的selector  channel  buffer三大要素,通过selector来监控所有事件,并通知ServerSocketChannel和SocketChannel,称之为多路复用IO

java aio,则是真正意义上的异步IO

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值