基于NIO的多人聊天室java
客户端
package com.yc.chat.nio;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Label;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;
import com.yc.chat.utils.ShowBoxUtil;
import com.yc.chat.utils.StringUtil;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
/**
*
* @author : 外哥
* 邮箱 : liwai2012220663@163.com
* 创建时间:2020年12月21日 下午9:04:13
*/
public class Client implements Runnable {
protected Shell shell;
private Text text;
private Text text_1;
private Text text_2;
private Table table;
private Text text_4;
private Text text_3;
// 表格中当前用户的昵称
private TableItem tItem;
// 存储昵称
private String nickName = null ;
// 判断是否连接
private boolean connected = false ;
// 线程
private Thread th = null ;
// 发送的消息
private String msg = null ;
// 客户端channel
private SocketChannel socketChannel;
private Selector selector;
/**
* Launch the application.
* @param args
*/
public static void main(String[] args) {
try {
Client window = new Client();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open() {
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Create contents of the window.
*/
protected void createContents() {
shell = new Shell();
shell.setSize(1111, 763);
shell.setText("源辰聊天室");
shell.setLayout(null);
Label label = new Label(shell, SWT.NONE);
label.setBounds(78, 35, 76, 20);
label.setText("服务器地址");
Label label_1 = new Label(shell, SWT.NONE);
label_1.setText("服务器端口");
label_1.setBounds(341, 35, 76, 20);
Label label_2 = new Label(shell, SWT.NONE);
label_2.setBounds(609, 35, 50, 20);
label_2.setText("昵称");
text = new Text(shell, SWT.BORDER);
text.setText("127.0.0.1");
text.setBounds(176, 32, 121, 26);
text_1 = new Text(shell, SWT.BORDER);
text_1.setText("8000");
text_1.setBounds(435, 32, 121, 26);
text_2 = new Text(shell, SWT.BORDER);
text_2.setText("李外");
text_2.setBounds(665, 32, 84, 26);
text_3 = new Text(shell, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI);
text_3.setBounds(78, 97, 671, 484);
Button button = new Button(shell, SWT.NONE);
button.setBounds(821, 17, 237, 56);
button.setText("登录");
table = new Table(shell, SWT.BORDER);
table.setBounds(821, 97, 237, 484);
table.setHeaderVisible(true);
TableColumn column = new TableColumn(table, SWT.NONE);
column.setText("在线用户");
column.setWidth(236);
text_4 = new Text(shell, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI);
text_4.setBounds(78, 606, 671, 78);
Button btnNewButton = new Button(shell, SWT.NONE);
btnNewButton.setBounds(821, 606, 237, 78);
btnNewButton.setText("发送");
/**
* 登录按钮的点击事件
*/
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if ( connected == true ) {
ShowBoxUtil.showMessageBox(shell, "已经登录", "提示");
return ;
}
// 连接服务器
connect();
// 如果连接成功,则启用一个线程,随时等待服务器端发过来的消息
th = new Thread( Client.this ) ;
th.start();
}
});
/**
* 发送消息按钮的点击事件
*/
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
String content = text_4.getText().trim() ;
try {
// 如果要发送的消息为null,则提示信息
if ( StringUtil.checkNull( content)) {
ShowBoxUtil.showMessageBox(shell, "消息不能为空", "提示");
return ;
}
// 否则发送消息
content = "info#" + nickName + "说:\n" + content ;
// 将消息写入channel中
socketChannel.write( Charset.forName("UTF-8").encode(content)) ;
text_4.setText("");
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
}
/**
* 连接服务器的方法
*/
private void connect() {
String ip = text.getText().trim() ;
int port = Integer.parseInt( text_1.getText().trim() ) ;
try {
// 连接服务器
socketChannel = SocketChannel.open( new InetSocketAddress( ip , port )) ;
System.out.println( "成功连接" + ip );
// 设置channel为非阻塞式
socketChannel.configureBlocking( false ) ;
selector = Selector.open() ;
// 注册channel的 可读 监听事件
socketChannel.register(selector, SelectionKey.OP_READ ) ;
nickName = text_2.getText().trim() ;
connected = true ;
// 向服务器发送消息
socketChannel.write( Charset.forName("UTF-8").encode("login#" + nickName )) ;
} catch (Exception e) {
connected = false ;
e.printStackTrace();
}
}
@Override
public void run() {
try {
// 6.循环等待处理监听事件
while ( true ) {
// 获取可用的channel数量
int readyChannels = selector.select();
if ( readyChannels == 0 ) {
// 如果可用的数量为0,则开始下一次循环
continue ;
}
// 否则获取可用的channel集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
// 获取这个集合的迭代器
Iterator<SelectionKey> iterator = selectedKeys.iterator();
/**
* 对集合中的所有事件进行处理
*/
while ( iterator.hasNext() ) {
// 获取SelectionKey实例
SelectionKey selectionKey = iterator.next();
// 移除当前集合中的SelectionKey
iterator.remove();
// 7.根据就绪的状态,调用相应的处理逻辑
if ( selectionKey.isReadable() ) {
// 如果是可读事件
readHandler(selectionKey, selector);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 可读事件处理器
* @throws IOException
*/
private void readHandler( SelectionKey selectionKey , Selector selector ) throws IOException {
// 先获取此可读事件的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel() ;
// 创建buffer,并指定大小
ByteBuffer byteBuffer = ByteBuffer.allocate( 1024 ) ;
// 循环读取服务器channel中的内容
msg = "" ;
while ( socketChannel.read( byteBuffer) > 0 ) {
// 切换buffer为读模式
byteBuffer.flip() ;
// 通过UTF-8方式解码
msg += Charset.forName("UTF-8").decode( byteBuffer ) ;
}
System.out.println( msg );
// 将channel重新加入selector中,监听可读事件
socketChannel.register( selector , SelectionKey.OP_READ ) ;
// 将服务器响应的消息打印到本地
if ( msg.startsWith("info#")) {
// 聊天消息
Display.getDefault().asyncExec( new Runnable() {
@Override
public void run() {
text_3.append( "\n" + msg.replaceFirst("info#", ""));
}
});
}else if ( msg.startsWith("login#")) {
// 登录信息
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
tItem = new TableItem(table, SWT.None) ;
tItem.setText( msg.replaceFirst("login#", ""));
}
});
}
}
}
服务器端
package com.yc.chat.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.Pipe.SourceChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/**
*
* @author : 外哥
* 邮箱 : liwai2012220663@163.com
* 创建时间:2020年12月30日 下午5:22:02
*/
@SuppressWarnings("all")
public class NIOServer {
private boolean connected = false ;
/**
* 主方法
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer() ;
server.start();
}
/**
* 启动方法
* @throws IOException
*/
public void start() throws IOException {
// 1.创建Selector
Selector selector = Selector.open() ;
// 2.创建channel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 3.为channel通道绑定监听端口
serverSocketChannel.bind( new InetSocketAddress(8000) ) ;
// 4.将channel设置为非阻塞模式
serverSocketChannel.configureBlocking( false ) ;
// 5.将channel注册到selector中,监听接入事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) ;
// 打印日志信息
System.out.println("服务器启动成功,占用端口8000");
connected = true ;
// 6.循环等待处理监听事件
while ( true ) {
// 获取可用的channel数量
int readyChannels = selector.select();
if ( readyChannels == 0 ) {
// 如果可用的数量为0,则开始下一次循环
continue ;
}
// 否则获取可用的channel集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
// 获取这个集合的迭代器
Iterator<SelectionKey> iterator = selectedKeys.iterator();
/**
* 对集合中的所有事件进行处理
*/
while ( iterator.hasNext() ) {
// 获取SelectionKey实例
SelectionKey selectionKey = iterator.next();
// 移除当前集合中的SelectionKey
iterator.remove();
// 7.根据就绪的状态,调用相应的处理逻辑
if ( selectionKey.isAcceptable() ) {
// 如果是客户端接入事件
acceptHandler(serverSocketChannel, selector);
}
if ( selectionKey.isReadable() ) {
// 如果是可读事件
readHandler(selectionKey, selector);
}
}
}
}
/**
* 接入事件处理器
* @throws IOException
*/
private void acceptHandler( ServerSocketChannel serverSocketChannel,
Selector selector)
throws IOException {
// 获取链接的客户端channel
SocketChannel socketChannel = serverSocketChannel.accept();
// 将socketchannel设置为非阻塞
socketChannel.configureBlocking( false );
// 将socketchannel注册到selector上,监听可读事件
socketChannel.register(selector, SelectionKey.OP_READ) ;
// 回复客户端提示信息,通过UTF-8方式编码
// socketChannel.write( Charset.forName("UTF-8").encode("请注意隐私安全")) ;
}
/**
* 可读事件处理器
* @throws IOException
*/
private void readHandler( SelectionKey selectionKey , Selector selector ) throws IOException {
// 先获取此可读事件的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel() ;
// 创建buffer,并指定大小
ByteBuffer byteBuffer = ByteBuffer.allocate( 1024 ) ;
// 循环读取客户端channel中的内容
String request = "" ;
while ( socketChannel.read( byteBuffer) > 0 ) {
// 切换buffer为读模式
byteBuffer.flip() ;
// 通过UTF-8方式解码
request += Charset.forName("UTF-8").decode( byteBuffer ) ;
}
// 将channel重新加入selector中,监听可读事件
socketChannel.register( selector , SelectionKey.OP_READ ) ;
// 如果是登录信息,则获取昵称
// if ( request.startsWith("login#")) {
// //request = request.replaceFirst("login#", "") ;
// }
// 发送群聊消息,将消息发送给每一个连接上这个服务器的人
// 将客户端发送的信息广播给其他的用户
if ( request.length() > 0 ) {
System.out.println( request );
broadCast( selector , socketChannel , request );
}
// 说明是下线消息
if ( request.startsWith("exit#")) {
this.connected = false ;
}
}
/**
* 广播方法
*/
private void broadCast( Selector selector , SocketChannel sourceChannel , String request) {
/**
* 获取所有的客户端channel
*/
Set<SelectionKey> selectionKeys = selector.keys();
/**
* 给所有的客户端channel发送消息
*/
selectionKeys.forEach( selectionKey -> {
// 获取客户端channel
Channel socketChannel = selectionKey.channel() ;
// 如果这个channel是客户端channel,则发送消息
if ( socketChannel instanceof SocketChannel) {
try {
((SocketChannel) socketChannel).write( Charset.forName("UTF-8").encode( request )) ;
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
总结:从整个开发过程中,对nio的使用流程和规律有了一定的掌握。通过对比nio和bio发现,bio的使用简单,但开启的线程数过多,占用资源,而nio拓展出了一个对线程事件的管理机制,有效的解决了bio的弊端。