首先,要实现两个客户端之间的聊天,并不是一个客户端直接发送给另一个客户端,而是需要有一个服务器
比如现在是客户端A和客户端B聊天,首先A会将信息发送给服务器,其中信息并不只是聊天的信息,还应该包括要发送给谁等关键信息,例如:B:hi~ 这表示A要给B发送信息,发送的内容是hi~;
然后服务器会将信息整理一下,将其中的关键信息(比如:要聊天的对象)和A原本要发送给B的信息(如上文的:hi~)分开,然后在转发给相应的用户,具体如下图:
具体代码实现:
1、客户端代码:
package fei.MultiThreadChatRoom;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 多线程聊天室的客户端要干的事:
* 两个线程
* 一个线程用来读取服务器发送的消息
* 一个线程用来给服务器发送消息
*/
/**
* 读取服务器发送的信息线程
*/
class ReadFromServer implements Runnable
{
private Socket client;
//通过构造方法传入当前客户端
public ReadFromServer(Socket client) {
this.client = client;
}
@Override
public void run() {
Scanner in;
try {
in = new Scanner(client.getInputStream());
in.useDelimiter("\n");
while (true)
{
if (in.hasNext())
{
System.out.println(in.next());
}
//如果客户端关闭
if (client.isClosed())
{
System.out.println("此客户端关闭!");
break;
}
}
in.close();
} catch (IOException e) {
System.out.println("读线程异常,错误为"+e);
}
}
}
/**
* 将信息发送给客户端线程
*/
class SendToServer implements Runnable
{
private Socket client;
public SendToServer(Socket cilent) {
this.client = cilent;
}
@Override
public void run() {
try {
Scanner in = new Scanner(System.in);
in.useDelimiter("\n");
PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
while (true)
{
System.out.println("请输入要发送的信息...");
String str = null;
if (in.hasNext())
{
str = in.nextLine().trim();
//发送消息
out.println(str);
}
//如果发送的消息是byebye,客户端关闭
if (str.startsWith("byebye"))
{
System.out.println("客户端关闭!");
in.close();
out.close();
client.close();
break;
}
}
} catch (IOException e) {
System.out.println("客户端写线程异常,错误为:"+e);
}
}
}
public class MultiThreadClient {
public static void main(String[] args){
//1、连接服务器
Socket client = null;//端口号建议使用1000以后的,因为1000之前的大多数都已经被网络协议占用
try {
client = new Socket("127.0.0.1", 6666);
//2、获取输入输出流
new Thread(new ReadFromServer(client)).start();
new Thread(new SendToServer(client)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、服务器端代码:
package com.bittech.MultiThreadChatRoom;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MultiThreadServer {
//定义一个map用来存放已经注册过的客户端,因为是多线程,所以用ConcurrentHashMap
private static Map map = new ConcurrentHashMap();
//定义一个内部类,用来具体处理每一个客户端的通信问题
public static class Chat implements Runnable
{
private Socket client;
//通过构造方法传入一个客户端对象
public Chat(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
//获取客户端输入
Scanner in = new Scanner(client.getInputStream());
String str ;
while(true)
{
if (in.hasNext())
{
str = in.nextLine();
System.out.println("G:-->"+str);
//windows下换行符为\r\n,在这里将\r\n换位\n,因为在Linux系统下换行符默认为\n
Pattern pattern = Pattern.compile("\r");
Matcher matcher = pattern.matcher(str);
str = matcher.replaceAll("");
//将从客户端接受的到的字符串处理好之后,开始处理客户端之间的通信问题
//如果字符串以userName:开头,比如:客户端:userName:xiaofeixia -->表示用户xiaofeixia正准备要注册
if (str.startsWith("userName:"))
{
//获取用户名
String userName = str.split(":")[1];
//调用注册的方法
registerUsers(userName, client);
continue;
}
//如果字符串以"G:"开头,表示群聊
//e.g: G:hi,大家在干嘛~~~
if (str.startsWith("G:"))
{
//获取聊天字符串
String strToEveryone = str.split(":")[1];
chatInGroup (strToEveryone, client);//----------------------------------------
continue;
}
/**
* 如果字符串以“P:”开头,表示私聊,其形式为: P:xianfeixia-hi,在干嘛呢~~~
* : 后面跟着的是用户名
* - 后面跟的是聊天内容
*/
if (str.startsWith("P:"))
{
//获取要聊天的人的用户名
String userName = str.split(":")[1].split("-")[0];
//获取聊天内容
String strToOne = str.split(":")[1].split("-")[1];
privateChat(userName, strToOne);
continue;
}
/**
* 如果字符串以A:开头,表示匿名聊天,例如:A:xianfeixia-hi,在干嘛呢~~
*/
if (str.startsWith("A:"))
{
//获取要聊天的人的用户名
String userName = str.split(":")[1].split("-")[0];
//获取聊天内容
String strToOne = str.split(":")[1].split("-")[1];
anonymousChat(userName, strToOne);
continue;
}
/**
* 用户退出
* 先找到对应的用户名,在将其从map中移除
*/
if (str.contains("byebye"))
{
String userName = null;
for (String name : map.keySet())
{
if (map.get(name).equals(client))
{
userName = name;
break;
}
}
chatInGroup(userName+"退出群聊~~~", client);//---------------------------
System.out.println("userName+\"退出群聊~~~+\r\n+当前群聊人数为"+(map.size()-1));
map.remove(userName);
continue;
}
if (true)
{
PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
out.println("系统提示:聊天格式错误,请重新输入!!!");
System.out.println(getName()+"聊天格式错误,请重新输入!!!");
continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断这个用户是否已经注册过了,如果已经注册过了,就不能再次注册
* @param client 客户端Socket对象
* @return 注册过了返回true,否则返回false
*/
private boolean ifRegister(Socket client)
{
Set> entrySet = map.entrySet();
for (Map.Entry es : entrySet)
{
if(es.getValue().equals(client))
{
System.out.println("此用户已经注册过了,不用再次注册^V^");
return true;
}
}
return false;
}
/**
* 用户注册
* @param userName 用户名
* @param client 客户端对象
*/
private void registerUsers (String userName, Socket client)
{
if (ifRegister(client))
{
try {
PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
out.println("此用户已经注册过了,不用再次注册^V^");
} catch (IOException e) {
e.printStackTrace();
}
}
else if (map.keySet().contains(userName))//如果用户名已经被注册
{
try {
PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
out.println("用户名重复,请重新输入-_-");
} catch (IOException e) {
e.printStackTrace();
}
}
else
{
System.out.println(userName+"加入群聊");
System.out.println("当前聊天人数为:"+(map.size()+1));
//将信息存入map中
map.put(userName, client);
//获取输出流
chatInGroup(userName+"加入群聊~~", client);
}
}
/**
* 群聊方法
* @param strToEveryone 要群发的消息
* @param client 客户端对象
*/
private void chatInGroup (String strToEveryone, Socket client)
{
String userName = null;
//取得map中所有Entry遍历发送群消息
Set> mapEntry = map.entrySet();
//取得当前发消息的人的用户名
for (Map.Entry getName : mapEntry)
{
if (getName.getValue().equals(client))
{
userName = getName.getKey();
}
}
//给每一个人转发消息,自己发出去的消息不用给自己转发
for (Map.Entry entry : mapEntry)
{
if (entry.getKey().equals(userName) == false)
{
Socket socket = entry.getValue();
//取得每个客户端的输出流
try {
PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
out.println(userName+"(群消息):"+strToEveryone);
} catch (IOException e) {
System.out.println("群聊异常,错误为"+e);
}
}
}
}
/**
* 获取用户名
* @return 返回用户名
*/
private String getName ()
{
String name = null;
//取得map中所有Entry遍历发送群消息
Set> mapEntry = map.entrySet();
//取得当前发消息的人的用户名
for (Map.Entry getName : mapEntry)
{
if (getName.getValue().equals(client))
{
name = getName.getKey();
}
}
return name;
}
/**
* 给名为mane的用户发送一条消息
* @param name 用户名
* @param msg 发送内容
**/
private void sendToName(String name, String msg)
{
Socket s = map.get(name);
PrintStream o = null;
try {
o = new PrintStream(s.getOutputStream(), true, "UTF-8");
o.println(msg);
} catch (IOException e) {
System.out.println("私聊提示用户不存在异常,错误为"+e);
}
}
/**
* 私聊
* @param userName 用户名
* @param strToOne 要发送的信息
*/
private void privateChat (String userName, String strToOne)
{
String name = getName();
//取得要私聊的人的输出流
Socket socket = map.get(userName);
if (socket != null)
{
try {
PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
out.println(name+"(私聊消息):"+strToOne);
} catch (IOException e) {
System.out.println("私聊异常,错误为"+e);
}
}
else
{
sendToName(name, "你输入的用户不存在,请重新输入~~");
System.out.println("该用户不存在!!!");
}
}
/**
* 匿名聊天
* @param userName 用户名
* @param strToOne 要发送的信息
*/
private void anonymousChat (String userName, String strToOne)
{
Socket socket = map.get(userName);
try {
PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
out.println("有人说:"+strToOne);
} catch (IOException e) {
System.out.println("匿名聊天异常,错误为"+e);
}
}
}
public static void main(String[] args) throws IOException {
ExecutorService executorService = Executors.newFixedThreadPool(50);
ServerSocket serverSocket = new ServerSocket(6666);
for (int i=0; i<50; i++)
{
System.out.println("等待客户端连接...");
Socket client = serverSocket.accept();
System.out.println("有新的客户连接,端口号为:"+client.getPort());
executorService.submit(new Chat(client));
}
executorService.shutdown();
serverSocket.close();
}
}
注:由于这个不是界面操作,所以群聊私聊都要按正确的格式来,格式如下:
聊天之前先注册用户信息,格式如下:
测试结果:
因为我用的是创建固定大小的线程池,最多可以允许有50个客户端线程,所以我测试了创建五十个客户端线程,通过测试,并且可以正常聊天:
群聊:
![在这里插入图片描述](https://img-
私聊:
匿名发送消息:
我也是刚学了JavaSE不久,所以写出来的代码看起来还不是很好,如果有不对的地方,欢迎大家指出…