基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信

一、本章内容

在第一章的代码基础上进行改造来实现第一章所提到的需求。
附上第一章链接:
基于TCP/IP通信协议的简易聊天工具(一) - - 理解思路与基础代码

在开始正文之前还是说一下,本章依旧是在java + Eclipse的环境下编写代码,本来想着直接上Android的,但是这样就不是很利于理解,所以下篇再单独上Android。

二、最终效果

这次直接先最终的效果,这样阅读代码会更有目的性和容易理解。
服务器输出
服务器
三个测试客户端
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
可能看起来不太直观,这里解释一下。首先,开启服务端,然后张三、李四、王五三个客户端登录,然后张三给王五发了一条私聊,说李四是内奸,因为是私发,所以第一条信息李四没有收到。再然后张三又发了一条群发类型的消息。这时候大家都收到了。

三、代码部分

项目目录
在这里插入图片描述
三个客户端代码
只贴出一个(张三的),其他基本一模一样的,就是用户名不同而已

package client;

import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;

import objpackage.MsgData;
import objpackage.PackageObj;
import objpackage.User;

public class Client_张三 {
    
    
	public static void main(String[] args) {
    
    
		try {
    
    
			Socket socket = new Socket("127.0.0.1", 9090);
//			登录测试包
			User user = new User();
			user.setUserID("张三");
			PackageObj packageObj = login(user);
//			打开输出流,准备发送数据
			OutputStream os = socket.getOutputStream();
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
//			写入数据(对象)并发送
			objectOutputStream.writeObject(packageObj);
			objectOutputStream.flush();
//			释放资源
			socket.shutdownOutput();
//			循环监听服务端过来的消息
			while (true) {
    
    
				InputStream is = socket.getInputStream();
					System.out.println("张三持续接受消息");
					ObjectInputStream obj = new ObjectInputStream(is);
					MsgData msgData_server = (MsgData) obj.readObject();
					System.out.println(msgData_server);
			}
			
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}
	// 登录请求
	public static PackageObj login(User user) {
    
    
		user.setTimestmp(System.currentTimeMillis());
		PackageObj packageObj = new PackageObj(0,user);
		return packageObj;
	}
}

客户端发送消息类

package client;

import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;

import objpackage.MsgData;
import objpackage.PackageObj;

public class ClientSendMsg {
    
    
	public static void main(String[] args) {
    
    
		try {
    
    
			Socket socket = new Socket("127.0.0.1", 9090);
//			信息测试包
			String msg = "李四是内奸"; // 信息内容
			String byUser = "王五"; // 发送者
			String toUser = "张三"; // 接收者
			int msgType = 1;  // 0群发 1私聊 
			PackageObj packageObj = sendMsg(msg,  byUser, toUser,msgType);
			
//			打开输出流,准备发送数据
			OutputStream os = socket.getOutputStream();
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
//			写入数据(对象)并发送
			objectOutputStream.writeObject(packageObj);
			objectOutputStream.flush();
//			释放资源
			socket.shutdownOutput();
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}
	
	public static PackageObj sendMsg(String msg, String byUser, String toUser, int msgType) {
    
    
		long timestmp = System.currentTimeMillis(); // 时间戳
//		实例化自定义对象
		MsgData msgData = new MsgData(msg, msgType ,byUser, toUser, timestmp);
		PackageObj packageObj = new PackageObj(1, msgData);
		return packageObj;
	}
}

服务器主类

package server;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import objpackage.User;

// 服务器主类
public class ServerMain {
    
    
	static List<User> userList; // 创建一个列表保存已经连接的客户端
	static boolean flag = false;
	public static void main(String[] args) {
    
    
		try {
    
    
			userList = new ArrayList<User>(); // 实例化
			// 实例化ServerSocket,设置监听的端口号
			ServerSocket serverSocket = new ServerSocket(9090);
			Socket socket = null;
			System.out.println("****** 服务端启动 ******");
			while (true) {
    
    
				// 阻塞监听客户端的接入,一旦接入,将继续向下执行
				socket = serverSocket.accept();
				ServerThread serverThread = new ServerThread(userList, socket);
				serverThread.start();
			}
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}
	
	// 回调函数
	public static void callback() {
    
    
		for (int i=0; i<userList.size(); i++) {
    
    
			User user = userList.get(i);
			System.out.println("当前在线:" 
		+ user.getUserID() + "socket:--->" + user.getSocket() + "登录时间--->" + user.getTimestmp());
		}
	} 
}

服务器线程类

package server;

import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;

import objpackage.MsgData;
import objpackage.PackageObj;
import objpackage.User;

// 接收信息线程类,用于转发服务器收到的消息
public class ServerThread extends Thread {
    
    
	private List<User> userList;
	private User user;
	private Socket socket;
	public ServerThread(List<User> userList, Socket socket) {
    
    
		this.userList = userList;
		this.socket = socket;
	}

