要实现多任务的服务器,就离不开线程的使用,首先通过使用Runnable接口来进行线程操作,改一改代码。
package comserver240808;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MServer {
//自定义服务器
public void creatServer() throws Exception{
//创建服务器
ServerSocket server = new ServerSocket(8899);
System.out.println("启动服务器。。。");
while (true) {
//阻塞监听连接过来的客户端
Socket socket = server.accept();
//获取输入输出流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
String msg = "Connection successful!\r\n";
//发送数据
outputStream.write(msg.getBytes());
outputStream.flush();
//启动线程跟客户端通信
ServerThread st = new ServerThread(socket);
new Thread(st).start();
}
}
public static void main(String[] args) {
try {
new MServer().creatServer();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package comserver240808;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
//保持跟客户端通信的线程
public class ServerThread implements Runnable{
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
public ServerThread(Socket socket){
this.socket = socket;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
try {
String msg = readMsg(inputStream);
//群聊3个人
System.out.println("msg:"+msg);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//读取客户端数据
public String readMsg(InputStream inputStream)throws Exception{
StringBuilder stringBuilder = new StringBuilder();
int b = 0;
while ((b=inputStream.read())!=13){
//拼接一条聊天消息
stringBuilder.append((char) b);
}
return stringBuilder.toString().trim();
}
}
测试一下是否可以通过这个线程,使多个输入被拦截,同时输出到运行栏里面,实现类似于群聊的效果
成功了,两不同的客户端的输入被拦截
但是这只是单向的,只有服务端可以收到两条消息,客户端只能看到自己发的消息
所以我们对代码进行一些改进,用一个列表来保存在线的客户,然后同过循环使一个人发的消息可以被所有人接收到
package comserver240808;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class MServer {
//自定义服务器
public void creatServer() throws Exception{
//创建服务器
ServerSocket server = new ServerSocket(8899);
System.out.println("启动服务器。。。");
//保存所有上线好友
List<Socket> socketList = new ArrayList<>();
while (true) {
//阻塞监听连接过来的客户端
Socket socket = server.accept();
socketList.add(socket);
//获取输入输出流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
String msg = "Connection successful!\r\n";
//发送数据
outputStream.write(msg.getBytes());
outputStream.flush();
//启动线程跟客户端通信
ServerThread st = new ServerThread(socket,socketList);
new Thread(st).start();
}
}
public static void main(String[] args) {
try {
new MServer().creatServer();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package comserver240808;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
//保持跟客户端通信的线程
public class ServerThread implements Runnable{
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
private List<Socket> socketList;
public ServerThread(Socket socket,List<Socket> socketList){
this.socket = socket;
this.socketList = socketList;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
while (true){
try {
String msg = readMsg(inputStream);
System.out.println("msg :"+msg);
//群聊
//遍历所有在线好友
for (Socket s:socketList){
if (socket != s){
//在线好友输出列表
OutputStream outputStream1=s.getOutputStream();
outputStream1.write((msg+"\r\n").getBytes());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
//读取客户端数据
public String readMsg(InputStream inputStream)throws Exception{
StringBuilder stringBuilder = new StringBuilder();
int b = 0;
while ((b=inputStream.read())!=13){
//拼接一条聊天消息
stringBuilder.append((char) b);
}
return stringBuilder.toString().trim();
}
}
然后运行一下
结果是,3个客户端和1个服务端都可以收到同样的信息,达到了我们的目的。
但是其实我们并没有实现群聊和私聊的区分,所以我们建立一个map,分别用Integer和Socket来存储用户和消息,并且通过分割符来对输入内容进行辨别,然后借此达到群聊和私聊的目的
package comserver240808;
import Tetrisofcyz24124.I;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class MServer {
// 自定义服务器方法,用于创建并启动服务器
public void creatServer() throws Exception {
// 创建一个服务器套接字,监听端口8899
ServerSocket server = new ServerSocket(8899);
System.out.println("启动服务器。。。");
// 用于保存所有连接的客户端,使用Map进行管理,键为客户端ID,值为对应的Socket
Map<Integer, Socket> map = new HashMap<>();
int ID = 1; // 客户端顺序ID
// 无限循环,持续监听客户端连接
while (true) {
// 阻塞式方法,等待客户端连接
Socket socket = server.accept();
map.put(ID, socket);
// 获取客户端的输入输出流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
// 发送消息给客户端,告知其ID
String msg = "Client" + ID + "\r\n";
ID++;
// 发送数据到客户端
outputStream.write(msg.getBytes());
outputStream.flush();
// 启动一个新的线程,与客户端进行通信
ServerThread st = new ServerThread(socket, map);
new Thread(st).start();
}
}
public static void main(String[] args) {
try {
// 创建并启动服务器
new MServer().creatServer();
} catch (Exception e) {
// 捕获异常并抛出运行时异常
throw new RuntimeException(e);
}
}
}
package comserver240808;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
// 定义一个线程类,用于保持与客户端的通信
public class ServerThread implements Runnable {
private Socket socket; // 当前客户端的Socket
private InputStream inputStream; // 客户端的输入流
private OutputStream outputStream; // 客户端的输出流
private Map<Integer, Socket> map; // 保存所有在线客户端的Map
// 构造方法,接收客户端Socket和所有在线客户端的Map
public ServerThread(Socket socket, Map<Integer, Socket> map) {
this.socket = socket;
this.map = map;
try {
// 初始化输入输出流
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
// 捕获异常并抛出运行时异常
throw new RuntimeException(e);
}
}
@Override
public void run() {
// 持续监听客户端的消息
while (true) {
try {
// 读取客户端发送的消息
String msg = readMsg(inputStream);
System.out.println("msg :" + msg);
// 消息格式:消息头:消息内容。通过":"分割,提取消息头和消息内容
String[] msgArr = msg.split(":");
if (msgArr[0].equals("@")) {
// 群聊模式,消息头为"@",所有在线客户端都接收消息
for (Map.Entry<Integer, Socket> entry : map.entrySet()) {
Socket s = entry.getValue();
if (socket != s) {
// 向其他在线客户端发送消息
OutputStream outputStream1 = s.getOutputStream();
outputStream1.write((msg + "\r\n").getBytes());
}
}
} else {
// 私聊模式,根据消息头中的ID查找对应的客户端
Socket s = map.get(Integer.parseInt(msgArr[0]));
if (s != null) {
// 向指定客户端发送消息
OutputStream outputStream1 = s.getOutputStream();
outputStream1.write((msgArr[0] + ":" + msgArr[1] + "\r\n").getBytes());
}
}
} catch (Exception e) {
// 捕获异常并抛出运行时异常
throw new RuntimeException(e);
}
}
}
// 读取客户端发送的消息
public String readMsg(InputStream inputStream) throws Exception {
StringBuilder stringBuilder = new StringBuilder();
int b = 0;
// 读取输入流中的数据,直到遇到回车符(ASCII 13)
while ((b = inputStream.read()) != 13) {
stringBuilder.append((char) b); // 将读取到的字符拼接到StringBuilder中
}
// 返回完整的消息,去除前后空格
return stringBuilder.toString().trim();
}
}
运行代码后在用户3输入对用户1的私聊,以及@对全部的群聊,能够实现预期的效果。(这里的代码增加了更多的注释便于理解)