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

相关文章
|
1月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
129 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
1月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
135 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
1月前
|
Java Linux
java读取linux服务器下某文档的内容
java读取linux服务器下某文档的内容
36 3
java读取linux服务器下某文档的内容
|
1月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
35 1
|
1月前
|
分布式计算 资源调度 Hadoop
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
76 4
|
1月前
|
Java Shell Maven
Flink-11 Flink Java 3分钟上手 打包Flink 提交任务至服务器执行 JobSubmit Maven打包Ja配置 maven-shade-plugin
Flink-11 Flink Java 3分钟上手 打包Flink 提交任务至服务器执行 JobSubmit Maven打包Ja配置 maven-shade-plugin
103 4
|
5天前
|
机器学习/深度学习 人工智能 弹性计算
什么是阿里云GPU云服务器?GPU服务器优势、使用和租赁费用整理
阿里云GPU云服务器提供强大的GPU算力,适用于深度学习、科学计算、图形可视化和视频处理等多种场景。作为亚太领先的云服务提供商,阿里云的GPU云服务器具备灵活的资源配置、高安全性和易用性,支持多种计费模式,帮助企业高效应对计算密集型任务。
|
7天前
|
存储 分布式计算 固态存储
阿里云2核16G、4核32G、8核64G配置云服务器租用收费标准与活动价格参考
2核16G、8核64G、4核32G配置的云服务器处理器与内存比为1:8,这种配比的云服务器一般适用于数据分析与挖掘,Hadoop、Spark集群和数据库,缓存等内存密集型场景,因此,多为企业级用户选择。目前2核16G配置按量收费最低收费标准为0.54元/小时,按月租用标准收费标准为260.44元/1个月。4核32G配置的阿里云服务器按量收费标准最低为1.08元/小时,按月租用标准收费标准为520.88元/1个月。8核64G配置的阿里云服务器按量收费标准最低为2.17元/小时,按月租用标准收费标准为1041.77元/1个月。本文介绍这些配置的最新租用收费标准与活动价格情况,以供参考。
|
5天前
|
机器学习/深度学习 人工智能 弹性计算
阿里云GPU服务器全解析_GPU价格收费标准_GPU优势和使用说明
阿里云GPU云服务器提供强大的GPU算力,适用于深度学习、科学计算、图形可视化和视频处理等场景。作为亚太领先的云服务商,阿里云GPU云服务器具备高灵活性、易用性、容灾备份、安全性和成本效益,支持多种实例规格,满足不同业务需求。
|
13天前
|
弹性计算
阿里云2核16G服务器多少钱一年?亲测价格查询1个月和1小时收费标准
阿里云2核16G服务器提供多种ECS实例规格,内存型r8i实例1年6折优惠价为1901元,按月收费334.19元,按小时收费0.696221元。更多规格及详细报价请访问阿里云ECS页面。
52 9