网络编程基础之七层协议及TCPUDPHttpNio解析

Posted 踩踩踩从踩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程基础之七层协议及TCPUDPHttpNio解析相关的知识,希望对你有一定的参考价值。

前言

本篇博客主要介绍的网络编程基础,包括七层协议,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的。

以上是关于网络编程基础之七层协议及TCPUDPHttpNio解析的主要内容,如果未能解决你的问题,请参考以下文章

企业运维之七层负载均衡--Haproxy

企业运维之七层负载均衡--Haproxy

Reactor网络编程模型解析

精简CNN模型系列之七:Xception

关于运维方面的网络基础

OSI七层网络协议及TCP/UDPC/S架构详解