简易版多人聊天室——NIO

基于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的弊端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值