重学Netty(一)——简单回顾BIO及网络编程

BIO

bio也叫同步阻塞IO,位于java.io包下,也就是面向数据流的IO(Stream)
在这里插入图片描述

它的整体架构如下图所示
在这里插入图片描述
可以从上图看出除去文件系统,剩下的就是BIO,可以按照读写单元将它们同意分为两类

  • 面向字节流
    • InputStream
      • FileInputSteam -> BufferedInputSteam (文件操作相关)
      • ObjectInputStream等等 (数据对象操作相关
    • OutputStream
    • FileOutputSteam -> BufferedOutputSteam(文件操作相关
    • ObjectOutputStream等等(数据对象操作相关
  • 面向字符流
    • Reader

      • InputStreamReader (提供InputStream转为Reader,可设置字符集
        • FileReader是InputStreamReader的子类,此类方法全是调用的InputStreamReader的方法(传入一个FileName或File对象,底层使用InputSteamReader构造方法初始化一个FileInputStream)
      • BufferedReader,CharArrayReader,PipedReader等等 (直接操作字符无转换
    • Writer

      • OutputStreamWriter(提供OutputStream转为Writer,可设置字符集
        • FileWriter 可以根据传入的FileName或File对象,调用父类构造方法构造出FileOutputStream使用父类方法
      • BufferedWriter,CharArrayWriter,PipedWriter等等 (直接操作字符无转换

在BIO中使用了大量的装饰者模式,因此代码的可读性还是比较高的。

下面就来结合BIO进行网络编程,编写一个TCP服务器和一个UDP服务器

网络编程

Socket简介

在网络编程中Socket是十分重要的,无论是发送还是接受消息都难免要经过Socket,俗称套接字,用于描述IP地址和端口号,它大致可以分为两种类型,一种是TCP Socket包含ServerSocket另外一种是UDP Socket包含DatagramSocket

  • ServerSocket是工作于TCP服务端的Socket,主要用于接受客户端Socket发来的请求并获取客户端Socket进行处理。其中的backlog可以指定连接请求队列的长度(一般为50,可以自己设定),binAdrr可以指定服务器要绑定的IP地址
  • DatagramSocket是UDP服务端的Socket,主要接受客户端发来的数据报
  • Socket是工作于客户端上的一个Socket,通过规定服务器IP和端口号保证数据的流向。

Socket的工作流程

发送数据
  1. 得到一个Socket对象
  2. 将socket与网络驱动层进行绑定
  3. 写数据到socket中
  4. 网络驱动层取出socket数据,从网卡发过去
    在这里插入图片描述
接收数据
  1. 得到一个Socket实例
  2. 网卡接受到数据,网络驱动层根据绑定关系将数据放入特定的Socket
  3. 从Socket中读取出数据
    *

Socket的几个额外配置参数

在这里插入图片描述
这几个选择都在SocketOption这个接口里面


public interface SocketOptions {


    // 子类实现设置可选项的方法
    public void setOption(int optID, Object value) throws SocketException;
      // 默认情况采用Neagle算法,具体开启还是关闭看自身的需求(True or False)
    @Native public final static int TCP_NODELAY = 0x0001;
     // 在一个进程关闭socket后(没有进行释放端口)统一主机上的其他进程可对端口进行重用(可以在重用前设置为true才会生效)
    @Native public final static int SO_REUSEADDR = 0x04;
     // 用来控制广播(只有数据报中支持)
    @Native public final static int SO_BROADCAST = 0x0020;
      // 用来控制多播参数选项
    @Native public final static int IP_MULTICAST_IF = 0x10;
     // 和上面一样,支持IPv6
    @Native public final static int IP_MULTICAST_IF2 = 0x1f;
     // 设置本地回环(Ip 多播)
    @Native public final static int IP_MULTICAST_LOOP = 0x12;
     //TOS就是Type Of Service(服务质量)
    @Native public final static int IP_TOS = 0x3;
     // linger(缓慢消失),在默认情况下调用socket.close方法会立刻返回但是底层的Socket并不会立刻关闭
     // 这个参数是设置调用close方法后不会立刻返回而是进入阻塞状态,在发送完成数据或是超过60s没发完才返回,然后关闭底层的socket(没发完的数据就丢弃)
    @Native public final static int SO_LINGER = 0x0080;
     // 用来设置读取数据超时,如果read阻塞的时间大于超时时间就会抛出异常SocketTimeoutException
    @Native public final static int SO_TIMEOUT = 0x1006;
     // Socket发送缓冲区的大小(可设置)
    @Native public final static int SO_SNDBUF = 0x1001;
     // Socket接收缓冲区的大小(可设置)
    @Native public final static int SO_RCVBUF = 0x1002;

     // 如果此选项为true,那就是在客户端和服务端的连接中加入心跳机制(True or False)
     // 连接空闲两个小时,会对远程Socket发送Tcp数据报,若无响应就继续发一段时间,若一直无响应,就关闭连接
    @Native public final static int SO_KEEPALIVE = 0x0008;
     // Socket类的sendUrgentData方法向服务器发送一个单字节的数据,这个单字节数据不经过输出缓冲区,而是立即发出
     // 客户端发送此字节虽然不使用Stream,但是在服务端还是和Stream发的数据混在一起
    @Native public final static int SO_OOBINLINE = 0x1003;
}

注:在网络中如果有很多小的数据,每次发的话,它的包头部长度可能都超过它本身长度,为了优化这一问题就有了Neagle算法,发送数据时不会立即发送出去,而是先在缓冲区中停留,等待数据到达一定大小才一次性发送出去(相当于分批次发送),具体开启还是关闭看自身的需求。

TCP编程

下面以一个简单的Echo Server为例回顾Tcp网络编程
Server

package BIO.tcp;
/*
 * @Author  Wrial
 * @Date Created in 22:45 2020/4/8
 * @Description TCPServer
 */

import java.io.*;
import java.net.*;

public class TCPServer {

    public static void main(String[] args) throws IOException {
        TCPServer tcpServer = new TCPServer(8090);
        tcpServer.service();
    }

    private ServerSocket serverSocket;

    public TCPServer(int port) throws IOException {
        init(port);
    }

    private void init(int port) throws IOException {
        serverSocket = new ServerSocket(port);
        System.out.println("server init ——> " + serverSocket.getInetAddress().getHostAddress()  + "port :"+  port);
    }

    private String serverReceiveAndEcho(String msg) {
        System.out.println("Receive :" + msg);
        return "echo from server :" + msg;
    }


    private PrintWriter getWriter(Socket socket) throws IOException {
        return new PrintWriter(socket.getOutputStream(),true);
    }

    private BufferedReader getReader(Socket socket) throws IOException {
        return new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }

    public void service() throws IOException {
        Socket accept;
        while (true) {
            accept = serverSocket.accept();
            System.out.println("client :" + accept.getInetAddress().getHostName() + "已经连接");

            BufferedReader bufferedReader = getReader(accept);
            PrintWriter printWriter = getWriter(accept);

            String msg;
            while ((msg = bufferedReader.readLine()) != null) {
                printWriter.println(serverReceiveAndEcho(msg));
                if (msg.equals("end")){
                    break;
                }
            }
            try {
                if (accept!=null) accept.close();
                if (bufferedReader!=null) bufferedReader.close();
                if (printWriter!=null) printWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


}

Client

package BIO.tcp;
/*
 * @Author  Wrial
 * @Date Created in 23:19 2020/4/8
 * @Description
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        new Client("localhost", 8090).sendMsg();
    }

    private String ip;
    private int port;
    Socket socket;

    public Client(String ip, int port) throws IOException {
        this.ip = ip;
        this.port = port;
        init();
    }

    private void init() throws IOException {
        socket = new Socket(ip, port);
        System.out.println("client  init - ->" + port);
    }

    private PrintWriter getWriter(Socket socket) throws IOException {
        // true是自动刷新
        return new PrintWriter(socket.getOutputStream(),true);
    }

    private BufferedReader getReader(Socket socket) throws IOException {
        return new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }

    private void sendMsg() throws IOException {
        BufferedReader bufferedReader = getReader(socket);
        PrintWriter printWriter = getWriter(socket);

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        String sendMsg;
        while ((sendMsg = reader.readLine())!=null){
            printWriter.println(sendMsg);
            System.out.println(bufferedReader.readLine());
            if (sendMsg.equals("end")){
                System.out.println("end");
                break;
            }
        }
        if (socket!=null) socket.close();
    }





}

当客户端输入end就会结束此次socket连接,双方都关闭socket
服务端
在这里插入图片描述
客户端
在这里插入图片描述
在这里插入图片描述
如果我们需要Server同时进行多个cllient就要适当的加入多线程,可以循环new Thread,也可以使用线程池来更好的管理线程。如下图所示 :
在这里插入图片描述
总的来说还是阻塞导致的执行多个任务需要多个线程来完成,无疑是加大了开销,降低了性能。

Java中引起线程阻塞的几种方式

  1. Thread.Sleep,可以让线程放弃CPU,睡眠一段时间,然后恢复
  2. I/O阻塞,如从控制台输入等等
  3. 执行的该对象的wait方法,只有notify/notifyAll才能唤醒
  4. 执行同步代码块的时候不能立即获取锁,就会处于阻塞状态
  5. Socket的带参构造方法或connect方法,直到连接成功才返回
  6. 当给Socket设置了Linger延时处理,在调用close会等待数据发完或者超时才会返回(期间是阻塞的)
  7. ServerSocket的accept方法等等

UDP编程

UDP编程和TCP编程不同,UDP是以Datagrampacket数据包发送的而不是根据IO流来完成的。其中比较核心的两个类为DatagramPacketDatagramSocket,DatagramPacket对于发送方可以看做是缓冲区数据+目的IP+目的port,对于接受放可以看做是receive的一个容器,可以用来接收发送方发来的数据报
具体代码如下

接收端 UDPServer

package BIO.udp;
/*
 * @Author  Wrial
 * @Date Created in 14:41 2020/4/9
 * @Description
 */

import java.io.IOException;
import java.net.*;

public class UDPServer {
    public static void main(String[] args) throws IOException {
        UDPServer udpServer = new UDPServer(8090);
        udpServer.service();
    }

    private DatagramSocket datagramSocket;

    public UDPServer(int port) throws SocketException {
        init(port);
    }

    private void init(int port) throws SocketException {
        datagramSocket = new DatagramSocket(port);
        System.out.println("server init ");
    }

    private void service() throws IOException {

        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        while (true) {
            datagramSocket.receive(packet);
            System.out.println("receive from " + packet.getAddress());
            String msg = new String(packet.getData());
            if (msg == "") break;
            System.out.println("msg == " + msg);
        }
    }
}

发送端:UDPClient

package BIO.udp;
/*
 * @Author  Wrial
 * @Date Created in 22:18 2020/4/9
 * @Description 发送端
 */

import java.io.IOException;
import java.net.*;

public class UDPClient {


    public static void main(String[] args) throws IOException, InterruptedException {

        UDPClient udpClient = new UDPClient();
        for (int i = 0; i < 5; i++) {

            DatagramPacket msg = udpClient.transferMsgToPacket("127.0.0.1", "hello server", 8090);
            udpClient.sendMessage(msg);
            Thread.sleep(100);
        }

    }

    private DatagramSocket datagramSocket;

    public UDPClient() throws SocketException {
        datagramSocket = new DatagramSocket();
        System.out.println("init datagramSocket");
    }

    public void sendMessage(DatagramPacket datagramPacket) throws IOException {
        datagramSocket.send(datagramPacket);
        System.out.println("Message send  success ---" + new String(datagramPacket.getData()));
    }

    public DatagramPacket transferMsgToPacket(String addr, String msg, int port) throws UnknownHostException {
        DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0,
                msg.length(), InetAddress.getByName(addr), port);
        return packet;
    }


}


演示如下:
在这里插入图片描述
在这里插入图片描述

这个就是大致对BIO和简单的网络编程的一个回顾,它用起来不难,但是它的缺陷也是显而易见的,由于线程的阻塞而导致资源利用不充分,因此就出现了NIO!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值