TCP/IP学习笔记一:BIO的网络编程-简单实例

TCP/IP学习笔记一:BIO的网络编程-简单实例

标签(空格分隔): BIO 网络编程


一、简单的BIO网络编程

实例:实现简单的web服务器(简单模仿Tomcat的请求和响应)

服务器端的实现:

    package com.bio.socket;

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

    /**
     * 服务器端
     * @author MOTUI
     *
     */
    public class BIOServerSocket {

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

            //1.创建serverSocket
            ServerSocket serverSocket = new ServerSocket();

            //2.设置端口号
            serverSocket.bind(new InetSocketAddress(8989));

            System.out.println("监听端口为8989的服务器");

            //3.获取报文
            Socket socket = serverSocket.accept();

            //a.获得客户端的意图   request
            InputStream is = socket.getInputStream();
            //字符流和字节流的转换
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            //存储读取的数据
            StringBuilder sb = new StringBuilder();
            String line = null;
            //读取数据
            while((line = br.readLine()) != null){
                //将数据存储在StringBuilder中
                sb.append(line);
            }
            System.out.println("服务器收到的数据:"+sb.toString());
            //b.返回客户端数据
            OutputStream os = socket.getOutputStream();
            //获得输出流
            PrintWriter pw = new PrintWriter(os);
            pw.write("接收的数据:"+sb.toString()+"  服务器已经接收到数据");
            //将数据发送
            pw.flush();
            //关闭流
            br.close();
            pw.close();
            socket.close();
        }
    }

客户端的实现:

    package com.bio.socket;

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.InetSocketAddress;
    import java.net.Socket;

    /**
     * 客户端
     * @author MOTUI
     *
     */
    public class BIOClientSocket {

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

            //1.创建Socket
            Socket socket = new Socket();

            //2.连接服务器
            socket.connect(new InetSocketAddress("192.168.0.117",8989));

            //a.发送数据到服务器
            OutputStream os = socket.getOutputStream();
            //获得输出流
            PrintWriter pw = new PrintWriter(os);
            pw.write("这是客户端数据");
            //将数据发送
            pw.flush();
            //告知服务器已经到了流的结尾
            socket.shutdownOutput();

            //b.获得服务器的回应
            InputStream is = socket.getInputStream();
            //字符流和字节流的转换
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            //存储读取的数据
            StringBuilder sb = new StringBuilder();
            String line = null;
            //读取数据
            while((line = br.readLine()) != null){
                //将数据存储在StringBuilder中
                sb.append(line);
            }
            //打印服务器回应数据
            System.out.println(sb.toString());

            //关闭流
            br.close();
            pw.close();
            socket.close();
        }
    }

运行服务器端:

    监听端口为8989的服务器

运行客户端:

    接收的数据:这是客户端数据  服务器已经接收到数据

运行客户端之后服务器端结果为:

    监听端口为8989的服务器
    服务器收到的数据:这是客户端数据
    到此服务器端和客户端运行结束。这样的结果并和我们想要的结果不同,我们需要的是服务器端一直监听客户端发送的请求并作出响应。我们【服务器端】做如下的修改,客户端代码不做任何修改。

服务器端修改为:

在执行的代码中加上while(true)【第180行】,保证执行代码一直执行。
package com.bio.socket;

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

/**
 * 服务器端
 * @author MOTUI
 *
 */
public class BIOServerSocket {

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

        //1.创建serverSocket
        ServerSocket serverSocket = new ServerSocket();

        //2.设置端口号
        serverSocket.bind(new InetSocketAddress(8989));

        while(true){
            System.out.println("监听端口为8989的服务器");

            //3.获取报文
            Socket socket = serverSocket.accept();

            //a.获得客户端的意图   request
            InputStream is = socket.getInputStream();
            //字符流和字节流的转换
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            //存储读取的数据
            StringBuilder sb = new StringBuilder();
            String line = null;
            //读取数据
            while((line = br.readLine()) != null){
                //将数据存储在StringBuilder中
                sb.append(line);
            }
            System.out.println("服务器收到的数据:"+sb.toString());
            //b.返回客户端数据
            OutputStream os = socket.getOutputStream();
            //获得输出流
            PrintWriter pw = new PrintWriter(os);
            pw.write("接收的数据:"+sb.toString()+"  服务器已经接收到数据");
            //将数据发送
            pw.flush();
            //关闭流
            br.close();
            pw.close();
            socket.close();
        }
    }
}

运行服务器端:

    监听端口为8989的服务器

运行客户端:

    接收的数据:这是客户端数据  服务器已经接收到数据

运行客户端之后服务器端结果为:

   监听端口为8989的服务器
   服务器收到的数据:这是客户端数据
   监听端口为8989的服务器
    到此服务器端和客户端已经执行结束。也达到了一直监听客户端的请求的目的,但是还是有问题:我们处理客户端的请求的时候会存在一个请求没有处理完成,另一个请求不能处理。(在处理的过程中最耗时的操作就是I/O操作)。为了解决这个问题,我们进行如下修改。

