---------------siwuxie095

 
 

 
 

 
 

 
 

 
 

 
 

 
 

关于
聊天服务器,详见本人博客的分类:来一杯Java,

里面的
使用ServerSocket建立聊天服务器(二)

 
 

本人博客(任选一个)链接:

https://www.baidu.com/s?ie=UTF-8&wd=siwuxie095

 
 

 
 

 
 

将 使用ServerSocket建立聊天服务器(二) 中的 ChatSocket.java

和 ChatManager.java 略作修改:

 
 

 
 

ChatSocket.java:

 
 

package com.siwuxie095.socket;

 
 

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.io.UnsupportedEncodingException;

import java.net.Socket;

 
 

/**

* run()循环执行的读取的工作,即当前的服务器会不断的从客户端读取内容

* 将读取到的内容发送到集合Vector中的所有客户端(除了自身)

*

* output()方法中,代码段(1)等效于代码段(2)

*

* @author siwux

*

*/

 
 

//创建用于Socket通信的线程:ChatSocket

public class ChatSocket extends Thread {

 

Socket socket;

 

//创建构造方法,传入Socket对象

public ChatSocket(Socket socket) {

this.socket=socket;

}

 

 

 

public
void output(String out) {

 

//(1)

// try {

// socket.getOutputStream().write((out+"\n").getBytes("UTF-8"));

// } catch (UnsupportedEncodingException e) {

// e.printStackTrace();

// } catch (IOException e) {

// System.out.println("断开了一个客户端连接");

// ChatManager.getChatManager().remove(this);

// e.printStackTrace();

// }

 

 

//(2)

try {

 
 

//对当前的Socket执行
数据输出相关功能的包装

//使用getOutputStream()获取输出流,通过输出流向外输出数据

//返回值是OutputStream类型,创建以接收返回值

OutputStream os=socket.getOutputStream();

 

 
 

//创建一个BufferedWriter作为数据的输出,传入匿名对象,指定编码,层层包装

BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os,"UTF-8"));

 
 

//让BufferedWriter输出字符串

//同时加一个换行符,表示一行已完结,这样flush时才会强制输出

//如果不加,数据只会传到当前socket的缓冲区,而发不到其他的客户端的界面上

bw.write(out+"\n");

//因为带缓冲,所以需要强制输出,不然无法输出

bw.flush();

 
 

}catch (UnsupportedEncodingException e) {

e.printStackTrace();

} catch (IOException e) {

System.out.println("断开了一个客户端连接");

ChatManager.getChatManager().remove(this);

e.printStackTrace();

}

 
 

}

 

//复写run()方法

@Override

public
void run() {

 

output("你已经连接到了服务器");

 

try {

 

//对Socket的输入流进行包装

//在指定InputStreamReader时,指定编码的字符集

//有异常抛出,用 try catch 捕获

BufferedReader br=new BufferedReader(

new InputStreamReader(

socket.getInputStream(),"UTF-8"));

 

String line=null;

//读取从客户端发送给服务器的数据

while ((line=br.readLine())!=null) {

System.out.println(line);

//通过静态方法,将自己传入,同时传入line

ChatManager.getChatManager().publish(this, line);

}

 

//关闭流

br.close();

 

System.out.println("断开了一个客户端连接");

ChatManager.getChatManager().remove(this);

 

} catch (IOException e) {

System.out.println("断开了一个客户端连接");

ChatManager.getChatManager().remove(this);

e.printStackTrace();

}

 

 

}

}

 
 

 
 

 
 

ChatManager.java:

 
 

package com.siwuxie095.socket;

 
 

import java.util.Vector;

 
 

 
 

/**

* ChatManager 将不同的socket所新建的ChatSocket线程管理起来

* 由于一个聊天服务器只能有一个聊天的管理器:ChatManager

* 所以要把这个类作单例化处理

* 单例化的第一步就是让这个类的构造方法变成 private 类型

*

* @author siwux

*

*/

