前面实现了服务器与多个客户端之间的通信,我们真正的目的是要实现多个客户端之间的通信
使用TCP协议实现的方案:
客户端的数据包通过服务器中转,发送到另一个客户端
实现步骤:
1、对象序列化:(对象需要在网络上传输)
数据包(发送消息者,接收消息者,消息类型(登录、发送),发送的消息内容)
2.定义消息类型:
消息类型:登录、发送
有兴趣的,可以自己扩展其他消息类型,如注册、退出等消息类型
3、服务器:
启动服务器、用动态序列容器Vector保存在服务器中保存的所有线程的任务对象、每个对象创建一个线程来处理
4、客户端:
启动客户端、发送登录信息、接收消息的线程、处理消息发送的线程
代码实现:
1、对象序列化:
package com.lemon.communication;
import java.io.Serializable;
/**
* 消息包
* @author lemonsun
*/
public class Message implements Serializable {
private String from; //发送者
private String to; //接收者
private int type; //消息类型
private String info;//消息包
public Message() {
}
public Message(String from, String to, int type, String info) {
this.from = from;
this.to = to;
this.type = type;
this.info = info;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
2、定义消息类型
package com.lemon.communication;
/**
* @author lemonsun
*/
public class MessageType {
public static final int TYPE_LOGIN = 0x1; //登录消息类型
public static final int TYPE_SEND = 0x2; //发送消息的类型
}
3、服务器
package com.lemon.communication;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 服务器:
* @author lemonsun
*/
public class Server {
public static void main(String[] args) {
//保存客户端处理的线程
Vector<UserThread> vector = new Vector <>();
//固定大小的线程池,用来处理不同客户端
ExecutorService es = Executors.newFixedThreadPool(5);
//创建服务器端的Socket
try {
ServerSocket server = new ServerSocket(8888);
System.out.println("服务器以启动,正在等待连接...");
while(true){
//接受客户端的Socket,若没有,阻塞在这
Socket socket = server.accept();
//每来一个客户端,创建一个线程处理它
UserThread user = new UserThread(socket,vector);
es.execute(user); //开启线程
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 客户端处理线程:
*/
class UserThread implements Runnable{
private String name; //客户端的用户名称,唯一
private Socket socket;
private Vector<UserThread> vector; //客户端处理线程的集合
private ObjectInputStream oIn; //输入流
private ObjectOutputStream oOut; //输出流
private boolean flag = true; //标记
public UserThread(Socket socket, Vector<UserThread> vector) {
this.socket = socket;
this.vector = vector;
vector.add(this); //把当前线程也加入vector中
}
@Override
public void run() {
try {
//1、构造输入输出流
System.out.println("客户端:" + socket.getInetAddress().getHostAddress() + "已连接!");
oIn = new ObjectInputStream(socket.getInputStream());
oOut = new ObjectOutputStream((socket.getOutputStream()));
//2、循环读取
while(flag){
//读取消息对象
Message message = (Message)oIn.readObject();
//获取消息类型,登录还是发送消息
int type = message.getType();
//3、判断
switch (type){
//如果是发送消息
case MessageType.TYPE_SEND:
String to = message.getTo();//发送给谁
UserThread ut;
//遍历vector,找到接收信息的客户端
int size = vector.size();
for (int i = 0; i < size; i++) {
ut = vector.get(i);
//如果名字相同,且不是自己,就把信息发给它
if(to.equals(ut.name) && ut != this){
ut.oOut.writeObject(message); //发送消息对象
}
}
break;
//如果是登录
case MessageType.TYPE_LOGIN:
name = message.getFrom();//获取用户名
message.setInfo("欢迎您!");//设置登录成功信息
oOut.writeObject(message);
break;
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4、客户端:
package com.lemon.communication;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 客户端:
*
* @author lemonsun
*/
public class Client {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//单线程池
ExecutorService es = Executors.newSingleThreadExecutor();
try {
//创建客户端
Socket socket = new Socket("localhost", 8888);
System.out.println("服务器连接成功!");
//构建输出输入流
ObjectOutputStream oOut = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream oIn = new ObjectInputStream(socket.getInputStream());
//1、客户端登录处理
//向服务器发送登录信息(名字和消息类型)
System.out.println("请输入名称:");
String name = input.nextLine();
//登录时,只自己的名字和消息类型为登录
Message message = new Message(name, null, MessageType.TYPE_LOGIN, null);
//发送给服务器
oOut.writeObject(message);
//服务器返回 欢迎信息
message = (Message) oIn.readObject();
//打印服务器返回的信息+当前客户端的名字
System.out.println(message.getInfo() + message.getFrom());
//2、启动读取消息的线程
es.execute(new readInfoThread(oIn)); //读取线程完成
//3、发送消息
//使用主线程来发送消息
boolean flag = true;
//循环
while(flag){
//创建对象
message = new Message();
//发给谁
System.out.println("To:");
message.setTo(input.nextLine());
//谁发的,从自己这发
message.setFrom(name);
//类型 发送消息
message.setType(MessageType.TYPE_SEND);
//发送的内容
System.out.println("Info:");
message.setInfo(input.nextLine());
/*----到此需要发送的消息 对象 封装完毕----*/
//发送给服务器
oOut.writeObject(message);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/**
* 读取其他客户端发来消息
*/
class readInfoThread implements Runnable {
private ObjectInputStream oIn; //输入流 用来读操作
private boolean flag = true; //标记
public readInfoThread(ObjectInputStream oIn) {
this.oIn = oIn;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
//循环 不断读取消息
while (flag) {
//读取信息
Message message = (Message) oIn.readObject();
//输出用户名+内容
System.out.println("[" + message.getFrom() + "]对我说:" + message.getInfo());
}
//没有数据就关闭
if(oIn != null){
oIn.close();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
开启服务器,开启几个客户端,就可以相互通信啦