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

相关文章
|
9月前
|
网络协议 开发者 Python
Socket如何实现客户端和服务器间的通信
通过上述示例,展示了如何使用Python的Socket模块实现基本的客户端和服务器间的通信。Socket提供了一种简单且强大的方式来建立和管理网络连接,适用于各种网络编程应用。理解和掌握Socket编程,可以帮助开发者构建高效、稳定的网络应用程序。
500 10
|
11月前
|
Java 物联网 定位技术
Java socket获取gps定位
通过Java Socket编程获取GPS定位信息可以实现实时的地理位置跟踪。本文介绍了如何搭建Socket服务器、解析GPS数据以及实现客户端发送GPS数据的流程。希望这篇文章能为开发者提供清晰的指导,帮助构建高效的GPS定位系统。
343 7
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
691 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
678 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
210 2
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
171 0
|
网络协议 安全 Java
Java Socket原理
Java Socket原理是指在Java中通过Socket实现的网络通信的基础理论与机制。Socket是网络中不同设备间通信的一种标准方式,它允许应用程序之间通过TCP/IP等协议进行数据交换。在Java中,利用Socket编程可以方便地创建客户端与服务器端应用,实现跨网络的数据传输功能,是互联网软件开发中的重要技术之一。它支持多种通信模式,如可靠的流式套接字(TCP)和数据报式套接字(UDP)。
255 10
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
849 0
|
Java
SpringBoot java 一个接口,多个实现,客户定制化
SpringBoot java 一个接口,多个实现,客户定制化
202 0
|
安全 网络协议 网络安全
Python Socket编程大揭秘:从菜鸟到黑客的进阶之路,你准备好了吗?
【7月更文挑战第27天】Python Socket编程是网络开发的关键技能,它开启从简单数据传输到复杂应用的大门。Socket作为网络通信的基础,通过Python的`socket`模块可轻松实现跨网通信。
152 0

热门文章

最新文章