有天在soul中聊天,想了会儿solu的聊天机制,应用所学的Java知识,计划写一个简单的聊天室,当天着手准备,经过了一下午的思考后,写了这个小的聊天室。随着现代计算机以及网络的发展,信息及时到达,不像木心老爷子诗中所写“从前的日色变得慢,车马邮件都慢。”有感而发,这个自己写的聊天室名字就叫车马疾吧。
聊天室分为客户端和服务器,客户端负责向服务器发送数据,接收服务器转发的数据。服务器负责接收客户端发来的消息,并转发至对应的客户端。聊天室可在局域网内通信
聊天室主要作用为:创建用户,私聊,群聊,佛系聊天(随机聊天),退出用户。
客户端:实例化一个Socket对象,创建读取服务器消息进程ReadFromServer,向服务器发送消息进程WriteToServer,通过PrintStream向服务器发送消息。
客户端代码:
class ReadFromServer implements Runnable{ //从服务器读取数据
private Socket client;
public ReadFromServer(Socket client) {
this.client = client; //构造器接收Socket
}
@Override
public void run() {
try {
Scanner in=new Scanner(client.getInputStream()); //创建输入流
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);
}
}
}
//向服务器发送消息,覆写Runnable创建线程
class WriteToServer implements Runnable{
private Socket client;
public WriteToServer(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
//建立输入流,发送要输入的消息
Scanner scanner=new Scanner(System.in);
scanner.useDelimiter("\n");
//建立输出流,向服务器发送消息
PrintStream out =new PrintStream(client.getOutputStream());
while(true){
menu();
System.out.println("请输入要发送的内容");
String msg;
if(scanner.hasNext()){
msg=scanner.nextLine().trim();
out.println(msg);
//退出标志位,byebye
if(msg.equals("bye")){
System.out.println("客户端关闭");
scanner.close();
out.close();
client.close();
break;
}
}
}
} catch (IOException e) {
System.out.println("客户端写程序异常,异常为"+e);
}
}
public static void menu(){
System.out.println("\t\t*********************个人聊天室***************************");
System.out.println("\t\t╔══════════════════════════════╗");
System.out.println("\t\t║**……********¥**请根据需要输入对应需求**¥******……***║");
System.out.println("\t\t║@ userName:用户名(申请账户) @║");
System.out.println("\t\t║ @ P:用户名-消息(私聊) @ ║");
System.out.println("\t\t║ @ G:消息(群聊) @ ║");
System.out.println("\t\t║ @ R:消息(佛系聊天) @ ║");
System.out.println("\t\t║ @ bye(退出聊天室) @ ║");
System.out.println("\t\t║ @ ……********¥**请根据需要输入对应需求**¥******…@ ║");
}
}
public class Client {
public static void main(String[] args) {
try {
Socket client=new Socket("127.0.0.1",6666);//创建客户端套接字
Thread readFromServer=new Thread(new ReadFromServer(client));
Thread writeToServer=new Thread(new WriteToServer(client));
writeToServer.start();//启动读取和写入线程
readFromServer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器:1、实例化一个ServerSocket对象,利用accpet方法获取客户端连接到服务器的端口。
2、使用Executors创建线程池实现多线程版本的聊天室,使用ConcurrentHashMap保存用户名和套接字,即保证了线程安全,也达到了高效的目的。
服务器代码:
public class Server {
//创建存储注册的客户端,使用ConcurrentHashMap防止线程不安全,且保证了效率
private static Map<String,Socket> clientMap=new ConcurrentHashMap<String,Socket>();
private static class ExecuteClient implements Runnable{
private Socket client;
//通过构造器接收客户端套接字
public ExecuteClient(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
//获取客户端的输入流
Scanner in=new Scanner(client.getInputStream());
String strFromClient;
while(true){
if(in.hasNextLine()){
//将Windows的默认换行\r\n改为空字符串
strFromClient=in.nextLine();
Pattern pattern=Pattern.compile("\r");
Matcher matcher=pattern.matcher(strFromClient);
strFromClient=matcher.replaceAll("");
//如果以userName开头,表示申请账号
if(strFromClient.startsWith("userName")){
//正则表达式,取出用户名
String userName=strFromClient.split("\\:")[1];
addUser(userName,client);
continue;
}
//如果以G开头,表示群聊
if(strFromClient.startsWith("G")){
String message=strFromClient.split("\\:")[1];
groupChat(message);
continue;
}
//以P开头表示私聊,后面跟用户名和消息
if(strFromClient.startsWith("P")){
//使用正则表达式,分别取出要发送的用户名和密码
String userName=strFromClient.split("\\:")[1].split("-")[0];
String messege=strFromClient.split("\\:")[1].split("-")[1];
privateChat(userName,messege);
}
//以R开头,表示随机匹配
if(strFromClient.startsWith("R")){
String userName=null;
for(String keyName:clientMap.keySet()){
//随机生成一个存在的用户名
double number1= Math.random()%clientMap.size();
double number2=Math.random()%clientMap.size();
if(number1<number2) {
userName=keyName;
}
}
if(userName==null) {
PrintStream out=new PrintStream(client.getOutputStream(),true);
out.println("缘分未到,请静候佳音");
continue;
}
String message=strFromClient.split("\\:")[1];
privateChat(userName,message);
}
//以bye开头,表示用户要退出
if(strFromClient.startsWith("bye")){
String userName=null;
for(String keyName:clientMap.keySet()){
if(clientMap.get(keyName).equals(client)){
userName=keyName;
}
}
System.out.println("用户"+userName+"下线了");
clientMap.remove(userName);
continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//私聊
private void privateChat(String userName, String messege) {
//根据用户名取得客户端套接字
Socket privateSocket=clientMap.get(userName);
try {
//向指定客户端发送消息
PrintStream out=new PrintStream(privateSocket.getOutputStream());
out.println("私聊消息为"+messege);
} catch (IOException e) {
e.printStackTrace();
}
}
//群聊
private void groupChat(String message) {
//将Map集合转为Entry集合,便于遍历
Set<Map.Entry<String,Socket>> clientSet=clientMap.entrySet();
for(Map.Entry<String,Socket>entry : clientSet){
try {
//取得每一个元素,分别给套接字对应的客户端发送消息
Socket socket=entry.getValue();
PrintStream out=new PrintStream(socket.getOutputStream(),true);
out.println("群聊消息为"+message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//注册用户
private void addUser(String userName, Socket client) {
//先将用户保存到clientMap中
clientMap.put(userName,client);
System.out.println("用户"+userName+"上线了");
System.out.println("当前聊天室有"+clientMap.size()+"人");
try {
//告知当前连接的客户端注册成功
PrintStream out=new PrintStream(client.getOutputStream(),true);
out.println("注册成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
//创建大小为20的线程池
ExecutorService executorService= Executors.newFixedThreadPool(20);
//创建服务器套接字
ServerSocket serverSocket=new ServerSocket(6666);
for(int i=0;i<20;i++){
System.out.println("等待客户端连接");
Socket client=serverSocket.accept();
System.out.println("有客户端连接,端口号为"+client.getPort());
executorService.submit(new ExecuteClient(client));
}
executorService.shutdown();
serverSocket.close();
}
}
多线程版的聊天室至此就已经结束,后续会慢慢在加一些功能,若有bug,欢迎随时指出,不胜感激。