IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> Java NIO之TCP通信 -> 正文阅读

[网络协议]Java NIO之TCP通信

目录

1.客户端相关知识与代码:

?SocketChannel API列表:

客户端代码执行流程:

Client端代码:

2.服务端知识与代码:

ServerSocketChannel API列表:

Server端代码执行流程:

Server端代码:


涉及到Buffer, ServerSocketChannel, SocketChannel, Selector,?SelectionKey等类的使用。

1.客户端相关知识与代码:

?SocketChannel API列表:

客户端代码执行流程:

Note: 这里只是连接服务端,并且向服务端发送数据的代码,没有从服务端读取数据的代码,感兴趣的可以自行研究,后期我会补上。

  1. 得到一个网络通道?SocketChannel socketChannel = SocketChannel.open(), 调用了open()这个API
  2. 设置非阻塞:socketChannel.configureBlocking(false), 调用了configureBlocking这个API
  3. 连接服务器:socketChannel.connect(inetSocketAddress),调用了connect(......)这个API
  4. 如果上面的连接不成功,则调用socketChannel.finishConnect(),非阻塞的持续连接服务器,直到成功为止,调用了finishConnect()这个API
  5. 连接成功,发送数据: socketChannel.write(buffer),调用了write(......)这个API

Client端代码:

package com.bruce.javanio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClient {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		//得到一个网络通道
		SocketChannel socketChannel = SocketChannel.open();
		
		//设置非阻塞
		try {
			socketChannel.configureBlocking(false);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//设置服务端的ip和端口号
		InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
		
		//连接服务器
		if (!socketChannel.connect(inetSocketAddress)) {
			//如果连接不成功,那么就用finishConnect(),进行非阻塞的循环
			while (!socketChannel.finishConnect()) {
				System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作");
			}
		}
		
		//如果连接成功,则发送数据
		String str = "hello, Bruce";
		ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
		//发送数据
		socketChannel.write(buffer);
		System.in.read();//让代码停在这里,不要结束。
	}

}

2.服务端知识与代码:

ServerSocketChannel API列表:

Server端代码执行流程:

Note: 这里只是读取客户端数据的代码,如果想要向客户端发送数据,很简单,调用selector.keys()得到所有注册到selector的通道(包括一个ServerSocketChannel和一个或多个SocketChannel),从中剔除掉ServerSocketChannel, 然后使用socketChannel.write(buffer),就可以完成向客户端发送数据。

  1. 创建serverSocketChannel: ServerSocketChannel.open(),调用了open()这个API
  2. 绑定端口: serverSocketChannel.socket().bind(new InetSocketAddress(6666)),调用了bind(......)这个API
  3. 设置为非阻塞:serverSocketChannel.configureBlocking(false),调用了configureBlocking(......)这个API
  4. 得到一个Selector对象:Selector selector = Selector.open(),调用了open()这个API
  5. 把serverSocketChannel注册到selector:serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT),调用了register(......)这个API
  6. 检测注册的通道有没有发生事件:if (selector.select(1000) == 0),前面只注册了一个serverSocketChannel,所有目前只能是客户端连接事件SelectionKey.OP_ACCEPT。如果有事件发生,则获取发生的事件的集合Set<SelectionKey> selectedKeys = selector.selectedKeys(),然后遍历这个集合。
  7. (1).如果发生了客户端连接事件,那么得到客户端的SockerChannel(通过serverSocketChannel.accept()),然后将其注册到selector。
  8. (2).如果客户端发送了数据,那么通过SelectionKey反向获取到对应的SocketChannel,然后将数据读取出来socketChannel.read(buffer),进行业务处理

Server端代码:

package com.bruce.javanio;

import java.io.IOException;
import java.net.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.util.Iterator;
import java.util.Set;

public class NIOServer {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			//1.创建serverSocketChannel -> 对应于BIO的serverSocket
			ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
			//2绑定一个端口6666,在服务器端监听
			serverSocketChannel.socket().bind(new InetSocketAddress(6666));
			//3.设置为非阻塞
			serverSocketChannel.configureBlocking(false);
			
			//4.得到一个Selector对象
			Selector selector = Selector.open();
			
			//5.把serverSocketChannel注册到selector
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
			
			//循环等待客户端连接
			while(true){
				//等待1s,没有事件发生,继续select
				if (selector.select(1000) == 0) { 
					System.out.println("Debug: 没有任何事件发生");
					System.out.println("Debug: 服务器等待了1s,无连接");
					continue;
				}
				//有事件发生,拿到有事件发生的SelectionKey集合(对应Selector类中的集合对象 Set<SelectionKey> selectedKeys)
				//通过SelectionKey反向获取通道
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				
				//使用迭代器遍历Set<SelectionKey> selectedKeys
				Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
				
				//循环处理每个事件
				while (keyIterator.hasNext()) {
					//获取到SelectionKey
					SelectionKey key = keyIterator.next();
					
					//根据key所对应的通道发生的事件,进行相应的处理
					if (key.isAcceptable()) {//如果是OP_ACCEPT,有新的客户端连接
						//为发送连接请求的客户端生成一个SocketChannel,这里不会阻塞,因为有客户端来连接了,这里会立即执行
						SocketChannel socketChannel = serverSocketChannel.accept();
						System.out.println("Debug: 客户端连接成功,生成了一个sockerChannel " + socketChannel.hashCode() );
						//将socketChannel设置为非阻塞
						socketChannel.configureBlocking(false);
						//将SocketChannel注册到selector, 本例中,由于是客户端发送数据到服务端,所以服务端应该是读取数据SelectionKey.OP_READ。
						//并且关联一个Buffer, 服务器端也是有Buffer的。
						socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
					}
					
					if(key.isReadable()){
						//通过SelectionKey反向获取SocketChannel
						SocketChannel socketChannel = (SocketChannel) key.channel();
						//获取到socketChannel关联的buffer(上面key.isAcceptable()注册的时候指定的Buffer)
						ByteBuffer buffer = (ByteBuffer) key.attachment();
						socketChannel.read(buffer);
						System.out.println("from 客户端 :" + new String(buffer.array()) );
					}
					
					//手动从集合中移除当前的key,因为已经处理完了,防止下次重复操作。
					keyIterator.remove();
				}
				
			}
			

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

两个错误的处理:

1.服务端在注册连接过来的SockerChannel的时候,首先要将其设置为非阻塞,不然会报下面的错误。

//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);

2. 客户端发送数据后,不能立即结束,不然服务端会报错:

需要在客户端代码的最后,加上下面两行代码,因为socketChannel.write(buffer);不是阻塞操作,如果不加System.in.read(),那么客户端代码直接结束,而socketChannel.write(buffer)还没有执行完毕。

?? ???? socketChannel.write(buffer);
?? ??? ?System.in.read();//让代码停在这里,不要结束。

?执行结果如下:

?此时再起另外一个客户端,两个客户端SockerChannel的hashCode不一致。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-10-30 12:49:11  更:2021-10-30 12:49:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年7日历 -2024/7/1 21:07:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码