public class ChatManager {

 

//让构造方法变成private类型,完成单例化的第一步

private ChatManager(){}

 

//为当前类创建一个实例

private static final ChatManager cm=new ChatManager();

 

//创建方法 getChatManager(),返回ChatManager类型

public static ChatManager getChatManager() {

return cm;

}

 

 

 

//至此,完成了这个类(ChatManager)的单例化

//接下来就是对ChatSocket线程进行管理

 

 

//创建一个Vector,指定泛型为ChatSocket

Vector<ChatSocket> vector=new Vector<>();

 

//创建add()方法,为当前集合添加一个新的ChatSocket对象

//在创建ChatSocket时使用,即ServerListener.java中使用

public
void add(ChatSocket cs) {

//调用Vector的add()方法,传入cs即可

vector.add(cs);

}

 

 

public
void remove(ChatSocket cs) {

vector.remove(cs);

}

 

 

//创建publish()方法,其中的某个ChatSocket线程可以调用publish()

//向其他的客户端(其他的ChatSocket线程)发送信息

//传入线程本身和需要发送的信息

public
void publish(ChatSocket cs,String msg) {

 

//因为要发送给集合Vector中的其他所有线程,要使用遍历

for (int i = 0; i < vector.size(); i++) {

//获取循环中的第 i 个对象

ChatSocket csx=vector.get(i);

 

//当前发送信息的线程就不用再接收这条信息,

//判断发送消息的对象是不是当前对象

if (!cs.equals(csx)) {

csx.output(msg);

}

}

}

 

 

 

 

 

 

 
 

}

 
 

 
 

 
 

 
 

 
 

 
 

聊天客户端:

 
 

工程名:MyJavaChatClient

包名:com.siwuxie095.main、com.siwuxie095.view

类名:StartClient.java(主类)、ChatManagerX.java、MainWindow.java

 
 

 
 

工程结构目录如下:

 
 

 
 

 
 

 
 

 
 

StartClient.java(主类):

 
 

package com.siwuxie095.main;

 
 

import java.awt.EventQueue;

 
 

import com.siwuxie095.view.MainWindow;

 
 

public class StartClient {

 
 

public static
void main(String[] args) {

 

//将窗体MainWindow.java的主方法里的代码复制粘贴到这里(StartClient.java)

//将MainWindow.java里的主方法删除,这样就可以在主程序中将窗体执行出来了

EventQueue.invokeLater(new Runnable() {

public
void run() {

try {

MainWindow frame = new MainWindow();

frame.setVisible(true);

//在创建Window时传递对MainWindow的引用

ChatManagerX.getCM().setWindow(frame);

} catch (Exception e) {

e.printStackTrace();

}

}

});

 

}

 
 

}

 
 

 
 

 
 

ChatManagerX.java:

 
 

package com.siwuxie095.main;

 
 

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStreamWriter;

import java.io.PrintWriter;

import java.net.Socket;

import java.net.UnknownHostException;

 
 

import com.siwuxie095.view.MainWindow;

 
 

//需要将这个类单例化

