题目
多线程实现多人聊天室
功能
功能如下:
1.实现多人聊天
2.别人进入和退出聊天室要进行提醒
3.自己的消息只体现在输入状态,其他人的消息通过服务端打印到客户端
代码
服务端
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Server{
private List<ThreadServer> sockets = new ArrayList<ThreadServer>();
private SimpleDateFormat date = new SimpleDateFormat( "HH:mm:ss");
public void startup(){
try {
//创建一个端口号 用来客户端连接及监听客户端消息
System.out.println("服务器开启");
System.out.println("监听8800端口");
ServerSocket serverSocket = new ServerSocket(8800);
while(true){
Socket accept = serverSocket.accept();
new Thread(new ThreadServer(accept)).start();//启动新线程
}
} catch (IOException e) {
e.printStackTrace();
}
}
class ThreadServer implements Runnable {
private Socket socket;
private DataInputStream in;
private DataOutputStream out;
private String name;
private Boolean flag = true;
public ThreadServer(Socket socket) throws IOException {
this.socket = socket;
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
String str = in.readUTF();
name=str;
System.out.println("[" + date.format(new Date()) + "]" +name+"加入该聊天室");
send("[" + date.format(new Date()) + "]" +name+"加入该聊天室");
sockets.add(this);
}
private void send(String message) throws IOException {
//System.out.println(sockets.size());
for (ThreadServer threadServer : sockets) {
if(threadServer.socket != socket){
threadServer.out.writeUTF(message);
threadServer.out.flush();
}
}
}
private void receive() throws IOException{
String message;
while (flag){
message = in.readUTF();
if(message.equalsIgnoreCase("quit")){
System.out.println("[" + date.format(new Date()) + "]" +name+"退出该聊天室");
//out.writeUTF("quit");
send("[" + date.format(new Date()) + "]" +name+"退出该聊天室");
out.flush();
//this.socket.close();
sockets.remove(this);
flag = false;
}else{
System.out.println("[" + date.format(new Date()) + "]" +name + ":" + message);
send("[" + date.format(new Date()) + "]" +name + ":" + message);
}
}
}
@Override
public void run() {
//输入输出流 QQ 图片 Fileinputstream byte int reader char
try {
while (flag){
receive();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Server server = new Server();
server.startup();
}
}
客户端(可重复利用,改类名即可)
客户端可以在服务器开启状态下重复退出和进入。
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;
public class Client01 {
static boolean flag = true;
Socket socket = null;
DataInputStream in = null;
DataOutputStream out = null;
public Client01(){
try {
socket = new Socket("localhost", 8800);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void startup(){
try {
//输入输出流 QQ 图片
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
//开启一个线程 用来监听服务器的消息
Thread ct = new Thread(new Thread(){
@Override
public void run() {
DataInputStream in = null;
while(flag){
try {
in = new DataInputStream(socket.getInputStream());
String accpet = null;
try {
accpet = in.readUTF();
} catch (SocketException e) {
System.out.println("你已退出群聊");
}
System.out.println(accpet);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
socket.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
ct.start();
//接收服务端的消息
//系统录入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的用户名:");
String name = scanner.nextLine();//接收用户名
out.writeUTF(name);
System.out.println(name+",欢迎进入聊天室,输入quit退出");
while (flag){
String send = scanner.nextLine();
if(send.equalsIgnoreCase("quit")){
flag = false;
}
out.writeUTF(send);
//String accpet = in.readUTF();
//System.out.println(accpet);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
Thread.sleep(100);
socket.close();
in.close();
out.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
Client01 client01 = new Client01();
client01.startup();
}
}
运行结果截图
总结
中途也遇到了一些问题:
1.在刚开始的存放客户端的线程中使用的是List<Socket> sockets集合来进行存储,但在测试用户连接情况的时候会报数组为空异常,这种异常也处理了好长时间,然后把这个集合改为了List< ThreadServer> sockets来进行存储,相当于每当一个客户端运行,服务端都会执行new Thread(new ThreadServer(accept)).start()语句,这个语句中的accept是Socket对象,用来存储服务器的请求连接的,然后在执行ThreadServer()构造函数时,执行sockets.add(this),这里的this指的是accept,这样就将每个客户端创建的线程使用sockets来进行存储,并方便管理。
2.输入输出流的使用,采用的是DataInputStream和DataOutputStream来进行输入输出流处理,因为它们更方便使用,可以发送图片、消息等,使用writeUTF()和readUTF()来进行读写操作。方便快捷。
3.在程序编写过程发现每个客户端检测不到其他客户端的消息,这里我们采用匿名内部类创建一个线程来检测服务端的消息,接收每个服务端发来的消息并打印,至于为什么使用匿名内部类来解决这个问题是因为如果创建多个客户端,我们应该保持每个客户端的代码基本一致,这样可以大大减少代码写入量。
4.SocketException异常,这个异常是在客户端创建的新线程中出现的,因为这个Thread线程是用来监测服务端的消息,而当客户端输入quit表示退出聊天室的时候,客户端的代码会对客户端所有流进行close()操作,然而这个关流操作是在Thread线程结束之前,当服务端打印消息”xxx已退出聊天室”,这个创建的线程也会执行读取这个消息,而客户端的所有流已经关闭,所以会报这个异常,使用try-catch方法解决了这个问题。
编写csdn文章中碰到的一些技巧:
//空格
//打印出<abc>的效果 : <abc>