Java Socket编程 - 基于TCP方式的客户服务器聊天程序

简介: Java Socket编程 - 基于TCP方式的客户服务器聊天程序

本文讲演示如何通过Java Socket建立C/S方式的聊天程序。实现的功能

主要包括如下几个方面:

1. 用户登录,在线用户列表刷新

2.客户端与服务器的TCP连接,实现消息的发送与接受

3.Java Swing与多线程编程技巧

一个整体的Class关系图如下:

1353172677_6048.png

程序实现的服务器端UI如下:

1353171124_7766.png

一个JList组件用来显示在线的所有用户,一个JTextArea组件用来显示所有消息

记录。所有消息必须通过服务器端转发。点击【start】按钮启动服务器端监听

默认监听端口为9999。

启动服务器端的Action中的代码如下:

  Thread startThread = new Thread(new Runnable() {
                public void run() {
                  startServer(9999);
                }
            });
  startThread.start();
  startBtn.setEnabled(false);
  shutDownBtn.setEnabled(true);

startServer()的代码如下:

private void startServer(int port) {
  try {
    serverSocket = new ServerSocket(port);
    System.out.println("Server started at port :" + port);
    while(true) {
      Socket client = serverSocket.accept(); // blocked & waiting for income socket
      System.out.println("Just connected to " + client.getRemoteSocketAddress());
      DataInputStream bufferedReader = new DataInputStream(client.getInputStream());
      byte[] cbuff = new byte[256];
      int size = bufferedReader.read(cbuff);
      char[] charBuff = convertByteToChar(cbuff, size);
      String userName = String.valueOf(charBuff);
      ChatServerClientThread clentThread = new ChatServerClientThread(userName, client, this);
      clientList.add(clentThread);
      userNameList.add(userName);
      clentThread.start();
      updateUserList();
    }
 
  } catch (IOException e) {
    e.printStackTrace();
  }
}

简单协议规则:

1.      任何消息发送完以后系统自动加上结束标志EOF

2.      接受到用户消息以后通过解析EOF来完成消息传递

3.      自动发送更新用户列表到所有客户端当有新客户登入时

为什么我要实现上述简单协议,其实任何网络通信都是基于协议实现

只有基于协议实现才可控制,可检查。协议是网络通信的最重要一环。

客户端UI设计如下:

1353171835_2114.png 一个自定义的JPanel实现背景渐进颜色填充。

- Message组件用来接受用户输入的聊天信息

- Friend List 会自动刷新用户列表,当有新用户登录时候

- History Record用来显示聊天记录

- 【Connect】点击连接到Server端,前提是必须填写设置中全部,默认

   的机器IP为127.0.0.1端口为9999

- 【send】按钮点击会发送用户输入的消息到指定的其它客户端。如果

   没有选择用户,则发送到服务器端。

-  一次发送消息的大小不得大于200个字节。

完整的客户端代码如下:

package com.gloomyfish.socket.tutorial.chat;
 
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
 
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
 
import com.gloomyfish.custom.swing.ui.CurvedGradientPanel;
 
public class ChatClient extends JFrame implements ActionListener {
  public final static String CONNECT_CMD = "Connect";
  public final static String DISCONNECT_CMD = "Disconnect";
  public final static String SEND_CMD = "Send";
  public final static String END_FLAG = "EOF";
 
  /**
   * 
   */
  private static final long serialVersionUID = 5837742337463099673L;
  private String winTitle;
  private JLabel userLabel;
  private JLabel passwordLabel;
  private JLabel ipLabel;
  private JLabel portLabel;
  
  // text field
  private JTextField userField;
  private JPasswordField passwordField;
  private JTextField ipField;
  private JTextField portField;
  
  private JList friendList;
  private JTextArea historyRecordArea;
  private JTextArea chatContentArea;
  
  // buttons
  private JButton connectBtn;
  private JButton disConnectBtn;
  private JButton sendBtn;
  private JCheckBox send2AllBtn;
  
  // socket
  private Socket mSocket;
  private SocketAddress address;
  private ChatClientThread m_client;
  
  public ChatClient() {
    super("Chat Client");
    initComponents();
    setupListener();
  }
  
  private void initComponents() {
    JPanel settingsPanel = new CurvedGradientPanel();
    JPanel chatPanel = new CurvedGradientPanel();
    GridLayout gy = new GridLayout(1,2,10,2);
    getContentPane().setLayout(gy);
    getContentPane().add(settingsPanel);
    getContentPane().add(chatPanel);
    
    // set up settings info
    settingsPanel.setLayout(new BorderLayout());
    settingsPanel.setOpaque(false);
    JPanel gridPanel = new JPanel(new GridLayout(4, 2));
    gridPanel.setBorder(BorderFactory.createTitledBorder("Server Settings & User Info"));
    gridPanel.setOpaque(false);
    userLabel = new JLabel("User Name:");
    passwordLabel = new JLabel("User Password:");
    ipLabel = new JLabel("Server IP Address:");
    portLabel = new JLabel("Server Port");
    userLabel.setOpaque(false);
    passwordLabel.setOpaque(false);
    ipLabel.setOpaque(false);
    portLabel.setOpaque(false);
    userField = new JTextField();
    passwordField = new JPasswordField();
    ipField = new JTextField();
    portField = new JTextField();
    connectBtn = new JButton(CONNECT_CMD);
    disConnectBtn = new JButton(DISCONNECT_CMD);
    JPanel btnPanel = new JPanel();
    btnPanel.setOpaque(false);
    btnPanel.setLayout(new FlowLayout());
    btnPanel.add(connectBtn);
    btnPanel.add(disConnectBtn);
    
    gridPanel.add(userLabel);
    gridPanel.add(userField);
    gridPanel.add(passwordLabel);
    gridPanel.add(passwordField);
    gridPanel.add(ipLabel);
    gridPanel.add(ipField);
    gridPanel.add(portLabel);
    gridPanel.add(portField);
    friendList = new JList();
    JScrollPane friendPanel = new JScrollPane(friendList);
    friendPanel.setOpaque(false);
    friendPanel.setBorder(BorderFactory.createTitledBorder("Friend List:"));
    settingsPanel.add(btnPanel, BorderLayout.SOUTH);
    settingsPanel.add(gridPanel, BorderLayout.NORTH);
    settingsPanel.add(friendPanel,BorderLayout.CENTER);
    
    chatPanel.setLayout(new GridLayout(3,1));
    chatPanel.setOpaque(false);
    historyRecordArea = new JTextArea();
    JScrollPane histroyPanel = new JScrollPane(historyRecordArea);
    histroyPanel.setBorder(BorderFactory.createTitledBorder("Chat History Record:"));
    histroyPanel.setOpaque(false);
    chatContentArea = new JTextArea();
    JScrollPane messagePanel = new JScrollPane(chatContentArea);
    messagePanel.setBorder(BorderFactory.createTitledBorder("Message:"));
    messagePanel.setOpaque(false);
    // chatPanel.add(friendPanel);
    chatPanel.add(histroyPanel);
    chatPanel.add(messagePanel);
    sendBtn = new JButton(SEND_CMD);
    send2AllBtn = new JCheckBox("Send to All online Users");
    send2AllBtn.setOpaque(false);
    JPanel sendbtnPanel = new JPanel();
    sendbtnPanel.setOpaque(false);
    sendbtnPanel.setLayout(new FlowLayout());
    sendbtnPanel.add(sendBtn);
    sendbtnPanel.add(send2AllBtn);
    chatPanel.add(sendbtnPanel);
  }
  
  private void setupListener() {
    connectBtn.addActionListener(this);
    disConnectBtn.addActionListener(this);
    sendBtn.addActionListener(this);
    disConnectBtn.setEnabled(false);
  }
  
  /**
   * <p></p>
   * 
   * @param content - byte array
   * @param bsize - the size of bytes
   */
  public synchronized void handleMessage(char[] content, int bsize) {
    // char[] inputMessage = convertByteToChar(content, bsize);
    String receivedContent = String.valueOf(content);
    int endFlag = receivedContent.indexOf(END_FLAG);
    receivedContent = receivedContent.substring(0, endFlag);
    System.out.println("Client " + userField.getText() + " Message:" + receivedContent);
    if(receivedContent.contains("#")) {
      String[] onlineUserList = receivedContent.split("#");
      friendList.setListData(onlineUserList);
    } else {
      // just append to chat history record...
      appendHistoryRecord(receivedContent + "\r\n");
    }
  }
  
