B:缓冲区设置太小的问题
package com.suns.socket;
import java.io.IOException;
import .InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
public class MyServer2 {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8000));
serverSocketChannel.configureBlocking(false);//Selector 只有在非阻塞的情况下 才可以使用。
//引入监管者
Selector selector = Selector.open();//1. 工厂,2. 单例
//监管者 管理谁? selector.xxxx(ssc); //管理者 ssc ---> Accept
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
// selector监控 SSC ACCEPT
// selector
// keys --> HashSet
// register注册 ssc
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("MyServler2.main");
//监控
while (true) {
selector.select();//等待.只有监控到了 有实际的连接 或者 读写操作 ,才会处理。
//对应的 有ACCEPT状态的SSC 和 READ WRITE状态的 SC 存起来
// SelectionsKeys HashSet
System.out.println("-------------------------");
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {//ServerSocketChannel ScoketChannel
SelectionKey key = iterator.next();
//用完之后 就要把他从SelectedKeys集合中删除掉。问题? ServerScoketChannel---SelectedKeys删除 ,后续 SSC建立新的连接?
iterator.remove();
if (key.isAcceptable()) {
//serverSocketChannel.accept();
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();
sc.configureBlocking(false);
//监控sc状态 ---> keys
SelectionKey sckey = sc.register(selector, 0, null);
sckey.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
try {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(5);
int read = sc.read(buffer);
if (read == -1) {
key.cancel();
} else {
buffer.flip();
System.out.println("Charset.defaultCharset().decode(buffer).toString() = " + Charset.defaultCharset().decode(buffer).toString());
}
} catch (IOException e) {
e.printStackTrace();
key.cancel();
}
}
}
}
}
}package com.suns.socket;
import java.io.IOException;
import .InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class MyClient {
public static void main(String[] args) throws IOException {
//连接服务端 端口号?
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(8000));
// socketChannel.write(Charset.defaultCharset().encode("hello\nsuns\n"));
socketChannel.write(Charset.defaultCharset().encode("hellosuns\n"));
System.out.println("------------------------------");
}
}//ByteBuffer buffer = ByteBuffer.allocate(7);
//hellosuns\n
private static void doLineSplit(ByteBuffer buffer) {
buffer.flip();
for (int i = 0; i < buffer.limit(); i++) {
if (buffer.get(i) == '\n') {//hellosu
int length = i + 1 - buffer.position();
ByteBuffer target = ByteBuffer.allocate(length);
for (int j = 0; j < length; j++) {
target.put(buffer.get());
}
//截取工作完成
target.flip();
System.out.println("StandardCharsets.UTF_8.decode(target).toString() =
" + StandardCharsets.UTF_8.decode(target).toString());
}
}
buffer.compact();
}
//这种情况就是缓冲区太小,导致解析数据的时候,读取到ByteBuffer当中没有任何的\n,
//导致后续的代码没发执行,compact方法执行了个寂寞。所以需要有接下来的处理的处理:
doLineSplit(buffer);
//以为缓冲区 不够了 没压懂 需要扩容了
if (buffer.position() == buffer.limit()) {
//1 空间扩大
ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
//2 老的缓冲区的数据 ---> 新的缓冲区
buffer.flip();
newBuffer.put(buffer);
//3 channel -- byteBuffer 绑定 newBuffer
key.attach(newBuffer);
}网络通信过程中采用处理半包粘包的范式,采用的方式是通过特殊字符进行分割\n,同时使用compact压缩
1:channel要和:byteBuffer绑定的问题,保证byte可以复用,而且不会丢失数据
2:bytebuffer容量不够,考虑扩容问题。库容之后,将旧的buyffer拷贝到新的buyffer当中,绑定新的buffer
注意:
1:bytebuffer不够了就要扩容,如果越扩越大,浪费了空间需要考虑缩容问题,毕竟当我们的客户端很大的时候,每一个客户端都会对应一个byteBuffer,如果只考虑扩容,不考虑缩容,那么会浪费计算机内存资源,最终导致内存不够,netty当中会帮我们解决这个问题。
开发当中需要把这个场景都考虑到位的话,会很麻烦
2:扩容问题:
老bytebuffer会考虑到新的bytebuffer当中,这个效率是非常低的,后续我们通过0拷贝的方式进行解决。现在我们的代码基本解决了半包粘包的问题,但是不完美,将来netty当中解决的很完美。
3:解决半包粘包的方式:
1:\n+compact \n区分了不同的信息,–保证了信息的完整性,这种方式需要不断的检索这个\n这个效率是非常低的,这个是一个线性的查找。下边这个好
2:头体分离: 一个信息分成两个部分,一个信息是头部分,记录的是元数据,记录信息的大小,体信息才是真正的数据本身,这个时候,我们结合这个大小。很快的可以处理掉后续的消息内容,这个是应用很广泛的,是很高效的。Http协议本身就是依赖于这种设计,他的头里边有一个Content-Length 基于这个属性,就可以去数据中直接读取到多大的数据。Netty如何解决半包和粘包,一定是采用这两种方式的一种。所有的协议都有头部分,说明这是一个好的设计,后续我们在自定义一种通信方式的时候也要遵循这种方式。
一个较为完美的服务器端代码:
package com.suns.socket;
import java.io.IOException;
import .InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
/*
缓冲区的扩容
1. 如何判断 缓冲区 是否该扩容
2. 怎么扩容 。
*/
public class MyServer4 {
private static void doLineSplit(ByteBuffer buffer) {
buffer.flip();
for (int i = 0; i < buffer.limit(); i++) {
if (buffer.get(i) == '\n') {//hellosu
int length = i + 1 - buffer.position();
ByteBuffer target = ByteBuffer.allocate(length);
for (int j = 0; j < length; j++) {
target.put(buffer.get());
}
//截取工作完成
target.flip();
System.out.println("StandardCharsets.UTF_8.decode(target).toString() = " + StandardCharsets.UTF_8.decode(target).toString());
}
}
buffer.compact();
}
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8000));
serverSocketChannel.configureBlocking(false);//Selector 只有在非阻塞的情况下 才可以使用。
//引入监管者
Selector selector = Selector.open();//1. 工厂,2. 单例
//监管者 管理谁? selector.xxxx(ssc); //管理者 ssc ---> Accept
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
// selector监控 SSC ACCEPT
// selector
// keys --> HashSet
// register注册 ssc
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("MyServler2.main");
//监控
while (true) {
selector.select();//等待.只有监控到了 有实际的连接 或者 读写操作 ,才会处理。
//对应的 有ACCEPT状态的SSC 和 READ WRITE状态的 SC 存起来
// SelectionsKeys HashSet
System.out.println("-------------------------");
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {//ServerSocketChannel ScoketChannel
SelectionKey key = iterator.next();
//用完之后 就要把他从SelectedKeys集合中删除掉。问题? ServerScoketChannel---SelectedKeys删除 ,后续 SSC建立新的连接?
iterator.remove();
if (key.isAcceptable()) {
//serverSocketChannel.accept();
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();
sc.configureBlocking(false);
//监控sc状态 ---> keyr
ByteBuffer buffer = ByteBuffer.allocate(7);
SelectionKey sckey = sc.register(selector, 0, buffer);
sckey.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
try {
SocketChannel sc = (SocketChannel) key.channel(); //ByteBuffer ---> s...
//ByteBuffer buffer = ByteBuffer.allocate(7);//创建一个ByteBuffer 新的ByteBuffer..
//从channel中获得 绑定的那个bytebuffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
int read = sc.read(buffer);
if (read == -1) {
key.cancel();
} else {
/*
buffer.flip();
System.out.println("Charset.defaultCharset().decode(buffer).toString() = " + Charset.defaultCharset().decode(buffer).toString());
*/
doLineSplit(buffer);
//以为缓冲区 不够了 没压懂 需要扩容了
if (buffer.position() == buffer.limit()) {
//1 空间扩大
ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
//2 老的缓冲区的数据 ---> 新的缓冲区
buffer.flip();
newBuffer.put(buffer);
//3 channel -- byteBuffer 绑定 newBuffer 把之前的覆盖掉。
key.attach(newBuffer);
}
}
} catch (IOException e) {
e.printStackTrace();
key.cancel();
}
}
}
}
/* 如何保证 前后2次操作的ByteBuffer是同一个。
ByteBuffer和谁有关的呢?---> channel 相关
ByteBuffer和Channel绑定就行了
怎么绑定 ? channel.register(selector,0,attament)--> attarment 附件
*
* */
}
}TCP协议进行通讯的时候的两个问题需要注意:
1:分包的问题
(TCP协议)假如我们需要传递10TB的数据给到服务端,不会一次性直接传递过去,会打成十个包,一个接一个的发送数据,一个包多大呢?1460K实际上有可能比1460小,这个是基于双方的第三次握手的MSS值得较小值确定的。
2:流量控制的问题
我们发包的过程中,我们担心会出现发包的过程中不要太快,但是服务端来不及接收导致丢包的情况。甚至为了控制速率发一些空包。
总结:
当前我们做的是web服务,也就是Server,对于Server来讲有两个很重要的问题
1:如何接收请求的问题?这里我们使用的是NIO,基于SSC的accept()即可
2:数据的读写操作:
分析了Select()方法的特点,只要是数据没处理完,就必须的处理
1):数据没有一次性处理完,频繁调用select();
2):特殊情况,例如client的正常和不正常退出Selectkey.cancel();里边封装的是channel
数据的编解码操作:
1):编码:将String转成Byte数组
2):解码:将buffer当中的数据转成String
对应的就是encode()和decode方法
数据的完整性问题-避免出现半包或者粘包的情况。
TCP协议当中的分包问题:一个包的数据最大是1460B(字节bit)
TCP协议当中的流量控制问题:数据发的太快的话,服务端有可能来不及接收,导致丢包的情况。需要控制发包的速度。
