服务器端代码修改为:

package com.bio.socket;

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

/**
 * 服务器端
 * @author MOTUI
 *
 */
public class BIOServerSocket {

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

        //1.创建serverSocket
        ServerSocket serverSocket = new ServerSocket();

        //2.设置端口号
        serverSocket.bind(new InetSocketAddress(8989));

        while(true){
            System.out.println("监听端口为8989的服务器");

            //3.获取报文
            final Socket socket = serverSocket.accept();

            //启动线程
            new Thread(){
                public void run() {
                    try {
                        //a.获得客户端的意图   request
                        InputStream is = socket.getInputStream();
                        //字符流和字节流的转换
                        InputStreamReader isr = new InputStreamReader(is);
                        BufferedReader br = new BufferedReader(isr);

                        //存储读取的数据
                        StringBuilder sb = new StringBuilder();
                        String line = null;
                        //读取数据
                        while((line = br.readLine()) != null){
                            //将数据存储在StringBuilder中
                            sb.append(line);
                        }
                        System.out.println("服务器收到的数据:"+sb.toString());
                        //b.返回客户端数据
                        OutputStream os = socket.getOutputStream();
                        //获得输出流
                        PrintWriter pw = new PrintWriter(os);
                        pw.write("接收的数据:"+sb.toString()+"  服务器已经接收到数据");
                        //将数据发送
                        pw.flush();
                        //关闭流
                        br.close();
                        pw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally{
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                };
            }.start();
        }
    }
}

客户端代码修改为:

package com.bio.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * 客户端
 * @author MOTUI
 *
 */
public class BIOClientSocket {

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

        //使用线程模拟用户 并发访问
        for (int i = 0; i < 20; i++) {
            new Thread(){
                public void run() {
                    try {
                        //1.创建Socket
                        Socket socket = new Socket();

                        //2.连接服务器
                        socket.connect(new InetSocketAddress("192.168.0.117",8989));

                        //a.发送数据到服务器
                        OutputStream os = socket.getOutputStream();
                        //获得输出流
                        PrintWriter pw = new PrintWriter(os);
                        pw.write("这是客户端数据");
                        //将数据发送
                        pw.flush();
                        //告知服务器已经到了流的结尾
                        socket.shutdownOutput();

                        //b.获得服务器的回应
                        InputStream is = socket.getInputStream();
                        //字符流和字节流的转换
                        InputStreamReader isr = new InputStreamReader(is);
                        BufferedReader br = new BufferedReader(isr);

                        //存储读取的数据
                        StringBuilder sb = new StringBuilder();
                        String line = null;
                        //读取数据
                        while((line = br.readLine()) != null){
                            //将数据存储在StringBuilder中
                            sb.append(line);
                        }
                        //打印服务器回应数据
                        System.out.println(sb.toString());

                        //关闭流
                        br.close();
                        pw.close();
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                };
            }.start();
        }   
    }
}

运行服务器端:

    监听端口为8989的服务器

运行客户端:

    接收的数据:这是客户端数据  服务器已经接收到数据
    ....(省略19个:监听端口为8989的服务器)

运行客户端之后服务器端结果为:

    监听端口为8989的服务器
    ....(省略17个:监听端口为8989的服务器)
    监听端口为8989的服务器
    当前线程ID:9
    监听端口为8989的服务器
    当前线程ID:10
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:11
    当前线程ID:12
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:13
    服务器收到的数据:这是客户端数据
    当前线程ID:14
    服务器收到的数据:这是客户端数据
    当前线程ID:15
    当前线程ID:17
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:18
    当前线程ID:21
    当前线程ID:20
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:23
    当前线程ID:22
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:24
    当前线程ID:25
    当前线程ID:26
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:28
    当前线程ID:27
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:16
    当前线程ID:19
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    到此运行结束,运行结果在不同的机器上有不同的顺序这与机器的运行效率有关。分析到这里我们感觉这种方式已经很合理了。在了解高并发之前,这的确看着不错。但是我们可以思考一个问题:如果我们的并发打到万级或者百万级或者更高的时候,我们的程序有没有问题呢?我们每有一个请求就创建一个线程,而且我们创建线程之后并没有关注这个线程是否正确执行?是否执行结束?我们都没有关注,这时候就出现问题了,当高并发情况下有很多的线程处于阻塞状态,而我们的系统资源已经占用,系统对I/O的处理就会慢,对I/O的处理变慢就会导致我们的线程阻塞,恶性循环直到系统假死(宕机)。这样的处理是不是不合理呢。我们现在虽然无法做到线程不阻塞(不阻塞就是NIO的方式),但是我们可以进行其他的做法,我们可以控制线程的创建个数,这样就会相对打到一个平衡。
    对于我们如何控制线程创建的个数呢?我们可能会想到用计数器,javaAPI中提供了线程池的方式,使用更简单。代码:

服务器端修改为:

package com.bio.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务器端
 * @author MOTUI
 *
 */
public class BIOServerSocket {

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

