热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

java基于TCP协议实现聊天程序

这篇文章主要为大家详细介绍了java基于TCP协议实现聊天程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

JAVA程序设计之基于TCP协议的socket聊天程序 ,供大家参考,具体内容如下

一、程序实现的功能

1、进入客户端界面
2、创建昵称
3、群发信息
4、@私聊
5、下线通知
6、在线人数统计

二、整体架构图

三、简单介绍

本程序实现了基于TCP通信的聊天程序:

1 服务器端:

服务器端继承JFrame框架,添加组件。创建服务器端的socket,起一个线程池,每接收到一个客户端的连接,分配给其一个线程处理与客户端的通信,将每个客户端的昵称和服务器分配给其的输出流存储到哈希表中。通过检索哈希表中昵称和输出流实现群聊和私聊。

2 客户端:

客户端继承JFrame框架,添加组件。创建客户端的socket,输入昵称,创建客户端socket对应的输入流输出流,与服务器端建立通讯。

四、设计描述

本程序的实现是以服务器端为中继,客户端的所有信息都先发送给服务器端。服务器端辨别是否为私聊信息,私聊信息发送给相应的私聊对象,否则,发送给所有的客户端对象。

(一) 服务器端:

1.1 服务器端继承JFrame框架,添加组件。
1.2 创建服务器端socket。建立一个哈希表,用于存储客户端的昵称以及服务器端对于每个连接的客户端建立的输出流。
1.3建立一个线程池,为每个连接的客户端分配一个执行线程。
1.3调用服务器端Socket的accept()函数,等待客户端的连接,每连接到一个客户端,线程池给相应的客户端分配一个线程。
1.4每个线程内,调用服务器端Socket,封装getOutputStream(),获得服务器端Socket相对应于连接的客户端的输出流和输入流。输入流首先读取到客户端发送来的昵称。将客户端的昵称和服务器端的输出流存储在哈希表中,并遍历整个哈希表,将某人上线的通知发送给所有的在线客户端。
1.5客户端将信息发送给服务器端,当服务器端传来的信息符合“@私聊对象:私聊信息”的格式时,服务器端认为这是私聊信息。服务器端将把私聊对象的昵称从信息中提取出来,通过哈希表找到与昵称对应的输出流。将私聊信息通过输出流发送给私聊对象。
当不是私聊信息时,服务器遍历整个哈希表,通过每个客户端对应的输出流发送给每个客户端。
1.6当客户端下线时,服务器端将移除哈希表中对应存储的客户端昵称以及输出流。并通知在线的每个客户端,某人已下线。

(二) 客户端:

2.1 客户端继承框架JFrame,添加各种组件。
2.2 创建客户端Socket,设置请求连接服务器端IP,以及使用的端口号。
2.3 封装客户端Socket的getInputStream()函数,获得客户端Socket的输入流接受服务器端发来的信息,封装Socket的getOutputStream()函数,获得Socket的输出流向服务器端发送信息。
2.4 通过向文本框添加动作事件监听器,监听文本框的输入。
2.5 当与服务器端连接成功时,系统提醒输入昵称。系统将对输入的昵称进行检索。判断是否有重复的昵称。如有重复则创建不成功,继续输入。
2.6 向服务器端发送信息时,如想对某人发送私聊信息,则需输入符合“@私聊对象的呢称:私聊信息”的格式,此时只有私聊对象和本人可以接收到。否则,发送的信息为公聊信息,所有的客户端都能够收到。
2.7 客户端不断接受服务器端的信息显示在文本域。

五、代码说话

1、服务器端:

package server;

import java.io.*; 
import java.net.*; 
import java.util.HashMap; 
import java.util.Map; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ThreadPoolExecutor; 
import javax.swing.*;
import java.awt.*;

public class TCPServer extends JFrame{  

 private JTextArea m_display=new JTextArea();

 private ServerSocket serverSocket; 

