大家好,我是一位在java学习圈中不愿意透露姓名并苟且偷生的小学员,如果文章有错误之处,还望海涵,欢迎多多指正
如果你从本文学到有用的干货知识,那么请您尽量点赞,关注,评论,收藏
若图片无法显示,图片链接如下:
https://img-blog.csdnimg.cn/20200721111323559.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2J3X2N4X2ZkX3N6,size_16,color_FFFFFF,t_70
package server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
public class Server {
//为了找寻用户信息方便,采用HashMap进行存储,静态无需创建对象,类名点即可
//权限修饰符默认不写方便同包下也能正常访问
static HashMap<String,User> userBox = new HashMap<>();
public static void main(String[] args){
try {
System.out.println("服务端启动");
//创建服务端
ServerSocket serverSocket = new ServerSocket(9999);
while(true) {
Socket socket = serverSocket.accept();
//每获取到一个用户信息就添加进集合中
//通过返回的socket获取一个字节型输入流
InputStream is = socket.getInputStream();
//将输入流包装成低级的字符型输入流,因为高级流里面不能直接放下字节型输入流
InputStreamReader isr = new InputStreamReader(is);
//构建高级字符型输入流 因为用BufferedReader流中有readLine方法可以直接读取一行
BufferedReader br = new BufferedReader(isr);
String uid = br.readLine();
//提示哪个用户已上线
System.out.println(uid + "上线了");
//接收客户端的传过来的uid 并存入集合中
User user = new User(uid, socket);
Server.userBox.put(uid, user);
ServerThread st = new ServerThread(user);
st.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package server;
import java.net.Socket;
public class User {
private String uid;
private Socket socket;
public User(String uid, Socket socket) {
this.uid = uid;
this.socket = socket;
}
public String getUid() {
return uid;
}
public Socket getSocket() {
return socket;
}
}
package server;
import java.io.*;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
/**
* 根据分析可知,本类的功能为将服务端读到的信息转发至另一人(单聊)或者一群人(群聊)
*/
public class ServerThread extends Thread {
private User user;
public ServerThread(User user){
this.user = user;
}
//为防止代码冗余 设计一个传递信息的方法
private void writerMessage(Socket socket , String message , String time){
OutputStream os = null;
PrintWriter writer = null;
try {
//回服务端消息
os = socket.getOutputStream();
writer = new PrintWriter(os);
writer.println(time + "####" + user.getUid() + ":" + message);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
//重写run方法 一直读写即可
public void run(){
//获取所有用户信息
HashMap<String,User> messages = Server.userBox;
try {
//接收客户端的消息
InputStream is = user.getSocket().getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
while(true) {
//根据实际情况 我们单聊假定为:发送的信息要满足 (内容@另一个人的uid)
//当然可能存在不止@一个人,比如(你在干嘛呀,小老弟@张三@李四)
//若未解析到 @ 我们假定为群聊
String message = br.readLine();
//拼接一个时间
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");
String time = sdf.format(date);
System.out.println(time + "####" + user.getUid() + ":" + message);
//按照@拆分成String数组
String[] messageAndUid = message.split("@");
//解析读到信息
if (messageAndUid.length == 1) {
//此时是群聊 获取所有的键(即uid)通过迭代器遍历
Iterator<String> uids = messages.keySet().iterator();
while (uids.hasNext()) { //判断是否有元素
String uid = uids.next(); //将元素取出来
User user1 = messages.get(uid);
//因为if和else中传递信息的代码一致 故另外封装一个方法负责传递信息
this.writerMessage(user1.getSocket(), message,time);
}
} else {
//先给自己发一份
this.writerMessage(user.getSocket(),messageAndUid[0],time);
//此时是单聊 可以做个严谨的判断 @的用户是否存在 但是QQ或者微信中@时弹出的页面显示的人都是存在的
//故此处我们假定@的都存在 由于未知@了几个人故此处采用循环
for (int i = 1; i < messageAndUid.length; i++) {
this.writerMessage(messages.get(messageAndUid[i]).getSocket(), messageAndUid[0],time);
}
}
}
} catch (IOException e) {
// e.printStackTrace();
// 防止客户端下线时出现异常 这里做个输出提示即可
System.out.println(user.getUid() + "下线了");
}
}
}
package client;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
public class QQFrame extends JFrame{
//添加一个属性---表示QQ窗口的名字
private String uid;
//添加一个属性---表示客户端连接的socket对象
private Socket socket;
private JPanel panel = new JPanel();//无色透明的小容器 小盒子
//2.有一些组件
// 文本域(接收信息并展示的 上面部分)
private JTextArea messArea = new JTextArea();
private JScrollPane messPane = new JScrollPane(messArea);
// 文本域(发送信息的 下面部分)
private JTextArea speakArea = new JTextArea();
private JScrollPane speakPane = new JScrollPane(speakArea);
// 按钮(发送)
private JButton sendButton = new JButton("发送");
// 按钮(取消)
private JButton cancelButton = new JButton("取消");
//构造方法(规定调用的流程)
public QQFrame(String uid){
super(uid);
//加载窗口的组件
this.setOther();
this.addElements();
this.addListener();
this.setFrameSelf();
//窗口相当于是一个客户端 产生一个客户端连接
try {
//与服务器连接
socket = new Socket("localhost",9999);
//通过连接后用socket来获取流
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(os);
writer.println(uid);
writer.flush();
//只需要一个读取信息的来为我们做事------客户端读线程
ClientReader cr = new ClientReader(socket);
cr.start();
} catch (IOException e) {
e.printStackTrace();
}
}
//设计一个方法 设置那些乱七八糟的东西
private void setOther(){
//设置组件的位置
//将默认布局清空
panel.setLayout(null);
//将所有的组件自定义放在panel中
messPane.setBounds(10,10,320,220);
speakPane.setBounds(10,240,320,140);
sendButton.setBounds(180,390,60,30);
cancelButton.setBounds(260,390,60,30);
//设置一下上面展示的文本域不允许修改了
messArea.setEditable(false);
messArea.setFont(new Font("宋体",Font.BOLD,18));
speakArea.setFont(new Font("宋体",Font.BOLD,18));
}
//设计一个方法 添加组件
private void addElements(){
//将这些组件放置在窗体里
panel.add(messPane);
panel.add(speakPane);
panel.add(sendButton);
panel.add(cancelButton);
this.add(panel);
}
//设计一个方法 给组件添加事件(功能)
private void addListener(){
//取消按钮绑定一个功能 这里是lanmbda表达式的写法
cancelButton.addActionListener(e -> {
speakArea.setText("");
});
//给发送按钮绑定一个功能 这里是lanmbda表达式的写法
sendButton.addActionListener(e -> {
try {
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(os);
String message = speakArea.getText();
writer.println(message);
writer.flush();
//发送完毕之后,让发送的说话框清空
speakArea.setText("");
} catch (IOException e1) {
e1.printStackTrace();
}
});
}
//设计一个方法 设置窗体自身的一些元素
private void setFrameSelf(){
//2.设置窗体一些样式
this.setResizable(false);
//设置窗体点击右上角X 程序结束
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗体的初始位置
this.setBounds(500,200,350,480);
//让窗体可见
this.setVisible(true);
}
//内部类写法 这个类帮客户端去接收消息 由于每个客户端之间的消息同步
//所以这个类需要实现线程
private class ClientReader extends Thread {
//通过属性来获取自己客户端的那个socket
private Socket socket;
public ClientReader(Socket socket){
this.socket = socket;
}
public void run(){
StringBuilder result = new StringBuilder();
try {
//客户端 接收发来的数据
InputStream is = socket.getInputStream();
//将字节流转化成字符流
InputStreamReader isr = new InputStreamReader(is);
//字符流基础上 可以读取一行
BufferedReader reader = new BufferedReader(isr);
while(true) {
//每次读取一行数据
String value = reader.readLine();
//一行数据进行处理 换行
value = value.replace("####","\r\n");
//追加到StringBuilder对象中 频繁拼接效果更好
result.append(value);
result.append("\n");
//展示在上面的聊天框中(文本域中)
messArea.setText(result.toString());
}
} catch (IOException e) {
//e.printStackTrace();
//避免异常 简单做个提示即可
System.out.println("服务器宕机了");
}
}
}
}
package client;
public class Client {
public static void main(String[] args){
new QQFrame("张三");
new QQFrame("李四");
new QQFrame("王五");
}
}
这里附上服务端与客户端的运行结果如下:
服务端启动
张三上线了
李四上线了
王五上线了
2020-07-21 09:35:50####李四:你们在干吗
2020-07-21 09:36:00####李四:我在喝西北风
2020-07-21 09:36:12####张三:啧啧啧@李四
2020-07-21 09:36:51####王五:我笑了,你呢@张三
2020-07-21 09:37:33####张三:李四要是真的喝西北风我直播倒立洗头
全剧终