网路编程
-
服务端 接受请求 ---> ServerScoketChannel
-
进行实际通信 ---> ScoketChannel
往服务器写操作
Seletor循环监听事件的方式 解决死循环空转的问题。
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();
}
}
}
}
}
}
结论:
当client连接服务器,发起操作后,服务器必须全部处理完成整个交互才算结束,如果没有处理完成,select方法就会一直被调用
案例:给ByteBuffer 分配6个字节,但一次传了8个字节的字符过来
1. 刚刚那个示例,数据没有处理完前slect方法就会被调用多次
2. 在某些特殊的操作下,服务器无法处理,那么select方法就会被频繁调用
解决粘包和半包问题:
1. 1. \n 作为分割符,进行行的区分。compact进行处理,把第一次没有读取完的数据,向前移动和后面的内容进行整合。(存在问题,效率低)
2. 把要读取的数据大小等信息存入请求头中(比如HTTP)
解决缓冲区扩容问题:
如何出现? 当读取的一组数据中没有读到\n,下次再进行就会到处这个部分数据读不到,因为缓存区的大小不够
何时扩容?
* 1. 如何判断 缓冲区 是否该扩容
当缓存区不够了 压不动,需要扩容 也就是buffer.position()==buffer.limit()
* 2. 怎么扩容
ByteBuffer newBuffer=ByteBuffer.allocate(buffer.capacity()*2);
buffer.flip();
newBuffer.put(buffer);
同时要保证两次buffer要一样,把channel ----byteBuffer 绑定
代码如下:
/**
* 缓冲区的扩容
* 1. 如何判断 缓冲区 是否该扩容
* 2. 怎么扩容
*/
public class Myserver4 {
public static void doLineSplit(ByteBuffer buffer, SelectionKey key) {
buffer.flip();
for (int i = 0; i < buffer.limit(); i++) {
if(buffer.get(i)=='\n'){
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();
//缓冲区 不够了 没压动,需要扩容
if(buffer.position()==buffer.limit()){}{
ByteBuffer newBuffer=ByteBuffer.allocate(buffer.capacity()*2);
buffer.flip();
newBuffer.put(buffer);
//channel ----byteBuffer 绑定
key.attach(newBuffer);
}
}
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//设置ServerSocketChannel非阻塞
serverSocketChannel.configureBlocking(false);//Selector 只有再非阻塞的情况下 才可以使用。
serverSocketChannel.bind(new InetSocketAddress(8000));
//引入监控着
Selector selector=Selector.open();//1. 工厂 2. 单例
//监管者管理谁?
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
//selector监控 SSC ACCEPT
//在selector 中
// keys --->HashSet
// register 注册 ssc
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("Myserver2.main");
//监控
while (true){
selector.select();//等待 ACCEPT ,对于的有ACCEPT的状态SSC和READ WRITE状态的SC存起来
//SelectionsKeys HashSet
System.out.println("------------------------");
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if(key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = channel.accept();
socketChannel.configureBlocking(false);
//监控SocketChannel状态---->key
ByteBuffer buffer=ByteBuffer.allocate(7);
SelectionKey sckey = socketChannel.register(selector, 0, buffer);
sckey.interestOps(SelectionKey.OP_READ);
}else if(key.isReadable()){
try {
SocketChannel channel = (SocketChannel) key.channel();
//从channel中获得它绑定的bytebuffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
// ByteBuffer buffer=ByteBuffer.allocate(20);
int read = channel.read(buffer);
if(read==-1){
key.cancel();
channel.close();
}else {
doLineSplit(buffer,key);
}
} catch (IOException e) {
e.printStackTrace();
key.cancel();
}
}
}
}
}
}
iterator.remove()
把用过的SelectionKey从SeletionKeys集合中剔除
selectionKey.cancle();
把一些无法实际解决的内容,通过cancle()来取消。避免每一次都被select()获取到从新进行循环过程。
读取数据
1. 通过附件的形式,把byteBuffer和channel进行了绑定,从而可以多次处理数据。
2. ByteBuffer的扩容。
数据的写出
1. 第一个问题 写一次数据,当发现数据没有写完,设置WRITE监听状态。
2. 每一次发生Write的状态,都把剩下的数据写出去。
往服务器写操作
**
* 这个代码 演示服务器 写操作
*/
public class Myserver5 {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8000));
Selector selector=Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey sscKeys = iterator.next();
iterator.remove();
if(sscKeys.isAcceptable()){
SocketChannel sc = serverSocketChannel.accept();
sc.configureBlocking(false);
SelectionKey scKey = sc.register(selector, SelectionKey.OP_READ);
//准备数据
StringBuilder sb=new StringBuilder();
for (int i = 0; i < 20000000; i++) {
sb.append("s");
}
//NIO Buffer 存储数据 channel写
ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
int write = sc.write(buffer);
System.out.println("write"+write);
if(buffer.hasRemaining()){
//为当前的socketChannel增加write监听
scKey.interestOps(scKey.interestOps()+SelectionKey.OP_WRITE);
scKey.attach(buffer);
}
} else if(sscKeys.isWritable()){
SocketChannel sc = (SocketChannel) sscKeys.channel();
ByteBuffer buffer = (ByteBuffer) sscKeys.attachment();
int write = sc.write(buffer);
System.out.println("write"+write);
if(!buffer.hasRemaining()){
sscKeys.attach(null);
sscKeys.interestOps(sscKeys.interestOps()-SelectionKey.OP_WRITE);
}
}
}
}
}
}