Java学习记录——简单的多线程群聊
最近学习了Java中的网络编程,实现了一个简单的多线程群聊。也参考了一些例子。记录一下
思路:
-
建立客户端(Client.java),服务端(Server.java)类。客户端可以新建用户并且开启聊天窗口,每新建一个用户 服务端就向线程池添加一个新线程。在服务端建立一个消息池便于发消息给每位用户。先运行Server再运行Client
-
Server:建立GUI界面。建立ArrayList线程池(用户池)和消息池。建立一个SocketServer,随后循环建立Socket.accept作为接收器。注意:accept会堵塞线程直到下一个客户端连接。连接一个客户线程池就启动一个专门的线程 。 通过DataInputStream和readUTF的方式接收来自客户端的消息。通过DataOutputStream和writeUTF的方式遍历用户池,给每个客户发送消息。
-
Client:
建立GUI界面。新建用户按钮点击时,创建Socket连接服务器。创建聊天框,包含接收框、发送框、发送按钮。接收框自动接收消息,发送按钮点击时给每位用户发消息。
通过DataInputStream和readUTF的方式接收来自服务端的消息。
通过DataOutputStream和writeUTF的方式给服务端发送消息。
代码如下:
Server:
import java.awt.BorderLayout;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import javax.swing.*;
public class Server implements Runnable{
ServerSocket server;
Socket s;
JTextArea area;
JFrame jf;
ArrayList <String> outAll= new ArrayList<>();//存放用户消息
ArrayList<ServerThread> threads = new ArrayList<>();//存放用户消息
Server(){
//界面
jf = new JFrame("服务器消息池");
jf.setSize(500, 600);
jf.setLayout(null);
area=new JTextArea();
area.setBounds(50, 10, 400,600);
area.setEditable(false);
area.setLineWrap(true); //激活自动换行功能
jf.add(area);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void run() {
Socket s = null;
while(true){
try {
Thread.sleep(1000);//每秒判断有无客户接入
} catch (InterruptedException e2) {
e2.printStackTrace();
}
try{
server=new ServerSocket(8765);
}
catch(IOException e1){
System.out.println("正在监听"); //ServerSocket对象不能重复创建
}
try{
System.out.println("等待一下个客户连接");
s=server.accept();
System.out.println("新客户的地址:"+s.getInetAddress());
}
catch (IOException e){
System.out.println("正在等待客户");
}
if(s!=null) {
ServerThread serverThread = new ServerThread(s,this);
threads.add(serverThread);
new Thread(serverThread).start(); //连接一个客户线程池就启动一个专门的线程
}
}
}
class ServerThread implements Runnable{
Socket socket;
DataOutputStream out=null;
DataInputStream in=null;
Server server = null;
ServerThread(Socket s,Server server){
socket = s;
this.server = server;
try{
in=new DataInputStream(socket.getInputStream());
out=new DataOutputStream(socket.getOutputStream());
}
catch (IOException e){
System.out.println("创建输入流失败");
}
}
public void run() {
while(true){
try {
if(!server.outAll.isEmpty()){
//发送消息给每个客户
String sent = server.outAll.get(server.outAll.size()-1);
area.append(sent+"\r\n");
for(int j = 0 ; j < threads.size(); j++){
try {
threads.get(j).out.writeUTF(sent);
//不然就只有本线程的客户收的到
out.flush();
} catch (IOException e) {
System.out.println("发送失败给客户"+j+"失败");
}
}
}
String msg = in.readUTF();//堵塞线程直到该线程读取消息
server.outAll.add(msg);
}catch (IOException e) {
System.out.println("接受失败,断开服务器");
}
}
}
}
public static void main(String args[]){
new Thread(new Server()).start();
}
}
Client:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.*;
public class Client implements Runnable{
void createUsers(String name){
new Thread(new Users(name)).start();//创建新用户
}
public void run() {
//建立客户端,用以新建用户
JFrame client = new JFrame("客户端");
client.setBounds(690,400,400, 300);
JButton register= new JButton("新建用户");
register.setBounds(150,100, 100, 40);
JLabel jl = new JLabel("输入姓名:");
jl.setBounds(80,50,70,30);
JTextField jt = new JTextField();
jt.setBounds(150, 50,120, 30);
client.setLayout(null);
client.add(jl);
client.add(jt);
client.add(register);
client.setVisible(true);
register.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//点击创建用户,建立Client的子线程
new Thread(new Users(jt.getText())).start();
jt.setText("");
}
});
}
class Users implements Runnable{
String name;
Socket s ;
JButton send2 ;
OutputStream os;
DataInputStream dis;
JTextArea jtC ;
JTextArea jt;
JLabel jl1;
JLabel jl2;
JButton send ;
DataOutputStream dos = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String getCurrentTime() {
return sdf.format(new Date());
}
Users(String name){
this.name = name;
//构造GUI界面******************
JFrame client = new JFrame(name);
client.setBounds(690,400,360, 600);
send = new JButton("send");
send.setBounds(130,450, 80, 40);
client.setLayout(null);
client.add(send);
jl1 = new JLabel("消息框");
jl2 = new JLabel("发送框");
jl1.setBounds(150,10,360,30);
jl2.setBounds(150,270,360,30);
client.add(jl1);
client.add(jl2);
jtC = new JTextArea();
jtC.setBounds(10, 50,320, 200);
jtC.setLineWrap(true) ;
jtC.setEditable(false);//设置不可编辑,只可展示
client.add(jtC);
jt = new JTextArea();
jt.setBounds(10, 320,320, 150);
jt.setLineWrap(true) ;
client.add(jt);
//在文本框上添加滚动条
JScrollPane jsp = new JScrollPane(jtC);
JScrollPane jsp2 = new JScrollPane(jt);
//设置矩形大小.参数依次为(矩形左上角横坐标x,矩形左上角纵坐标y,矩形长度,矩形宽度)
jsp.setBounds(10, 50, 320, 200);
jsp2.setBounds(10, 320, 320, 100);
//默认的设置是超过文本框才会显示滚动条,以下设置让滚动条一直显示
jsp.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
jsp2.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
//把滚动条添加到容器里面
client.add(jsp2);
client.add(jsp); //加入到JFrame
client.setVisible(true);
//构造GUI界面*****************************
//设置发送消息
send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String msg = jt.getText();
jt.setText("");
try {
dos = new DataOutputStream(s.getOutputStream());
//打开输出流
dos.writeUTF(getCurrentTime()+"\n"+name+": "+msg);
//使用writeUTF发送字符串,发完之后清空输入框
jt.setText("");
dos.flush();
} catch (IOException e1) {
System.out.println("发送失败");
}
}
});
}
public void run() {
try {
s = new Socket("localhost",8765);
} catch (Exception e) {
System.out.println("创建客户端失败");
return;
}
//打开输入流
InputStream is = null;
try {
is = s.getInputStream();
} catch (IOException e1) {
System.out.println("创建输入流失败");
}
//把输入流封装在DataInputStream中
dis = new DataInputStream(is);
while(true){
try{
String msg = dis.readUTF();//堵塞状态,直到接收到信息
jtC.append(msg+"\r\n"); //接受消息池的所有消息
}
catch (IOException e){
System.out.println("网络连接中断");
return;
}
}
}
}
public static void main(String args[]){
new Thread(new Client()).start();
}
}
运行结果截图: