首页 > 编程 > Java > 正文

详解基于java的Socket聊天程序——客户端(附demo)

2019-11-11 02:31:41
字体:
来源:转载
供稿:网友
这篇文章主要介绍了详解基于java的Socket聊天程序——客户端(附demo),客户端设计主要分成两个部分,分别是socket通讯模块设计和UI相关设计。有兴趣的可以了解一下。

写在前面:

上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细设计和Common模块记录一下,因为这个周末开始就要去忙其他东西了。

设计:

客户端设计主要分成两个部分,分别是socket通讯模块设计和UI相关设计。

客户端socket通讯设计:

这里的设计其实跟服务端的设计差不多,不同的是服务端是接收心跳包,而客户端是发送心跳包,由于客户端只与一个服务端进行通讯(客户端之间的通讯也是由服务端进行分发的),所以这里只使用了一个大小为2的线程池去处理这两件事(newFixedThreadPool(2)),对应的处理类分别是ReceiveListener、KeepAliveDog,其中ReceiveListener在初始化的时候传入一个Callback作为客户端收到服务端的消息的回调,Callback的默认实现是DefaultCallback,DefaultCallback根据不同的事件通过HF分发给不同Handler去处理,而ClientHolder则是存储当前客户端信息,设计如下:

Socket通讯模块具体实现:

[Client.java]

Client是客户端连接服务端的入口,创建Client需要指定一个Callback作为客户端接收服务端消息时的回调,然后由Client的start()方法启动对服务端的监听(ReceiveListener),当ReceiveListener接收到服务端发来的数据时,调用回调(Callback)的doWork()方法去处理;同时Client中还需要发送心跳包来通知服务端自己还在连接着服务端,发心跳包由Client中keepAlive()启动,由KeepAliveDog实现;这两个步骤由一个固定大小为2为线程池newFixedThreadPool(2)去执行,可能这里使用一个newFixedThreadPool(1)和newScheduledThreadPool(1)去处理更合理,因为心跳包是定时发的,服务端就是这样实现的(这个后续调整),Client的具体代码如下(这里暴露了另外两个方法用于获取socket和当前socket所属的用户):

?
1234567891011121314151617181920212223242526272829303132333435/** * 客户端 * @author yaolin * */public class Client {     PRivatefinal Socket socket;  privateString from;  privatefinal ExecutorService pool;  privatefinal Callback callback;   publicClient(Callback callback) throwsIOException {    this.socket =new Socket(ConstantValue.SERVER_ip, ConstantValue.SERVER_PORT);    this.pool = Executors.newFixedThreadPool(2);    this.callback = callback;  }     publicvoid start() {    pool.execute(newReceiveListener(socket, callback));  }     publicvoid keepAlive(String from) {    this.from = from;    pool.execute(newKeepAliveDog(socket, from));  }     publicSocket getSocket() {    returnsocket;  }     publicString getFrom() {    returnfrom;  }}

[KeepAliveDog.java]

客户端在与服务端建立连接之后(该程序中是指登陆成功之后,因为登陆成功之后客户端的socket才会被服务端的SocketHolder管理),需要每个一段时间就给服务端发送心跳包告诉服务端自己还在跟服务端保持联系,不然服务端会在一段时间之后将没有交互的socket丢弃(详见服务端那篇博客),KeepAliveDog的代码实现如下(后期可能会调整为newScheduledThreadPool(1),所以这里的代码也会调整):

?
12345678910111213141516171819202122232425262728293031323334/** * KeepAliveDog : tell Server this client is running; * * @author yaolin */public class KeepAliveDog implementsRunnable {   privatefinal Socket socket;  privatefinal String from;     publicKeepAliveDog(Socket socket, String from) {    this.socket = socket;    this.from = from;  }   @Override  publicvoid run() {    while(socket != null&& !socket.isClosed()) {      try{                 PrintWriter out =new PrintWriter(socket.getOutputStream());        AliveMessage message =new AliveMessage();        message.setFrom(from);        out.println(JSON.toJSON(message));        out.flush();                 Thread.sleep(ConstantValue.KEEP_ALIVE_PERIOD *1000);               }catch (Exception e) {        LoggerUtil.error("Client send message failed !"+ e.getMessage(), e);      }    }  }}

[ReceiveListener.java]

