最近老师叫我们做一个基于Socket的多人聊天室,网上很多教程都只讲了如何通过Socket来建立连接以及通过控制台一遍打印证明连接已经完成但还没有具体实现多人聊天。这次我整理了一下自己的实现代码,希望能帮助到和我一样学习时感到困惑的兄弟姐妹。

这是基于TCP协议实现的Socket多人聊天室,分别用到ServerSocket(服务器)和Socket(客户端),说到TCP当然也离不开建立我们常常听到的三次握手、四次挥手,这里附上几位大佬的总结。
https://blog.csdn.net/jia281460530/article/details/41901069

https://blog.csdn.net/u013782203/article/details/51767763

聊天室有一个简单的图形界面,每一次客户端请求并完成连接时,服务器端会显示当前在线人数,同时在侧边栏添加了一个更新在线客户端信息的功能界面,显示在线的客户端(具体是显示每个客户端的名字,当然有一个局限就是客户端的名字不能相同)。如果想要在多台计算机之间实现通信,可以以管理员身份运行cmd命令行输入ipconfig查看相应的ip地址。

服务器主要功能:
服务器端的任务是不断地接收来自于多个客户端的连接请求,并且为每一个请求连接的客户端分配一个线程管理,每一个线程只管理与它相连接的客户端Socket。这里我用了Client内部类来封装每一个已经连接的客户端,这样子的好处是Socket的信息及输入输出流都能够一起保存;并将它们添加到List中,用于记录在线的客户端信息。同时,服务器起到的一个最重要的作用是转发,将A客户端发出的消息转发给B、C、D等多个在线客户端,这个时候保存了各个Client类的List就派上了关键用场,服务器每次收到一个客户端的消息就在List中查找每个在线客户端并向他们转发这个客户端所发出的消息(当然,跳过它自己本身)。总的来说服务器有以下几个功能:

  1. 循环监听并响应各个客户端的连接并为他们分配线程 **
  2. 接受各个客户端的消息并处理(代码中主要是在消息前面加上这个客户端的名字 如:Client_Name: + aLine(消息))
  3. 通过已保存好各个客户端信息的List来转发通过处理后的消息 **
  4. 如果有某个客户端A下线,则告诉其他在线客户端这个客户端A下线(代码中通过客户端发送Good Bye来说明离线)
  5. 更新【在线客户端信息界面】并将更新通知发往每一个在线客户端

客户端功能:

  1. 发送消息
  2. 接收消息并处理显示
  3. 更新在线客户端

其中,接收到的消息为:【Client_Name:+消息 】的时候显示在主文本框中;接收到的消息为:【update:+内容】的时候代表这是一个更新在线客户端的通知,显示在侧边的在线Client文本框中,内容中就是在线客户端的全部信息,并且每个客户端是以一个空格分隔的(这样子处理是因为我用BufferedReader读取消息时每次只读一行,(因为本人对输入输出流也并非太了解。。。))这个地方感觉做的不太好,另外,如果大家有什么建议或想法欢迎大家能跟我说说讨论讨论。

用到的技术:

  • 图形界面GUI及响应事件
  • 基于TCP的ServerSocket、Socket套接字
  • 输入输出流
  • Thread类
  • 其中,服务器Thread主要是为每一个【封装了Socket的Client类】分配一个线程,客户端Thread主要是为了能实现不断地发送消息和同时不断地接受消息的功能。

以下是具体实现的代码

服务器:

