目的:
将昨天实现的私聊、群聊等功能用一个demo实现,不仅能很好地复习一遍,也不简简单单地写在一起,其中还有许多新的的问题等待着我们去解决,需要考虑的方面更多更全,还有运用一些新的编程知识。
技术:
1.字符串的split函数:
Java中的 split 函数是用于按指定字符(串)或正则去分割某个字符串,结果以字符串数组形式返回;
注意事项:对于分割的字符(串),通常是常见,普通的,没什么问题;但是对某些特殊字符,如果字符(串)正好是正则的一部分,则需要转义才能使用这些字符有 | , + , * , ^ , $ , / , | , [ , ] , ( , ) , - , . , \等, 因它们是正则表达式中的一部分。
解决办法: 所以如果想用该字符本身, 这些字符需要进行转义才能表示它本身;
eg:想用 | 竖线去分割某字符,因 | 本身是正则表达式中的一部分,所以需要 \ 去转义,因转义使用 , 而这个 \ 正好也是正则表达式的字符,所以还得用一个 \ , 所以需要两个 \\。
2.登录界面的优化 JOptionPane类是使用
从直接在界面下方显示窗口输入实现到提高登录界面进行登录:
JOptionPane类其中封装了很多的方法。很方便的,于是就简单的整理了一下。
方法具体使用上网查询撒。
1.1 showMessageDialog
1.2 showOptionDialog
1.3 showInoutDialog
实际编程:
需求:
1.每个客户端一个名称
2.私聊
3.群聊
4.发文件等
核心思想:
* 客户端只能向服务器端发送文件或者文字 服务器端只能得到客户端发来的数据
* 所以 必须服务器端和客户端有一个规范
* 客户的意图可以在其发送的字符里面体现
* 1.登录 u+ 姓名 u+
* 2.私聊 p+ 姓名!:聊天内容 p+
* 3.群聊 a+ 聊天内容 a+
* 4.发文件 f+
* 5.发语音 v+
* 服务器的返回体现意思
* 1.登录 成功 1 失败 -1
*
* 问题:1.怎么知道用户是私聊还是群聊
* 2.保存一个用户 [jack-socket]
* 实现名字和socket一一对应 Map<String Socket> 键值对
* 保存所有用户 Map<String Socket>
(1)定义一个接口 实现自定义规则:
public interface ChatProtocol {
//登录
String LOGING_FLAG = "u+";
//私聊
String PRIVATE_FLAG = "p+";
//群聊
String PUBLIC_FLAG = "a+";
//分隔符
String SPLIT_FLAG = "gg";
//成功的状态
String SUCCESS = "1";
String FAILURE = "-1";
}
(2)定义UserManager类 管理理所有登录⽤用的信息,虽然方法就一句话,但为了便于管理,均由UserManager管理:
/**
* 管理所有的登录用户<String,Socket>
* 判断用户是否已经登录
*
*/
public class UserManager {
//用于保存用户对于的姓名和socket
public static Map<String, Socket> users = new HashMap<>();
/**
* 判断用户是否已经登录
*/
public boolean isLogined(String name ){
//遍历数组
for(String key : users.keySet()){
if(key.equals(name)){
return true;
}
}
return false;
}
/**
* 保存当前用户信息
*/
public void save(String name, Socket socket){
users.put(name,socket);
}
/**
* 通过用户名找到对应的socket
*/
public Socket socketByName(String name){
return users.get(name);
}
/**
* 通过socket对象找到对应的姓名
*/
public String nameBySocket(Socket socket){
for(String key : users.keySet()){
//
if(socket == users.get(key)){
return key;
}
}
return null;
}
/**
* 获取所有人的socket
*/
public Collection<Socket> allUsers(){
return users.values();
}
}
(3)Server类 :
主线程:
public class Server {
//用于保存用户对于的姓名和socket
public static UserManager manager = new UserManager();
public static void main(String[] args){
try(ServerSocket ss = new ServerSocket(8888)){
//监听所有来连接的客户端
while (true){
Socket socket = ss.accept();
//让子线程处理这个socket
new ServerThread(socket).start();
}
}catch (IOException e){
}
}
}
子线程:
class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//登录
BufferedReader br = null;
PrintStream ps = null;
try {
//1.获取输入流对象
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//得到对应的输出流
ps = new PrintStream(socket.getOutputStream());
String line = null ;
while ((line = br.readLine()) != null){
//是否登录
//判断是否私聊
//群聊
//其他情况
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
是否登录成功:
if(line.startsWith(ChatProtocol.LOGING_FLAG) && line.endsWith(ChatProtocol.LOGING_FLAG)){
//u+姓名u+
//获取姓名
int endIndox = line.length()-2;
String name = line.substring(2,endIndox);
//判断这个用户是否已登录
if(Server.manager.isLogined(name)){
//已经登录过了
//发送结果给客户端
ps.println(ChatProtocol.FAILURE);
} else {
//没有登录
//这次登录成功
//保存当前登录的用户信息
Server.manager.save(name,socket);
ps.println(ChatProtocol.SUCCESS);
}
}
私聊:
else if(line.startsWith(ChatProtocol.PRIVATE_FLAG) && line.endsWith(ChatProtocol.PRIVATE_FLAG)){
//p+jack?hellep+
//获取信息
int m = line.length()-2;
String msq = line.substring(2,m);
//分割
String[] itmes = msq.split(ChatProtocol.SPLIT_FLAG);
//System.out.println(msq.split("gg").length);
//用户名
String name = itmes[0];
//System.out.println(name);
//聊天内容
String message = itmes[1];
//System.out.println(message);
// String name = line.substring(2,6);
// String message = line.substring(6,line.length()-2);
//通过用户名找到对应的socket
Socket destSocket = Server.manager.socketByName(name);
PrintStream destps = new PrintStream(destSocket.getOutputStream());
//获取当前用户名姓名
String currentName = Server.manager.nameBySocket(socket);
//发送信息
destps.println(currentName+"向你发来信息:"+message);
}
群聊:
else if(line.startsWith(ChatProtocol.PUBLIC_FLAG) && line.endsWith(ChatProtocol.PUBLIC_FLAG)){
//处理数据
String message = line.substring(2,line.length()-2);
//System.out.println(message);
//获取当前用户姓名
String currentName = Server.manager.nameBySocket(socket);
//遍历所有用户信息
Collection<Socket> sockets = Server.manager.allUsers();
for(Socket s :sockets){
PrintStream p = new PrintStream(s.getOutputStream());
p.println(currentName+"发来群聊:"+message);
}
}
else:
else {
PrintStream p = new PrintStream(socket.getOutputStream());
p.println("输入不合法 请重新输入");
}
}
(4)客户端:为模拟多个客户端实现群聊等功能,可重复复制客户端的代码实现多个
主线程:
public class Client {
public static void main(String[] args){
BufferedReader br = null;
PrintStream ps = null;
BufferedReader brServer = null;
//连接服务器端
try(Socket socket = new Socket("192.168.43.117",8888)){
//登录
ps = new PrintStream(socket.getOutputStream());
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
br = new BufferedReader(new InputStreamReader(System.in));
while (true){
String line = JOptionPane.showInputDialog("请输入用户名:");
String loginStr = ChatProtocol.LOGING_FLAG+line+ChatProtocol.LOGING_FLAG;
//发送给服务器端
ps.println(loginStr);
//接收服务器端返回的结果
String result = brServer.readLine();
//判断登录结果
if(result.equals(ChatProtocol.SUCCESS)){
System.out.println("登录成功");
break;
}else {
System.out.println("用户名已存在 请重新登录");
}
}
//登录成功后;
//开启线程处理服务器端的输入
new ClientThread(socket).start();
//接收终端输入信息 向服务器端发送数据
String line = null;
while ((line = br.readLine()) != null){
ps.println(line);
}
}catch (IOException e){
//System.out.println("网络出错!");
}
}
}
子线程:
class ClientThread extends Thread{
private Socket socket;
public ClientThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while ((line = br.readLine()) != null){
System.out.println(line);
}
}catch (IOException e){
System.out.println("网络出错!");
}finally {
try {
if(br != null) {
br.close();
}
if(socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
心得:
不知道为什么以前上课很多时候都对代码每太多兴趣,就是跟着东哥敲代码,但今天却越写越有劲,对后面的代码十分的期待,会提前想一下自己应该怎么编,再满怀期待地验证东哥打出来的代码,时间一晃就过去了,都还没反应过来,希望自己以后能经常进入这种状态。