注意事项
事情是这样的,今天我在写案例的时候,遇到了一个BUG,一直抛出一个我从来没见过的异常,看到异常嘛,当然第一时间就是上百度查了,结果百度还是那个百度没有一个靠谱的,于是我就利用我毕生所学打断点,一点一点看,我就发现程序总是走到一个getinputstream的时候就终止了,我就纳了闷了,为啥啊?我逻辑也没写错啊?我找啊找,找了2个小时,我他喵的差点就没耐心在找下去了,天不负有心人,在一个网友的启发下,我试了试,将我的2个,一个是客户端,一个是服务端,他们用来存放User的文件夹Common,一个叫common,结果就悲剧了,因为这里的对象,刚好是我序列化和反序列化的一个类User类的对象,序列化的时候,必须保证文件相对路径是一样的,不能有半点差错,否则就像我一样整了2小时,结果是因为一个字母大写一个字母小写的问题,我真他喵的心态炸了,但是不得不说发现之后修复的感觉是真爽!
客户端代码
package com.LiveChat.InformationValidation;
import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.MessageType;
import java.io.ObjectInputStream;
import java.net.Socket;
public class ClientConnectServerThreac extends Thread{
//线程类
private Socket socket;
public ClientConnectServerThreac(Socket socket){
this.socket=socket;
}
@Override
public void run() {
//为了让客户端和服务端一直进行通信,我们在后台保持两者的联通
//即 客户端一直等待服务端发来的数据,只要有东西就接收
while (true){
try {
System.out.println("客户端线程,正在等待服务端的数据变动中。。。");
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//我们知道这里返回的一定是一个Message对象,因此我们直接强转
Message o = (Message)objectInputStream.readObject();
if(o.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
String[] onlineUsers=o.getContent().split(" ");
System.out.println("==========当前在线用户列表=========");
for (int i = 0; i < onlineUsers.length; i++) {
System.out.println("用户:"+onlineUsers[i]);
}
}else {
System.out.println("暂时不处理其他类型的message!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
}
and
package com.LiveChat.InformationValidation;
import com.LiveChat.QQCommon.MessageType;
import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class LoginAndRegistered {
private User user=new User();
private Socket socket;
public boolean cheackLogin(String userID, String passwd) throws IOException, ClassNotFoundException {
//定义一个bool变量 用于返回
boolean b = false;
//创建一个user对象,一会发给服务端进行验证
user.setUserId(userID);
user.setPasswd(passwd);
//连接服务器,把该对象发给服务端
//这个sorckt我们可能在其他地方也会使用,因此最好做成属性
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
//然后把对象发过去
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
//然后读取服务端发来的验证是否通过的信息
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Message message = (Message) objectInputStream.readObject();//如果服务端一直没发送object对象,就会阻塞在这里
//根据message返会的是1还是2来判断是否登录成功
if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {
//如果成功,则让客户端与服务端一直进行通信
//创建一个线程,用来保持客户端与服务端通信
//每次成功都创建一个线程对象
ClientConnectServerThreac clientConnectServerThreac = new ClientConnectServerThreac(socket);
//这里线程就收到了一个socke对象,就可以启动线程,让服务端和客户端一直保持通信,一旦有数据的变化就客户端就接收
clientConnectServerThreac.start();//该类继承了线程类,可以调用线程类的重要方法start启动线程
//把线程放到集合里面,方便扩展,以及之后的调用
ManageClientConnectServerThreac.addClientConnectServerThread(userID,clientConnectServerThreac);
//return true;这里最好就先别return,因为else可能还要执行如果没验证成功是不是就。。对吧
b = true;
} else {
//否则就不建立连接,关闭socket
socket.close();
}
return b;
}
public void ShowUser(){
//发送一个message对象给服务端
Message message = new Message();
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
//先要得到当前线程的socket对应的objectoutputstream对象
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageClientConnectServerThreac.getClientConnectServerThread(user.getUserId()).getSocket().getOutputStream());
objectOutputStream.writeObject(message);
//把数据传送给服务端
} catch (Exception e) {
e.printStackTrace();
}
}
}
and
package com.LiveChat.InformationValidation;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ManageClientConnectServerThreac {
//把多个线程放到该集合中 K=string类型:用户ID,Value=ClientConnectServerThreac:线程
private static HashMap<String, ClientConnectServerThreac> hashMap=new HashMap<>();
//将某个线程加入到集合中
public static void addClientConnectServerThread(String userId, ClientConnectServerThreac clientConnectServerThreac){
hashMap.put(userId,clientConnectServerThreac);
}
//将某个线程取出来
public static ClientConnectServerThreac getClientConnectServerThread(String userId){
return hashMap.get(userId);
//返回一个线程,那么该线程为什么会对应一个socket呢
//在我们创建线程对象的时候,我们就传入了一个socket,当我们用该对象的时候,自然就能使用getSocket方法了
}
}
and
package com.LiveChat.QQCommon;
import java.io.Serializable;
public class Message implements Serializable {
private static final long seriaVersiononID = 1L;
private String sender;//发送者
private String getter;//接收者
private String content;//消息内容
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
private String sendTime;//发送时间
private String mesType;//消息的类型
}
and
package com.LiveChat.QQCommon;
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED="1";//表示登录成功
String MESSAGE_LOGIN_FAIL="2";//表示登录失败
String MESSAGE_COMM_MES="3";//表示普通信息
String MESSAGE_GET_ONLINE_FRIEND="4";//要求返回在线用户列表
String MESSAGE_RET_ONLINE_FRIEND="5";//要求返回在线用户列表
String MESSAGE_CLIENT_EXIT="6";//要求返回在线用户列表
}
and
package com.LiveChat.QQCommon;
import java.io.Serializable;
public class User implements Serializable {
private String userId;//用户名
private String passwd;//密码
private static final long seriaVersiononID = 1L;//增加兼容性
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", passwd='" + passwd + '\'' +
'}';
}
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
public User() {
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
and
package com.LiveChat.QQView;
import com.LiveChat.InformationValidation.LoginAndRegistered;
import java.io.IOException;
import java.util.Scanner;
public class View {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Show();
}
//显示主菜单
private static void Show() throws IOException, ClassNotFoundException {
boolean flag = true;
int count = 1;
int n = 0;
Scanner scanner = new Scanner(System.in);
LoginAndRegistered userClientLolgin = new LoginAndRegistered();
//如果没有输入9那么就一直是下面这个页面
while (flag) {
if (count > 0) {
System.out.println("===============欢迎登录网络通信系统===============");
System.out.println("\t\t\t\t1.登录系统\t\t\t\t");
System.out.println("\t\t\t\t9.退出系统\t\t\t\t");
System.out.print("请输入您的选择:");
n = scanner.nextInt();
count--;
} else {
if (n == 9) {
return;
} else if (n == 1) {
//否则就展示登录界面
System.out.print("请输入用户名:");
String name = scanner.next();
System.out.print("请输入密码:");
String passwd = scanner.next();
//然后判断账号密码是否正确,正确则跳转另外一个页面
//根据发送给服务端的对象User,来验证该用户是否合法
//如果合法服务端就会返回一个值,说明该用户合法
//然后同时启动线程,让客户端一直输出服务端的信息
//即需要一直socket.getinputstream,只要服务端有东西发过来,客户端就接受
if (userClientLolgin.cheackLogin(name,passwd)) {
System.out.println("=================欢迎 " + name + " 用户=================");
//进入另外一个界面
boolean b=true;
System.out.println("==============网络通信系统二级菜单(用户:" + name + ")===============");
while (b) {
System.out.println();
System.out.println("\t\t1.显示在线用户列表");
System.out.println("\t\t2.群发消息");
System.out.println("\t\t3.私聊消息");
System.out.println("\t\t4.发送文件");
System.out.println("\t\t9.退出系统");
System.out.print("\t\t请输入您的选择:");
int change = scanner.nextInt();
System.out.println();
switch (change) {
case 1:
userClientLolgin.ShowUser();
//(1)类的对象可以调用非静态方法
//(2)而如果你所在的方法是静态的类名、方法名只能调用静态方法
break;
case 2:
System.out.println("=====群发消息=====");
break;
case 3:
System.out.println("=====私发消息=====");
break;
case 4:
System.out.println("=====发送文件=====");
break;
case 9:
System.out.println("成功退出系统!");
return;
default:
System.out.println("\t\t\t==========请重新选择!==========");
}
System.out.println();
//b=false;
}
return;
} else {
System.out.println("您的账号或密码错误,请重新输入!");
}
} else {
System.out.println("请输入1或9!");
count++;
}
}
}
}
}
服务端代码
package com.LiveChat.QQCommon;
import java.io.Serializable;
public class Message implements Serializable {
private static final long seriaVersiononID = 1L;
private String sender;//发送者
private String getter;//接收者
private String content;//消息内容
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
private String sendTime;//发送时间
private String mesType;//消息的类型
}
and
package com.LiveChat.QQCommon;
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED="1";//表示登录成功
String MESSAGE_LOGIN_FAIL="2";//表示登录失败
String MESSAGE_COMM_MES="3";//表示普通信息
String MESSAGE_GET_ONLINE_FRIEND="4";//要求返回在线用户列表
String MESSAGE_RET_ONLINE_FRIEND="5";//要求返回在线用户列表
String MESSAGE_CLIENT_EXIT="6";//要求返回在线用户列表
}
and
package com.LiveChat.QQCommon;
import java.io.Serializable;
public class User implements Serializable {
private String userId;//用户名
private String passwd;//密码
private static final long seriaVersiononID = 1L;//增加兼容性
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", passwd='" + passwd + '\'' +
'}';
}
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
public User() {
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
and
package com.LiveChat.QQServer;
import com.LiveChat.QQCommon.MessageType;
import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**
* @author TMJIE5200
*/
public class ChatsystemServer {
private ServerSocket ss=null;
private static HashMap<String,User> validUsers=new HashMap<>();
static {//在静态代码块中初始化 valiUser
validUsers.put("张三",new User("001","123456"));
validUsers.put("李四",new User("002","123456"));
validUsers.put("王二",new User("003","123456"));
validUsers.put("赵武",new User("004","123456"));
validUsers.put("王青",new User("005","123456"));
}
//验证用户是否有效的方法
public boolean cheackUser(String userId,String passwd){
User user = validUsers.get(userId);
if(user==null){
return false;
}
if(!user.getPasswd().equals(passwd)){
return false;
}
return true;
}
public ChatsystemServer() throws IOException, ClassNotFoundException {
try {
System.out.println("服务端在9999端口监听!!");
ss = new ServerSocket(9999);
//监听不是说监听到一个对象就结束了
//当与某个客户端建立连接后继续监听
while (true) {
Socket socket = ss.accept();
//通过socket读取客户端发来的User对象
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//我们清楚的知道第一次发过来的应该是一个User对象,我们直接转,后面就可以调用该对象的方法了
User user =(User) objectInputStream.readObject();
//这里本来是应该与数据库来比对的,因为没有学习,所以我们规定死
Message message=new Message();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
if(cheackUser(user.getUserId(), user.getPasswd())){
//登陆成功,发送Message对象
//我们的Message对象放在外面因为不管是成功还是失败你都要返回一个Message对象
//同样ObjectOutputStream也放在外面,减少冗余
System.out.println("用户: {"+user.getUserId()+"} 已上线!");
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
objectOutputStream.writeObject(message);
//创建一个线程和客户端保持通信
ServerConnectServerThreac serverThreac = new ServerConnectServerThreac(socket, user.getUserId());
serverThreac.start();
ManageServerThreac.addClientConnectServerThread(user.getUserId(),serverThreac);
}else {
System.out.println("用户: {"+user.getUserId()+"} 登录失败!");
message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
objectOutputStream.writeObject(message);
socket.close();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ss.close();
}
}
}
and
package com.LiveChat.QQServer;
import java.util.HashMap;
import java.util.Map;
public class ManageServerThreac {//把多个线程放到该集合中 K=string类型:用户ID,Value=ClientConnectServerThreac:线程
private static HashMap<String, ServerConnectServerThreac> hashMap=new HashMap<>();
//将某个线程加入到集合中
public static void addClientConnectServerThread(String userId, ServerConnectServerThreac serverConnectServerThreac){
hashMap.put(userId,serverConnectServerThreac);
}
//将某个线程取出来
public static ServerConnectServerThreac getClientConnectServerThread(String userId){
return hashMap.get(userId);
}
//返回用户列表
public static String getOnlineUser(){
String users="";
for (Map.Entry<String, ServerConnectServerThreac> s : hashMap.entrySet()) {
users+=s.getKey().toString()+" ";
}
//遍历结合
return users;
}
}
and
package com.LiveChat.QQServer;
import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.MessageType;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class ServerConnectServerThreac extends Thread{
//线程类
private Socket socket;
private String userId;
public ServerConnectServerThreac(Socket socket,String userId){
this.socket=socket;
this.userId=userId;
}
@Override
public void run() {
//为了让客户端和服务端一直进行通信,我们在后台保持两者的联通
//即 客户端一直等待服务端发来的数据,只要有东西就接收
while (true){
try {
System.out.println("服务端线程,正在等待客户端的数据信息。。。");
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//只要有个socket发送请求,我们就接受一个
//那么socket发送什么请求呢,就是在登录的时候,才建立连接的,因此传送的message就是一个user对象
//我们知道这里返回的一定是一个Message对象,因此我们直接强转
Message o = (Message)objectInputStream.readObject();
//判断类型,然后做相应的业务处理
//Message里面对应的
if(o.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
System.out.println(o.getSender()+"在线用户列表");
String onlineuser=ManageServerThreac.getOnlineUser();
//构建一个Message返回给客户端
Message message=new Message();
message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message.setContent(onlineuser);
message.setGetter(o.getSender());
//返回给客户端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(message);
}else {
System.out.println("其他类型,暂时不处理!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
}
and
package com.LiveChat.StartServer;
import com.LiveChat.QQServer.ChatsystemServer;
import java.io.IOException;
public class Start {
public static void main(String[] args) throws IOException, ClassNotFoundException {
new ChatsystemServer();
}
}
END