博主介绍:✌java资深开发工程师、Java领域优质创作者,博客之星、专注于Java技术领域和学生毕业项目实战,面试讲解跟进,高校老师/讲师/同行交流合作✌
胡广愿景:
"比特星球",致力于帮助底层人员找到工作,让每个底层人员都能找到属于自己的星球。
拓展学习领域,获取社会知识,让你更好地面对职业挑战。与此同时,我们将实时关注社会热点,分享最新科技动态,激励你不断进步。加入比特星球,共同构建一个互助的学习社区。
👇🏻 感兴趣的可以先收藏起来👇🏻 不然下次找不到哟
大家在毕设选题,项目以及论文编写、就业面试等相关问题都可以给我留言咨询,希望帮助更多的人
🍅文末获取源码联系🍅
🍅文末获取源码联系🍅
🍅文末获取源码联系🍅
大家好,我叫胡广,废话不多说咱们直接进入正题!!!
socket科普
参考《Socket通信原理》https://www.***blogs.***/wangcq/p/3520400.html
整体流程图
我们制作的这个实时在线聊天系统分为客户端、服务端,
两个步骤:
1、连接注册
客户端往服务端发起注册请求,服务端将客户端的地址、端口存入HashSet在内存中缓存起来
2、消息处理(聊天功能)
客户端发送消息至服务端,服务端处理消息格式私密消息格式为 @targetUser(私密用户名) 消息内容。如果不为此格式那么就向除自己以外的用户都发送消息
整个的实现原理用一张图即可解释
相比你看到了此文章这里,基本有个大概的了解了吧
接下来就到了你最期待的时候了,没错就是实现代码,哈哈哈,我不知道有多少人是为了代码过来的,可否给我点个免费的小赞和收藏呢,胡广拜谢了。
如果你也是底层程序员正在水生活热的生活中迷茫,感到就业困难,面试少,可以到此文章末尾的公众号联系到我哦,我帮你一起克服眼前的困难。《同是天涯沦落人,相逢何必曾相识》
实现代码
客户端代码
1.这里使用 Socket
类创建一个与指定服务器地址和端口的socket连接
2.通过 BufferedReader
和 PrintWriter
分别设置输入和输出流。reader
用于读取服务器的消息,writer
用于向服务器发送消息。consoleReader
用于读取用户在控制台输入的消息
3.使用一个线程来不断地从服务器接收消息,并将其显示在客户端的控制台上。
4.在主线程中,用户可以输入消息,程序将其发送到服务器。如果用户输入 "exit",则退出循环,关闭连接
5.异常处理部分用于捕捉可能发生的 IOException
异常,并打印异常信息。
package org.sqs.socketchat.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.***.Socket;
/**
* 通信客户端ds
*/
public class ChatClient {
public static void main(String[] args) {
String serverHost = "127.0.0.1";
int serverPort = 8080;
try (Socket socket = new Socket(serverHost, serverPort);
//字符流读取
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//输出字符流
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
//读取控制台么
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
// 启动接收消息线程
new Thread(() -> {
try {
while (true) {
String message = reader.readLine();
if (message == null) {
break;
}
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
// 发送用户输入的消息
while (true) {
String userInput = consoleReader.readLine();
//退出链接
if (userInput.equals("exit")) {
break;
}
//往服务端输出消息
writer.println(userInput);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码
消息处理器
1.当客户端发送消息时,服务端会检查消息是否以 "@" 开头,如果是,则认为是私密消息。私密消息格式为 @targetUser message
。如果消息格式正确,服务端会将私密消息发送给目标用户,否则会通知发送者用户不存在。广播消息给所有用户的方法和之前相同。这只是一个简单的实现,实际场景中可能需要更复杂的逻辑和安全性保障。
package org.sqs.socketchat.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.***.Socket;
import java.util.Set;
/**
* 消息处理
*/
public class ChatHandler implements Runnable{
//socket客户端
private Socket clientSocket;
//客户端集
private Set<ChatHandler> handlers;
//缓冲输入字符流
private BufferedReader reader;
//输出字符流
private PrintWriter writer;
//添加用户名字段
private String username;
/**
* 消息处理
* @param socket
* @param handlers
*/
public ChatHandler(Socket socket, Set<ChatHandler> handlers) {
this.clientSocket = socket;
this.handlers = handlers;
try {
//输入流与输出流
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream(), true);
// 为每个客户端生成一个唯一的用户名(可根据实际需求修改)这里用的是当前时间,小伙伴可以用UUID来代替都行
this.username = "User" + System.currentTimeMillis();
sendMessage("您的客户端连接名为 " + username);
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取用户名
public String getUsername() {
return username;
}
/**
* 开启消息处理
*/
@Override
public void run() {
try {
while (true) {
//读取消息
String message = reader.readLine();
//消息非空校验
if (message == null) {
break;
}
// 解析私密消息格式(示例:@targetUser message)
if (message.startsWith("@")) {
String[] parts = message.split(" ", 2);
if (parts.length == 2) {
ChatServer.sendPrivateMessage(parts[1], parts[0].substring(1), this);
continue;
}
}
// 广播消息给所有客户端
ChatServer.broadcast(username + ": " + message, this);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 异常之后移除处理器
handlers.remove(this);
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void sendMessage(String message) {
writer.println(message);
}
}
服务端的端口监听
1.监听客户端连接端口8080
2.将客户端信息存入内存缓存处理
3.提供群聊广播消息、私密消息功能
package org.sqs.socketchat.server;
import java.io.IOException;
import java.***.ServerSocket;
import java.***.Socket;
import java.util.HashSet;
import java.util.Set;
/**
* 通信服务端
*/
public class ChatServer {
//定义端口号
private static final int PORT = 8080;
//定义聊天处理器,将每个客户端存入处理,用Set不会重复
private static Set<ChatHandler> handlers = new HashSet<>();
public static void main(String[] args) {
//监听端口号
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("聊天服务端正在此端口上运行:" + PORT);
//开始循环监听
while (true) {
//1.服务器调用 serverSocket.a***ept() 方法:该方法会一直阻塞,直到有客户端请求连接到服务器。
//2.客户端发起连接请求: 当有客户端发起连接请求时,serverSocket.a***ept() 会返回一个新的 Socket 对象,该对象代表与客户端建立的连接。
//3.创建新的 Socket 对象: 服务器通过 serverSocket.a***ept() 创建一个新的 Socket 对象,该对象包含了客户端的信息,包括客户端的地址和端口等。
//4.处理客户端连接: 服务器可以使用返回的 Socket 对象与客户端进行通信,发送和接收数据
Socket clientSocket = serverSocket.a***ept();
System.out.println("新的客户端已连接 " + clientSocket);
//将此socket客户端连接存入Set当中
ChatHandler handler = new ChatHandler(clientSocket, handlers);
handlers.add(handler);
//创一个新线程来处理此客户端的消息。建议:新线程可以用线程池来管理,固定1线程的线程池或者调整线程池策略
Thread handlerThread = new Thread(handler);
handlerThread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Broadcast消息给所有客户端。广播
public static void broadcast(String message, ChatHandler sender) {
for (ChatHandler handler : handlers) {
// 避免将消息发回给发送者
if (handler != sender) {
handler.sendMessage(message);
}
}
}
// 发送私密消息给指定客户端
public static void sendPrivateMessage(String message, String targetClient, ChatHandler sender) {
for (ChatHandler handler : handlers) {
// 找到目标客户端并发送私密消息
if (handler.getUsername().equals(targetClient)) {
handler.sendMessage(sender.getUsername() + " (私密): " + message);
return;
}
}
// 如果目标客户端不存在,通知发送者
sender.sendMessage("用户 '" + targetClient + "' 找不到.");
}
}
以上就是所有的代码咯,当然还有标题中提到的心跳以及超时的机制解决方案,以下代码可供大家参考
心跳机制代码
弄一个Timer定时器,定时的向服务端发送消息来保持socket连接的活跃性
/**
* 往上机系统发送指令,接收响应
*
* @param message
*/
public void sendMessage(String message) {
try {
log.info(message);
if(null != out){
out.println(message);
}else{
log.error("发送消息失败!!! Socket out 为null,请检查连接是否成功");
}
String response;
if(null != in){
response = in.readLine();
log.info(response);
}else{
log.error("发送消息失败!!! Socket in 为null,请检查连接是否成功");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
closeConnection();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 定时心跳
*
* @param intervalSeconds
*/
private void startHeartbeatTimer(int intervalSeconds) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sendMessage("客户端:我的心跳信息,俺还活着哦!\t" + sdf.format(new Date()));
}
}, 0, intervalSeconds * 1000L);
}
超时机制代码
连接socket服务端时,捕获一个超时的异常就行
try {
socket = new Socket(serverHost, serverPort);
//读写操作的超时时间
socket.setSoTimeout(timeout);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
} catch (IOException e) {
// 捕获连接超时异常
if (e instanceof ConnectException) {
log.error("初始化连接失败,请检查server的活跃性!!!");
} else {
// 处理其他异常
e.printStackTrace();
}
} finally {
try {
closeConnection();
} catch (IOException e) {
e.printStackTrace();
}
}
效果图展示
客户端1效果图:
客户端2效果图:
客户端3效果图:
服务端效果图:
源码+项目部署
数据库还有源码一起都打包再下边的地址里了,下载开箱即用,跟springboot一样,嘻嘻嘻!
嗯嗯嗯......终于到了激动人心的时候了,我来帮你搞定一切各种面试,技术问题,毕业设计,帝王般的服务你值得拥有,免费的哟。
各类源码扫描搜索公众号《与胡广一起探索比特星球》发送任何消息免费获取
各类源码扫描搜索公众号《与胡广一起探索比特星球》发送任何消息免费获取
各类源码扫描搜索公众号《与胡广一起探索比特星球》发送任何消息免费获取
最后附上
一寸光阴一寸金,寸金难买寸光阴。请珍惜现在美好的青春,咱们一起努力奋斗,创造美好未来
拜托拜托!!!拜托拜托!!!拜托拜托!!!
BIT PLA***