 /** 
 * 创建线程池来管理客户端的连接线程 
 * 避免系统资源过度浪费 
 */ 
 private ExecutorService exec; 

 // 存放客户端之间私聊的信息 
 private Map storeInfo; 

 public TCPServer() { 
  super("聊天程序服务器端");
  Container c=getContentPane();
  c.add(new JScrollPane(m_display),BorderLayout.CENTER);
  try { 

   serverSocket = new ServerSocket(6666); 
   storeInfo = new HashMap(); 
   exec = Executors.newCachedThreadPool(); 

  } catch (Exception e) { 
   e.printStackTrace(); 
  } 
 } 

 // 将客户端的信息以Map形式存入集合中 
 private void putIn(String key,PrintWriter value) { 
  synchronized(this) { 
   storeInfo.put(key, value); 
  } 
 } 

 // 将给定的输出流从共享集合中删除 
 private synchronized void remove(String key) { 
  storeInfo.remove(key); 
  m_display.append("当前在线人数为:"+ storeInfo.size());
  //for(String name: storeInfo.key)
 } 

 // 将给定的消息转发给所有客户端 
 private synchronized void sendToAll(String message) { 
  for(PrintWriter out: storeInfo.values()) { 
   out.println(message); 


   // m_display.append("已经发送了");
  } 
 } 

 // 将给定的消息转发给私聊的客户端 
 private synchronized void sendToSomeone(String name,String message) { 
  PrintWriter pw = storeInfo.get(name); //将对应客户端的聊天信息取出作为私聊内容发送出去 
  if(pw != null) pw.println("私聊:  "+message); 
 } 

 public void start() { 
  try { 
   m_display.setVisible(true);
   //m_display.append("mayanshuo");
   while(true) { 

   m_display.append("等待客户端连接... ... \n"); 

   Socket socket = serverSocket.accept(); 

   // 获取客户端的ip地址 
   InetAddress address = socket.getInetAddress(); 
   m_display.append("客户端:“" + address.getHostAddress() + "”连接成功! "); 
   /* 
   * 启动一个线程,由线程来处理客户端的请求,这样可以再次监听 
   * 下一个客户端的连接 
   */ 
   exec.execute(new ListenrClient(socket)); //通过线程池来分配线程 
   } 
  } catch(Exception e) { 
   e.printStackTrace(); 
  } 
 } 

 /** 
 * 该线程体用来处理给定的某一个客户端的消息,循环接收客户端发送 
 * 的每一个字符串,并输出到控制台 
 */ 
 class ListenrClient implements Runnable { 

  private Socket socket; 
  private String name; 

  public ListenrClient(Socket socket) { 
   this.socket = socket; 
  } 

  // 创建内部类来获取昵称 
  private String getName() throws Exception { 
   try { 
    //服务端的输入流读取客户端发送来的昵称输出流 
    BufferedReader bReader = new BufferedReader( 
     new InputStreamReader(socket.getInputStream(), "UTF-8")); 
    //服务端将昵称验证结果通过自身的输出流发送给客户端 
    PrintWriter ipw = new PrintWriter( 
     new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),true); 

    //读取客户端发来的昵称 
    while(true) { 
     String nameString = bReader.readLine(); 
     if ((nameString.trim().length() == 0) || storeInfo.containsKey(nameString)) { 
      ipw.println("FAIL"); 
     } else { 
      ipw.println("OK"); 
      return nameString; 
     } 
    } 
   } catch(Exception e) { 
    throw e; 
   } 
  } 

