声明:本文参考多篇java聊天室设计的文章。
java简易聊天室制作要求
1. 具有简易的GUI界面
2. 能实现一个聊天室中多人聊天
3. 可以两人私聊
提示:使用socket通信
首先我参考的是某b站上的彭姐直播间的一个多人聊天室。跟着UP主一行行写代码,我认为这样可以学习到他人写一个项目时的思路,从而运用到自己日常生活中,但是在俺看来(仅俺个人意见),这个UP主的思路略微有一点点混乱,不够简洁明了。不过优点就是,他有许多带领萌新修补bug或者回头修改不够好的代码的过程,大家能看到作为一个程序猿的思考方式。
后来我又去某b站上看了一些相关的视频,我认为一个对于做GUI界面的程序来说,可以先清晰明了地设计好操作界面,然后对于按钮进行处理,再设计内逻辑是比较好的思路(这样能让代码看起来没那么凌乱)
二话不说,先上多人聊天室的代码:(初级1.0:只有多人聊天的功能)
ClientChat.java
package test1217.chat;
import java.awt.BorderLayout;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class ClientChat extends JFrame{
public static void main(String[] args) {
//调用构造方法
ClientChat cc=new ClientChat();
cc.init();
}
//定义客户端窗口组件
//设置放置多行文本域(设置长度为行10列20)
private JTextArea ta=new JTextArea(10,20);
//设置滚动条,并将文本域添加到滚动条中
private JScrollPane sp=new JScrollPane(ta);
//设置放置单行文本框
private JTextField tf=new JTextField(20);
//设置面板
private JPanel jp=new JPanel();
//设置按钮
private JButton jb=new JButton("发送");
/********************************TCP 客户端 start**********************************/
//基于TCP的Socket通信
// 静态常量主机端口号
private static final String CONNSTR="127.0.0.1";
// 静态常量服务器端口号
private static final int CONNPORT=8888;//定义端口号
private Socket s=null;
//输出流
private DataOutputStream dos=null;
//客户端连接上服务器判断符号
private boolean isConn=false;
//构造方法
public ClientChat() throws HeadlessException {
super();
// TODO 自动生成的构造函数存根
}
//设计窗口
public void init() {
//设置标题
this.setTitle("多人聊天室 客户端");
//将文本框和按钮添加到面板中
jp.add(tf);
jp.add(jb);
//布局,将sp放在中间
this.add(sp, BorderLayout.CENTER);
//将jp放在南边
this.add(jp,BorderLayout.SOUTH);
//将jp放在南边
this.setBounds(400,400,400,400);
//关闭窗口
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//禁止ta里用光标输入文字,文字显示区域不能编辑
ta.setEditable(false);
//光标聚焦在tf里
tf.requestFocus();
//匿名内部类创建监视器(对tf做监听,拿到tf的内容,append到ta)
//输入流
tf.addActionListener (new ActionListener(){
public void actionPerformed(ActionEvent e) {
//将输入的字符显示在tf里
String strSend=tf.getText();
if(strSend.trim().length()==0) {//判断输入的字符串的长度是否为0,.trim()可以去掉字符串前后空格
return;
}
//需要将监听的输入内容strSend发送到服务器上
send(strSend);
//使得输入字符回车键之后将原输入框里的字符清空
tf.setText(" ");
}
});
try {
s=new Socket(CONNSTR,CONNPORT);//这里做的Scoket连接
//表示已连上服务器
isConn=true;
} catch (UnknownHostException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
} catch (IOException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
}
//设置窗口可见
this.setVisible(true);
//窗口打开即启动多线程
new Thread(new Receive()).start();
}
//输出流:发送信息到服务器上的方法
public void send(String str) {
try {
dos=new DataOutputStream(s.getOutputStream());
dos.writeUTF(str);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//多线程的类-----实现了Runnable接口的,需要被封装在多线程类里
class Receive implements Runnable {
public void run() {
try {
//窗口打开即启动多线程
while(isConn) {
DataInputStream dis=new DataInputStream(s.getInputStream());
String str=dis.readUTF();
ta.append(str);
}
} catch (SocketException e) {
System.out.println("服务器意外终止!");
ta.append("服务器意外中断!");
}
catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
/********************************TCP 客户端 end**********************************/
ServerChat.java
package test1217.chat;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowListener;
import java.awt.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
/********************************TCP 服务端 start**********************************/
public class ServerChat extends JFrame{
//main()方法
public static void main(String[] args) {
ServerChat sc=new ServerChat();
}
//端口
private static final int PORT=8888;
//创建多行文本区Ta
private JTextArea serverTa=new JTextArea();
//滚动条的设置,并入ta
private JScrollPane Sp=new JScrollPane(serverTa);
//因为有两个按钮在布局的同一个方位,因此需要创建一个面板容器放置
private JPanel btnTool=new JPanel();
//创建按钮
private JButton startBtn=new JButton("启动");
private JButton stopBtn=new JButton("停止");
//Socket通信
private static ServerSocket ss=null;
private Socket s=null;
// 多个客户端访问时,客户端对象存放入List中
private ArrayList<ClientConn> ccList=new ArrayList<ClientConn>();
// 服务器启动的标志 (其实ServerSocket ss 初始化出来时以为者服务器的启动)
private boolean isStart=false;
//构造方法,也可以写成public void init()初始化,打开窗口
public ServerChat() {
//设置标题
this.setTitle("服务器端");
//设置serverTa的位置
this.add(Sp,BorderLayout.CENTER);
//将按钮加到btnTool面板里
btnTool.add(startBtn);
btnTool.add(stopBtn);
//将该面板放置在窗口的南面
this.add(btnTool,BorderLayout.SOUTH);
//设置窗口大小
this.setBounds(0,0,500,500);
//设置窗口是否可见
this.setVisible(true);
//判断是否启动服务器
if(isStart) {
serverTa.append("服务器已经成功启动了!\n");
}else {
serverTa.append("服务器还未启动,请点击启动按钮!\n");
}
//关闭窗口
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//对停止按钮建立监视器
stopBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
try {
if(ss!=null) {
ss.close();
isStart=false;
}
System.exit(0);
serverTa.append("服务器断开!");
System.out.println("服务器停止!");
}catch (IOException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
}
}
});
//对启动按钮建立启动键监视器
startBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("hhh");
try {
if(ss==null){
ss=new ServerSocket(PORT);
}
isStart=true;
serverTa.append("服务器已经启动了!"+"\n");
} catch(IOException e1) {
e1.printStackTrace();
}
}
});
//让serverTa区域只能显示不能改变
serverTa.setEditable(false);
//调用启动方法
startServer();
}
//服务器启动的方法
public void startServer() {
try{
try {
ss=new ServerSocket(PORT);//给予ss端口号(常量,在上面已经定义)
isStart=true;
}catch(IOException e2) {
e2.printStackTrace();
}
/**
*为保证多个客户端连上,这里应采用while循环
*可以接受多个客户端的连接
*判断符为服务器开关的判断符
**/
while (isStart) {
//accept()从客户端“连接”的队列中返回一个最近的新连接。accept正常返回,就代表有新客户连接。
s = ss.accept();
//接收一个客户端,就将它加到ccList1里。
ccList.add(new ClientConn(s));
System.out.println("一个客户端连接服务器:" + s.getInetAddress() + "/" + s.getPort()+"\n");//连接上的客户端的地址和客户端
serverTa.append("一个客户端连接服务器:" + s.getInetAddress() + "/" + s.getPort()+"\n");
}
}catch(SocketException e) {
System.out.println("服务器中断了!");
}catch(IOException e) {
e.printStackTrace();
}
}
//服务器停止的方法
public void stopServer() {
}
//服务器端接收数据的方法应该是多线程的接收
/*public void reciveStr() {
try {
dis=new DataInputStream(s.getInputStream());
String str=dis.readUTF();
System.out.println(str);
serverTa.append(str);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}*/
//内部类声明 这个对象是属于服务器端一个连接对象,客户端与服务器的联系
class ClientConn implements Runnable{
Socket s=null;
public ClientConn(Socket s) {
this.s=s;
/**
* 线程启动在这里:
* 初始化方法里 初始化一个线程 ,线程中封装的是自己,做整个线程的调用
*/
(new Thread(this)).start();
}
//同时接受客户端信息的---多线程run()方法接收数据
public void run() {
try {
DataInputStream dis=new DataInputStream(s.getInputStream());
//为了让服务器能够收到每个客户端的多句话,则采用循环模式
while(isStart) {
//读数据
String str=dis.readUTF();
System.out.println(s.getInetAddress()+"|"+s.getPort()+"说:"+"\n"+str+"\n");
serverTa.append(s.getInetAddress()+"|"+s.getPort()+"说:"+"\n"+str+"\n");
//为了方便之后的信息返回,将客户发送内容设定为字符串strSend
String strSend=s.getInetAddress()+"|"+s.getPort()+"说:"+"\n"+str+"\n";
//需要遍历ccList,调用send方法,在客户端接收信息是多线程的接收
Iterator<ClientConn> it=ccList.iterator();
while(it.hasNext()) {
ClientConn o=it.next();
o.send(strSend);
}
}
} catch(SocketException e){
System.out.println(s.getInetAddress()+"|"+s.getPort()+"一个客户端下线了"+"\n");
serverTa.append(s.getInetAddress()+"|"+s.getPort()+"一个客户端下线了"+"\n");
}catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//内部类里:向每个连接对象发送数据的方法
public void send(String str) {
try {
DataOutputStream dos=new DataOutputStream(this.s.getOutputStream());
dos.writeUTF(str);
}catch (SocketException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
/********************************TCP 服务端 end**********************************/
多人聊天室主要是要有服务端和多个客户端这两个界面, 他们通过相同的端口号进行连接,要实现多个客户端同时在线,则需要服务端开启多线程服务。
我使用的是eclispe。下一篇文章则会解决私聊问题。