public class ChatManagerX {

 
 

//让构造方法变成private类型,完成单例化的第一步

private ChatManagerX(){}

 

//创建一个 private static final 修饰的实例

private static final ChatManagerX instance=new ChatManagerX();

 

//创建公有的 getChatManager()获取上面的私有实例

//这样,当前的ChatManager类就只会有唯一的一个实例

public static ChatManagerX getCM() {

return instance;

}

 

 

//现在界面中只能向ChatManager发送数据

//为了让接收到的数据放到界面上,最简便的方法即在本地引用MainWindow

MainWindow window;

//实现setWindow()方法

public
void setWindow(MainWindow window) {

this.window = window;

//在ChatManager中操作window.appendText()在界面中显示数据

window.appendText("提示:文本框已经和ChatManager绑定了");

}

 

 

//创建一个Socket对象

Socket socket;

//创建输入流 BufferedReader

BufferedReader reader;

//创建输出流 PrintWriter 这里不使用 BufferedWriter

PrintWriter writer;

 

//具体实现连接服务器的操作

public
void connect(String ip) {

 

//呼叫连接时创建一个新的线程

//对网络的处理放到线程中执行

//(先建立连接,之后循环从服务器读取数据)

new Thread(){

 

 

@Override

public
void run() {

 

try {

socket=new Socket(ip, 12345);

 

//获取输入和输出流,层层包装

writer=new PrintWriter(

new OutputStreamWriter(

socket.getOutputStream(),"UTF-8"));

 

reader=new BufferedReader(

new InputStreamReader(

socket.getInputStream(),"UTF-8"));

 

//创建完输入输出流,需要循环监听当前输入流是否有数据

String line;

while ((line=reader.readLine())!=null) {

//通过window.appendText()将数据输出到界面中

window.appendText("收到:"+line);

}

 

//读取数据为空,则连接结束,关闭流

writer.close();

reader.close();

//置空,更安全

writer=null;

reader=null;

 

} catch (UnknownHostException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

 
 

 

 

}.start();

}

 

//点击发送按钮时的操作

public
void send(String out) {

//发送时,writer不为空才能发送

if (writer!=null) {

//为当前的socket输出内容时,需要添加 \n 进行换行

//这时flush才能强制把这一行输出,

//即这一行完结了,flush才会把它输出

//

//如果不加 \n 数据实际上是传出去了,

//但只是传到了当前socket的缓冲区域

//只有关闭了socket,socket才会把它强制输出,这是不可取的

//因为聊天要保证实时性,所以要加一个换行符

writer.write(out+"\n");

//flush强制刷新输出

writer.flush();

}else {

//提示信息

window.appendText("提示:当前的连接已经中断...");

}

 

 

}

}

 
 

 
 

 
 

MainWindow.java:

 
 

package com.siwuxie095.view;

 
 

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.event.MouseAdapter;

import java.awt.event.MouseEvent;

 
 

import javax.swing.GroupLayout;

import javax.swing.GroupLayout.Alignment;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JTextArea;

import javax.swing.JTextField;

import javax.swing.LayoutStyle.ComponentPlacement;

import javax.swing.border.EmptyBorder;

 
 

import com.siwuxie095.main.ChatManagerX;

 
 

/**

* 添加
默认序列版本ID

* 添加
一个TextArea:JTextArea

* 选择根容器 contentPane,将布局改为 GroupLayout

*

* 在 JTextArea 上下方各添加一个JTextField、JButton

*

* @author siwux

*

*/

public class MainWindow extends JFrame {

 
 

/**

* 添加默认序列版本ID

* Add default serial version ID

*/

private static final
long serialVersionUID = 1L;

private JPanel contentPane;

 

//将文本控件等声明到类当中,作为整个类的变量

JTextArea txt;

private JTextField ip;

private JTextField send;

 
 

 

 
 

/**

* Create the frame.

*/

public MainWindow() {

//添加属性 setAlwaysOnTop(true)

//这样打开多个窗口都可以显示在其他应用的上方

setAlwaysOnTop(true);

 

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

setBounds(100, 100, 504, 353);

contentPane = new JPanel();

contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));

setContentPane(contentPane);

 

txt = new JTextArea();

txt.setText("Ready...");

 

ip = new JTextField();

ip.setText("127.0.0.1");

ip.setColumns(10);

 

JButton button = new JButton("连接到服务器");

button.addMouseListener(new MouseAdapter() {

@Override

public
void mouseClicked(MouseEvent arg0) {

//使用ChatManager的connect()和按钮的监听事件绑定

//通过ip.getText()获得需要连接的服务器的IP地址

//通过IP来建立Socket连接

ChatManagerX.getCM().connect(ip.getText());

 

}

});

button.addActionListener(new ActionListener() {

public
void actionPerformed(ActionEvent arg0) {

}

});

 

send = new JTextField();

send.setText("你好");

send.setColumns(10);

 

JButton btnNewButton = new JButton("发送");

btnNewButton.addMouseListener(new MouseAdapter() {

@Override

public
void mouseClicked(MouseEvent arg0) {

//使用ChatManager的send()和按钮的监听事件绑定

//传入send文本框的文本

//第一个send()到服务器

//第二个appendText()添加到中间的显示界面

ChatManagerX.getCM().send(send.getText());

appendText("我说:"+send.getText());

//当前文本发送出去后,需要将当前的文本框清空

send.setText("");

 

}

});

btnNewButton.addActionListener(new ActionListener() {

public
void actionPerformed(ActionEvent e) {

}

});

 

 

//下面这一大段是自动生成的代码,不用管

GroupLayout gl_contentPane = new GroupLayout(contentPane);

gl_contentPane.setHorizontalGroup(

gl_contentPane.createParallelGroup(Alignment.LEADING)

.addGroup(gl_contentPane.createSequentialGroup()

.addContainerGap()

.addGroup(gl_contentPane.createParallelGroup(Alignment.LEADING)

.addComponent(txt, GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE)

.addGroup(gl_contentPane.createSequentialGroup()

.addComponent(send, GroupLayout.PREFERRED_SIZE, 302, GroupLayout.PREFERRED_SIZE)

.addPreferredGap(ComponentPlacement.RELATED)

.addComponent(btnNewButton, GroupLayout.DEFAULT_SIZE, 109, Short.MAX_VALUE))

.addGroup(gl_contentPane.createSequentialGroup()

.addComponent(ip, GroupLayout.PREFERRED_SIZE, 321, GroupLayout.PREFERRED_SIZE)

.addPreferredGap(ComponentPlacement.RELATED)

.addComponent(button, GroupLayout.PREFERRED_SIZE, 90, Short.MAX_VALUE)))

.addContainerGap())

);

gl_contentPane.setVerticalGroup(

gl_contentPane.createParallelGroup(Alignment.LEADING)

.addGroup(gl_contentPane.createSequentialGroup()

.addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)

.addComponent(ip, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)

.addComponent(button))

.addPreferredGap(ComponentPlacement.RELATED)

.addComponent(txt, GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE)

.addPreferredGap(ComponentPlacement.RELATED)

.addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)

.addComponent(send, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)

.addComponent(btnNewButton)))

);