  @Override   
  public void run() { 
   try { 
    /* 
    * 通过服务器端的socket分配给每一个 
    * 用来将消息发送给客户端 
    */ 
    PrintWriter pw = new PrintWriter( 
     new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true); 

    /* 
    * 将客户昵称和其所说的内容存入共享集合HashMap中 
    */ 
    name = getName(); 
    putIn(name, pw); 
    Thread.sleep(100); 

    // 服务端通知所有客户端,某用户上线 
    sendToAll("*系统消息* “" + name + "”已上线"); 

    /* 
    * 通过客户端的Socket获取输入流 
    * 读取客户端发送来的信息 
    */ 
    BufferedReader bReader = new BufferedReader( 
     new InputStreamReader(socket.getInputStream(), "UTF-8")); 
    String msgString = null; 


    while((msgString = bReader.readLine()) != null) { 
     // 检验是否为私聊(格式:@昵称:内容) 
     if(msgString.startsWith("@")) { 
      int index = msgString.indexOf(":"); 
      if(index >= 0) { 
       //获取昵称 
       String theName = msgString.substring(1, index); 
       String info = msgString.substring(index+1, msgString.length()); 
       info = name + ":"+ info; 
       //将私聊信息发送出去 
       sendToSomeone(theName, info);

       sendToSomeone(name,info);

       continue; 
      } 
     } 
     // 遍历所有输出流,将该客户端发送的信息转发给所有客户端 
     m_display.append(name+":"+ msgString+"\n"); 
     sendToAll(name+":"+ msgString); 
    }  
   } catch (Exception e) { 
    // e.printStackTrace(); 
   } finally { 
    remove(name); 
    // 通知所有客户端,某某客户已经下线 
    sendToAll("*系统消息* "+name + "已经下线了。\n"); 

    if(socket!=null) { 
     try { 
      socket.close(); 
     } catch(IOException e) { 
      e.printStackTrace(); 
     } 
    }  
   } 
  } 
 } 

 public static void main(String[] args) { 
  TCPServer server = new TCPServer();
  server.setSize(400,400);
  server.setVisible(true);
  server.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  server.start(); 
 } 
} 

2、客户端:

package server;

import java.io.*; 
import java.net.*; 
import java.util.Scanner; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ThreadPoolExecutor; 
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class TCPClient extends JFrame { 

 private JTextField m_enter=new JTextField();
 private JTextArea m_display=new JTextArea();
 private int m_count=0;
 private static Socket clientSocket; 
 //private ExecutorService exec = Executors.newCachedThreadPool(); 
 private BufferedReader br;
 private PrintWriter pw;

 public TCPClient() 
 {
  super("聊天程序客户端");


  Container c=getContentPane();
  //m_enter.setSize(100,99);
  //m_display.setSize(200,100);
  m_enter.setVisible(true);
  m_display.setVisible(true);
  m_enter.requestFocusInWindow();  //转移输入焦点到输入区域

  //将光标放置在文本区域的尾部
  m_display.setCaretPosition(m_display.getText().length());


  c.add(m_enter,BorderLayout.SOUTH);
  c.add(new JScrollPane(m_display),BorderLayout.CENTER); 
  // this.add(panel);
  // this.setContentPane(jp);

 } 


 public static void main(String[] args) throws Exception { 
  TCPClient client = new TCPClient();
  client.setVisible(true);
  client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  client.setSize(470,708);
  client.start(); 
 } 


 public void start() { 
  try { 
   m_display.append("请创建用户名:");
   clientSocket=new Socket("localhost",6666);
   BufferedReader br = new BufferedReader( 
     new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); 
   PrintWriter pw = new PrintWriter( 
     new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true); 
   //ListenrServser l=new ListenrServser();
   m_enter.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent event)
    {
     try{
     String s=event.getActionCommand();
     m_enter.setText("");
      if(m_count==0)
     {
       pw.println(s);
       m_display.append("\n'"+s+"'"+"昵称设置成功。\n");

     }
     else
     {

       pw.println(s);

     }
     m_count++;

     }catch(Exception e)
     {
      e.printStackTrace();
     }
    }
   });


   String msgString;
   while((msgString = br.readLine())!= null) { 
    m_display.append(msgString+"\n"); 
   } 


  } catch(Exception e) { 
   e.printStackTrace(); 
  } finally { 
   if (clientSocket !=null) { 
    try { 
     clientSocket.close(); 
    } catch(IOException e) { 
     e.printStackTrace(); 
    } 
   } 
  } 
 } 



} 

