网络编程基础之七层协议及TCP、UDP、Http、Nio解析

前言
本篇博客主要介绍的网络编程基础,包括七层协议,TCP和UDP对比特性,Http协议,以及何为Nio编程,有何优缺点,应用场景等,都有一个比较大介绍。

OSI网络七层模型
为了不同的厂家的计算机可以通信,以便在更大范围内建立计算机能够通信,就必须要建立一个国际范围内的网络体系结构标准。

 

 通过网络建立好对应的关系,而每个层次不相关联,每个层次进行开发各自层次的内容,互不影响。 我们在开发应用是,并不关系物理层的东西,这就是七层表现形式。

 

 物理层  是原始的数据比特流能够在物理介质上传输,例如常见的网线光纤等
数据链路层 通过校验、确认和重发等手段、形成稳定的数据链路

网络层 进行路由器的选择和流量的控制 ip协议

传输层 udp tcp 建立端口,提供可靠的端口到端口的数据传输服务 协议

会话层 表示层 应用层 负责建立、管理和终止进程之间的会话和数据交换  高三层模型包括http协议等等

TCP协议
TCP协议(传输控制层协议),是Internet一个重要的传输层协议。TCP提供面向连接、可靠、有序、字节流服务。应用程序在使用TCP之前,必须建立TCP连接。

 

 TCP协议中内容
数据和头部信息; 源端口号16位和目的端口号16位,tcp完成的事就是根据端口号找到应用 ,建立应用之间的连接;
确认序号、 序号都是32位 主要进行做占位
标志位   syn 建立连接 fin 关闭连接 ack 确认序号。 psh 有数据传输   rst 连接重置  用来建立连接使用保证连接能够正常建立和取消
TCP握手机制


通过请求头标志位建立连接 确认网络是通的,然后可以建立连接

 

客户端syn_send等待确认   客户端发送信息 头部  syn=1 seq=1 建立连接  客户端发送要准备建立连接
  服务端返回 syn=1 ack=1   携带着, ack_seq=x+1;  syn_recv收到请求等待确认  确认客户端是否可以建立连接
客户端established  发送seq=x+1 ack_seq=y+1  发送给服务段建立起连接。 发送给服务端建立起来了
请求头标志位去断开连接,断开掉

客户端等待确认状态  头部标志位 fin=1 seq=u  
服务端 处于半开闭状态  ack=1 seq=v,ack_seq=u+1 客户端处于等待释放状态
服务端处理数据,等待确认状态 fin=1 ack=1 seq=w ack_seq=u+1
客户端发起 seq=u+1 ack_seq=w+1 ack=1  确定好服务端断开连接
UDP协议
用户数据协议UDP是Internet传输层协议。提供、无连接、不可靠、数据报尽力传输服务。

 

 

 少了标志位和建立连接得步骤,这样数据量更新,并且建立连接得时间更短;控制在源端口和目的端口号

在UDP上构造应用,关注下面几点:

应用进程更容易控制发送什么数据及何时发送
无需建立连接
无连接状态
首部开销小
在头部少了标志位,来校验连接是否正常,因此说它不可靠,但是可以在应用层做处理,对于一些视频流的传输,可以允许视频流有少量的丢失,因此基本都采用udp进行视频流传输。

TCP和UDP对比


TCP进行多建立连接,通过头部标志位,进行三此握手和四次挥手,才有了他的特性,安全可靠,但是相对UDP来说会导致速度变慢,并且资源占用多。

 

Socket网络编程
Socket是网络编程的API,都是用来做网络编程的;原意是电源插座,被翻译为套接字的;计算机通信的一种约定或者方式;  在java中来实现传输控制层的发送数据就是采用socket套接字封装成不同的对象进行操作的。
Internet中应用最广泛的网络应用编程接口,实现与3种底层协议的交互:

数据报类型的套接字Sock_DGRAM(面向UDP接口)
流式套接字Sock_Stream(面向TCP接口)
原始套接字接口Sock_RAW(面向网络层编程接口 ip ICMP等)
网络编程的接口,每个协议都会实现,他是操作系统底层给jvm应用层提供的API。

 

这就是jdk中提供给我们进行发送数据 实现tcp协议的API进行操作

当然也包括udp协议的api

 