	public void run() {
    
    
		try {
    
    
			// 实例化输入字节流(准备写入客户端数据阶段)
			InputStream is = socket.getInputStream();
			// 将输入字节流转换为对象输入流
			ObjectInputStream obj = new ObjectInputStream(is);
			// 读取PackageObj对象
			PackageObj packageObj = (PackageObj) obj.readObject();
			// 取出请求类型并判断
			int objType = packageObj.getPagType();
			if (objType == 0) {
    
    
				 System.out.println("收到登录包");
				User user = (User) packageObj.getObject();
				user.setSocket(socket); // 为该用户绑定一个socket
				this.userList.add(user); // 添加进列表
				ServerMain.callback(); // 调用主线程的回调方法
			} 
			else if (objType == 1) {
    
    
				System.out.println("收到信息包");
				MsgData msgData = (MsgData) packageObj.getObject();
				int msgType = msgData.getMsgType();
				String byName = msgData.getByUser();
				String toName = msgData.getToUser();
				// 私聊消息
				if (msgType == 1) {
    
    
					for (int i = 0; i < this.userList.size(); i++) {
    
    
						User user = this.userList.get(i);
						if (user.getUserID().equals(toName)) {
    
    
							OutputStream os = user.getSocket()
									.getOutputStream();
							ObjectOutputStream objectOutputStream = new ObjectOutputStream(
									os);
							// 写入数据(对象)并发送
							objectOutputStream.writeObject(msgData);
							objectOutputStream.flush();
							System.out.println(msgData.toString() + "---发送成功");
							break;
						}
						// 指定用户没上线
						if (i == this.userList.size() - 1) {
    
    
							System.out.println(toName + "没有上线---发送失败");
						}
					}
				} 
				// 群发消息
				else {
    
    
//					遍历在线的用户,全部发送
					for (int i = 0; i < this.userList.size(); i++) {
    
    
						User user = this.userList.get(i);
//						不给自己发送
						if (user.getUserID().equals(byName)) {
    
    continue;}
						OutputStream os = user.getSocket().getOutputStream();
						ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
						// 写入数据(对象)并发送
						objectOutputStream.writeObject(msgData);
						objectOutputStream.flush();
						System.out.println(msgData.toString() + "---发送成功");
					}
				}
			} else {
    
    
				System.out.println("数据有误");
			}
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}

	public Socket getSocket() {
    
    
		return socket;
	}
	public void setSocket(Socket socket) {
    
    
		this.socket = socket;
	}
	public void setUserList(List<User> userList) {
    
    
		this.userList = userList;
	}
	public List<User> getUserList() {
    
    
		return userList;
	}
}

== 三个对象类==
user

package objpackage;

import java.io.Serializable;
import java.net.Socket;

// 继承Serializable接口,对对象进行序列化和反序列化
public class User  implements Serializable{
    
    
	// 自动生成序列号
	private static final long serialVersionUID = -4456125616575782442L; 
	private String userID; // 用户ID/用户名
	private Socket socket;  // 登录时的socket
	private long timestmp;  // 登陆时间
	public User(String userID) {
    
    
		this.userID = userID;
	}
	public User() {
    
    
	}
	public String getUserID() {
    
    
		return userID;
	}
	public void setUserID(String userID) {
    
    
		this.userID = userID;
	}
	public void setSocket(Socket socket) {
    
    
		this.socket = socket;
	}
	public Socket getSocket() {
    
    
		return socket;
	}
	public long getTimestmp() {
    
    
		return timestmp;
	}
	public void setTimestmp(long timestmp) {
    
    
		this.timestmp = timestmp;
	}
}

MagData

package objpackage;

import java.io.Serializable;

public class MsgData implements Serializable{
    
    
	private static final long serialVersionUID = 7303068744021046271L;
	private String msg; // 具体传递的消息
	private String byUser; // 发送消息用户
	private String toUser; // 接收消息用户
	private long timestmp; // 时间戳
	private int msgType; // 类型(0群发, 1私聊)
	public MsgData(String msg, int msgType, String byUser, String toUser, long timestmp) {
    
    
		this.msg = msg;
		this.byUser = byUser;
		this.toUser = toUser;
		this.timestmp = timestmp;
		this.msgType = msgType;
	}
	MsgData(String msg, int msgType, String byUser, long timestmp){
    
    
		this.msg = msg;
		this.byUser = byUser;
		this.timestmp = timestmp;
		this.msgType = msgType;
	}
	public MsgData() {
    
    
	}
	public String getMsg() {
    
    
		return msg;
	}
	public void setMsg(String msg) {
    
    
		this.msg = msg;
	}
	public String getByUser() {
    
    
		return byUser;
	}
	public void setByUser(String byUser) {
    
    
		this.byUser = byUser;
	}
	public String getToUser() {
    
    
		return toUser;
	}
	public void setToUser(String toUser) {
    
    
		this.toUser = toUser;
	}
	public void setTimestmp(long timestmp) {
    
    
		this.timestmp = timestmp;
	}
	public long getTimestmp() {
    
    
		return timestmp;
	}
	public void setMsgType(int msgType) {
    
    
		this.msgType = msgType;
	}
	public int getMsgType() {
    
    
		return msgType;
	}
	@Override
	public String toString() {
    
    
		String str0 = "这是一条群发消息---" + this.byUser + "说" + this.msg + "---" + this.timestmp;
		String str1  = "这是一条私聊消息---" + this.byUser + "对" + this.toUser + "说:" + this.msg + "---" + this.timestmp;
		if (this.msgType == 0) {
    
    
			return str0;
		}else {
    
    
			return str1;
		}
	}
}

PackageObj

package objpackage;

import java.io.Serializable;

public class PackageObj implements Serializable{
    
    
	private static final long serialVersionUID = 7438720444784119922L;
	private int pagType; // 请求类型  0登录 1消息
	private Object object; // 请求的对象
	public PackageObj(int pagType, Object object) {
    
    
		this.pagType = pagType;
		this.object = object;
	}
	public void setObject(Object object) {
    
    
		this.object = object;
	}
	public Object getObject() {
    
    
		return object;
	}
	public void setPagType(int pagType) {
    
    
		this.pagType = pagType;
	}
	public int getPagType() {
    
    
		return pagType;
	}
}

四、总结

还有一些隐藏的bug,但是不影响我们去学习这种思路,其实应该做了个心跳包的类,防止socket被回收掉。后期再完善吧,目前能实现基本功能先。网上关于客户端与客户端之间的通信的资料其实并不多,希望能给你带来一点点的参考意义。
本人技术有限,如果有什么错误之处,欢迎斧正。谢谢。

猜你喜欢

转载自blog.csdn.net/weixin_44702572/article/details/106196219