网络编程基础之七层协议及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解析的主要内容,如果未能解决你的问题,请参考以下文章