package Server;import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.LineBorder;public class Server extends JFrame {// server 对象private ServerSocket server;// Client's socket对象private Socket socket;// 存放客户端ClientList<Client> list;// 窗口标签private JLabel label;// 面板用于容纳文本区和按钮private JPanel labelPanel, clientPanel;// 显示区private JTextArea centerTextArea, clientTextArea;public Server(int port) throws IOException {server = new ServerSocket(port);//控制台输出服务器端口号和ip地址System.out.println(server.getLocalPort());System.out.println(InetAddress.getLocalHost().getHostAddress());//初始化存放Client的链表list = new ArrayList<Client>();setTitle("服务器");//图形界面GUIcreate();//添加客户端addClient();}private void create() {setSize(500, 500);// 窗口label,显示服务器名字label = new JLabel("10086 服务器");label.setFont(new Font("宋体", 5, 15));labelPanel = new JPanel();labelPanel.setSize(150, 20);labelPanel.add(label, BorderLayout.CENTER);// 窗口center部分,显示接收到的Client的消息centerTextArea = new JTextArea("    ---聊天室已连接---    " + "\r\n");centerTextArea.setFont(new Font("微软雅黑", 5, 13));centerTextArea.setEditable(false); // 不可编辑centerTextArea.setBackground(Color.LIGHT_GRAY);// 窗口client部分,显示当前在线的ClientclientTextArea = new JTextArea("--在线Client--" + "\r\n");clientTextArea.setEditable(false); // 不可编辑//装载clientTextArea的容器clientPanel = new JPanel(new FlowLayout());clientPanel.setBorder(new LineBorder(null));clientPanel.add(clientTextArea);//JFrame框架用于添加各种容器add(clientPanel, BorderLayout.EAST);add(labelPanel, BorderLayout.NORTH);add(new JScrollPane(centerTextArea), BorderLayout.CENTER);setVisible(true);setResizable(false); // 窗口大小不可调整setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}// 添加客户端private void addClient() throws IOException {//循环监听客户端的连接请求并逐个分配线程while (true) {// 接受客户端连接请求并保存socket = server.accept();//图形界面输出当前客户端ip地址以及在线人数String ip = socket.getInetAddress().getHostAddress();centerTextArea.append(ip + "连接成功,当前客户端数为:" + (list.size() + 1) + "\r\n");Client client = new Client(socket);// 添加用户到列表list.add(client);// 为用户创建并启动一个接受线程new Thread(client).start();//【在线客户端】更新通知client.update();}}//客户端处理类(【接受线程】用于不断接受消息 同时不影响自己发送消息)class Client implements Runnable {String name;Socket socket;// 输出流private PrintWriter out;// 输入流private BufferedReader in;public Client(Socket socket) throws IOException {this.socket = socket;// 获得相应Client's Socket 输入流in = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 获得相应Client's Socket 输出流out = new PrintWriter(socket.getOutputStream());//获取当前Client的名字并更新clientTextAreaname = in.readLine();clientTextArea.append(name+"\r\n");}//向对应流发送消息public void send(String str) {out.println(str);out.flush();}//给【每一个客户端】发送更新clientTextArea的消息public void update() {StringBuffer sBuffer = new StringBuffer("update:");clientTextArea.setText("--在线Client--\r\n");for(int i = 0;i<list.size();i++) {clientTextArea.append(list.get(i).name+"\r\n");sBuffer.append(list.get(i).name+" ");}for(int i = 0;i<list.size();i++) {list.get(i).out.println(sBuffer);list.get(i).out.flush();}}@Overridepublic void run() {try {String aLine;boolean flag = true;while (flag && (aLine = in.readLine()) != null) {//处理并保存当前客户端发来的消息(消息前加上当前客户端名字)String str = this.name + "说:" + aLine;//内容显示centerTextArea.append(str + "\r\n");// 给每一个保存在List中的客户端(除了当前客户端)转发当前客户端发来的消息for (int i = 0; i < list.size(); i++) {Client client = list.get(i);if (client != this) {client.send(str);/** 如果当前客户端发来的消息是“Good Bye”表示其要下线* 服务端则转告给【其他的客户端】该客户端下线的消息并进行对* 当前客户端Socket关闭的处理*/if (aLine.equals("Good Bye")) {client.send(this.name + "已离线");flag = false;}}}}} catch (Exception e1) {} finally {try {//当前客户端Socket关闭处理//1.链表中移除该Clientif (list.contains(this))list.remove(this);/** 2.更新服务器界面中的【在线客户端窗口】以及* 为其他的客户端发送【更新在线客户端窗口】的消息*/update();System.out.println(this.name+"已离开");//3.输出输入流关闭处理socket.shutdownOutput();socket.shutdownInput();socket.close();} catch (Exception e) {System.out.println(this.name + "出现异常强行关闭");}}}}public static void main(String[] args) throws IOException {Server Test = new Server(10086);}}

客户端:

package Client;import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.LineBorder;public class Client extends JFrame implements Runnable {// Client_Nameprivate String name;// Client's socket对象private Socket socket;// 输出流private PrintWriter out;// 输入流private BufferedReader in;// 用于关闭线程Thread receivethread;// 窗口标签private JLabel label;// 面板用于容纳文本区和按钮private JPanel buttomPanel, inputPanel, labelPanel, centenPanel;// 显示区以及输入区private JTextArea centerTextArea, inputTextArea, clientTextArea;// 发送与清除按钮private JButton send, clear;//Clientpublic Client(String name, Socket socket) throws IOException {this.name = name;this.socket = socket;//控制台输出端口号以及主机地址System.out.println(socket.getLocalPort());System.out.println(InetAddress.getLocalHost().getHostAddress());//得到输入输出流in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream());//发送名字给服务器out.println(name);out.flush();setTitle(name);//Client端GUIcreate();//创建并启动自己的接收线程receivethread = new Thread(this);receivethread.start();//监听程序启动setActionLister();}//GUIprivate void create() {setSize(500, 500);// 窗口labellabel = new JLabel("10086 聊天室");label.setFont(new Font("宋体", 5, 15));labelPanel = new JPanel();labelPanel.setSize(150, 20);labelPanel.add(label, BorderLayout.CENTER);// 窗口center部分centerTextArea = new JTextArea("    ---聊天室已连接---    " + "\r\n");centerTextArea.setFont(new Font("微软雅黑", 5, 13));centerTextArea.setEditable(false); // 不可编辑centerTextArea.setBackground(Color.LIGHT_GRAY);// 窗口client部分clientTextArea = new JTextArea("--在线Client--" + "\r\n");clientTextArea.setEditable(false); // 不可编辑clientTextArea.setBorder(new LineBorder(null));// centerPanel 充当center和client的容器centenPanel = new JPanel(new BorderLayout());centenPanel.add(new JScrollPane(centerTextArea), BorderLayout.CENTER);centenPanel.add(clientTextArea, BorderLayout.EAST);// 窗口button按钮send = new JButton("发送");clear = new JButton("清空");buttomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 5));buttomPanel.add(clear);buttomPanel.add(send);// 窗口input部分inputPanel = new JPanel(new BorderLayout());inputTextArea = new JTextArea();inputTextArea = new JTextArea(7, 20);inputPanel.add(new JScrollPane(inputTextArea), BorderLayout.CENTER);inputPanel.add(buttomPanel, BorderLayout.SOUTH);add(labelPanel, BorderLayout.NORTH);add(centenPanel, BorderLayout.CENTER);add(inputPanel, BorderLayout.SOUTH);setVisible(true);setResizable(false); // 窗口大小不可调整setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);}//inpuTextArea以及按钮send&clear的监听程序private void setActionLister() {//关闭窗口时通知服务器本客户端已关闭addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {try {if(out!=null) {out.println("Good Bye");out.flush();}}catch (Exception e2) {System.out.println("客户端已关闭");}}});//点击send按钮将inputTextArea的消息全部发送send.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {String aLine = inputTextArea.getText();inputTextArea.setText("");  //输入框清空centerTextArea.append("你说: " + aLine + "\r\n");try {out.println(aLine);out.flush();if (aLine.equals("Good Bye")) {//接受线程中断receivethread.interrupt();socket.shutdownOutput();socket.shutdownInput();socket.close();}} catch (Exception e1) {System.out.println("已断开");}}});//键盘监听事件inputTextArea.addKeyListener(new KeyAdapter() {public void keyReleased(KeyEvent e) {//CTRL+ENTER相当于点击send按钮if (e.getKeyCode() == KeyEvent.VK_ENTER && e.isControlDown()) {send.doClick();}}});//清除inputTextArea的内容clear.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {inputTextArea.setText("");}});}@Overridepublic void run() {try {String aLine = "";while ((aLine = in.readLine()) != null) {//centerTextAre显示接受的消息 if (!aLine.split(":")[0].equals("update"))centerTextArea.append(aLine + "\r\n");//在綫client更新else {String[] strings = (aLine.split(":")[1]).split(" ");clientTextArea.setText("--在线Client--\r\n");for (String s : strings)clientTextArea.append(s + "\r\n");}}} catch (Exception e) {centerTextArea.append("当前连接已断开\r\n");System.out.println("当前连接已断开");}}public static void main(String[] args) throws IOException {new Client("Client5", new Socket("localhost", 10086));// new clientGUI("Client2", new Socket("localhost", 10086));}}

【Java Socket】TCP协议的多人聊天室相关推荐

  1. 基于Python Tkiner、thread与socket实现的简单多人聊天室,在Python中创建TCP服务器与客户端进行通信

    基于Python Tkiner.thread与socket实现的简单多人聊天室,在Python中创建TCP服务器与客户端进行通信 完整代码下载地址:基于Python Tkiner.thread与soc ...

  2. socket.io php 聊天室,WebSocket学习(一)——基于socket.io实现简单多人聊天室

    前言 什么是Websocket呢? 我们都知道在Http协议中,客户端与服务器端的通信是靠客户端发起请求,然后服务器端收到请求再进行回应,这个过程中,客户端是主动的,服务器端是被动的.Websocke ...

  3. c语言tcp多线程聊天室,66 网络编程(五)——TCP多线程实现多人聊天室

    思路 客户端读写各一个类,可以使内部类,实现Runnable.读写类都与服务器端建立连接,一个收,一个发. 客户端实现接收和转发.多线程实现每个客户端的连接(使与各客户端的连接独立). 服务器端中创建 ...

  4. python socket基于TCP/IP协议实现多人聊天室

    文章目录 前言 一.实现原理 二.queue队列 三.代码实现 四.需要注意的地方 五.总结 前言 所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象.一个套接字 ...

  5. 【Chat】实验 -- 实现 C/C++下TCP, 服务器/客户端 多人聊天室

    本次实验利用TCP/IP, 语言环境为 C/C++ 利用套接字Socket编程,以及线程处理, 实现Server/CLient 之间多人的聊天系统的基本功能. 结果大致如: 下面贴上代码(参考参考.. ...

  6. Java NIO基于控制台的多人聊天室

    闲来无事写了个基于NIO的聊天室项目,费话不说了,直接贴代码吧. Server端代码如下: package com.xz.helloworld.nettyt.nio.im;import java.io ...

  7. 【JAVA实战】用socket通信编程制作多人聊天室

     个人简介

  8. 使用nodejs+Socket打造P2P实现多人聊天室

  9. Java基于TCP(Socket)协议的网络语音聊天

    Java基于TCP协议的网络语音聊天 本聊天是基于tcp协议进行的,其本质为:本地录音->将录音通过网络编程转发给他人->他人进行录音的播放. 所需知识:多线程,基于tcp协议的网络编程 ...

最新文章

  1. 维度爆炸?Python实现数据压缩如此简单
  2. CVPR 2020 | 利用强化学习进行交互式3D医学图像分割
  3. 公开致铁道部 高效运营从细节入手
  4. python 同花顺thstrader_GitHub - fswzb/THSTrader: 量化交易。同花顺免费模拟炒股软件客户端的python API。(Python3)...
  5. WINKEY功能键你会用吗??
  6. CISCO 2950,3550交换机的端口隔离
  7. java中判断两个方法是否相同
  8. mysql 索引计划_Mysql索引、查询计划、优化方向
  9. (多线程)leetcode1195. 交替打印字符串 最简单解法一个变量搞定
  10. 数据结构 --- 线性表学习(php模拟)
  11. touch: cannot touch ‘***’: Read-only file system
  12. c++ 11 中显式默认设置的函数和已删除的函数 总结
  13. dedecms使用AB模板后台如何静态化tag标签
  14. MySQL 数据库语句基础
  15. MarkdownNote
  16. 浅析瞬态抑制二极管双向tvs管
  17. 安装VC,NTVDM CPU 遇到无效指令 --绝对能用的解决方法
  18. 浪潮森林防火智能监控解决方案
  19. 数字IC设计随笔之一(Verdi自动添加波形脚本应用)
  20. 如何将caj转换成word

热门文章

  1. Collection体系结构图
  2. 算法 排序4 统计工龄
  3. shell pigz高效压缩解压命令
  4. 【LeetCode-中等】46. 全排列(图文详解)
  5. 数据资产管理 与 智能设备_如何使用“智能管理器”释放三星设备上的空间
  6. 计算机美术基础课程标准,服装设计与工艺专业核心课程标准.doc
  7. 使用Relational Cache加速Spark数据分析
  8. 深度学习之理解神经网络的四个公式
  9. C语言-转化大写字母为小写字母输入一个大写字母,要求用小写字母输出
  10. Sofia的同步与多线程