最近在学习网络编程,我相信,除了做游戏的,IM的程序员们,其他的后端开发估计对于网络编程都挺薄弱,所以决定补一补网络编程,以及为了更好的学习Netty。下面是简单得使用ServerSocketChannel、SocketChannel和Selector做一个简单的服务端和客户端通信。ServerSocketChannel和SocketChannel都将注册到Selector中,然后轮询Selector去处理进入准备状态的通道的通信。
Talk is cheap,show you the code!
服务端:
package com.hyf.nio.socket;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
/**
* @author Howinfun
* @desc 服务端
* @date 2019/6/21
*/
public class TestServerSocketChannel {
public static void main(String[] args)throws Exception {
// 打开一个服务端通道
ServerSocketChannel channel = ServerSocketChannel.open();
// 非阻塞
channel.configureBlocking(false);
// 监听端口号8080
channel.socket().bind(new InetSocketAddress(8080));
// 打开一个Selector
Selector selector = Selector.open();
// 注册到Selector中,ACCEPT操作
channel.register(selector, SelectionKey.OP_ACCEPT);
// handler处理
ServerHandler handler = new ServerHandler();
// 不断轮询Selector
while (true){
// 当准备好的通道大于0才有往下的操作
if (selector.select()>0){
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
// 接收状态
if (key.isAcceptable()){
handler.handleAccept(key);
}
// 可读状态
if (key.isReadable()){
handler.handleRead(key);
}
// 处理过的key要移除掉
iterator.remove();
}
}
}
}
}
服务端通信处理:上面轮询Selector时,根据通道的状态来调用此处理器的方法来处理此次的通信
package com.hyf.nio.socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @author Howinfun
* @desc 服务器的通信处理
* @date 2019/6/21
*/
public class ServerHandler {
public void handleAccept(SelectionKey key) throws Exception{
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
// 获取客户端链接,并注册到Selector中
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
// 讲通道注册到Selector里头,然后设置为读操作,第三个参数是将你需要带的东西,可通过key获取
clientChannel.register(key.selector(),SelectionKey.OP_READ,ByteBuffer.allocate(48));
}
public void handleRead(SelectionKey key) throws Exception{
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
byteBuffer.clear();
long byteRead = clientChannel.read(byteBuffer);
if (byteRead == -1){
clientChannel.close();
}else{
// 读取客户端发送的消息,并做出相应返回对应的消息
byteBuffer.flip();
String receiveMsg = new String(byteBuffer.array(),0,byteBuffer.limit());
System.out.println("接收来自"+clientChannel.socket().getRemoteSocketAddress()+"的消息:"+receiveMsg);
// 返回回应给客户端
String sendMsg = "你好客户端,我已经接收到你的信息";
byteBuffer.clear();
byteBuffer.put(sendMsg.getBytes());
byteBuffer.flip();
clientChannel.write(byteBuffer);
// 关闭通道 PS:千万不要关闭通道,不然客户端那边就被关闭掉了。。
//clientChannel.close();
}
}
}
客户端:因为客户端要一直循环接收用户的输入,所以Selector的轮询是多开一条线程去处理
package com.hyf.nio.socket;
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.SocketChannel;
import java.util.Scanner;
/**
* @author Howinfun
* @desc 客户端
* @date 2019/6/21
*/
public class TestSocketChannel {
private static boolean flag = true;
public static void main(String[] args) throws Exception{
// 打开一个客户端通道
SocketChannel socketChannel = SocketChannel.open();
// 网络连接
socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
// 往selector里注册读操作,客户端往后的读操作都由selector去搞定
socketChannel.register(selector, SelectionKey.OP_READ);
// 启动一个线程去处理客户端的消息接收
Thread thread = new Thread(new ClientSelectorThread(selector));
thread.start();
new Thread(()->{
try {
// 可循环输入,读取控制台的输入
while (flag) {
System.out.print("请输入:");
//键盘输入数据
Scanner scan = new Scanner(System.in);
String msg = scan.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(48);
byteBuffer.clear();
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
// 给服务器写消息
socketChannel.write(byteBuffer);
}
} catch (IOException e) {
// 如果报错则断开循环输入
flag = false;
} finally {
//最后关闭资源
try {
socketChannel.close();
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
轮询客户端Selector的线程:
package com.hyf.nio.socket;
import lombok.AllArgsConstructor;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
/**
* @author Howinfun
* @desc 客户端的Selector,负责客户端的读操作
* @date 2019/6/21
*/
@AllArgsConstructor
public class ClientSelectorThread implements Runnable{
private Selector selector;
@Override
public void run() {
try {
ClientHandler clientHandler = new ClientHandler();
while(true){
if (selector.select()>0){
Iterator iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isReadable()){
clientHandler.handleRead(key);
}
iterator.remove();
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
客户端通信处理:上面轮询Selector时,根据通道的状态来调用此处理器的方法来处理此次的通信:
package com.hyf.nio.socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
/**
* @author Howinfun
* @desc 客户端的通信处理
* @date 2019/6/21
*/
public class ClientHandler {
public void handleRead(SelectionKey key) throws Exception{
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(48);
byteBuffer.clear();
long byteRead = clientChannel.read(byteBuffer);
if (byteRead == -1){
clientChannel.close();
}else{
byteBuffer.flip();
String receiveMsg = new String(byteBuffer.array(),0,byteBuffer.limit());
System.out.println("接收来自服务器的消息:"+receiveMsg);
}
}
}
这是自己写的例子,必须是能运行并且能玩的!如果各位老铁有什么疑问或者出现什么问题,欢迎留言~