六、运行结果

1、这里是服务器端,显示当前连接人数,以及公聊信息:

2、此时为群内成员公聊:

这里创建了三个角色:马衍硕、李琦琦、小红。

 src=

3、私聊:

私聊格式“@名称:”+内容。
私聊时,只有私聊的两个人可以接收到信息,其余人接收不到交流信息。
例:马衍硕和李琦琦私聊,小红接收不到私聊信息。
马衍硕和李琦琦接收到了私聊信息:



小红没有接收到私聊信息:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Git版本控制基础解析
    本文探讨了Git作为版本控制工具的基本概念及其重要性,不仅限于代码管理,还包括文件的历史记录与版本切换功能。通过对比Git与SVN,进一步阐述了分布式版本控制系统的独特优势。 ... [详细]
  • 本文探讨了在不同场景下如何高效且安全地存储Token,包括使用定时器刷新、数据库存储等方法,并针对个人开发者与第三方服务平台的不同需求提供了具体建议。 ... [详细]
  • 本文详细介绍了如何在Windows和Linux系统上配置Openfire服务器,包括安装步骤、数据库配置及端口映射等关键环节。 ... [详细]
  • 初探Hadoop:第一章概览
    本文深入探讨了《Hadoop》第一章的内容,重点介绍了Hadoop的基本概念及其如何解决大数据处理中的关键挑战。 ... [详细]
  • 本文详细介绍了如何使用Linux下的mysqlshow命令来查询MySQL数据库的相关信息,包括数据库、表以及字段的详情。通过本文的学习,读者可以掌握mysqlshow命令的基本语法及其常用选项。 ... [详细]
  • PHP 图形函数中实现汉字显示的方法
    本文详细介绍了如何在 PHP 的图形函数中正确显示汉字,包括具体的步骤和注意事项,适合初学者和有一定基础的开发者阅读。 ... [详细]
  • 利用Docker部署JupyterHub以支持Python协同开发
    本文介绍了如何通过Docker容器化技术安装和配置JupyterHub,以实现多用户的Python开发环境,特别适合团队协作场景。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • 在使用 MySQL 6.0.x 及以上版本的 JDBC 驱动时,若未正确配置 `serverTimezone` 参数,可能会导致连接异常。本文探讨了这一问题的原因及解决方法。 ... [详细]
  • Maven快照版本管理及更新策略详解
    本文深入探讨了Maven中的快照版本管理和更新策略,解释了快照版本与正式版本的区别,并提供了如何配置快照更新策略的方法,以确保项目依赖始终保持最新。 ... [详细]
  • 在CentOS 7中部署Nginx并配置SSL证书
    本文详细介绍了如何在CentOS 7操作系统上安装Nginx服务器,并配置SSL证书以增强网站的安全性。适合初学者和中级用户参考。 ... [详细]
  • 本文由公众号【数智物语】(ID: decision_engine)发布,关注获取更多干货。文章探讨了从数据收集到清洗、建模及可视化的全过程,介绍了41款实用工具,旨在帮助数据科学家和分析师提升工作效率。 ... [详细]
  • 页面预渲染适用于主要包含静态内容的页面。对于依赖大量API调用的动态页面,建议采用SSR(服务器端渲染),如Nuxt等框架。更多优化策略可参见:https://github.com/HaoChuan9421/vue-cli3-optimization ... [详细]
  • STM32代码编写STM32端不需要写关于连接MQTT服务器的代码,连接的工作交给ESP8266来做,STM32只需要通过串口接收和发送数据,间接的与服务器交互。串口三配置串口一已 ... [详细]
  • 本文详细探讨了 Android Service 组件中 onStartCommand 方法的四种不同返回值及其应用场景。Service 可以在后台执行长时间的操作,无需提供用户界面,支持通过启动和绑定两种方式创建。 ... [详细]
author-avatar
对方尽快
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有