Client的start()方法启动对服务端的监听由ReceiveListener实现,ReceiveListener接收到服务端的消息之后会回调Callback的doWork()方法,让回调去处理具体的业务逻辑,所以ReceiveListener只负责监听服务端的消息,具体的处理由Callback负责,这里需要提一下的是当消息类型是文件类型的时候会睡眠配置执行的间隔时间,这样Callback中的doWork才能对读取来至服务端的文件流,而不是直接进入下一次循环,这里的设计跟服务端是类似的。ReceiveListener的具体实现代码如下:

?
12345678910111213141516171819202122232425262728293031323334353637383940414243444546public class ReceiveListener implementsRunnable {   privatefinal Socket socket;  privatefinal Callback callback;   publicReceiveListener(Socket socket, Callback callback) {    this.socket = socket;    this.callback = callback;  }   @Override  publicvoid run() {    if(socket != null) {      while(!socket.isClosed()) {        try{          InputStream is = socket.getInputStream();          String line =null;          StringBuffer sb =null;           if(is.available() > 0) {             BufferedReader bufr =new BufferedReader(newInputStreamReader(is));            sb =new StringBuffer();            while(is.available() > 0&& (line = bufr.readLine()) != null) {              sb.append(line);            }            LoggerUtil.trach("RECEIVE ["+ sb.toString() + "] AT "+ new Date());                         callback.doWork(socket, sb.toString());            BaseMessage message = JSON.parSEObject(sb.toString(), BaseMessage.class);            if(message.getType() == MessageType.FILE) {              // PAUSE TO RECEIVE FILE              LoggerUtil.trach("CLIENT:PAUSE TO RECEIVE FILE");              Thread.sleep(ConstantValue.MESSAGE_PERIOD);            }          }else {            Thread.sleep(ConstantValue.MESSAGE_PERIOD);          }        }catch (Exception e) {          LoggerUtil.error("Client send message failed !"+ e.getMessage(), e);        }      }    }  } }

[Callback.java、DefaultCallback.java]

从上面可以看出Client对消息的处理是Callback回调,其Callback只是一个接口,所有Callback实现该接口根据自己的需要对消息进行相应地处理,这里Callback默认的实现是DefaultCallback,DefaultCallback只对三种消息进行处理,分别是聊天消息、文件消息、返回消息。对于聊天消息,DefaultCallback将通过UI中的Router路由获取到相应的界面(详见下面的UI设计),然后将消息展现在对应的聊天框中;对于文件消息,DefaultCallback则是将文件写入到配置中指定的路径中(这里没有通过用户的允许就接收文件,这种设计不是很友好,目前先这样);对于返回消息,DefaultCallback会根据返回消息中的KEY叫给不同的Handler去处理。具体代码如下:

?
123public interface Callback {  publicvoid doWork(Socket server, Object data);}
?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109public class DefaultCallback implementsCallback {   @Override  publicvoid doWork(Socket server, Object data) {    if(data != null) {      BaseMessage message = JSON.parseObject(data.toString(), BaseMessage.class);      switch(message.getType()) {      caseMessageType.CHAT:        handleChatMessage(data);        break;      caseMessageType.FILE:        handleFileMessage(server, data);        break;      caseMessageType.RETURN:        handleReturnMessage(data);        break;      }    }  }   privatevoid handleChatMessage(Object data) {    ChatMessage m = JSON.parseObject(data.toString(), ChatMessage.class);    String tabKey = m.getFrom();// FROM    JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.CHATTABBED);    if(comp instanceofJTabbedPane) {      JTabbedPane tab = (JTabbedPane) comp;      intindex = tab.indexOfTab(tabKey);      if(index == -1) {        tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane());      }      JTextArea textArea = ResultHolder.get(tabKey).getTextArea();      textArea.setText(newStringBuffer()          .append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator())          .append(" [").append(m.getOwner()).append("] : ").append(System.lineSeparator())          .append(m.getContent())          .toString());      // SCROLL TO BOTTOM      textArea.setCaretPosition(textArea.getText().length());    }  }   privatevoid handleFileMessage(Socket server, Object data) {    FileMessage message = JSON.parseObject(data.toString(), FileMessage.class);    if(message.getSize() > 0) {      OutputStream os =null;      try{        if(server != null) {          InputStream is = server.getInputStream();          File dir =new File(ConstantValue.CLIENT_RECEIVE_DIR);          if(!dir.exists()) {            dir.mkdirs();          }          os =new FileOutputStream(              newFile(PathUtil.combination(ConstantValue.CLIENT_RECEIVE_DIR,new Date().getTime() + message.getName())));          inttotal = 0;          while(!server.isClosed()) {            if(is.available() > 0) {              byte[] buff =new byte[ConstantValue.BUFF_SIZE];              intlen = -1;              while(is.available() > 0&& (len = is.read(buff)) != -1) {                os.write(buff,0, len);                total += len;                LoggerUtil.debug("RECEIVE BUFF ["+ len + "]");              }              os.flush();              if(total >= message.getSize()) {                LoggerUtil.info("RECEIVE BUFF [OK]");                break;              }            }          }        }      }catch (Exception e) {        LoggerUtil.error("Receive file failed ! "+ e.getMessage(), e);      }finally {        if(os != null) {          try{            os.close();          }catch (Exception ignore) {          }          os =null;        }      }    }  }   privatevoid handleReturnMessage(Object data) {    ReturnMessage m = JSON.parseObject(data.toString(), ReturnMessage.class);    if(StringUtil.isNotEmpty(m.getKey())) {      switch(m.getKey()) {      caseKey.NOTIFY: // Notify client to update usr list        HF.getHandler(Key.NOTIFY).handle(data);        break;      caseKey.LOGIN:        HF.getHandler(Key.LOGIN).handle(data);        break;      caseKey.REGISTER:        HF.getHandler(Key.REGISTER).handle(data);        break;      caseKey.LISTUSER:        HF.getHandler(Key.LISTUSER).handle(data);        break;      caseKey.TIP:        HF.getHandler(Key.TIP).handle(data);        break;      }    }  }}

[Handler.java、HF.java、ListUserHdl.java...]

Handler组件负责对服务端返回消息类型的消息进行处理,DefaultCallback根据不同的KEY将消息分发给不同的Handler进行处理,这里也算一套简单的工厂组件吧,跟服务端处理接收到的数据设计是类似的,完整的类图如下:

下面给出这一块的代码,为了缩小篇幅,将所有Handler实现的代码收起来。 

?
123public interface Handler {   publicObject handle(Object obj); }
?
123456789101112131415161718public class HF {   publicstatic Handler getHandler(String key) {    switch(key) {    caseKey.NOTIFY:      returnnew NotifyHdl();    caseKey.LOGIN:      returnnew LoginHdl();    caseKey.REGISTER:      returnnew RegisterHdl();    caseKey.LISTUSER:      returnnew ListUserHdl();    caseKey.TIP:      returnnew TipHdl();    }    returnnull;  }}
?
12345678910111213141516171819202122232425262728public class ListUserHdl implementsHandler {   @Override  publicObject handle(Object obj) {    if(obj != null) {      try{        ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class);        if(rm.isSuccess() && rm.getContent() != null) {          ClientListUserDTO dto = JSON.parseObject(rm.getContent().toString(), ClientListUserDTO.class);          JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.LISTUSRLIST);          if(comp instanceofJList) {            @SuppressWarnings("unchecked")//            JList<String> listUsrList = (JList<String>) comp;            List<String> listUser =new LinkedList<String>();            listUser.addAll(dto.getListUser());            Collections.sort(listUser);            listUser.add(0, ConstantValue.TO_ALL);            listUsrList.setListData(listUser.toArray(newString[]{}));          }        }      }catch (Exception e) {        LoggerUtil.error("Handle listUsr failed! "+ e.getMessage(), e);      }    }    returnnull;  } }
?
1234567891011121314151617181920212223242526public class LoginHdl implementsHandler {   @Override  publicObject handle(Object obj) {    if(obj != null) {      try{        ReturnMessage rm = JSON.parseObject(obj.toString(),ReturnMessage.class);        if(rm.isSuccess()) {          Router.getView(RegisterAndLoginView.class).trash();          Router.getView(ChatRoomView.class).create().display();          ClientHolder.getClient().keepAlive(rm.getTo());// KEEP...        }else {          Container container = Router.getView(RegisterAndLoginView.class).container();          if(container != null) {            // show error            JOptionPane.showMessageDialog(container, rm.getMessage());          }        }      }catch (Exception e) {        LoggerUtil.error("Handle login failed! "+ e.getMessage(), e);      }    }    returnnull;  } }
?
1234567891011121314151617181920212223242526272829303132333435363738394041424344public class NotifyHdl implementsHandler {   @Override  publicObject handle(Object obj) {    if(obj != null) {      try{        ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage.class);        if(rm.isSuccess() && rm.getContent() != null) {          ClientNotifyDTO dto = JSON.parseObject(rm.getContent().toString(), ClientNotifyDTO.class);          JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.LISTUSRLIST);          if(comp instanceofJList) {            @SuppressWarnings("unchecked")//            JList<String> listUsrList = (JList<String>) comp;            List<String> listUser = modelToList(listUsrList.getModel());            if(dto.isFlag()) {              if(!listUser.contains(dto.getUsername())) {                listUser.add(dto.getUsername());                listUser.remove(ConstantValue.TO_ALL);                Collections.sort(listUser);                listUser.add(0, ConstantValue.TO_ALL);              }            }else {              listUser.remove(dto.getUsername());            }            listUsrList.setListData(listUser.toArray(newString[]{}));          }        }      }catch (Exception e) {        LoggerUtil.error("Handle nofity failed! "+ e.getMessage(), e);      }    }    returnnull;  }   privateList<String> modelToList(ListModel<String> listModel) {    List<String> list =new LinkedList<String>();    if(listModel != null) {      for(int i = 0; i < listModel.getSize(); i++) {        list.add(listModel.getElementAt(i));      }    }    returnlist;  }}
?
1234567891011121314151617181920212223public class RegisterHdl implementsHandler {   @Override  publicObject handle(Object obj) {    if(obj != null) {      try{        ReturnMessage rm = JSON.parseObject(obj.toString(),ReturnMessage.class);        Container container = Router.getView(RegisterAndLoginView.class).container();        if(container != null) {          if(rm.isSuccess()) {            JOptionPane.showMessageDialog(container, rm.getContent());          }else {            JOptionPane.showMessageDialog(container, rm.getMessage());          }        }      }catch (Exception e) {        LoggerUtil.error("Handle register failed! "+ e.getMessage(), e);      }    }    returnnull;  } }
?
1234567891011121314151617181920212223242526272829303132333435public class TipHdl implementsHandler {   @Override  publicObject handle(Object obj) {    if(obj != null) {      try{        ReturnMessage m = JSON.parseObject(obj.toString(), ReturnMessage.class);        if(m.isSuccess() && m.getContent() != null) {          String tabKey = m.getFrom();          String tip = m.getContent().toString();          JComponent comp = Router.getView(ChatRoomView.class).getComponent(ChatRoomView.CHATTABBED);          if(comp instanceofJTabbedPane) {            JTabbedPane tab = (JTabbedPane) comp;            intindex = tab.indexOfTab(tabKey);            if(index == -1) {              tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane());            }            JTextArea textArea = ResultHolder.get(tabKey).getTextArea();            textArea.setText(newStringBuffer()                .append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator())                .append(" [").append(m.getOwner()).append("] : ").append(System.lineSeparator())                .append(tip)                .toString());            // SCROLL TO BOTTOM            textArea.setCaretPosition(textArea.getText().length());          }        }      }catch (Exception e) {        LoggerUtil.error("Handle tip failed! "+ e.getMessage(), e);      }    }    returnnull;  } }

 对于Socket通讯模块还有一个类,那就是ClientHolder,这个类用于存储当前Client,跟服务端的SocketHolder是类似的。

?
123456789101112131415/** * @author yaolin */public class ClientHolder {   publicstatic Client client;   publicstatic Client getClient() {    returnclient;  }   publicstatic void setClient(Client client) {    ClientHolder.client = client;  }}

UI模块具体实现:

上面记录了socket通讯模块的设计,接下来记录一下UI的设计模块,我不打算自己写UI,毕竟自己写出来的太丑了,所以后期可能会叫同学或朋友帮忙敲一下,所以我将UI的事件处理都交由Action去处理,将UI设计和事件响应简单分离,所有UI继承JFrame并实现View接口,上面的Handler实现类通过Router获取(存在则直接返回,不存在则创建并存储)指定的UI,View中提供了UI的创建create()、获取container()、获取UI中的组件getComponent(),显示display(),回收trash();ResultWrapper和ResultHolder只是为了创建和存储聊天选项卡。设计如下:

[Router.java、View.java]

所有UI继承JFrame并实现View接口,Handler实现类通过Router获取(存在则直接返回,不存在则创建并存储)指定的UI,View中提供了UI的创建create()、获取container()、获取UI中的组件getComponent(),显示display(),回收trash(),具体实现如下:

?
123456789101112131415161718192021/** * View 路由 * @author yaolin */public class Router {   privatestatic Map<String, View> listRoute =new HashMap<String,View>();     publicstatic View getView(Class<?> clazz) {    View v = listRoute.get(clazz.getName());    if(v == null) {      try{        v = (View) Class.forName(clazz.getName()).newInstance();        listRoute.put(clazz.getName(), v);      }catch (Exception e) {        LoggerUtil.error("Create view failed! "+ e.getMessage(), e);      }    }    returnv;  }}
?
123456789101112131415161718192021222324252627282930313233/** * 所有界面的规范接口 * @author yaolin * */public interface View {     /**   *   */  publicView create();   /**   *   */  publicContainer container();     /**   * @param key   */  publicJComponent getComponent(String key);     /**   *   */  publicvoid display();     /**   *   */  publicvoid trash();   }

[RegisterAndLoginView.java、ChatRoomView.java]

由于不想自己写UI,我这里只是简单的写了两个UI界面,分别是注册和登陆界面、聊天界面,这里给出两个丑丑的界面:

注册登录界面

聊天界面

下面给出这两个这界面的具体代码:

?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119/** * 注册、登陆 * @author yaolin */public class RegisterAndLoginView extendsJFrame implementsView {   privatestatic final long serialVersionUID = 6322088074312546736L;  privatefinal RegisterAndLoginAction action =new RegisterAndLoginAction();     privatestatic booleanCREATE = false;     @Override  publicView create() {    if(! CREATE) {      init();      CREATE =true;    }    returnthis;  }     publicContainer container() {    create();    returngetContentPane();  }     @Override  publicJComponent getComponent(String key) {    returnnull;  }     @Override  publicvoid display() {    setVisible(true);  }     @Override  publicvoid trash() {    dispose();  }        privatevoid init() {    // Attribute    setSize(500,300);    setResizable(false);    setLocationRelativeTo(null);         // Container    JPanel panel =new JPanel();    panel.setLayout(null);         // Component    // username    JLabel lbUsername =new JLabel(I18N.TEXT_USERNAME);    lbUsername.setBounds(100,80, 200,30);    finalJTextField tfUsername = newJTextField();    tfUsername.setBounds(150,80, 230,30);    panel.add(lbUsername);    panel.add(tfUsername);    // passsWord    JLabel lbPassword =new JLabel(I18N.TEXT_PASSWORD);    lbPassword.setBounds(100,120, 200,30);    finalJPasswordField pfPassword = newJPasswordField();    pfPassword.setBounds(150,120, 230,30);    panel.add(lbPassword);    panel.add(pfPassword);    // btnRegister    JButton btnRegister =new JButton(I18N.BTN_REGISTER);    btnRegister.setBounds(100,175, 80,30);    // btnLogin    finalJButton btnLogin = newJButton(I18N.BTN_LOGIN);    btnLogin.setBounds(200,175, 80,30);    // btnCancel    JButton btnExit =new JButton(I18N.BTN_EXIT);    btnExit.setBounds(300,175, 80,30);    panel.add(btnRegister);    panel.add(btnLogin);    panel.add(btnExit);         // Event    pfPassword.addKeyListener(newKeyAdapter() {      publicvoid keyPressed(finalKeyEvent e) {        if(e.getKeyCode() == KeyEvent.VK_ENTER)          btnLogin.doClick();      }    });// end of addKeyListener         btnRegister.addActionListener(newActionListener() {      publicvoid actionPerformed(finalActionEvent e) {        if(StringUtil.isEmpty(tfUsername.getText())             || StringUtil.isEmpty(newString(pfPassword.getPassword()))) {          JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_REGISTER_EMPTY_DATA);          return;        }        action.handleRegister(tfUsername.getText(),new String(pfPassword.getPassword()));      }    });// end of addActionListener         btnLogin.addActionListener(newActionListener() {      publicvoid actionPerformed(finalActionEvent e) {        if(StringUtil.isEmpty(tfUsername.getText())             || StringUtil.isEmpty(newString(pfPassword.getPassword()))) {          JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_LOGIN_EMPTY_DATA);          return;        }        action.handleLogin(tfUsername.getText(),new String(pfPassword.getPassword()));      }    });// end of addActionListener         btnExit.addActionListener(newActionListener() {      publicvoid actionPerformed(finalActionEvent e) {        System.exit(0);      }    });// end of addActionListener     getContentPane().add(panel);    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  }}
?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147/** * Client 聊天窗口 * * @author yaolin */public class ChatRoomView extendsJFrame implementsView {   privatestatic final long serialVersionUID = -4515831172899054818L;   publicstatic final String LISTUSRLIST = "LISTUSRLIST";  publicstatic final String CHATTABBED = "CHATTABBED";   privatestatic booleanCREATE = false;  privateChatRoomAction action = newChatRoomAction();   privateJList<String> listUsrList = null;  privateJTabbedPane chatTabbed = null;   @Override  publicView create() {    if(!CREATE) {      init();      CREATE =true;    }    returnthis;  }   publicContainer container() {    create();    returngetContentPane();  }   @Override  publicJComponent getComponent(String key) {    create();    switch(key) {    caseLISTUSRLIST:      returnlistUsrList;    caseCHATTABBED:      returnchatTabbed;    }    returnnull;  }   @Override  publicvoid display() {    setVisible(true);  }   @Override  publicvoid trash() {    dispose();  }   publicvoid init() {    setTitle(I18N.TEXT_APP_NAME);    setSize(800,600);    setResizable(false);    setLocationRelativeTo(null);     setLayout(newBorderLayout());    add(createChatPanel(), BorderLayout.CENTER);    add(createUsrListView(), BorderLayout.EAST);     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  }   privateJComponent createChatPanel() {    // FILE SELECTOR    finalJFileChooser fileChooser = newJFileChooser();     JPanel panel =new JPanel(newBorderLayout());    // CENTER    chatTabbed =new JTabbedPane();    chatTabbed.addTab(ConstantValue.TO_ALL, ResultHolder.get(ConstantValue.TO_ALL).getScrollPane());    panel.add(chatTabbed, BorderLayout.CENTER);     // SOUTH    JPanel south =new JPanel(newBorderLayout());    // SOUTH - FILE    JPanel middle =new JPanel(newBorderLayout());    middle.add(newJLabel(), BorderLayout.CENTER); // JUST FOR PADDING    JButton btnUpload =new JButton(I18N.BTN_SEND_FILE);    middle.add(btnUpload, BorderLayout.EAST);    south.add(middle, BorderLayout.NORTH);    // SOUTH - TEXTAREA    finalJTextArea taSend = newJTextArea();    taSend.setCaretColor(Color.BLUE);    taSend.setMargin(newInsets(10,10, 10,10));    taSend.setRows(10);    south.add(taSend, BorderLayout.CENTER);    // SOUTH - BTN    JPanel bottom =new JPanel(newBorderLayout());    bottom.add(newJLabel(), BorderLayout.CENTER); // JUST FOR PADDING    JButton btnSend =new JButton(I18N.BTN_SEND);    bottom.add(btnSend, BorderLayout.EAST);     south.add(bottom, BorderLayout.SOUTH);     btnUpload.addActionListener(newActionListener() {      publicvoid actionPerformed(finalActionEvent e) {        if(! ConstantValue.TO_ALL.equals(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()))) {          intreturnVal = fileChooser.showOpenDialog(ChatRoomView.this);          if(returnVal == JFileChooser.APPROVE_OPTION) {            File file = fileChooser.getSelectedFile();            action.upload(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()), file);          }        }else {          JOptionPane.showMessageDialog(getContentPane(), I18N.INFO_FILE_TO_ALL_ERROR);        }      }    });     btnSend.addActionListener(newActionListener() {      publicvoid actionPerformed(finalActionEvent e) {        if(StringUtil.isNotEmpty(taSend.getText())) {          action.send(chatTabbed.getTitleAt(chatTabbed.getSelectedIndex()), taSend.getText());          taSend.setText(null);        }      }    });     panel.add(south, BorderLayout.SOUTH);    returnpanel;  }   privateJComponent createUsrListView() {    listUsrList =new JList<String>();    listUsrList.setBorder(newLineBorder(Color.BLUE));    listUsrList.setListData(newString[] { ConstantValue.TO_ALL });    listUsrList.setFixedCellWidth(200);    listUsrList.setFixedCellHeight(30);    listUsrList.addListSelectionListener(newListSelectionListener() {      @Override      publicvoid valueChanged(ListSelectionEvent e) {// chat to        if(chatTabbed.indexOfTab(listUsrList.getSelectedValue()) == -1            && listUsrList.getSelectedValue() !=null            && !listUsrList.getSelectedValue().equals(ClientHolder.getClient().getFrom())) {          chatTabbed.addTab(listUsrList.getSelectedValue(),              ResultHolder.get(listUsrList.getSelectedValue()).getScrollPane());          chatTabbed.setSelectedIndex(chatTabbed.indexOfTab(listUsrList.getSelectedValue()));        }      }    });    returnlistUsrList;  }}

[RegisterAndLoginAction.java、ChatRoomAction.java]

这里UI的事件处理都交由Action去处理,将UI设计和事件响应简单分离,RegisterAndLoginView的事件由RegisterAndLoginAction处理,ChatRoomView的事件由ChatRoomAction处理。具体实现如下:

?
12345678910111213141516171819202122232425public class RegisterAndLoginAction {   publicvoid handleRegister(String username, String password) {    if(StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) {      return;    }    RegisterMessage message =new RegisterMessage()        .setUsername(username)        .setPassword(password);    message.setFrom(username);    SendHelper.send(ClientHolder.getClient().getSocket(), message);  }        publicvoid handleLogin(String username, String password) {    if(StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) {      return;    }    LoginMessage message =new LoginMessage()        .setUsername(username)        .setPassword(password);    message.setFrom(username);    SendHelper.send(ClientHolder.getClient().getSocket(), message);  }}

对于UI设计还有两个类,分别是ResultHolder和ResultWrapper,ResultWrapper和ResultHolder只是为了创建和存储聊天选项卡,具体实现如下:

?
12345678910111213141516171819202122public class ResultWrapper {     privateJScrollPane scrollPane;  privateJTextArea textArea;     publicResultWrapper(JScrollPane scrollPane, JTextArea textArea) {    this.scrollPane = scrollPane;    this.textArea = textArea;  }  publicJScrollPane getScrollPane() {    returnscrollPane;  }  publicvoid setScrollPane(JScrollPane scrollPane) {    this.scrollPane = scrollPane;  }  publicJTextArea getTextArea() {    returntextArea;  }  publicvoid setTextArea(JTextArea textArea) {    this.textArea = textArea;  }}
?
1234567891011121314151617181920212223242526272829public class ResultHolder {   privatestatic Map<String, ResultWrapper> listResultWrapper =new HashMap<String,ResultWrapper>();     publicstatic void put(String key, ResultWrapper wrapper) {    listResultWrapper.put(key, wrapper);  }     publicstatic ResultWrapper get(String key) {    ResultWrapper wrapper = listResultWrapper.get(key);    if(wrapper == null) {      wrapper = create();      put(key, wrapper);    }    returnwrapper;  }        privatestatic ResultWrapper create() {    JTextArea resultTextArea =new JTextArea();    resultTextArea.setEditable(false);    resultTextArea.setBorder(newLineBorder(Color.BLUE));    JScrollPane scrollPane =new JScrollPane(resultTextArea);    scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);    scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);    ResultWrapper wrapper =new ResultWrapper(scrollPane, resultTextArea);    returnwrapper;  }}

最后的最后给出,客户端运行的入口:

?
12345678910111213141516171819/** * * @author yaolin * */public class NiloayChat {   publicstatic void main(String[] args) {    View v = Router.getView(RegisterAndLoginView.class).create();    try{      v.display();      Client client =new Client(newDefaultCallback());      client.start();      ClientHolder.setClient(client);    }catch (IOException e) {      JOptionPane.showMessageDialog(v.container(), e.getMessage());    }  }}

demo下载地址:demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表