一.说明:
本例是一个简易的Java TCP多人聊天室,先启动服务器端,再启动客户端,客户端敲入用户名,然后可以开始聊天,敲入信息,每一个在线的用户都会收到相应信息。
演示如下图:
二.服务器端代码
ServerMain.java
import java.io.*;
public class ServerMain {
public static void main(String[] args) {
ChatServer server = new ChatServer();
try {
server.ServerStart();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
ChatServer.java
import java.io.*;
import java.net.*;
import java.util.HashMap;
public class ChatServer {
// 设定端口号
private final int port = 12345;
public static HashMap clientMap = new HashMap<>();
public static int onlineNum = 0;
public static void ServerSendToAll(ClientThread thread,String msg) throws IOException {
for (ClientThread t : clientMap.keySet()) {
if (t.clientName.equals(thread.clientName)) {
Message.printMessage(msg);
continue;
}
t.objOutput.writeObject(new DataPacket(Message.CMD.SENDALL, msg));
}
}
public static void ServerSendToAll(ClientThread thread) throws IOException {
onlineNum++;
// 服务端记录
Message.printMessage(thread.clientName + "上线了,当前在线人数" + onlineNum + "人");
// 群发
for (ClientThread t : clientMap.keySet()) {
if (t.login == true) {
t.objOutput.writeObject(new DataPacket(Message.CMD.LOGIN, "当前在线人数" + onlineNum + "人"));
}
}
}
public void ServerStart() throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
Message.printMessage("Server Start.");
// 死循环等待客户端连接
while (true) {
// 每来一个客户端连接,保存Socket对象
Socket clientSocket = serverSocket.accept();
ClientThread client = new ClientThread(clientSocket);
// 每连接一个客户端,创建一个对应的线程类
Thread clientThread = new Thread(client);
// 添加对象-Socket映射
clientMap.put(client, clientSocket);
// 启动线程
clientThread.start();
}
}
}
ClientThread.java
import java.io.*;
import java.net.*;
/*
线程类:处理每一个登录成功的客户端
*/
public class ClientThread implements Runnable {
// 保存每个客户端Socket
private Socket client = null;
public ObjectInputStream objInput = null;
public ObjectOutputStream objOutput = null;
public String clientName = null;
public boolean login = false;
public ClientThread(Socket client) {
this.client = client;
try {
this.objInput = new ObjectInputStream(this.client.getInputStream());
this.objOutput = new ObjectOutputStream(this.client.getOutputStream());
} catch (IOException e) {
Message.printLog(Message.logType.ERROR, "ClientThread:IOException!", true);
e.printStackTrace();
}
}
public int HandleReceiveMsg(Object data) throws IOException {
DataPacket dataPacket = (DataPacket) data;
switch (dataPacket.cmd) {
case LOGIN:
clientName = dataPacket.msg;
login = true;
ChatServer.ServerSendToAll(this);
break;
case SEND:
// 收到消息,调用群发其他用户
String msg = this.clientName + "说:" + dataPacket.msg;
ChatServer.ServerSendToAll(this, msg);
break;
case SENDALL:
// 收到其他用户群发的消息
SendMessage(dataPacket);
break;
}
return 0;
}
public void SendMessage(DataPacket data) throws IOException {
this.objOutput.writeObject(data);
}
public Object ReceiveMessage() throws ClassNotFoundException, IOException {
try {
Object obj = this.objInput.readObject();
return obj;
} catch (EOFException e) {
}
return null;
}
@Override
public void run() {
try {
Object data;
while ((data = ReceiveMessage()) != null) {
HandleReceiveMsg(data);
}
} catch (SocketException e) {
login = false;
} catch (IOException e) {
Message.printLog(Message.logType.ERROR, "ClientThread-run:IOException!", true);
e.printStackTrace();
} catch (ClassNotFoundException e) {
Message.printLog(Message.logType.ERROR, "ClientThread-run:ClassNotFoundException!", true);
e.printStackTrace();
} finally {
// 用户断开连接
if (login == false) {
// 从HashMap 移走映射
ChatServer.clientMap.remove(this);
// 释放资源
if (client != null) {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
ChatServer.onlineNum--;
// 打印信息
String msg = this.clientName + "下线了,当前在线" + ChatServer.onlineNum + "人";
Message.printMessage(msg);
try {
ChatServer.ServerSendToAll(this, msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
三.客户端代码
ClientMain.java
public class ClientMain {
public static void main(String[] args) {
ChatClient client = new ChatClient();
client.ClientStart();
}
}
ChatClient.java
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class ChatClient {
private static final int port = 12345;
private static String serverAddress = "localhost";
private Socket client = null;
private ObjectOutputStream objOutput = null;
private ObjectInputStream objInput = null;
public void SendMessage(DataPacket data) throws IOException {
// 发送数据给服务器
objOutput.writeObject(data);
}
public void ClientStart() {
Scanner in = new Scanner(System.in);
String userInput = null;
// 用户输入用户名登录
Message.printLog(Message.logType.INFO, "请输入你的用户名:", false);
userInput = in.nextLine();
// 连接服务器
try {
this.client = new Socket(serverAddress, port);
// 服务器已连接
if (this.client.isConnected()) {
// 获取输出流
this.objOutput = new ObjectOutputStream(this.client.getOutputStream());
// 获取输入流
this.objInput = new ObjectInputStream(this.client.getInputStream());
SendMessage(new DataPacket(Message.CMD.LOGIN, userInput));
// 创建一个线程用于接收信息
ClientReceiveMessageThread receiveMessage = new ClientReceiveMessageThread(this.objInput, userInput);
Thread receiveThread = new Thread(receiveMessage);
receiveThread.start();
// 这个线程用于发送信息
while (true) {
userInput = in.nextLine();
if (userInput != null) {
SendMessage(new DataPacket(Message.CMD.SEND, userInput));
}
}
}
} catch (ConnectException e) {
Message.printLog(Message.logType.ERROR, "ChatClient-ClientStart:ConnectException!", true);
e.printStackTrace();
} catch (SocketException e) {
Message.printLog(Message.logType.ERROR, "服务器异常!", true);
System.exit(0);
} catch (IOException e) {
Message.printLog(Message.logType.ERROR, "ChatClient-ClientStart:IOException!", true);
e.printStackTrace();
}
}
}
class ClientReceiveMessageThread implements Runnable {
private ObjectInputStream in = null;
private String clientName = null;
public ClientReceiveMessageThread(ObjectInputStream in, String name) {
this.in = in;
this.clientName = name;
}
public int HandleReceiveMsg(Object data) throws IOException {
DataPacket dataPacket = (DataPacket) data;
switch (dataPacket.cmd) {
case LOGIN:
case SEND:
case SENDALL:
Message.printMessage(dataPacket.msg);
break;
}
return 0;
}
@Override
public void run() {
Object data;
try {
while ((data = in.readObject()) != null) {
HandleReceiveMsg(data);
}
} catch (EOFException e) {
} catch (SocketException e) {
Message.printLog(Message.logType.ERROR, "服务器异常!", true);
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
四.共用的类
Message.java
import java.text.SimpleDateFormat;
import java.util.Date;
public class Message {
// 是否打开debug输出
private static boolean debug = false;
// 设定日期时间格式
public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyy-MM-dd hh:mm:ss");
public static enum logType {
ERROR,
INFO,
WARN,
DEBUG,
};
public static enum CMD {
LOGIN,
SEND,
SENDALL,
}
public static void printLog(logType type, String msg, boolean newLine) {
if( debug == true || type != logType.DEBUG ) {
StringBuilder sb = new StringBuilder(128);
sb.append("[").append(type).append("]").append(msg);
if (newLine)
System.out.println(sb);
else
System.out.print(sb);
}
}
public static void printMessage(String msg) {
StringBuilder sb = new StringBuilder(1024);
sb.append("[").append(dateFormat.format(new Date())).append("]").append(msg);
System.out.println(sb);
}
}
DataPacket.java
import java.io.Serializable;
public class DataPacket implements Serializable {
/**
* 自动生成的UID
*/
private static final long serialVersionUID = -5366828169862565011L;
public Message.CMD cmd;
public String msg;
public DataPacket(Message.CMD cmd, String msg) {
this.cmd = cmd;
this.msg = msg;
}
}