主要socket api调用过程

 

 这里整个套接字的处理过程

Socket Api函数定义

listen()、accept() 函数只能用于服务端;阻塞等待数据的到来
connect()函数只能作用于客户端;建立连接
socket()、bind()、send()、recv()、sendto()、recvfrom()、close()

在java中对socket套接字进行封装了的  在jdk1.0中

public
class Socket implements java.io.Closeable {
    /**
     * Various states of this socket.
     */
    private boolean created = false;
    private boolean bound = false;
    private boolean connected = false;
    private boolean closed = false;
    private Object closeLock = new Object();
    private boolean shutIn = false;
    private boolean shutOut = false;
 
    /**
     * The implementation of this Socket.
     */
    SocketImpl impl;
 
    /**
     * Are we using an older SocketImpl?
     */
    private boolean oldImpl = false;
import java.io.InputStream;
import java.io.OutputStream;
在网络编程中找到 inputstream outputstream 进行接收流和发送流

java代码实现


客户端代码     一个利用tcp协议的socket类进行发送数据 随意造的一个数据

 

public static void main(String[] args) throws UnknownHostException, IOException {
        Socket s = new Socket("localhost", 8080);
        OutputStream out = s.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "utf-8"));
        writer.write("test \r\n\r\n"); // 阻塞,写完成
        writer.flush();
 
        InputStream inputStream = s.getInputStream(); // net + i/o
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
        String msg = "";
        while ((msg = reader.readLine()) != null) { // 没有数据,阻塞
            if (msg.length() == 0) {
                break;
            }
            System.out.println("收到服务端:" + msg);
        }
        s.close();
    }
 服务端代码 接受到数据并返回数据

public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动成功");
        while (!serverSocket.isClosed()) {
            Socket socket = serverSocket.accept(); // 启动时阻塞阻塞着
            System.out.println("收到新连接 : " + socket.toString());
            try {
                InputStream inputStream = socket.getInputStream(); // net + i/o 客户端来请求了 等待数据
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
                String msg;
                while ((msg = reader.readLine()) != null) { // 没有数据,阻塞
                    if (msg.length() == 0) {
                        break;
                    }
                    System.out.println("收到客户端数据:" + msg);
                }
 
                OutputStream oust = socket.getOutputStream();
                oust.write("server".getBytes());
                oust.flush();
 
            } finally {
                socket.close();
            }
        }
 
        serverSocket.close();
    }
这是一个BIO操作简单实现TCP协议的小例子

服务器启动成功
收到新连接 : Socket[addr=/127.0.0.1,port=59768,localport=8080]
收到客户端数据:test 
 
收到服务端:server
这个BIO是最简单以及最基础的实现,在开发过程中并不会用到,但所有的套路和操作都是围绕这个展开的。因为服务端socket 数据信息中存在客户端  ip和端口号,例如我在工作中,进行反爬虫,ip限制,是不是就是通过这个可以实现了,虽然可以使用ip池来做到让服务端反爬失效,这都是后话了,我在其他文章中会具体介绍的。然后继续深入理解socket

服务端线程只会处理一个连接。服务端 当前线程会在及readLine 等待读取数据 阻塞着,因此无法进行获取新的客户端连接

这里demo会出现阻塞问题,因为如果你按照我的例子执行一下,在构造多个客户端同时访问服务端,accept会将当前连接客户端之外的客户端给拒绝掉,这里就是BIO的特性  , 这里在阻塞的地方

 String readLine(boolean ignoreLF) throws IOException {
        StringBuffer s = null;
        int startChar;
 
        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;
 
        bufferLoop:
            for (;;) {
 
                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars) { /* EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();
                    else
                        return null;
                }
                boolean eol = false;
                char c = 0;
                int i;
 
                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;
                omitLF = false;
 
            charLoop:
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;
                        break charLoop;
                    }
                }
 
                startChar = nextChar;
                nextChar = i;
 
                if (eol) {
                    String str;
                    if (s == null) {
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;
                    }
                    return str;
                }
 
                if (s == null)
                    s = new StringBuffer(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);
            }
        }
    }
然后说一个解决BIO的方法,利用线程池在应用层进行解决;服务端代码添加线程池过后的代码

private static ExecutorService threadPool = Executors.newFixedThreadPool(50);
 
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动成功");
        while (!serverSocket.isClosed()) {
            Socket socket = serverSocket.accept(); // 启动时阻塞着
            System.out.println("收到新连接 : " + socket.toString());
            threadPool.execute(new Runnable() {
 
                @Override
                public void run() {
                    try {
                        InputStream inputStream = socket.getInputStream(); // net + i/o 客户端来请求了 等待数据
                        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
                        String msg;
                        while ((msg = reader.readLine()) != null) { // 没有数据,阻塞
                            if (msg.length() == 0) {
                                break;
                            }
                            System.out.println("收到客户端数据:" + msg);
                        }
 
                        OutputStream oust = socket.getOutputStream();
                        oust.write("server".getBytes());
                        oust.flush();
 
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } finally {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            });
 
        }
 
        serverSocket.close();
    }
 HTTP协议
 Http协议是应用层的协议,高于传输控制层的协议,总的来说基于TCP进行封装的协议。
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。

Http请求格式


 这里主要需要看的是请求头里面包含着 连接 数据内容  连接信息,以及 编码方式

 

 

Http响应格式


响应格式 包括状态行, http版本  响应头里面的内容大小等等  以及 必须要有的换行符才能区别开

 

        outputStream.write("HTTP/1.1 200 OK\r\n".getBytes());
                    outputStream.write("Content-Length: 11\r\n\r\n".getBytes());
 响应码

1xx(临时响应) 表示临时响应并需要请求者继续执行的操作码
2xx(成功) 表示处理了请求的操作码
3xx(重定向) 表示要完成请求,需要进一步操作,通常,这些状态代码来重定向。
4xx(请求错误)  包括404等请求地址未找到等错误  403 
5xx(服务器内部错误) 这些错误可能是服务器内部本身出现了错误。500 501
BIO-阻塞IO的含义
阻塞(blocking)IO:资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)。

非阻塞(no-blocking)IO:资源不可用时,IO请求离开返回,返回数据标识资源不可用。

同步 (synchronous)Io:应用阻塞在发送或接收数据的状态,直到数据成功传输或失败。

异步(asynchronous)IO:应用发送或接收数据后立刻返回,实际处理异步执行。

这里阻塞和非阻塞获取资源的方式,同步/异步是程序如何处理资源的逻辑设计。代码中使用API   servierSocket.accept inputstrem.read 都是阻塞的api,操作系统底层api中,默认操作都是Blocking型,send/recv等接口都是阻塞的

出现阻塞带来的问题,在处理IO请求时,一个线程只能处理一个网络连接。

NIO
在jdk1.4,提供了新的java IO操作非阻塞的API,用意代替JAVAIO 和java Networking 相关的API

NIO中有的三个核心组件:

Buffer缓冲区 ByteBuffer

channel 通道 SocketChannel

Selector 选择器

整个api在nio包下面的

 

里面包含着NIO相关的各个核心组件

 

分别介绍一下这三大核心组件

Buffer缓冲区(ByteBuffer)
buffer缓冲区的本质上是一个可以写入数据的内存块(类似数组),然后可以再次读取。此内存块包含在NIO Buffer对象中 该对象提供一组方法,可以更轻松地使用内存块。 相比较直接对数组的操作。使得buffer Api 更加容易操作与管理。

nio中buffer抽象类  
使用buffer进行数据写入与读取

将数据写入缓冲区
调用buffer.flip方法翻转此缓冲区。将限制设置为当前位置,然后该位置设置为零。如果标记已定义,则为丢弃,也就是下面的将缓冲区转换为读取模式
  public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
缓冲区读取数据
调用 clear方法,或者bytebuffer.compact()(native中的方法)转换为写模式
 public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
这四个步骤来源于源码中对位置变量等标志位的处理。

buffer的工作原理

从他的属性和构造方法来看

   private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
 Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
具有三个重要的属性:

capacity容量:作为一个内存块,buffer具有一定的固定大小,称为容量。

position位置:写入模式代表写的位置。读取位置时代表读取数据的位置。

limit限制:写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量

 

至于为什么要调用 filp 转换为读模式,还有  position 转换成写模式。这是这块缓冲区。

维护这个指针转换成读取模式  将limit 等于position限制读取的模式   将position等于0
  public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
 转换成写模式,将postion为0   然后还原成 原始情况 limit 等于容量
 public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
NIO中ByteBuffer抽象类 
在jdk源码中就可以看出bytebuffer是继承自buffer

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>
{
对象中维护这一个byte数组 并且 判断可读 和偏移量

final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers
 并且在 bytebuffer的源码中可以对应申请不同的内存区域

申请堆外内存 
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
申请堆内内存 
  public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
也就是说无论在netty还是自己直接使用 bytebuffapi  其实也做了封装  最终操作的是HeapByteBuffer对象或者DirectByteBuffer对象 ByteBuffer requestBuffer = ByteBuffer.allocate(1024);

而 HeapByteBuffer 和 DirectByteBuffer 对需要发送的数据做下面的处理交给系统的socket API进行发送数据

 

使用堆外内存的优缺点
优点:

进行网络IO或者文件IO时比heapbuffer少一次拷贝。file/socket-os memory--jvm heap GC会移动对象内存,在写file或socket 的过程中,JVM的实现中,会先把数据复制到堆外,再进行写入。
GC范围之外,降低GC压力,但实现了自动管理。DirectByteBufer中有Cleaner对象,被GC前会执行clean方法,触发DirectByteBuffer中定义的Deallocator 
    private final Cleaner cleaner;
 
    public Cleaner cleaner() { return cleaner; }
主要场景:

性能确实可观的时候才使用;分配给大型、长寿命;网络传输、文件读写场景

通过虚拟机参数 maxDirectMemorySize限制大小,防止耗尽整个机器的内存;

小例子

   ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put((byte) 1);
        byteBuffer.put((byte) 2);
        byteBuffer.put((byte) 3);
         byteBuffer.flip();
        byte a = byteBuffer.get();
        System.out.println(a);
        byte b = byteBuffer.get();
        System.out.println(b);
添加了数据进行读取,这就是一个bytebuffer进行操作,默认也是申请的堆内内存的

   public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
Channel通道
Channel是一个通道,Channel原理类似于传统的BIO中流对象  fileinputStream outputStream

.

 

也就是说这里 通过channel进行操作。

Channel的API涵盖了UDP/TCP网络和文件IO

 文件io FileChannel 里面包括对文件的处理
public abstract class FileChannel
    extends AbstractInterruptibleChannel
    implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
{
UDP DatagramChannel
ServerSocketChannel TCP Client
SocketChannel    TCP Client
public abstract class SocketChannel
    extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
{
这里的channel通道不只是针对网络数据传输,还会在文件中使用

和标准IO stream操作的区别:

在一个通道内进行读取和写入stream通常是单向的(input或output) 可以非阻塞读取和写入通道 通道始终读取或写入缓冲区。 只有一个channel进行读取 。

SocketChannel
SocketChannel 用于建立TCP网络连接,类似与BIO中的socket 。

一般采用SocketChannel.open()方法。

 

这里使用open的方式,SocketChannel是一个抽象类,具体的操作也不是这个类去实现的,这个可以看一下源码中实现

   public static SocketChannel open() throws IOException {
        return SelectorProvider.provider().openSocketChannel();
    }
    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }
这里采用java spi去找对应的实现类。

 private static boolean loadProviderFromProperty() {
        String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
        if (cn == null)
            return false;
        try {
            Class<?> c = Class.forName(cn, true,
                                       ClassLoader.getSystemClassLoader());
            provider = (SelectorProvider)c.newInstance();
            return true;
        } catch (ClassNotFoundException x) {
            throw new ServiceConfigurationError(null, x);
        } catch (IllegalAccessException x) {
            throw new ServiceConfigurationError(null, x);
        } catch (InstantiationException x) {
            throw new ServiceConfigurationError(null, x);
        } catch (SecurityException x) {
            throw new ServiceConfigurationError(null, x);
        }
    }
write写:write()在尚未写入任何内容时,就有可能返回。需要在循环中使用write().

read读:read()方法可能直接返回而根本不读取任何数据,根据返回的int值判断读取多少字节。

ServerSocketChannel 
ServerSocketChannel可以监听新建的TCP连接通道,类似BIO中ServerSocket

severSocketChannel.accept():如果处于非阻塞模式,那么没有挂起的连接,该方法立即返回Null,必须检查SocketChannel是否为null

 

这里为什么要设置非阻塞模式,这个可以在源代码中可以看出来

    // Blocking mode, protected by regLock
    boolean blocking = true;
在AbstractSelectableChannel中 默认属性设置为false

public final SelectableChannel configureBlocking(boolean block)
        throws IOException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if (blocking == block)
                return this;
            if (block && haveValidKeys())
                throw new IllegalBlockingModeException();
            implConfigureBlocking(block);
            blocking = block;
        }
        return this;
    }
只有设置,才能是成为非阻塞。

  SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
        while (!socketChannel.finishConnect()) {
            // 没连接上,则一直等待configureBlocking
            Thread.yield();
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        // 发送内容
        String msg = scanner.nextLine();
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
        // 读取响应
        System.out.println("收到服务端响应:");
        ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
 
        while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
            // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
            if (requestBuffer.position() > 0) break;
        }
        requestBuffer.flip();
        byte[] content = new byte[requestBuffer.limit()];
        requestBuffer.get(content);
        System.out.println(new String(content));
        scanner.close();
        socketChannel.close();
ServerSocketChannel里面中accept方法虽然是非阻塞的ByteBuffer.read方法是阻塞的,因为在连接未中断的情况下,服务端并不知道多久会传递数据过来,因此这个read方法一定是阻塞的。

而nio提出的效果就是解决阻塞问题的。因此在jdk中有Selector选择器的出现,当然可以采用最笨的方式,通过list将socketchannel存储起来,进行循环读取,判断是否数据过来,效率是相当低的

Selector选择器
Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备好进行读取或写入。实现单个线程可以管理多个通道,从而管理多个网络连接。

一个线程使用Selector监听多个channel的不同事件:

四个事件分别对应SelectionKey四个常量。

Connect连接(selectionKey.op_connect)
accept准备就绪(op_accept)
读取 read (op_read)
write写入(op_write)


服务端实现的例子

 

public class SelectorDemo {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
 
        // 创建selector
        Selector selector = Selector.open();
 
        // 注册selector
        ssc.register(selector, SelectionKey.OP_ACCEPT);// 这里需要设置默认的事件
        ssc.socket().bind(new InetSocketAddress(8080));// 绑定端口
        while (true) {
            int readyChannels = selector.select();// 会阻塞,直到有事件触发
 
            if (readyChannels == 0)
                continue;
 
            Set<SelectionKey> selectedKeys = selector.selectedKeys();// 获取被触发的事件集合
 
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            while (keyIterator.hasNext()) {
 
                SelectionKey key = keyIterator.next();
 
                if (key.isAcceptable()) {
                    SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    // serverSocketChannel 收到一个新连接,只能作用于ServerSocketChannel
 
                } else if (key.isConnectable()) {
                    // 连接到远程服务器,只在客户端异步连接时生效
 
                } else if (key.isReadable()) {
                    // SocketChannel 中有数据可以读
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
                        // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
                        if (requestBuffer.position() > 0)
                            break;
                    }
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    socketChannel.register(selector, SelectionKey.OP_WRITE);
                } else if (key.isWritable()) {
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    // SocketChannel 可以开始写入数据
                    ByteBuffer buffer = ByteBuffer.wrap("sucess".getBytes());
                    socketChannel.write(buffer);
                    socketChannel.close();
                }
 
                // 将已处理的事件移除
                keyIterator.remove();
            }
 
        }
    }
}
 实现一个线程多个通道的核心概念理解:事件驱动机制

非阻塞的网络通道下,开发者通过selector注册对于通道感兴趣的事件类型,线程通过监听事件来触发响应的代码执行。

NIO对比BIO的优缺点


nio线程利用率是很高的,性能更加强大。对于现在项目上对于大连接是非常重要的。所以在tomcat8中完全采用nio操作,都在往nio上转了。

 

总结 
本篇文章主要以网络七层模型为基础,逐步的介绍作为基础的TCP、UDP 以及http协议。比较重要的点是nio,我们最常用的也是nio非阻塞操作。nio并且为我们提供功能丰富及强大的io处理Api,也需要和多线程技术相结合起来。才能作用于项目中,包括netty框架的基础就是nio的。
————————————————
版权声明:本文为CSDN博主「踩踩踩从踩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33373609/article/details/120467165

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值