---------------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. ural 2021 Scarily interesting!(贪心)

    2021. Scarily interesting! Time limit: 1.0 secondMemory limit: 64 MB This year at Monsters Universit ...

  2. Linux中的固件加载例子

    AP6335模块(BCM4339)在上电运行时,是需要刷入固件的,其在普通WIFI模式和AP模式之间切换时,也是需要加载不同的固件的,其位于/system/etc/firmware/下面:fw_bcm ...

  3. gradle_学习_02_gradle多模块构建实例

    一.前言 二.多模块构建 1.工程结构 父工程:weixin-service 子模块:weixin-gz weixin-qy 2.父工程 weixin-service (1)build.gradle ...

  4. BZOJ5302: [Haoi2018]奇怪的背包

    BZOJ5302: [Haoi2018]奇怪的背包 https://lydsy.com/JudgeOnline/problem.php?id=5302 分析: 方程\(\sum\limits_{i=1 ...

  5. LeetCode 323. Number of Connected Components in an Undirected Graph

    原题链接在这里:https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/ 题目: Giv ...

  6. BZOJ1116:[POI2008]CLO

    浅谈并查集:https://www.cnblogs.com/AKMer/p/10360090.html 题目传送门:https://lydsy.com/JudgeOnline/problem.php? ...

  7. Salesforce注册教程

    打开网址 https://developer.salesforce.com/,点击右上角的Sign up(注册) 在页面输入基本信息 >Name:First(姓名) Last(姓)  >E ...

  8. System.Data.SQLite.dll控件常规安装方法

    原文地址:http://www.jb51.net/dll/System.Data.SQLite.dll.html 文件运行必须安装   Microsoft Visual C++ 2010 SP1 Re ...

  9. 通过php的MongoDB driver连接Azure的DocumentDB PaaS

    Azure的DocumentDB是NoSQL类型的数据库.它还可以和目前流行的mongodb兼容,采用mongodb的driver可以直接连接Azure的DucumentDB. 目前在国内的Azure ...

  10. @property_@synthesize 配套使用

    @property 类默认实现变量的get set方法 @synthesize 是指定那个变量的 get和set方法 eg: .h文件中定义 类Student中含有两个 int age,和int _a ...