Java简单聊天室

时间:2024-05-18 16:05:50

实现Java简单的聊天室

  所用主要知识:多线程+网络编程

    效果如下图

Java简单聊天室

/**
*
* @author Administrator
*
* 简单的多人聊天系统——重点:同时性,异步性
* 1、客户端:发送消息,并且接收消息
* 1.1 消息发送至服务器:服务器每次都将客户发过来的信息发送到每个客户端
* 1.2 接收消息:发送的同时也要接收消息,所以必须有两个线程,一个发送消息,一个接收消息
* 1.3 关于这两个线程:如果没有线程,接收和发送就是按顺序执行的了,那么是发送后接收还是接收后发送?或者是
* 一边可以发送一边又可以接收呢?答案不言而喻
* 1.4 在客户端,用可以通过界面的点击按钮事件发送消息,所以就要开启一个线程来接收服务器发送给客户端的消息了
* 1.5 多线程:不需按步骤逐步循环,可以和其他程序同时执行,不相互干扰
* 2、服务器端:接收所有用户的消息并且借每个用户的消息发送给连接了服务器的客户端
* 2.1 服务器既要接收C端信息又要想所有C端发送信息,先收后发。一收就发。
* 2.2 S端还要显示C端上线提醒
* 2.3 上线提醒和收发信息是不影响的,互不干扰的,但是都要在S端上执行,如果没有线程,所以的程序都要按顺序执行了所以又要 多线程
* 2.4 一个线程负责向所以C端发送接收到的任何一条信息,一个线程负责显示C端上线的信息
*
* 3、类:S端:Server、ServerFrame、sendThread 三个类
* C端:Client、ClientFrame 两个类
*
*/

 package com.java.chat;

 import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List; import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea; public class Server {
//用于保存Socket的集合,也可以说是把个C端与S端的一个连接通道保存起来
//作用:服务器将接收到的信息发给集合里的所以socket,也就是发给每个C端
public static List<Socket> list = new ArrayList<Socket>(); public static void main(String[] args) {
new ServerFrame("服务器端");
} } /*
* 继承Jframe类表示该类可以创建一个窗口程序了,
* 实现ActionListener:动作监听,在S端点击“启动服务器”是要执行的代码
* 实现Runnable:实现多线程,该窗口是个客户端窗口,要开启一个线程接收显示服务器发过来的信息
*/
class ServerFrame extends JFrame implements ActionListener, Runnable { private static final long serialVersionUID = 1L; private ServerSocket serverSocket = null; private Socket socket; private JButton start;
private JTextArea message; public ServerFrame(String name) {
super(name); start = new JButton("启动服务器");
start.addActionListener(this);
message = new JTextArea();
message.setEditable(false);
this.add(start, "North");
this.add(message, "Center"); this.setResizable(false);
this.setBounds(0, 0, 400, 300);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} @Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == start) {
// 如果点击的按钮是开始按钮。则启动服务器。
try {
serverSocket = new ServerSocket(6666);
message.setText("服务器已启动。。。"); //单击“启动服务器”开启一个线程用于获取用户上线的情况
new Thread(this).start(); } catch (IOException e1) {
e1.printStackTrace();
}
}
} //获取C端上线的情况
@Override
public void run() {
String address = null;
int port = 0;
//用一个死循环一直让S端开启接收C端的连接,将C端的IP和端口显示在面板上
//如果用循环的话就只能接收一次
while (true) {
try {
socket = serverSocket.accept();
Server.list.add(socket);
address = socket.getInetAddress().getHostAddress();
port = socket.getPort();
message.append("\r\nip/" + address + ":" + port + "\t上线了"); //开启转发信息的线程
new sendThread(socket).start(); } catch (IOException e) {
// System.out.println(address + ":" + port + "\t退出了服务器");
}
}
} } /*
* 接收每个C端的信息并向每个C端发送接收到的信息
*/
class sendThread extends Thread {
private Socket socket; public sendThread(Socket socket) {
super();
this.socket = socket;
} @Override
public void run() {
InputStream is = null;
BufferedReader br = null;
String str = null;
OutputStream os = null;
BufferedWriter bw = null;
while (true) {
try {
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
str = br.readLine();
for (Socket s : Server.list) {
os = s.getOutputStream();
bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write(str);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
//如果断开连接则移除对于的socket
Server.list.remove(socket);
}
}
}
}

服务器端代码

