原程序见疯狂讲义第4版 第811页至814页 下面贴出源代码
AIOClient.java
package fkJava4.ch17_3;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.concurrent.*;
/**
* Description:
* 网站: 疯狂Java联盟
* Copyright (C), 2001-2018, Yeeku.H.Lee
* This program is protected by copyright laws.
* Program Name:
* Date:
* @author Yeeku.H.Lee [email protected]
* @version 1.0
*/
public class AIOClient
{
final static String UTF_8 = "utf-8";
final static int PORT = 30000;
// 与服务器端通信的异步Channel
AsynchronousSocketChannel clientChannel;
JFrame mainWin = new JFrame("多人聊天");
JTextArea jta = new JTextArea(16 , 48);
JTextField jtf = new JTextField(40);
JButton sendBn = new JButton("发送");
public void init()
{
mainWin.setLayout(new BorderLayout());
jta.setEditable(false);
mainWin.add(new JScrollPane(jta), BorderLayout.CENTER);
JPanel jp = new JPanel();
jp.add(jtf);
jp.add(sendBn);
// 发送消息的Action,Action是ActionListener的子接口
Action sendAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
String content = jtf.getText();
if (content.trim().length() > 0)
{
try
{
// 将content内容写入Channel中
clientChannel.write(ByteBuffer.wrap(content
.trim().getBytes(UTF_8))).get(); //①
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
// 清空输入框
jtf.setText("");
}
};
sendBn.addActionListener(sendAction);
// 将Ctrl+Enter键和"send"关联
jtf.getInputMap().put(KeyStroke.getKeyStroke('\n'
, InputEvent.CTRL_DOWN_MASK) , "send");
// 将"send"和sendAction关联
jtf.getActionMap().put("send", sendAction);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.add(jp , BorderLayout.SOUTH);
mainWin.pack();
mainWin.setVisible(true);
}
public void connect()
throws Exception
{
// 定义一个ByteBuffer准备读取数据
final ByteBuffer buff = ByteBuffer.allocate(1024);
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(80);
// 以指定线程池来创建一个AsynchronousChannelGroup
AsynchronousChannelGroup channelGroup =
AsynchronousChannelGroup.withThreadPool(executor);
// 以channelGroup作为组管理器来创建AsynchronousSocketChannel
clientChannel = AsynchronousSocketChannel.open(channelGroup);
// 让AsynchronousSocketChannel连接到指定IP、指定端口
clientChannel.connect(new InetSocketAddress("127.0.0.1"
, PORT)).get();
jta.append("---与服务器连接成功---\n");
buff.clear();
clientChannel.read(buff, null
, new CompletionHandler<Integer,Object>() //②
{
@Override
public void completed(Integer result, Object attachment)
{
buff.flip();
// 将buff中内容转换为字符串
String content = StandardCharsets.UTF_8
.decode(buff).toString();
// 显示从服务器端读取的数据
jta.append("某人说:" + content + "\n");
buff.clear();
clientChannel.read(buff , null , this);
}
@Override
public void failed(Throwable ex, Object attachment)
{
System.out.println("读取数据失败: " + ex);
}
});
}
public static void main(String[] args)
throws Exception
{
AIOClient client = new AIOClient();
client.init();
client.connect();
}
}
上面代码大致意思是使用JavaSwing写一个客户端,使用AsynchronousSocketChannel进行通信,当多个客户端连接到服务端时,任何一个客户端更新消息后,服务端都会把消息传送给多个客户端
服务端更新消息代码
sc.read(buff , null
, new CompletionHandler<Integer,Object>() // ②
{
@Override
public void completed(Integer result
, Object attachment)
{
buff.flip();
// 将buff中内容转换为字符串
String content = StandardCharsets.UTF_8
.decode(buff).toString();
// 遍历每个Channel,将收到的信息写入各Channel中
for(AsynchronousSocketChannel c : AIOServer.channelList)
{
try
{
c.write(ByteBuffer.wrap(content.getBytes(
AIOServer.UTF_8))).get();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
buff.clear();
// 读取下一次数据
sc.read(buff , null , this);
}
下面是完整服务端代码
AIOServer.java
package fkJava4.ch17_3;
import java.net.*;
import java.io.*;
import java.util.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.concurrent.*;
/**
* Description:
* 网站: 疯狂Java联盟
* Copyright (C), 2001-2018, Yeeku.H.Lee
* This program is protected by copyright laws.
* Program Name:
* Date:
* @author Yeeku.H.Lee [email protected]
* @version 1.0
*/
public class AIOServer
{
static final int PORT = 30000;
final static String UTF_8 = "utf-8";
static List<AsynchronousSocketChannel> channelList
= new ArrayList<>();
public void startListen() throws InterruptedException,
Exception
{
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(20);
// 以指定线程池来创建一个AsynchronousChannelGroup
AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup
.withThreadPool(executor);
// 以指定线程池来创建一个AsynchronousServerSocketChannel
AsynchronousServerSocketChannel serverChannel
= AsynchronousServerSocketChannel.open(channelGroup)
// 指定监听本机的PORT端口
.bind(new InetSocketAddress(PORT));
// 使用CompletionHandler接受来自客户端的连接请求
serverChannel.accept(null, new AcceptHandler(serverChannel)); // ①
Thread.sleep(100000);
}
public static void main(String[] args)
throws Exception
{
AIOServer server = new AIOServer();
server.startListen();
}
}
// 实现自己的CompletionHandler类
class AcceptHandler implements
CompletionHandler<AsynchronousSocketChannel, Object>
{
private AsynchronousServerSocketChannel serverChannel;
public AcceptHandler(AsynchronousServerSocketChannel sc)
{
this.serverChannel = sc;
}
// 定义一个ByteBuffer准备读取数据
ByteBuffer buff = ByteBuffer.allocate(1024);
// 当实际IO操作完成时候触发该方法
@Override
public void completed(final AsynchronousSocketChannel sc
, Object attachment)
{
// 记录新连接的进来的Channel
AIOServer.channelList.add(sc);
// 准备接受客户端的下一次连接
serverChannel.accept(null , this);
sc.read(buff , null
, new CompletionHandler<Integer,Object>() // ②
{
@Override
public void completed(Integer result
, Object attachment)
{
buff.flip();
// 将buff中内容转换为字符串
String content = StandardCharsets.UTF_8
.decode(buff).toString();
// 遍历每个Channel,将收到的信息写入各Channel中
for(AsynchronousSocketChannel c : AIOServer.channelList)
{
try
{
c.write(ByteBuffer.wrap(content.getBytes(
AIOServer.UTF_8))).get();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
buff.clear();
// 读取下一次数据
sc.read(buff , null , this);
}
@Override
public void failed(Throwable ex, Object attachment)
{
System.out.println("读取数据失败: " + ex);
// 从该Channel读取数据失败,就将该Channel删除
AIOServer.channelList.remove(sc);
}
});
}
@Override
public void failed(Throwable ex, Object attachment)
{
System.out.println("连接失败: " + ex);
}
}
由于客户端是使用Swing写的界面,本人自己修改成JavaFx
并把数据收发做了点修改
工程目录
package sugar.utils.socket;
import com.alibaba.fastjson.JSON;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.UnsupportedEncodingException;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ExecutionException;
public class AioClientApp extends Application implements Observer {
private static TextArea textArea ;
private boolean isGetData ;
public static void main(String[] args)
throws Exception
{
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
TextField field = new TextField();
Button btn = new Button("发送");
vBox.getChildren().addAll(field,btn);
Scene scene = new Scene(vBox,650,600);
Stage stage = new Stage();
stage.setScene(scene);
stage.setTitle("客户端");
stage.show();
if (stage.isShowing()){
//当调用start()方法时
//会发送sugar.utils.socket.SimpleClientData
//的json数据,包含主机ip等
//等于第一次连接服务端,服务端会做一些处理
//服务端连接数据库处理客户端群、联系人等
//至于客户端需要下载大文件时应该让客户端重新发送请求,使用javaWeb技术作为后端
AioClientUtils clientUtils = AioClientUtils.getClient();
clientUtils.start();
textArea = clientUtils.getTextArea(); //使用 AioClientUtils的 TextArea
vBox.getChildren().add(0,textArea);
btn.setOnAction(event -> {
try {
SimpleClientData clientData = new SimpleClientData();
clientData.setInfo(field.getText());
String str= JSON.toJSONString(clientData);
clientUtils.sendData(str);
textArea.setText(field.getText());
} catch (UnsupportedEncodingException | ExecutionException | InterruptedException e) {
e.printStackTrace();
}
});
stage.setOnCloseRequest(event -> {
clientUtils.getExecutor().shutdown();
});
}
if (textArea.getText().trim()!= ""){
System.out.println("--------------客户端收到数据---------------");
}
}
@Override
public void update(Observable o, Object arg) {
}
public static TextArea getTextArea() {
return textArea;
}
}
在上面代码中实现了Observer接口
本想打算使用观察者模式,当被观察者发送通知,观察者自动更新
即下面代码会重写。但是后面并没有使用此方法。但是这并不影响。
@Override
public void update(Observable o, Object arg) {
}
下面我抽出核心写成一个工具类AioClientUtils
引用了com.alibaba.fastjson.JSON类用于发送和接收Json格式的数据。
package sugar.utils.socket;
import com.alibaba.fastjson.JSON;
import javafx.scene.control.TextArea;
import sugar.utils.obser.BaseObservable;
import sugar.utils.obser.BaseObserver;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Observer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AioClientUtils
{
private StringBuilder stringBuilder = OnlyRead.STRING_BUILDER;
public AioClientUtils() {
}
public static AioClientUtils getClient() {
return new AioClientUtils();
}
private final TextArea textArea = new TextArea();
private final BaseObserver observer = new BaseObserver() ;
private boolean isGetData = false;
private StringBuilder builder = new StringBuilder();
final static String UTF_8 = OnlyRead.UTF_8;
final static int PORT = OnlyRead.PORT;
// 创建一个线程池
private ExecutorService executor = Executors.newFixedThreadPool(10);
// 与服务器端通信的异步Channel
private AsynchronousSocketChannel clientChannel;
private Charset charset = Charset.forName(OnlyRead.UTF_8);
// 定义一个ByteBuffer准备读取数据
private final ByteBuffer byteBuffer = ByteBuffer.allocate(OnlyRead.BYTE_Buffer_Size);
private synchronized ByteBuffer getByteBuffer(){
return byteBuffer;
}
private synchronized AsynchronousSocketChannel getClientChannel(){
return clientChannel;
}
public void start() throws Exception {
// 以指定线程池来创建一个AsynchronousChannelGroup
AsynchronousChannelGroup channelGroup =
AsynchronousChannelGroup.withThreadPool(executor);
// 以channelGroup作为组管理器来创建AsynchronousSocketChannel
clientChannel = AsynchronousSocketChannel.open(channelGroup);
// 让AsynchronousSocketChannel连接到指定IP、指定端口
clientChannel.connect(new InetSocketAddress(OnlyRead.HOST
, PORT)).get();//必须调用get方法
//jta.append("---与服务器连接成功---\n");
byteBuffer.clear();
//
BaseObservable observable = new BaseObservable();
// BaseObserver observer = new BaseObserver();
observable.addObserver(observer);//使用全局变量的BaseObserver
//-----------发送的json数据
SocketAddress address = clientChannel.getLocalAddress();
InetAddress addr = InetAddress.getLocalHost();
System.out.println("Local HostAddress: "+ addr.getHostAddress());
String hostname = addr.getHostName();
System.out.println("Local host name: "+hostname);
SimpleClientData data = new SimpleClientData();
data.setIp(addr.getHostAddress());
data.setHostName(addr.getHostName());
data.setTitle("第一次客户端请求");
data.setDataType(DataType.Str);
//data.setId();
data.setInfo("第一次客户端请求");
//
String jsonString= JSON.toJSONString(data);
sendData(jsonString);
//----------初始化接收
getDataInit();
}
//发送json数据到服务端 String str= JSON.toJSONString(obj);
//传入的必须是json
public void sendData(String jsonString) throws UnsupportedEncodingException, ExecutionException, InterruptedException {
ByteBuffer byteSend = getByteBuffer();
getClientChannel()
.write(ByteBuffer
.wrap(jsonString.getBytes(OnlyRead.UTF_8)))
.get();
}
//当服务端发送数据就会执行 completed方法
public void getDataInit(){
ByteBuffer byteGet = getByteBuffer();
getClientChannel().read(byteGet, null, new CompletionHandler<Integer,Object>(){
@Override
public void completed(Integer result, Object attachment) {
// builder.delete(0,builder.length());
byteGet.flip();
// 将buff中内容转换为字符串
// builder.append(StandardCharsets.UTF_8
// .decode(byteGet).toString());
// String serverData = builder.toString();
//服务端传来的json转为SimpleServerData
SimpleServerData data =
JSON.parseObject(StandardCharsets.UTF_8
.decode(byteGet).toString(), SimpleServerData.class);
//
getServerData(data.toString());
byteGet.clear();
getClientChannel().read(byteGet, null , this);
}
@Override
public void failed(Throwable ex, Object attachment) {
System.out.println("读取数据失败: " + ex);
}
});
}
private void getServerData(String str){
textArea.setText(str);
stringBuilder.append(str);
}
//主动获取数据
public String getData2() throws ExecutionException, InterruptedException {
ByteBuffer byteGet = getByteBuffer();
Future<Integer> future = getClientChannel().read(byteGet);
future.get(); //必须调用get方法
byteGet.flip();
builder.append(charset.decode(byteGet).toString());
byteGet.clear();
return builder.toString();
}
public ExecutorService getExecutor() {
return executor;
}
public TextArea getTextArea() {
return textArea;
}
}
package sugar.utils.socket;
import javafx.scene.control.TextArea;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public final class OnlyRead {
public final static String UTF_8 = "utf-8";
public final static String HOST = "127.0.0.1";
public final static int PORT = 9971;
public final static List<AsynchronousSocketChannel> SOCKET_CHANNEL_LIST
= new ArrayList<>();
//使用HashSet
public final static Set<SocketChannelBean> SOCKET_CHANNEL_SET = new HashSet<>();
public final static TextArea TEXT_AREA_1 = new TextArea();
public final static TextArea TEXT_AREA_2 = new TextArea();
public final static TextArea TEXT_AREA_3 = new TextArea();
public static StringBuilder STRING_BUILDER = new StringBuilder();
public final static Integer BYTE_Buffer_Size = 1024*100;
public static TextArea getTextArea(){
return new TextArea();
}
}
package sugar.utils.socket;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//只作为客户端和服务端轻量级双向通信
public class AioServer extends Application {
static final int PORT = OnlyRead.PORT;
final static String UTF_8 = OnlyRead.UTF_8;
// static List channelList
// = new ArrayList<>();
public void startListen() throws InterruptedException,
Exception
{
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(20);
// 以指定线程池来创建一个AsynchronousChannelGroup
AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup
.withThreadPool(executor);
// 以指定线程池来创建一个AsynchronousServerSocketChannel
AsynchronousServerSocketChannel serverChannel
= AsynchronousServerSocketChannel.open(channelGroup)
// 指定监听本机的PORT端口
.bind(new InetSocketAddress(PORT));
// 使用CompletionHandler接受来自客户端的连接请求
//后期仍然需要再次调用
serverChannel.accept(null, new ServerHandler(serverChannel)); //
Thread.sleep(1000);
}
public static void main(String[] args)
throws Exception
{
AioServer server = new AioServer();
server.startListen();
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
//客户端连接数量
OnlyRead.SOCKET_CHANNEL_SET.size();
Scene scene = new Scene(vBox,650,600);
Stage stage = new Stage();
stage.setScene(scene);
stage.setTitle("服务端");
stage.show();
}
}
package sugar.utils.socket;
import com.alibaba.fastjson.JSON;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Iterator;
import java.util.stream.Collectors;
public class ServerHandler implements
CompletionHandler<AsynchronousSocketChannel, Object> {
private AsynchronousServerSocketChannel serverChannel;
public ServerHandler(AsynchronousServerSocketChannel sc) {
this.serverChannel = sc;
}
// 定义一个ByteBuffer准备读取数据
ByteBuffer buff = ByteBuffer.allocate(OnlyRead.BYTE_Buffer_Size);
// 当实际IO操作完成时候触发该方法
@Override
public void completed(final AsynchronousSocketChannel socketChannel
, Object attachment) {
// 记录新连接的进来的Channel
OnlyRead.SOCKET_CHANNEL_LIST.add(socketChannel);
//放入set
SocketChannelBean bean = new SocketChannelBean();
bean.setSocketChannel(socketChannel);
OnlyRead.SOCKET_CHANNEL_SET.add(bean);
// 准备接受客户端的下一次连接
serverChannel.accept(null, this);
//
socketChannel.read(buff, null
, new CompletionHandler<Integer, Object>() // ②
{
@Override
public void completed(Integer result
, Object attachment) {
buff.flip();
// 将buff中内容转换为字符串
String clientData = StandardCharsets.UTF_8
.decode(buff).toString();
//
//String转Object
SimpleClientData clientData1= JSON.parseObject(clientData, SimpleClientData.class);
System.out.println("客户端传来的数据"+clientData1.toString());
//解析 SimpleClientData
//删除不符合的
if (false) {
HashSet<SocketChannelBean> beans = (HashSet<SocketChannelBean>)
OnlyRead.SOCKET_CHANNEL_SET
.stream().filter(bean -> bean.getId().equals(""))
.collect(Collectors.toSet());
Iterator iterator = beans.iterator(); //迭代器
//迭代器使用方法
while (iterator.hasNext()) {
SocketChannelBean channelBean = (SocketChannelBean) iterator.next();
}
}
//解析客户端传来的数据
if (true) {
// 遍历每个Channel,将收到的信息写入各Channel中
for (AsynchronousSocketChannel c : OnlyRead.SOCKET_CHANNEL_LIST) {
try {
//返回服务端到客户端
SimpleServerData simpleServerData = new SimpleServerData();
simpleServerData.setInfo(clientData1.getInfo());
//转换为json
String serverData = JSON.toJSONString(simpleServerData);
//写入数据到 SocketChannel
c.write(ByteBuffer.wrap(serverData.getBytes(
AioServer.UTF_8))).get();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
//
buff.clear();
// 读取下一次数据
socketChannel.read(buff, null, this);
}
@Override
public void failed(Throwable ex, Object attachment) {
System.out.println("读取数据失败: " + ex);
// 从该Channel读取数据失败,就将该Channel删除
OnlyRead.SOCKET_CHANNEL_LIST.remove(socketChannel);
}
});
}
@Override
public void failed(Throwable ex, Object attachment) {
System.out.println("连接失败: " + ex);
}
}
完整代码已上传
传送门 https://download.csdn.net/download/pianai_s/12274784
注意客户端(AioClientApp)可以有多个实例,但是不同客户端
引用的 textArea需要注意了。
AioClientUtils clientUtils = AioClientUtils.getClient();
clientUtils.start();
textArea = clientUtils.getTextArea(); //使用 AioClientUtils的 TextArea
服务端只有一个。