网络编程初步之BIO、NIO

       在这之前先讲一下面试可能会问到的三次握手与四次挥手?

       假设A城市往B城市发送信件,先A发到B,B收到,在发给A,在A发给B,建立起初步通信。三次挥手是为了证明A,B的收信和发信能力是ok的,这样就证明连接是通常的。

        第一次握手:当A发到B时,B收到信后,此时B城市就明白了,A城市的发信能力和B城市的收信能力是ok。

        第二次握手:当B发到A时,A收到信后,此时A城市就明白了,B城市的发信能力和A城市的收信能力是ok,加上之前的发信,同时也就知道了A(自己)的发信能力和B城的收信能力是ok的,这就相当于A知道了双方都是OK的,但B还疑惑,因为它第一次虽然知道了A的发信能力个自身的收信能力是OK的,但并不不知道城B(自身)的的发信能力和A城市的收信能力如何,所以需要第三次握手。

        第三次握手:A发给B  当B收到后,就知道了B(自身的发信能力)和A城市的收信能力同样是ok的,既然双方都知根知底,那就掏心掏肺喜结连理吧。即完成首次通信的建立。

       大概流程可见如下图:

三次握手TCP协议 连接成功后,可以得出结论
第一次握手A-->B A得出结论:啥也不知道,不确定自己是否发送ok 
B得出结论:A发  B收 ok 
第二次握手B-->A A可以得出结论:A收  B发,加上之前的第一次发送可以推出A发  B收也ok,所以明白AB发送接受都ok
B得出结论:基于第一次知道A发  B收 ok,但是并不知道自身的发送是否成功和A的接受能力
第三次握手A-->B A发给B,B收到消息后就可以证明:B的发信能力和A的自收信能力ok

其实这个还得牵涉到我之前做的一个模块,当时是在负责写一个日志下载的功能,其实现就是利用B/S架构去完成的,也就是利用socket编程在结合多线程去完成这样的一个功能。设备作为服务端(日志源),浏览器作为客户端(发请求),形成一个通信,由于InputStream类read()方法是阻塞的,所以就必须利用到多线程或者线程池,每发一个请求就利用一个新的线程这样就互不干扰了,下面是我一个小demo模拟(类似于聊天)了上述业务。

客户端代码:

package NIO.clientpkg;

import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Scanner;


/**
 *目的:客户端向服务端发送请求服务端接受请求,并回应相同的信息,一旦客户端写入bye,则结束本次操作
 */
public class Client {


    public static void main(String[] args) throws Exception{
        SocketAddress socketAddress = new InetSocketAddress ("localhost",9001);
        Socket socket = new Socket ();
        socket.connect (socketAddress,1000*5);//5second
        //发送输入的数据  以打印流的方式
        PrintStream out = new PrintStream (socket.getOutputStream ());
        //接受服务端传来的数据,将其保存在打印流中
        Scanner in = new Scanner (socket.getInputStream ());
        System.out.println ("请输入你要发送给服务端的信息");
        Scanner scanner = new Scanner (System.in);
        while (true){
            //发送消息
            if (scanner.hasNext ()){ //hasNext和next都是半阻塞的方法,会一直处于等待,所以必须用一个变量来接收
                //输入的消息
                String str = scanner.next ();
                out.println (str);
                if (str.equals ("bye")){
                    System.out.println ("服务端发过来的消息:" + in.next ());
                    break;//不能立即退出,会导致客户端退出服务端还处于连接丢包
                }
                //接收消息
                if (in.hasNext ()){
                    System.out.println ("服务端发过来的消息:" + in.next ());
                }
            }
        }
        socket.close ();
        scanner.close ();
        in.close ();
        out.close ();
    }

}

服务端代码:

package NIO.serverpkg;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 /**
 *目的:客户端向服务端发送请求服务端接受请求,并回应相同的信息,一旦客户端写入byebye,则结束本次操作
 *TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。
 *TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,
 *就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
 */
public class Server2 {

    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor (10,50,5, TimeUnit.SECONDS,new LinkedBlockingQueue<> (100));

    public static void main(String[] args) throws Exception{

        //通常服务端在启动的时候回绑定一个众所周知的地址(ip+端口)用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动
        //分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
        ServerSocket serverSocket = new ServerSocket (9001);//初始化socket,对端口进行绑定,并且对端口进行监听
        //这里要用一个while来接受不断发过来的请求,,但是因为accept会阻塞,故这里必须用线程
        while (!serverSocket.isClosed ()){
            Socket socket = serverSocket.accept ();//侦听并接受到此套接字的连接。此方法在进行连接之前一直阻塞。这应该是一个一直在进行的新线程,因为她面对的可能是诸多追求者
            if (socket.isConnected ()){
                System.out.println ("连接成功男朋友为:" + socket.toString ());
            }
            threadPoolExecutor.execute (() -> {
                try {
                    PrintStream out = new PrintStream (socket.getOutputStream ());//发送输入的数据  以打印流的方式
                    Scanner in = new Scanner (socket.getInputStream());//扫描流负责接受客户端发来的请求,一直不断变化的,因为socket是不断变化的
                    while (true){
                        if (in.hasNext ()){//阻塞的方法 inputStream类的read()方法
                            String str = in.next ();
                            System.out.println ("接受到客户端发来的信息:" + str);
                            out.println (str);//接收到消息  并回复同等内容
                            if (str.equals ("bye")){
                                break;
                            }
                        }
                    }
                    out.close ();
                    in.close ();
                    socket.close ();
                } catch (IOException e) {
                    e.printStackTrace ();
                }

            });
        }
        System.out.println ("结束服务端");
        serverSocket.close ();

    }

}

启动一个服务端和两个客户端,客户端分别向服务端发送:我是client2,我是client2_1:

题外话:

我们都知道socket是遵守tcp协议的,那如果浏览器访问我的服务端可行吗?我们测试一下:显然是不可行的因为浏览器请求是http请求,明显可以看出客户端请求是发过来了的,但是响应给浏览器确实无效的,因为协议不一致,所以只需要返回固定格式的数据(基于http协议)给客户端即可。

改写代码(返回http格式):

package NIO.serverpkg;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerHttp {


    public static void main(String[] args) throws Exception{

        ServerSocket serverSocket = new ServerSocket (9001);
        while (!serverSocket.isClosed ()){
            Socket socket = serverSocket.accept ();
            if (socket.isConnected ()){
                System.out.println ("连接成功男朋友为:" + socket.toString ());
            }
            InputStream in = socket.getInputStream ();
            BufferedReader reader = new BufferedReader(new InputStreamReader (in, "utf-8"));
            String msg = "";
            while ( (msg = reader.readLine () ) != ""){
                System.out.println (msg);
                if (msg.length () == 0){
                    break;
                }
            }
            System.out.println("收到数据,来自:"+ socket.toString());
            OutputStream out = socket.getOutputStream ();
            out.write("HTTP/1.1 200 OK\r\n".getBytes());
            out.write("Content-Length: 11\r\n\r\n".getBytes());
            out.write("Hello World".getBytes());
            out.flush();
        }
        System.out.println ("结束服务端");
        serverSocket.close ();

    }

}

http格式如下:

上面只是演示下而已,言归正传,多线程或者固定线程池这样设计是存在问题的,当并发高的话,线程会出现不够用的情况,而且当线程处理的业务逻辑属于耗时操作io或者长期占用不关闭连接时,必定会出现线程不够用的情况,则又会衍生性能问题,那么该如何解决呢?

正题  NIO的引入

猜你喜欢

转载自blog.csdn.net/qq_40826106/article/details/82665716