Java实现Socket⽹络聊天室
最近在学Socket编程,为了巩固知识,简单实现了⼀个⽹络聊天室;⽬前只实现了个聊功能,有时间继续更新和完善,下⾯附上代码截图,代码上都有详细的注释,如果有看不懂的地⽅,欢迎留⾔或私信我。
⼆、本地多客户端调试效果图:(为了⽅便本地调试区分不同客户端,这⾥把⽤户名都设置为了“路⼈xxxx”,可以调整为⽤户名)
三、项⽬结构:
四、类代码分析:
1、ChatProtocol类:存放了⼀些公共的变量和⽅法。
/*
* Copyright 2019-2022 the original author or authors.
*/
public class ChatProtocol {
/** 服务端⼝号 */
public static final int PORT_NUM = 8080;
/** 消息类型为登录 */
public static final char CMD_LOGIN = 'A';
/** 消息类型为私发信息,暂未⽤上 */
public static final char CMD_MESG = 'B';
/** 消息类型为登出 */
public static final char CMD_QUIT = 'C';
/
** 消息类型为⼴播(⽬前所有消息都为⼴播) */
public static final char CMD_BCAST = 'D';
/** 分隔符,⽤于分隔消息⾥的不同部分,识别各种信息*/  public static final int SEPARATOR = '|';
/**
* 判断消息体⾥⾯是否含有登录名
* @param message 消息
* @return 是否含有登录名
*/
public static boolean isValidLoginName(String message) {  return message != null && message.length() != 0;
}
}
2、ChatServer类:聊天室服务端实现类。
/*
* Copyright 2019-2022 the original author or authors.
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.ServerSocket;
import java.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Java聊天服务器
*/
public class ChatServer {
/**
*
*/
protected final static String CHATMASTER_ID = "Server";
/
**
/**
* 任何handle和消息之间分隔串
*/
protected final static String SEP = ": ";
/**
* 服务器套接字
*/
protected ServerSocket serverSocket;
/**
* 连接到服务器的客户端列表
*/
protected List<ChatHandler> clients;
/**
* 调试状态,调整是否以调试的形式启动
*/
private static boolean DEBUG = false;
/**
* main⽅法,仅构造⼀个ChatServer,永远不返回
*/
public static void main(String[] args) throws IOException {
System.out.println("java.ChatServer 0.");
/
/ 启动时传⼊-debug,则以debug模式启动,会打印调试信息
if (args.length == 1 && args[0].equals("-debug")) {
DEBUG = true;
}
// 服务器启动后不会终⽌
ChatServer chatServer = new ChatServer();
chatServer.runServer();
// 如果终⽌了,说明出现了异常,程序停⽌了
System.out.println("**Error* java.ChatServer 0.1 quitting");
}
/**
* 构造并运⾏⼀个聊天服务
* @throws IOException
*/
public ChatServer() throws IOException {
clients = new ArrayList<>();
serverSocket = new ServerSocket(ChatProtocol.PORT_NUM);
System.out.println("Chat Server Listening on port " + ChatProtocol.PORT_NUM);  }
/**
* 运⾏服务器
*/
public void runServer() {
try {
// 死循环持续接收所有访问的socket
while (true) {
// 开启监听
Socket userSocket = serverSocket.accept();
// 输⼊连接到服务器的客户端主机名
String hostName = InetAddress().getHostName();
System.out.println("Accepted from " + hostName);
// 每个客户端的连接都开启⼀个线程来负责通信
ChatHandler client = new ChatHandler(userSocket, hostName);
// 给客户端返回登录消息
String welcomeMessage;
synchronized (clients) {
// 把处理⽤户连接信息的线程引⽤保存起来
clients.add(client);
// 构建欢迎信息
if (clients.size() == 1) {
welcomeMessage = "Welcome! you're the first one here";
} else {
welcomeMessage = "Welcome! you're the latest of " + clients.size() + " users.";
}
}
/
/ 启动客户端线程来处理通信
client.start();
client.send(CHATMASTER_ID, welcomeMessage);
}
} catch (IOException ex) {
// 当前客户端处理报错,输出错误信息,但不抛出异常,服务器需要继续运⾏,服务其他客户端      log("IO Exception in runServer:  " + ex.toString());
}
}
/**
* ⽇志打印
* @param logMessage 需要打印的信息
*/
protected void log(String logMessage) {
socket编程聊天室基本流程System.out.println(logMessage);
}
/**
* 每个线程处理⼀个⽤户对话
*/
protected class ChatHandler extends Thread {
/** 客户端套接字 */
protected Socket clientSocket;
/** 从套接字读取数据 */
protected BufferedReader is;
/** 从套接字上发送⾏数据 */
protected PrintWriter pw;
/** 客户端的IP */
protected String clientIp;
/** ⽤户句柄(名称)*/
protected String login;
/** 构造⼀个聊天程序 */
public ChatHandler(Socket clientSocket, String clientIp) throws IOException {
this.clientSocket = clientSocket;
this.clientIp = clientIp;
/
/ TODO 正式使⽤时删掉下⾯这⼀⾏,这⾥为了本地运⾏多个客户端时,可以区分⽤户
this.clientIp = "路⼈"+ UUID.randomUUID().toString().substring(0,8);
is = new BufferedReader(
new InputStream(),"utf-8"));
pw = new PrintWriter(
new OutputStream(),"utf-8"), true);
}
@Override
public void run() {
String line;
try {
/
**
* 只要客户端保持连接,我们就应该⼀直处于这个循环
* 当循环结束时候,我们断开这个连接
*/
while ((line = is.readLine()) != null) {
// 消息的第⼀个字符是消息类型
char messageType = line.charAt(0);
line = line.substring(1);
switch (messageType) {
case ChatProtocol.CMD_LOGIN:  // 登录消息类型:A + login(登录名)
// 登录信息内不包含登录名
if (!ChatProtocol.isValidLoginName(line)) {
// 回复登录消息,登录信息不合法
send(CHATMASTER_ID, "LOGIN " + line + " invalid");
// ⽇志记录
log("LOGIN INVALID from " + clientIp);
continue;
}
// 包含登录名
login = line;
broadcast(CHATMASTER_ID, login + " joins us, for a total of " +
clients.size() + " users");
break;
case ChatProtocol.CMD_MESG:  // 私⼈消息类型:B + 接受⽤户名 + :+ message(消息内容)ps:私法消息在客户端上还没有实现              // 未登录,⽆法发送消息
if (login == null) {
send(CHATMASTER_ID, "please login first");
continue;
}
// 解析出接收信息的⽤户名,消息内容
int where = line.indexOf(ChatProtocol.SEPARATOR);
String recip = line.substring(0, where);
String message = line.substring(where + 1);
log("MESG: " + login + "-->" + recip + ": " + message);
// 查接收消息的⽤户线程
ChatHandler client = lookup(recip);
if (client == null) {
// 不到后,发送该⽤户未登陆
psend(CHATMASTER_ID, recip + "not logged in");
} else {
// 到⽤户,把信息私⼈发送过去
client.psend(login, message);
}
break;
case ChatProtocol.CMD_QUIT: // 离线消息类型: C
broadcast(CHATMASTER_ID, "Goodbye to " + login + "@" + clientIp);
close();
return; // 这个时候,该ChatHandler线程结束
case ChatProtocol.CMD_BCAST: // ⼴播消息类型: D + message(消息内容)
if (login != null) {
// this.send(login + "@" + clientIp , line);
login = clientIp; // TODO 正式使⽤的时候去除这⼀⾏,⽤于本地多客户端调试
broadcast(login, line);
} else {
// 记录谁⼴播了消息,消息内容是什么
log("B<L FROM " + clientIp);
}
break;
default: // 消息类型⽆法识别
log("Unknown cmd " + messageType + " from" + login + "@" + clientIp);
}
}
} catch (IOException ex) {
log("IO Exception: " + ex.toString());
} finally {
// 客户端套接字结束(客户端断开连接,⽤户下线)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。