欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-19 10:51:30
...

一、本章内容

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

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

二、最终效果

这次直接先最终的效果,这样阅读代码会更有目的性和容易理解。
服务器输出
基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信
三个测试客户端
基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信
基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信
可能看起来不太直观,这里解释一下。首先,开启服务端,然后张三、李四、王五三个客户端登录,然后张三给王五发了一条私聊,说李四是内奸,因为是私发,所以第一条信息李四没有收到。再然后张三又发了一条群发类型的消息。这时候大家都收到了。

三、代码部分

项目目录
基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信
三个客户端代码
只贴出一个(张三的),其他基本一模一样的,就是用户名不同而已

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被回收掉。后期再完善吧,目前能实现基本功能先。网上关于客户端与客户端之间的通信的资料其实并不多,希望能给你带来一点点的参考意义。
本人技术有限,如果有什么错误之处,欢迎斧正。谢谢。

相关标签: java socket