接着昨天的发文,在昨天的项目中,存在着许多的问题,今天就来将这些问题给解决了,同时会以本人思路来一步步的写代码:
要实现的功能:
/**
* 1.每一个客户端一个名称
* 2.向某一个客户端发起私聊
* 3.群聊
*
* 客户端只能向服务器发文件或者字符
* 服务器端只能得到客户端发来的数据
* 必须客户端和服务器有一个规范
* 客户端的需求可以在发送的字符里面体现
*
* 定义规范:
* 1.登陆 u+ 姓名 u+
* 2.返回结果 成功 1 ;失败 -1
* 3.私聊 p+ 姓名♥内容 P+
* 4.群聊 a+ 聊天内容 a+
* 使用Map来保存信息:保存对应的名字信息和一个对应的socket,才能调用每个信息
* Map<String,Socket>
*/
思路:要实现群聊、私聊、并且给每个客户端都取好名字,首先第一步就是要创建几个类,分别是:服务器类(srever)客户端类(Client)用户管理类(userManage)
由于本阶段没有界面操作,要如何判断客户端发起的是群聊还是私聊呢?为了解决这个问题,我们就需要来定义一套规范:
1.当终端接收到以u+开头和结尾的语句时,如 u+ 内容 u+,要将此内容判定为d登陆,从而去执行登陆相对应的代码块;
2.当终端接收到以a+开头和结尾的语句时,如 a+ 内容 a+,要将此内容判定为群聊,从而去执行群聊相对应的代码块;
3.当终端接收到以p+开头和结尾的语句时,如 p+ 内容 p+,要将此内容判定为私聊,从而去执行私聊相对应的代码块;
服务器端
服务器使用多线程,主线程主要就是在不停的接收客户端链接,并且获取每个客户端的socket
public class Server{
//创建一个manage对象
public static UserManage manage = new UserManage();
//主线程只负责来监听客户端链接
public static void main(String[] args) {
//创建ServerSocket
try (ServerSocket ss = new ServerSocket(8989)){
//监听所有来链接的客户端
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()));
//2.获取对应的输出流,向客户端发送数据
ps = new PrintStream(socket.getOutputStream());
//2.接收数据
String line;
while ((line = br.readLine()) != null){
//登陆 U+...U+
if (line.startsWith(ChatProtocol.LOGIN_FLAG)&&line.endsWith(ChatProtocol.LOGIN_FLAG)){
// String[] item = line.substring(2).split("u+");
// String name = item[0];
//获取用户名
String name = line.substring(2,line.length()-2);
//调用manage方法,判断这个用户名是否已经登陆
if (Server.manage.isLonined(name)){
//已经登陆
//发送结果给客户端
ps.println(ChatProtocol.FAILURE);
}else {
//没有登陆
//调用save保存当前的登陆的用户信息
Server.manage.save(name, socket);
ps.println(ChatProtocol.SUCCESS);
}
}//判断是不是私聊
else if (line.startsWith(ChatProtocol.PRIVATE_FLAG)&&line.endsWith(ChatProtocol.PRIVATE_FLAG)){
//获取信息
String msg = line.substring(2,line.length()-2);
//int endIndex
//分割
String[] items = msg.split(ChatProtocol.STLIT_FLAG);
//用户
String name = items[0];
//聊天内容
String message = items[1];
//通过用户名找到对应的socket
Socket desSocket = Server.manage.socketByName(name);
PrintStream desPs = new PrintStream(desSocket.getOutputStream());
//获取当前用户名称
String currentName = Server.manage.nameBySocket(desSocket);
desPs.println(currentName+"向你发来私聊:"+message);
}else {
//群聊
//处理数据
String msg = line.substring(2,line.length()-2);
//获取当前用户名称
String currentName = Server.manage.nameBySocket(socket);
//遍历所有用户消息
Collection<Socket> sockets = Server.manage.allUsers();
for (Socket s: sockets){
//创建输出流
PrintStream tempps = new PrintStream(s.getOutputStream());
tempps.println(currentName+":"+msg);
tempps.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
//私聊
//群聊
}
}
客户端
客户端也采用多线程的模式,这是因为客户端需要接收服务器返回的消息,同时还要向服务器发送数据,多线程主要接收终端输入,发送给服务器端,具体实现如下:
public class Client{
public static void main(String[] args) {
BufferedReader br = null;
PrintStream ps = null;
BufferedReader brServer = null;
//链接服务器端
try (Socket socket = new Socket("127.0.0.1",8989)){
//登陆
//接收终端的输入流
br = new BufferedReader(new InputStreamReader(System.in));
//发给服务器的输出流
ps = new PrintStream(socket.getOutputStream());
//接收服务器端的输入流
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true){
//接收终端输入信息
//BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//String line = br.readLine();
String line = JOptionPane.showInputDialog("请输入用户名");
//拼接好发送
String loginStr = ChatProtocol.LOGIN_FLAG+line+ChatProtocol.LOGIN_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;
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();
}
}
}
}
客户端管理
package day14;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 管理所有登陆的用户Map<String, Socket>
* 方法:
* 判断用户是否登陆
*/
public class UserManage {
/**
* 定义一个Map来管理所有链接,保存每一个用户对应的姓名和Socket
*/
private Map<String, Socket> users = new HashMap<>();
/**
* 判断用户是否登陆
*/
public boolean isLonined(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()){
//取出这个key对应的socket对象
if (socket == users.get(key)){
return key;
}
}
return null;
}
/**
* 获取所有人的放回对象
*/
public Collection<Socket> allUsers(){
return users.values();
}
}
接口类---存放定义的规范
package day14;
public interface ChatProtocol {
//登陆
String LOGIN_FLAG = "u+";
//私聊
String PRIVATE_FLAG = "p+";
//群聊
String PUBLIC_FLAG = "a+";
//分割符
String STLIT_FLAG = "♥";
//成功状态
String SUCCESS = "1";
String FAILURE = "-1";
}
感悟
一天下来,很累,但是很开心。一个项目看似简单,可是写了两天,这个过程中出现了许多bug,虽然找bug这个过程是痛苦的,但是还是学会了不少,继续努力吧。
网友评论