contentPane.setLayout(gl_contentPane);

}

 

 

 

//为当前类创建一个appendText()方法,传入一个String

public
void appendText(String in) {

//添加到中间的显示界面

txt.append("\n"+in);

}

}

 
 

 
 

先将服务器运行:

 
 

 
 

 
 

 
 

再运行客户端:

 
 

 
 

 
 

 
 

客户端界面:

 
 

 
 

 
 

 
 

点击
连接到服务器,服务器
弹出提示:

 
 

 
 

 
 

 
 

点击
确定,客户端弹出服务器发出的消息:

 
 

 
 

 
 

 
 

再次运行另一个客户端,两个客户端之间发送消息:

 
 

 
 

 
 

 
 

 
 

此时,服务器的控制台也会打印所有经过服务器的消息:

 
 

 
 

 
 

 
 

 
 

整个流程:

 
 

两个客户端
通过 IP 和 端口 向服务器发起 Socket 通信请求,

服务器监听到请求并同意

 
 

两个客户端就是两个
Socket 对象,服务器创建两个 ChatSocket 线程,

分别处理这两个连接,且这两个线程由
ChatManager 管理

 
 

这两个
ChatSocket 线程先向两个客户端发出消息:"你已经连接

到了服务器",实际上这是服务器发送给客户端的消息

 
 

客户端之间发送消息:客户端
A 发出消息后,服务器接收到该消息,

并将其转发到客户端 B,即 服务器 相当于 中转站

 
 

如果有多个客户端,服务器依然是中转站,将一个客户端的消息转发

给其他客户端(相当于
群聊)

 
 

 
 

 
 

 
 

 
 

【made by siwuxie095】