        //1.创建serverSocket
        ServerSocket serverSocket = new ServerSocket();

        //2.设置端口号
        serverSocket.bind(new InetSocketAddress(8989));

        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        while(true){
            System.out.println("监听端口为8989的服务器");

            //3.获取报文
            final Socket socket = serverSocket.accept();

            executorService.submit(new Runnable() {

                @Override
                public void run() {
                    try {
                        //打印当前线程ID
                        System.out.println("当前线程ID:"+Thread.currentThread().getId());

                        //a.获得客户端的意图   request
                        InputStream is = socket.getInputStream();
                        //字符流和字节流的转换
                        InputStreamReader isr = new InputStreamReader(is);
                        BufferedReader br = new BufferedReader(isr);

                        //存储读取的数据
                        StringBuilder sb = new StringBuilder();
                        String line = null;
                        //读取数据
                        while((line = br.readLine()) != null){
                            //将数据存储在StringBuilder中
                            sb.append(line);
                        }
                        System.out.println("服务器收到的数据:"+sb.toString());
                        //b.返回客户端数据
                        OutputStream os = socket.getOutputStream();
                        //获得输出流
                        PrintWriter pw = new PrintWriter(os);
                        pw.write("接收的数据:"+sb.toString()+"  服务器已经接收到数据");
                        //将数据发送
                        pw.flush();
                        //关闭流
                        br.close();
                        pw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally{
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }       
                }
            });
        }
    }
}

运行服务器端:

    监听端口为8989的服务器

运行客户端:

    接收的数据:这是客户端数据  服务器已经接收到数据
    ....(省略19个:监听端口为8989的服务器)

运行客户端之后服务器端结果为:

    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    当前线程ID:9
    监听端口为8989的服务器
    当前线程ID:10
    监听端口为8989的服务器
    监听端口为8989的服务器
    当前线程ID:12
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:13
    服务器收到的数据:这是客户端数据
    监听端口为8989的服务器
    当前线程ID:11
    监听端口为8989的服务器
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:14
    监听端口为8989的服务器
    监听端口为8989的服务器
    服务器收到的数据:这是客户端数据
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    监听端口为8989的服务器
    当前线程ID:14
    服务器收到的数据:这是客户端数据
    当前线程ID:9
    当前线程ID:13
    当前线程ID:14
    服务器收到的数据:这是客户端数据
    当前线程ID:10
    当前线程ID:12
    服务器收到的数据:这是客户端数据
    当前线程ID:9
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:12
    当前线程ID:10
    服务器收到的数据:这是客户端数据
    当前线程ID:9
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:15
    当前线程ID:17
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    当前线程ID:18
    当前线程ID:16
    服务器收到的数据:这是客户端数据
    服务器收到的数据:这是客户端数据
    到此我们的程序就算相对完善的。在服务器端我们设置了线程的最大数为10,所以我们的程序最大并发量为10,这样我们的程序就不需要一直新建线程浪费资源,只需要等待别人用完还回线程池,然后拿到继续使用即可。但是我们的程序依然是线程阻塞的。

总结:

    对于BIO的网络编程,我们实现的是传输层的TCP/IP协议,这样的传输效率更高。对于webService的SOP协议是Http+XML,过度的封装使得效率上不适合对速度要求较高的应用。
    多线程(线程池)实现BIO并非访问,将IO的处理交给线程池里的线程,限定系统线程无止境的创建节省系统资源实现线程复用.
    缺点: 线程会因为IO没有就绪挂起.

问题分析:

    上述程序属于伪异步I/O,存在什么样的弊端?

/**
* Reads some number of bytes from the input stream and stores them into
* the buffer array b. The number of bytes actually read is
* returned as an integer. **This method blocks until input data is
* available, end of file is detected, or an exception is thrown.**
*
*

If the length of b is zero, then no bytes are read and
* 0 is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at the
* end of the file, the value -1 is returned; otherwise, at
* least one byte is read and stored into b.
* ……..
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}

上文中的粗体字对Socket的读操作的说明,当Socket的输入流进行读操作的时候,它会一致阻塞下去,知道发生三件事:

  1. 有数据可读
  2. 可用数据已经读完
  3. 发生空指针或者I/O异常

这样就意味着当发送方请求说这响应消息较慢,或者网络传输较慢,读取输入流的一方的通信线程将会阻塞很长时间,如果对方60s发送完数据,读入一方的通信线程将阻塞60s,在此期间其他接入消息将在消息队列中排队.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章