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

相关文章
|
2月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
165 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
2月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
175 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
2月前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
171 1
|
4月前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
3月前
|
网络协议 Linux
TCP 和 UDP 的 Socket 调用
【9月更文挑战第6天】
|
4月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
150 0
|
5月前
|
Java
如何在Java中实现多线程的Socket服务器?
在Java中,多线程Socket服务器能同时处理多个客户端连接以提升并发性能。示例代码展示了如何创建此类服务器:监听指定端口,并为每个新连接启动一个`ClientHandler`线程进行通信处理。使用线程池管理这些线程,提高了效率。`ClientHandler`读取客户端消息并响应,支持简单的文本交互,如发送欢迎信息及处理退出命令。
|
前端开发 Java Linux
Java服务器宕机解决方法论(上)
Java服务器宕机解决方法论(上)
756 0
Java服务器宕机解决方法论(上)
|
Java 调度
Java服务器宕机解决方法论(下)
Java服务器宕机解决方法论(下)
378 0
|
8天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
38 6