JAVA基于NIO客户端对客户端简单聊天DEMO(服务器转发消息)

来源:互联网 发布:淘宝8点上新怎么抢最快 编辑:程序博客网 时间:2024/09/21 08:59

自学JAVA,学到网络通信socket,很困惑,想写一个客户端对客户端的简单例子,但是网上一搜,全都是客户端对服务端,很无奈,百度提问一个月没回答,因此自己寻找各种途径,终于写成。代码如下,其中必然有很多代码冗余和bug,例子并不规范,只是实现了核心功能。希望对有同样困惑的同学有所帮助启发。

不过代码中有一些问题我自己还没思考到,但是不影响本帖子核心功能点的实现。其次,有些注释代码是写代码的过程中出现的错误,我保留了痕迹没删除,只是注释掉了。


首先需要几个工具类:

1,窗体拖动功能类,因为JFrame和JDialog去除边框后不能拖动。代码如下

package nio.file;import java.awt.Point;import java.awt.Window;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.awt.event.MouseMotionAdapter;/*window drag 工具类*/class WindowDrag{private static Point point;//匿名内部类中访问局部变量,这个局部变量一定要用final修饰public static void allowDrag(final Window window){window.addMouseListener(new MouseAdapter(){public void mousePressed(MouseEvent e){//获取鼠标按下时的坐标point=e.getPoint();}});//注册鼠标拖动监听器window.addMouseMotionListener(new MouseMotionAdapter(){//鼠标拖动方法public void mouseDragged(MouseEvent e){//获取窗体的坐标点Point win_old=window.getLocation();//获取鼠标的时时坐标点Point mouse_new=e.getPoint();//鼠标旧坐标x y值double mouse_x_old=point.getX();double mouse_y_old=point.getY();//鼠标新坐标x y值double mouse_x_new=mouse_new.getX();double mouse_y_new=mouse_new.getY();//鼠标坐标x y增长值double mouse_x_increment=mouse_x_new - mouse_x_old;double mouse_y_increment=mouse_y_new - mouse_y_old;//窗体旧坐标x y值double win_x_old=win_old.getX();double win_y_old=win_old.getY();//窗体新坐标x y值(旧值加上增量)//窗体用setLocation方法设置坐标的时候要用int,因此需要强转int win_x_new=(int)(win_x_old + mouse_x_increment);int win_y_new=(int)(win_y_old + mouse_y_increment);//设置窗体坐标值window.setLocation(win_x_new,win_y_new);}});}}

2,消息类型参数类,存放的就是一堆字符串常量静态字段

package nio.file;/*参数常量存储消息类的字段该类存储的都是消息类的常量字符串字段*/class MessageConstants{/**发出消息方:服务端*/public static String SERVER="SERVER";/**发出消息方:客户端*/public static String CLIENT="CLIENT";/**消息类型:请注册消息*/public static String REGISTER="REGISTER";/**消息类型:注册成功消息*/public static String REGISTER_SUCCESS="REGISTER_SUCCESS";/**消息类型:欢迎消息*/public static String WELCOME="WELCOME";/**消息类型:申请会话*/public static String APPLY="APPLY";/**消息类型:申请成功*/public static String APPLY_SUCCESS="APPLY_SUCCESS";/**消息类型:申请失败*/public static String APPLY_FIELD="APPLY_FIELD";/**消息类型:正常消息*/public static String NORMAL="NORMAL";}

3,图片对象获取类,这个是为了简单优化界面用的,获取图片ImageIcon类对象,设置一些组件的背景,icon什么的,代码如下

package nio.file;import javax.swing.ImageIcon;/*图片获取工具*/class PictureUtil{public static ImageIcon getImageIcon(String pic_name){return new ImageIcon(PictureUtil.class.getClassLoader().getResource("nio/file/image/"+pic_name));}}
4,消息类,这个很重要,不管是客户端还是服务端,在发送消息的时候,消息必须具备特定的格式,要不然收到消息方很难判断这个消息类型,或者消息的发送者是谁。消息类要包含几个必须的字段,比如发送者nick,消息类型style(消息类型字段就存放在上面2中的消息类型工具类中),比如是验证消息,申请消息,注册消息,等等,还有消息内容,content等,喜欢的话还可以加上发送消息时间等。消息类其实就是一个JavaBean,代码如下:

package nio.file;/*消息类,存放消息的各种参数1,发送者/接受者ip,端口,时间,以及发送者身份,如客户端,服务端2,发送消息类型,以及消息内容*/class Message{/**发送者ip*/private String sender_ip;/**发送者端口*/private int sender_port;/**发送时间*/private String send_date;/**发送者昵称*/private String sender_nick;/**接收方IP*/private String receive_ip;/**接收方端口*/private int receive_port;/**聊天对方nick*/private String talk_nick;/**消息类型*/private String style;/**消息内容*/private String content;public String getSender_ip() {return sender_ip;}public void setSender_ip(String sender_ip) {this.sender_ip = sender_ip;}public int getSender_port() {return sender_port;}public void setSender_port(int sender_port) {this.sender_port = sender_port;}public String getSender_nick(){return sender_nick;}public void setSender_nick(String sender_nick){this.sender_nick=sender_nick;}public String getTalk_nick(){return talk_nick;}public void setTalk_nick(String talk_nick){this.talk_nick=talk_nick;}public String getSend_date() {return send_date;}public void setSend_date(String send_date) {this.send_date = send_date;}public String getReceive_ip() {return receive_ip;}public void setReceive_ip(String receive_ip) {this.receive_ip = receive_ip;}public int getReceive_port() {return receive_port;}public void setReceive_port(int receive_port) {this.receive_port = receive_port;}public String getStyle() {return style;}public void setStyle(String style) {this.style = style;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}/**构造函数*/public Message(String style,String content){this.style=style;this.content=content;}public Message(String content){this.content=content;}}

5,将消息类转换成Json字符串的工具类JsonUtil,为什么要转换?因为不管是服务器还是客户端,在通过通道channel发送消息的时候发送的都是用缓冲区发送的字节,4步骤中的消息类包含的信息是要发送的内容,肯定不能直接把消息对象发出去,必须把消息类对象转成字符串,但是转化后的字符串必须符合一定的规范,要么是XML,要么是JSon,Json更常用,这里需要导入一个第三方jar包,叫:gson-1.6.jar,网上搜索随便下载,很多。代码如下

package nio.file;import com.google.gson.Gson;/*Gson工具类功能:1,将对象转换成Json字符串2,将字符串转成对应的类对象*/class JsonUtil{//将类对象转成Json字符串public static String toJson(Message msg){Gson gson=new Gson();String content=gson.toJson(msg);return content;}//将字符串转成指定的类对象public static Message toBean(String content){Gson gson=new Gson();Message msg= gson.fromJson(content,Message.class);return msg;}}

6,服务端,其实客户端对客户端,就是通过服务端的消息转发,比如A要发消息给B,AB首先都需要链接服务器,然后注册自己的NICK,服务器吧个字的NICK和socketAddress全存入一个map集合,然后再把socketAddress和对应的socketChannel存入一个map集合,A要发消息给B,那么A发送给服务器的消息类中就必须包含聊天对象B的nick,服务器根据B的nick找到B的通道,然后将消息转发给B的通道即可。这就是逻辑,服务器代码如下(服务器代码有很多中写法,可以用线程,我这个不是标准,只是实现)

package nio.file;import java.io.IOException;import java.net.InetSocketAddress;import java.net.SocketAddress;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.HashMap;import java.util.Set;/*基于NIO的服务端*/class ServerByNio{/**通道管理器*/private Selector selector;/**服务器通道*/private ServerSocketChannel serverSocketChannel;/**为服务器分配的ip*/private String host="localhost";/**为服务器程序分配的端口*/private int PORT=8888;/**客户端通道*//**存储发送消息者昵称和发送方ip和端口对象的map集合*/private HashMap<String,SocketAddress> nick_Address_map;/**存储发送方地址对象,和发送方通道对象的集合*/private HashMap<SocketAddress,SocketChannel> address_SocketChannel_map;//构造函数,初始化各个参数ServerByNio() throws IOException{//初始两个map集合nick_Address_map=new HashMap<String,SocketAddress>();address_SocketChannel_map=new HashMap<SocketAddress,SocketChannel>();//初始化选择器selector=Selector.open();//初始化服务器通道serverSocketChannel=ServerSocketChannel.open();//至此,服务器已经启动System.out.println("服务器已经启动!");//服务器通道绑定ip和端口serverSocketChannel.socket().bind(new InetSocketAddress(host,PORT));//设置通道非阻塞serverSocketChannel.configureBlocking(false);//将通道与通道管理器绑定,并且注册通道感兴趣的事件,通过register方法实现/**注释:服务器通道感兴趣的事件一般就是接收事件*/serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);}//通道管理器轮询监听其管理(即与之绑定)的通道是否有处于就绪状态的public void listen(){while(true){try{selector.select();//该方法阻塞,返回的是就绪的通道的个数//如果程序接着执行,代表有通道就绪,即有事件发生,此时,选择器通过selectedKeys方法返回触发的选择键//选择键,每触发一个事件,代表一个选择键被选择System.out.println("客户端链接成功!!");Set<SelectionKey> keys=selector.selectedKeys();for(SelectionKey key:keys){keys.remove(key);if(!key.isValid())continue;handelKey(key);}}catch(IOException e){System.out.println("跑了异常了");e.printStackTrace();}}}public void handelKey(SelectionKey key) throws IOException{//客户端通道对象SocketChannel socketChannel=null;//判断key的类型if(key.isValid() && key.isAcceptable()){//客户端通道通过accept方法接收到申请链接的客户端通道socketChannel=serverSocketChannel.accept();//至此,客户端链接服务器成功System.out.println("客户端链接服务器成功!");//设置客户端通道非阻塞socketChannel.configureBlocking(false);//将这个通道与服务端通道管理器绑定,并且注册感兴趣的事件socketChannel.register(selector,SelectionKey.OP_READ);//欢迎消息doWelcome(socketChannel);//将客户端通道的原地址,和客户端通道存入map集合address_SocketChannel_map.put(socketChannel.socket().getRemoteSocketAddress(),socketChannel);System.out.println(socketChannel.socket().getRemoteSocketAddress().toString());System.out.println(address_SocketChannel_map);}if(key.isValid()&& key.isReadable()){//获取触发key的通道socketChannel=(SocketChannel)key.channel();ByteBuffer buffer=ByteBuffer.allocate(1024);//定义存储读取到的数据的StringString content="";buffer.clear();//通过socketChannel的read方法把数据读取入缓冲区int count=socketChannel.read(buffer);if(count>0){//count>0代表读取到数据buffer.flip();byte[] data=new byte[buffer.remaining()];buffer.get(data);//用缓冲区的数据元素依次填充date数组content+=new String(data);//处理读取到的数据doReadData(socketChannel,content);}//难道这句话的执行逻辑是这样的:将key设置为下一次可读,没读完继续读?//如果不是,上面的不是应该用while循环吗?if只是读取依次,万一没读完,咋办?key.interestOps(SelectionKey.OP_READ);if(count==-1){//System.out.println("count="+count);//count==-1代表客户端已经关闭,那这个键就没意义了,那么就取消改建,关闭客户端通道key.cancel();//这句话的实际意思是,取消触发该键的通道到选择器的绑定注册System.out.println("count="+count);try{if(socketChannel!=null){socketChannel.close();System.out.println("关闭客户端通道");}}catch(IOException e){e.printStackTrace();}}//处理读取到的数据//doReadData(socketChannel,content);}}public void doWelcome(SocketChannel socketChannel) throws IOException{//链接服务器成功,服务器要求客户端输入昵称信息String wel_msg="连接服务器成功,请注册您的昵称:";Message msg=new Message(wel_msg);msg.setStyle(MessageConstants.REGISTER);String msg_json=JsonUtil.toJson(msg);socketChannel.write(ByteBuffer.wrap(msg_json.getBytes()));System.out.println("服务端欢迎消息执行完成");}public void doReadData(SocketChannel socketChannel,String content) throws IOException{Message msg=JsonUtil.toBean(content);if(MessageConstants.REGISTER.equals(msg.getStyle())){//获取消息发送方的nickString nick=msg.getSender_nick();//这个地方要注意,getLocalSocketAddress()返回的是该通道申请链接的服务器的socketAddress,即使申请连接的服务器的ip和端口//因为开启的客户端申请连接的服务器都一样,因此通过该方法获取的socketAddress对象都一样//这里存储的必须是客户端通道自身的socketAddress,必须是自己的ip和端口,在同一台主机上,开启多个客户端的时候,ip虽然一样,但是随机分配的端口是不一样的,获取的socketAddress也不一样,因此//可以在同一台主机上启动多个端口互发消息而不会产生错发或者错收消息的情况。一台主机开多个qq道理一样。nick_Address_map.put(nick,socketChannel.socket().getRemoteSocketAddress()); System.out.println(nick+":"+nick_Address_map.get(nick).toString()); System.out.println(nick_Address_map);// System.out.println(nick+":"+socketChannel.socket().getRemoteSocketAddress().toString());// System.out.println("服务端存放nick和地址成功");doRegistSuc(socketChannel,nick);}if(MessageConstants.APPLY.equals(msg.getStyle())){//获取申请聊天对象的nickString talk_nick=msg.getTalk_nick();//获取nick和地址map集合的key值set集合,判断这个talk_nick是否存在.// Set<String>keys=nick_Address_map.keySet();boolean flag=judgeKey(talk_nick,nick_Address_map);if(flag){String text=msg.getSender_nick()+",您好,您申请与"+talk_nick+"会话连接成功,请开始会话!";Message msg1=new Message(MessageConstants.APPLY_SUCCESS,text);String msg_json=JsonUtil.toJson(msg1);socketChannel.write(ByteBuffer.wrap(msg_json.getBytes()));}else{String text=msg.getSender_nick()+",您好,您申请与"+talk_nick+"会话连接失败,因为不存在这个id,请检查!";Message msg2=new Message(MessageConstants.APPLY_FIELD,text);String msg_json=JsonUtil.toJson(msg2);socketChannel.write(ByteBuffer.wrap(msg_json.getBytes()));}}if(MessageConstants.NORMAL.equals(msg.getStyle())){//正常的通信,服务器找到nick对应的客户端通道,进行消息转发String nick=msg.getTalk_nick();System.out.println(nick);SocketChannel socketChannel1=address_SocketChannel_map.get(nick_Address_map.get(nick));//将需要转发的消息写入对应的通道,需要转发的消息就是刚刚读取到的消息contentsocketChannel1.write(ByteBuffer.wrap(content.getBytes()));}}public boolean judgeKey(String key,HashMap<String,SocketAddress> map){Set<String>keys=map.keySet();for(String k:keys){if(key.equals(k))return true;}return false;}public void doRegistSuc(SocketChannel socketChannel,String nick) throws IOException{//注册成功后,服务器返回消息:nick+注册成功,欢迎开启聊天!String content=nick+"您好,恭喜您注册成功,欢迎开启聊天!";Message msg=new Message(MessageConstants.REGISTER_SUCCESS,content);String msg_json=JsonUtil.toJson(msg);socketChannel.write(ByteBuffer.wrap(msg_json.getBytes()));}public void close(){if(serverSocketChannel!=null){try{serverSocketChannel.close();}catch(IOException e){e.printStackTrace();}try{selector.close();}catch(IOException e){e.printStackTrace();}}}public static void main(String[] args) throws IOException {new ServerByNio().listen();}}

7,客户端,用swing简单的界面实现

package nio.file;import java.awt.BorderLayout;import java.awt.Color;import java.awt.Graphics;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.KeyAdapter;import java.awt.event.KeyEvent;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;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.text.SimpleDateFormat;import java.util.Date;import java.util.Set;import javax.swing.BorderFactory;import javax.swing.Box;import javax.swing.BoxLayout;import javax.swing.JButton;import javax.swing.JDialog;import javax.swing.JLabel;import javax.swing.JOptionPane;import javax.swing.JPanel;import javax.swing.JTextArea;import javax.swing.JTextField;import javax.swing.UIManager;import javax.swing.WindowConstants;import javax.swing.border.TitledBorder;/*客户端*/class ClientByNio extends JDialog implements ActionListener{/** *  */private static final long serialVersionUID = 1L;/**客户端通道管理器*/private Selector selector;/**客户端通道*/private SocketChannel socketChannel;/**申请链接服务器的IP*/private String host="localhost";/**申请链接服务器端口*/private int PORT=8888;/**主面板*/private JPanel contentPane;/**顶部面板*/private JPanel top_pane;/**我是文本框*/private JTextField my_nick;/**注册按钮*/private JButton btn_register;/**对方名字文本框*/private JTextField talk_nick;/**开启聊天按钮*/private JButton talk_btn;/**关闭标签*/private JLabel exit_btn;/**中部消息文本区域*/private JTextArea show_msg_area;/**底部面板*/private JPanel bottom_pane;/**发送消息文本区域*/private JTextArea send_msg_area;/**清空消息面板按钮*/private JButton clear_btn;/**取消发送消息按钮*/private JButton cancel_btn;/**发送消息按钮*/private JButton send_btn;/**关闭按钮*/private JButton close_btn;//构造函数,初始化各字段ClientByNio() throws IOException{//初始化界面initGui();//注册监听器initLisenter();//通道初始化initChannel();}public void initChannel() throws IOException{selector=Selector.open();//开启客户端通道socketChannel=SocketChannel.open(new InetSocketAddress(host,PORT));//设置通道非阻塞socketChannel.configureBlocking(false);//与通道管理器绑定并注册感兴趣事件socketChannel.register(selector,SelectionKey.OP_READ);listen();}public void listen(){while(true){try{selector.select();//该方法阻塞,返回的是就绪的通道的个数//如果程序接着执行,代表有通道就绪,即有事件发生,此时,选择器通过selectedKeys方法返回触发的选择键//选择键,每触发一个事件,代表一个选择键被选择Set<SelectionKey> keys=selector.selectedKeys();for(SelectionKey key:keys){keys.remove(key);if(!key.isValid())continue;handelKey(key);}}catch(IOException e){e.printStackTrace();}}}public void handelKey(SelectionKey key) throws IOException{if(key.isValid() && key.isReadable()){//获取触发key的通道SocketChannel socketChannel=(SocketChannel)key.channel();String content="";//空字符串的长度就是0ByteBuffer buffer=ByteBuffer.allocate(1024);buffer.clear();int count=socketChannel.read(buffer);if(count>0){buffer.flip();byte[] data=new byte[buffer.remaining()];buffer.get(data);content+=new String(data);//处理读取到的数据doReadData(socketChannel,content);}//设置下一次可读key.interestOps(SelectionKey.OP_READ);if(count==-1){//count==-1代表客户端已经关闭,那这个键就没意义了,那么就取消改建,关闭客户端通道key.cancel();//这句话的实际意思是,取消触发该键的通道到选择器的绑定注册try{if(socketChannel!=null){socketChannel.close();}}catch(IOException e){e.printStackTrace();}}//处理读取到的数据,不该在这里处理//doReadData(socketChannel,content);}}public void doReadData(SocketChannel socketChannel,String content){Message msg=JsonUtil.toBean(content);String data=msg.getContent();if(MessageConstants.REGISTER.equals(msg.getStyle())){JOptionPane.showConfirmDialog(null,data);}if(MessageConstants.REGISTER_SUCCESS.equals(msg.getStyle())){JOptionPane.showConfirmDialog(null,data);//聊天对象窗口获取焦点talk_nick.requestFocus();}if(MessageConstants.APPLY_SUCCESS.equals(msg.getStyle())){JOptionPane.showConfirmDialog(null,data);send_msg_area.setText("");send_msg_area.requestFocus();}if(MessageConstants.APPLY_FIELD.equals(msg.getStyle())){JOptionPane.showConfirmDialog(null,data);talk_nick.setText("");talk_nick.requestFocus();}if(MessageConstants.NORMAL.equals(msg.getStyle())){String nick_from=msg.getSender_nick();String date=msg.getSend_date();//获取talk_nick//String talk_nick_value=msg.getTalk_nick();talk_nick.setText(nick_from);//String content=msg.getContent();String text=nick_from+"  "+date+"\n"+data+"\n"+"\n";show_msg_area.append(text);}}public void initLisenter(){btn_register.addActionListener(this);talk_btn.addActionListener(this);close_btn.addActionListener(this);clear_btn.addActionListener(this);cancel_btn.addActionListener(this);send_btn.addActionListener(this);send_msg_area.addKeyListener(new KeyAdapter(){public void keyPressed(KeyEvent e){if(e.getKeyCode()==KeyEvent.VK_ENTER){send_msg();}}//添加keyReleased方法,避免发送消息后系统认为ENTER键换行public void keyReleased(KeyEvent e){if(e.getKeyCode()==KeyEvent.VK_ENTER){send_msg_area.setText("");send_msg_area.requestFocusInWindow();send_msg_area.setCaretPosition(0);}}});//退出按钮事件exit_btn.addMouseListener(new MouseAdapter(){//鼠标进入事件@Overridepublic void mouseEntered(MouseEvent e){exit_btn.setIcon(PictureUtil.getImageIcon("close_active.png"));}//鼠标退出事件public void mouseExited(MouseEvent e){exit_btn.setIcon(PictureUtil.getImageIcon("close.png"));}//鼠标单击事件public void mouseClicked(MouseEvent e){//退出程序之前关闭通道对象if(socketChannel!=null){try{socketChannel.close();}catch(IOException ex){ex.printStackTrace();}}System.exit(0);}});}public void actionPerformed(ActionEvent e){//判断事件源if(e.getSource()==close_btn){//退出程序之前关闭通道对象if(socketChannel!=null){try{socketChannel.close();}catch(IOException ex){ex.printStackTrace();}}System.exit(0);}if(e.getSource()==btn_register){String nick=my_nick.getText();if(nick.length()==0){//注意,对话框也是阻塞的JOptionPane.showMessageDialog(null,"注册昵称不能为空!","错误提示",JOptionPane.ERROR_MESSAGE);//设置注册框获取焦点my_nick.requestFocus();}else{String content=nick+"申请注册!";Message msg=new Message(MessageConstants.REGISTER,content);msg.setSender_nick(nick);String msg_str=JsonUtil.toJson(msg);//其实应该利用数据库判断这个nick是否重复try {socketChannel.write(ByteBuffer.wrap(msg_str.getBytes()));} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}}}if(e.getSource()==talk_btn){String nick=talk_nick.getText();if(nick.length()==0){//注意,对话框也是阻塞的JOptionPane.showMessageDialog(null,"聊天对方名称不能为空!","错误提示",JOptionPane.ERROR_MESSAGE);//设置注册框获取焦点talk_nick.requestFocus();}else{String content=my_nick.getText()+"申请与"+nick+"聊天!";Message msg=new Message(MessageConstants.APPLY,content);msg.setSender_nick(my_nick.getText());msg.setTalk_nick(nick);System.out.println(nick);String msg_str=JsonUtil.toJson(msg);//其实应该利用数据库判断这个nick是否重复try {socketChannel.write(ByteBuffer.wrap(msg_str.getBytes()));} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}}}if(e.getSource()==clear_btn){show_msg_area.setText("");}if(e.getSource()==send_btn){send_msg();}if(e.getSource()==cancel_btn){send_msg_area.setText("");send_msg_area.requestFocus();}}public void send_msg(){String text=send_msg_area.getText();if(text.length()==0){//注意,对话框也是阻塞的JOptionPane.showMessageDialog(null,"消息内容不能为空!","错误提示",JOptionPane.ERROR_MESSAGE);//设置注册框获取焦点send_msg_area.requestFocus();}else{String date=new SimpleDateFormat("yy年MM月dd日 HH时mm分ss秒").format(new Date());String msg_text=my_nick.getText()+"  "+date+"\n"+text+"\n"+"\n";Message msg=new Message(MessageConstants.NORMAL,text);msg.setSend_date(date);msg.setSender_nick(my_nick.getText());msg.setTalk_nick(talk_nick.getText());String msg_str=JsonUtil.toJson(msg);try {socketChannel.write(ByteBuffer.wrap(msg_str.getBytes()));} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}// show_msg_area.setText(msg_text);show_msg_area.append(msg_text);send_msg_area.setText("");send_msg_area.requestFocus();}}public void initGui(){//设置大小setSize(600,400);//设置无边框setUndecorated(true);//设置可拖动WindowDrag.allowDrag(this);//设置居中setLocationRelativeTo(null);//设置UI风格,注意MAC中会报错,因为这个风格是windows专属的try{UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");}catch(Exception e){e.printStackTrace();}//设置默认关闭setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);//设置主面板用给定背景重绘contentPane=new JPanel(){protected void paintComponent(Graphics g){super.paintComponent(g);g.drawImage(PictureUtil.getImageIcon("back2.jpg").getImage(), 0,0,null);this.setOpaque(false);}};//添加到主面板的中间getContentPane().add(contentPane,BorderLayout.CENTER);//设置布局为空contentPane.setLayout(null);contentPane.setBorder(BorderFactory.createLineBorder(Color.GRAY));top_pane=new JPanel();BoxLayout box=new BoxLayout(top_pane,BoxLayout.X_AXIS);top_pane.setLayout(box);top_pane.add(new JLabel("我是",JLabel.CENTER));top_pane.add(my_nick=new JTextField(10));top_pane.add(btn_register=new JButton("注册"));//添加间隔top_pane.add(Box.createHorizontalStrut(5));top_pane.add(new JLabel("对方姓名",JLabel.CENTER));top_pane.add(Box.createHorizontalStrut(5));top_pane.add(talk_nick=new JTextField(10));top_pane.add(Box.createHorizontalStrut(5));top_pane.add(talk_btn=new JButton("开始聊天"));//top_pane.setPreferredSize(new Dimension(555,30));//将顶部面板添加到主窗体的顶端(对话框默认为边界布局)// getContentPane().add(top_pane,BorderLayout.NORTH);contentPane.add(top_pane);top_pane.setBounds(5,5,555,30);//添加关闭标签exit_btn=new JLabel();contentPane.add(exit_btn);exit_btn.setBounds(561,0,39,20);exit_btn.setIcon(PictureUtil.getImageIcon("close.png"));//显示消息面板添加到主窗体的中间// getContentPane().add(show_msg_area=new JTextArea(),BorderLayout.CENTER);//设置显示消息面板自动换行show_msg_area=new JTextArea();show_msg_area.setLineWrap(true);show_msg_area.setWrapStyleWord(true);//设置不可编辑show_msg_area.setEditable(false);//设置边框//show_msg_area.setBorder(BorderFactory.createLineBorder(Color.RED));TitledBorder tb=BorderFactory.createTitledBorder("显示消息面板");tb.setTitleColor(Color.red);tb.setTitleJustification(TitledBorder.CENTER);show_msg_area.setBorder(tb);//show_msg_area.setPreferredSize(new Dimension(550,230));//滚动条?将JTextArea放入JScrollPane即可contentPane.add(show_msg_area);show_msg_area.setBounds(25,45,550,230);//初始化底部面板,设置为边界布局bottom_pane=new JPanel(new BorderLayout());//承载底部面板按钮的子面板,流式布局JPanel btn_pane=new JPanel();btn_pane.add(close_btn=new JButton("关闭"));btn_pane.add(clear_btn=new JButton("清空消息"));btn_pane.add(cancel_btn=new JButton("取消发送"));btn_pane.add(send_btn=new JButton("发送"));bottom_pane.add(send_msg_area=new JTextArea(),BorderLayout.CENTER);//设置发送消息面板自动换行,并显示滚动条send_msg_area.setLineWrap(true);//设置换行不断字send_msg_area.setWrapStyleWord(true);//设置边界//send_msg_area.setBorder(BorderFactory.createLineBorder(Color.BLUE));TitledBorder tb_2=BorderFactory.createTitledBorder("发送消息面板");tb_2.setTitleJustification(TitledBorder.CENTER);tb_2.setTitleColor(Color.red);send_msg_area.setBorder(tb_2);bottom_pane.add(btn_pane,BorderLayout.SOUTH);//bottom_pane.setPreferredSize(new Dimension(550,120));contentPane.add(bottom_pane);bottom_pane.setBounds(25,285,550,110);//将底部面板添加到主窗体的最下面// getContentPane().add(bottom_pane,BorderLayout.SOUTH);//设置可见setVisible(true);}public static void main(String[] args) throws IOException {new ClientByNio();}}

客户端可以运行多了,单个客户端关闭不影响其他客户端运行,但是注意,服务端通道我没做单独处理,如果在没有关闭客户端的情况下强行关闭服务端,会出现无限制的IOException,以为客户端通道一直在读取。

实现图片如下,客户端启动界面(启动客户端前必须启动服务端,服务端没有用界面):

客户端注册成功界面:

可启动多个客户端



申请聊天成功界面(如果输入对方的nick不存在,则会出现申请聊天失败提示对话框)



开始聊天







0 0
原创粉丝点击