  public synchronized void appendHistoryRecord(String record) {
    historyRecordArea.append(record);
  }
  
  private String getSelectedUser() {
    int index = friendList.getSelectedIndex();
    if(index >= 0) {
    String user = (String)friendList.getSelectedValue();
    return user;
    } else {
      return "Server";
    }
  }
  
//  private char[] convertByteToChar(byte[] cbuff, int size) {
//    char[] charBuff = new char[size];
//    for(int i=0; i<size; i++) {
//      charBuff[i] = (char)cbuff[i];
//    }
//    return charBuff;
//  }
  
  public void setTitle(String title) {
    winTitle = title;
    super.setTitle(winTitle);
  }
  
  public String getTitle() {
    return super.getTitle();
  }
  
  public static void main(String[] args) {
    ChatClient client = new ChatClient();
    client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    client.pack();
    client.setVisible(true);
  }
 
  @Override
  public void actionPerformed(ActionEvent e) {
    if(SEND_CMD.equals(e.getActionCommand())) {
      String chatContent = chatContentArea.getText();
      if(checkNull(chatContent)) {
        JOptionPane.showMessageDialog(this, "Please enter the message at least 6 characters!");
        return;
      } else if(chatContent.getBytes().length > 200) {
        JOptionPane.showMessageDialog(this, "The length of the message must be less than 200 characters!");
        return;
      }
      try {
        m_client.dispatchMessage(getSelectedUser() + "#" + chatContent);
        m_client.dispatchMessage(END_FLAG);
        appendHistoryRecord("me :" + chatContent + "\r\n");
        chatContentArea.setText(""); // try to clear user enter......
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    } else if(DISCONNECT_CMD.equals(e.getActionCommand())) {
      enableSettingsUI(true);
    } else if(CONNECT_CMD.equals(e.getActionCommand())) {
      String serverHostName = ipField.getText();
      String portStr = portField.getText();
      String userName = userField.getText();
      char[] password = passwordField.getPassword();
      System.out.println("Password = " + password.length);
      if(checkNull(serverHostName) || checkNull(portStr) || checkNull(userName)) {
        JOptionPane.showMessageDialog(this, "Please enter user name, server host name, server port!");
        return;
      }
      setTitle("Chat Client-" + userName);
      address = new InetSocketAddress(serverHostName, Integer.parseInt(portStr));
      mSocket = new Socket();
      try {
        mSocket.connect(address);
        m_client = new ChatClientThread(this, mSocket);
        m_client.dispatchMessage(userName); // send user name
        // m_client.dispatchMessage(END_FLAG); // send end flag
        m_client.start();
        enableSettingsUI(false);
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
      
    }
  }
  
  private void enableSettingsUI(boolean enable) {
    ipField.setEditable(enable);
    portField.setEnabled(enable);
    userField.setEditable(enable);
    passwordField.setEnabled(enable);
    connectBtn.setEnabled(enable);
    disConnectBtn.setEnabled(!enable);
  }
  
  private boolean checkNull(String inputString) {
    if(inputString ==  null || inputString.length() == 0) {
      return true;
    } else {
      return false;
    }
  }
 
}

客户端SOCKET通信线程的代码如下:

package com.gloomyfish.socket.tutorial.chat;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
 
public class ChatClientThread extends Thread {
  private ChatClient _mClient;
  private Socket _mSocket;
  private DataOutputStream dos;
  
  public ChatClientThread(ChatClient cclient, Socket socket) {
    this._mClient = cclient;
    this._mSocket = socket;
  }
  
  public void run() {
    try {
      DataInputStream bufferedReader = new DataInputStream(_mSocket.getInputStream());
      byte[] cbuff = new byte[256];
      char[] tbuff = new char[256];
      int size = 0;
      int byteCount = 0;
      int length = 0;
      while(true) {
        if((size = bufferedReader.read(cbuff))> 0) {
          char[] temp = convertByteToChar(cbuff, size);
          length = temp.length;
          if((length + byteCount) > 256) {
            length = 256 - byteCount;
          }
          System.arraycopy(temp, 0, tbuff, byteCount, length);
          byteCount += size;
          if(String.valueOf(tbuff).indexOf(ChatClient.END_FLAG) > 0) {
            _mClient.handleMessage(tbuff, byteCount);
            byteCount = 0;
            clearTempBuffer(tbuff);
          }
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  
  private void clearTempBuffer(char[] tbuff) {
    for(int i=0; i<tbuff.length; i++) {
      tbuff[i] = ' ';
    }
  }
  
  private char[] convertByteToChar(byte[] cbuff, int size) {
    char[] charBuff = new char[size];
    for(int i=0; i<size; i++) {
      charBuff[i] = (char)cbuff[i];
    }
    return charBuff;
  }
  
  public synchronized void dispatchMessage(String textMsg) throws IOException {
    if(dos == null) {
      dos = new DataOutputStream(_mSocket.getOutputStream());
    }
    byte[] contentBytes = textMsg.getBytes();
    dos.write(contentBytes, 0, contentBytes.length);
  }
}

服务器端的消息转发代码如下:

  public synchronized void dispatchMessage(String[] keyValue, String userName) throws IOException {
    chatArea.append(userName + " to " + keyValue[0] + " : " + keyValue[1] + "\r\n");
    for(ChatServerClientThread client : clientList) {
      if(client.getUserName().equals(keyValue[0])) {
        client.dispatchMessage(userName + " says: " + keyValue[1]);
        client.dispatchMessage(END_FLAG);
        break;
      }
    }
  }

服务器端的客户端线程run方法的代码如下:

public void run() {
  System.out.println("start user = " + userName);
  try {
    DataInputStream bufferedReader = new DataInputStream(userSocket.getInputStream());
    byte[] cbuff = new byte[256];
    char[] tbuff = new char[256];
    int size = 0;
    int byteCount = 0;
    int length = 0;
    while(true) {
      if((size = bufferedReader.read(cbuff))> 0) {
        char[] temp = convertByteToChar(cbuff, size);
        length = temp.length;
        if((length + byteCount) > 256) {
          length = 256 - byteCount;
        }
        System.arraycopy(temp, 0, tbuff, byteCount, length);
        byteCount += size;
        if(String.valueOf(tbuff).indexOf(ChatServer.END_FLAG) > 0) {
          String receivedContent = String.valueOf(tbuff);
          int endFlag = receivedContent.indexOf(ChatServer.END_FLAG);
          receivedContent = receivedContent.substring(0, endFlag);
          String[] keyValue = receivedContent.split("#");
          if(keyValue.length > 1) {
            server.dispatchMessage(keyValue, userName);
          }
          byteCount = 0;
          clearTempBuffer(tbuff);
        }
      }
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
}

最终程序的运行结果截屏如下: 1353172575_7105.png

1353172606_9574.png

相关文章
|
18天前
|
搜索推荐 Java 索引
Java中的服务器端渲染(SSR)
Java中的服务器端渲染(SSR)
|
17天前
|
网络协议 网络架构
【网络编程入门】TCP与UDP通信实战:从零构建服务器与客户端对话(附简易源码,新手友好!)
在了解他们之前我们首先要知道网络模型,它分为两种,一种是OSI,一种是TCP/IP,当然他们的模型图是不同的,如下
|
19天前
|
监控 负载均衡 Java
如何设计高可用性的Java Web应用程序
如何设计高可用性的Java Web应用程序
|
21天前
|
安全 前端开发 Java
Java中的服务器端渲染(SSR)技术深入剖析
Java中的服务器端渲染(SSR)技术深入剖析
|
11天前
|
Java 数据格式
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
16 0
|
16天前
|
网络协议 Linux
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
|
16天前
|
Java 调度 Windows
Java面试之程序、进程、线程、管程和并发、并行的概念
Java面试之程序、进程、线程、管程和并发、并行的概念
13 0
|
19天前
|
网络协议 网络安全
使用NetAssist网络调试助手在单台计算机上配置TCP服务器和客户端
使用NetAssist网络调试助手在单台计算机上配置TCP服务器和客户端
37 0
|
19天前
|
缓存 Java 数据库
Java中的服务器端渲染(SSR)优化与实现
Java中的服务器端渲染(SSR)优化与实现
|
20天前
|
搜索推荐 Java 索引
Java中的服务器端渲染(SSR)
Java中的服务器端渲染(SSR)