 package com.java.chat;

 import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date; import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField; /**
*
* @author Administrator
*
*/ public class Client {
public static void main(String[] args) {
new ClientFrame("客户端");
}
} /*
* 继承Jframe类表示该类可以创建一个窗口程序了,
* 实现ActionListener:动作监听,就是一个事件发送后应该执行什么就要重新它的方法
* 实现Runnable:实现多线程,该窗口是个客户端窗口,要开启一个线程接收显示服务器发过来的信息
*/
class ClientFrame extends JFrame implements ActionListener, Runnable { private static final long serialVersionUID = 1L; private Socket socket;
OutputStream os = null;
BufferedWriter bw = null; JTextArea message;
JScrollPane scroll;
JTextField input;
JButton submit;
JPanel panel; String uname = "";
// 聊天页面
JPanel chat; // 登录页面
JPanel login;
JTextField username;
JButton ok; public ClientFrame(String name) {
super(name); message = new JTextArea();
message.setEditable(false);
scroll = new JScrollPane(message);
input = new JTextField(20);
submit = new JButton("发送");
panel = new JPanel(); panel.add(input);
panel.add(submit);
chat = new JPanel(new BorderLayout());
chat.add(scroll, "Center");
chat.add(panel, "South"); // 在创建客户端窗体是就要把客户端与服务端连接
try {
//127.0.0.1表示本机地址,地址好端口都可以改,这要看服务器那边是什么地址和端口
socket = new Socket("127.0.0.1", 6666);
os = socket.getOutputStream();
bw = new BufferedWriter(new OutputStreamWriter(os)); } catch (IOException e1) {
e1.printStackTrace();
} /* 在创建C端窗体的时候也应该开启一个线程接收显示来自服务器的信息
* 为什么要开启一个线程呢?因为在这个窗体里既要实现消息的发送,
* 也要接收信息,而且两个不能按顺序进行,也互不干扰,所以开启一个线程时时刻刻等着S端发来的信息
*/
//接收信息
new Thread(this).start(); //提交按钮工作监听器,点击提交时触发
submit.addActionListener(this); login = new JPanel();
username = new JTextField(20);
ok = new JButton("确定");
ok.addActionListener(this);
JLabel label = new JLabel("请输入用户名");
label.setFont(new Font(Font.DIALOG, Font.BOLD, 28));
label.setForeground(Color.red);
login.add(label);
login.add(username);
login.add(ok); this.add(chat);
this.add(login); this.setResizable(false);
this.setBounds(400, 100, 400, 300);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} @Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == submit) {
// 如果点击提交按钮,则表示需要将信息发送出去。
String str = null;
//以下三行是创建发送时间的代码
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
String dateStr = format.format(date); str = uname+" "+dateStr+" "+input.getText(); try {
bw.write(str);
bw.newLine();
// System.out.println("客户端发送了:"+str);
bw.flush();
} catch (IOException e1) {
System.out.println("无法发送,服务器连接异常!");
} // System.out.println(str); } else if (e.getSource() == ok) {// 用户登录
uname = username.getText();// 获取文本框输入的文本信息
if ("".equals(uname)) {
JOptionPane.showMessageDialog(this, "用户名不能为空!");
} else {
login.setVisible(false);
chat.setVisible(true);
this.setTitle(uname + " 的客户端");
this.add(chat);
}
}
} //run方法里面的是接受S端信息和将信息显示的代码
@Override
public void run() {
BufferedReader br = null;
InputStream is = null;
String str = null;
try {
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
while (true) {
str = br.readLine()+"\r\n";
message.append(str);
}
} catch (IOException e) {
System.out.println("无法发送,服务器连接异常!");
}
} }

客户端代码

(完)