使用Java建立聊天客户端的更多相关文章

  1. 使用 Java 创建聊天客户端-1

    1.聊天客户端文本框的搭建. 项目截图:java project 代码: (1).ChatManager.java package com.nantian.javachatclient.main; i ...

  2. 使用 Java 创建聊天客户端-2

    1.项目截图 java聊天核心代码: MyJavaChatClient ================================================================ ...

  3. Java Socket聊天室编程(二)之利用socket实现单聊聊天室

    这篇文章主要介绍了Java Socket聊天室编程(二)之利用socket实现单聊聊天室的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 在上篇文章Java Socket聊天室编程(一)之 ...

  4. Java Socket聊天室编程(一)之利用socket实现聊天之消息推送

    这篇文章主要介绍了Java Socket聊天室编程(一)之利用socket实现聊天之消息推送的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 网上已经有很多利用socket实现聊天的例子了 ...

  5. 使用 ServerSocket 建立聊天服务器-1

    1.代码目录 2.ChatSocket.java --------------------------------------------------------------------------- ...

  6. 看完这篇包你进大厂,实战即时聊天,一文说明白:聊天服务器+聊天客户端+Web管理控制台。

    一.前言 说实话,写这个玩意儿是我上周刚刚产生的想法,本想写完后把代码挂上来赚点积分也不错.写完后发现这东西值得写一篇文章,授人予鱼不如授人以渔嘛(这句话是这么说的吧),顺便赚点应届学生MM的膜拜那就 ...

  7. 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)

    搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...

  8. 用c#写的一个局域网聊天客户端 类似小飞鸽

    用c#写的一个局域网聊天客户端 类似小飞鸽 摘自: http://www.cnblogs.com/yyl8781697/archive/2012/12/07/csharp-socket-udp.htm ...

  9. Socket 基础解析使用ServerSocket建立聊天服务器

    很简单的教程哦! 1.socket 简介 Socket 又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求.ServerSocket 用于 ...

随机推荐

  1. javascript ArrayBuffer类型化数组和视图的操作

    个人理解类型化数据就是内存分配区域,不同数据的存储就是视图DataView咯 var buffers = []; var json = {"id":100, "name& ...

  2. Windows7 如何关闭系统更新

    我们点击开始菜单,找到控制面板这个选项,如图:  然后进入操作中心,如图:  然后选择如图所示的选项,如图:  然后选择更改设置选项,如图:  然后我们选择从不检查更新并点击确定按钮,如图:

  3. kylin_学习_02_kylin使用教程

    一. 二.参考资料 1.kylin从入门到实战:实际案例

  4. 数组中累加和为k的最大子数组的长度

    package com.hzins.suanfa; import java.util.HashMap; public class demo { /** * 数组中累加和为k的最大子数组的长度 * @p ...

  5. Busybox shell脚本修改密码

    /****************************************************************************** * Busybox shell脚本修改密 ...

  6. 大整数乘法(Comba 乘法 (Comba  Multiplication)原理)

    Comba 乘法以(在密码学方面)不太出名的 Paul G. Comba 得名.上面的笔算乘法,虽然比较简单, 但是有个很大的问题:在 O(n^2) 的复杂度上进行计算和向上传递进位,看看前面的那个竖 ...

  7. 综述c++

    1.背景 C语言作为结构化和模块化的语言,在处理较小规模的程序时,比较得心应手.但是当问题比较复杂,程序的规模较大时,需要高度的抽象和建模时,C语言显得力不从心. 2.应用领域 如果项目中,既要求效率 ...

  8. Dubbo与Zookeeper

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...

  9. Raid信息丢失数据恢复及oracle数据库恢复验证方案

    早些时候,有个客户14块盘的磁盘阵列出现故障,需要恢复的数据是oracle数据库,客户在寻求数据恢复技术支持,要求我提供详细的数据恢复方案,以下是提供给客户的详细数据恢复解决方案,本方案包含Raid数 ...

  10. mysql之 xtrabackup原理、备份日志分析、备份信息获取

    一. xtrabackup备份恢复工作原理: extrabackup备份简要步骤 InnoDB引擎很大程度上与Oracle类似,使用redo,undo机制,XtraBackup在备份的时候,以read ...