基于TCP/IP通信协议的简易聊天工具(二) - -客户端与客户端间的通信
程序员文章站
2022-05-19 10:51:30
...
一、本章内容
在第一章的代码基础上进行改造来实现第一章所提到的需求。
附上第一章链接:
基于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被回收掉。后期再完善吧,目前能实现基本功能先。网上关于客户端与客户端之间的通信的资料其实并不多,希望能给你带来一点点的参考意义。
本人技术有限,如果有什么错误之处,欢迎斧正。谢谢。