使用TCP实现一个简易版的聊天系统,首先说一下思路
1.在服务器开多个线程来对应客户端的连接,服务器只做信息的中转工作画个图来理解
在做一对一客户端服务器的消息交换时,只需要通过socket来不断的交换信息即可,但是现在是实现客户端和客户端之间,所以我们所发送的消息不能是简单的字符串,封装一个数据的集合来表示数据,服务器端要识别数据的一些信息,所以必须要有以下几个属性:来向,去向,数据类型,数据内容。这个数据信息在传送的过程中是以对象流来传递的,如果想要真正实现局域网的功能,对象流是不行的,可以以json或者xml封装,这里只是为了练习不过多要求。
2.当客户端发送信息流到服务器端后,服务器端解析信息的属性,根据信息的类型和去向,在服务器端的多个线程中去找与去向对应的线程,然后通过这一连接将数据返回去,当然这个就要求服务器端对多个线程进行管理,我用到了集合Vector。客户端主要是一个死循环,在一直不停的发消息,同时为了能读到服务器端发来的消息,另起了一个线程不停的去接收服务器端的数据。
下面将代码贴上
第一个类,MessageType
final class MessageType {
public static final int TYPE_LOGIN = 0X1;
public static final int TYPE_SEND = 0X2;
}
这个类主要是用来区分后面的类Message,也就是我们所说的信息的类型,这里我们设定了两种类型,一种是登录信息,一种是发送信息,服务器端可以通过这个属性来区分信息的类型。
下来是Message类
class Message implements Serializable {
private String from;
private String to;
private String info;
private int message;
public Message(String from, String to, String info, int message) {
this.from = from;
this.to = to;
this.info = info;
this.message = message;
}
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", info='" + info + '\'' +
", message=" + message +
'}';
}
public Message() {}
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 String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public int getMessage() {
return message;
}
public void setMessage(int message) {
this.message = message;
}
}
根据上面我们的分析,也就是信息所要携带的四种属性:来向,去向,信息内容,信息类型,再就是一些getset方法。
下面是服务器端
public class Server {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Vector<UserThread> userThreads = new Vector<>(5);
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(6666);
System.out.println("服务器启动成功");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("客户端:"+socket.getRemoteSocketAddress()+"连接成功");
executorService.execute(new UserThread(socket, userThreads));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class UserThread implements Runnable {
private String name;
private Message message;
private Socket socket;
private Vector<UserThread> userThreads;
private ObjectInputStream objectInputStream = null;
private ObjectOutputStream objectOutputStream = null;
public UserThread( Socket socket,Vector<UserThread> userThreads) {
this.socket = socket;
this.userThreads = userThreads;
userThreads.add(this);
}
@Override
public void run() {
try {
objectInputStream = new ObjectInputStream(socket.getInputStream());
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
while(true) {
try {
message =(Message) objectInputStream.readObject();
System.out.println(message);
} catch (ClassNotFoundException|IOException e) {
e.printStackTrace();
}
if(message.getMessage() == MessageType.TYPE_LOGIN) {
this.name = message.getFrom();
Message message1 = new Message("服务器", this.message.getFrom(), "登录成功", MessageType.TYPE_LOGIN);
try {
objectOutputStream.writeObject(message1);
objectOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
if(message.getMessage() == MessageType.TYPE_SEND) {
String to = message.getTo();
Iterator<UserThread> iterator = userThreads.iterator();
while(iterator.hasNext()) {
UserThread next = iterator.next();
if(next.getName().equals(to)) {
try {
next.getObjectOutputStream().writeObject(message);
next.getObjectOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}
}
public Vector<UserThread> getUserThreads() {
return userThreads;
}
public void setUserThreads(Vector<UserThread> userThreads) {
this.userThreads = userThreads;
}
public ObjectInputStream getObjectInputStream() {
return objectInputStream;
}
public void setObjectInputStream(ObjectInputStream objectInputStream) {
this.objectInputStream = objectInputStream;
}
public ObjectOutputStream getObjectOutputStream() {
return objectOutputStream;
}
public void setObjectOutputStream(ObjectOutputStream objectOutputStream) {
this.objectOutputStream = objectOutputStream;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
这块贴上了两个类,一个是服务器类,一个是服务器中用来对应客户端的线程类。
先来说服务器类:这块我用到了线程池来创建线程,给了5个线程,所以这个服务器端可以接受的客户端是5个,然后就是一个accept阻塞等待连接,直接给线程池提交任务。
再来说UserThread类:这个类是真正实现逻辑的地方,说一下大体思路,就是不断的读信息,如果读到的信息类型(MessageType)是登录信息,那么就证明第一次连接建立,如果读到的是发送信息,那么就根据信息的去向(to),在线程集合(Vector)中去找对应的线程,然后将Message转发给对应的线程。
下面是客户端类:
public class Clint {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Scanner scanner = new Scanner(System.in);
Socket socket = null;
String name;
ObjectOutputStream objectOutputStream = null;
try {
System.out.println("请输入名称");
name = scanner.nextLine();
socket = new Socket("192.168.178.1",6666);
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
Message message = new Message();
//启动一个线程用来读消息
executorService.execute(new ReadThread(socket,name));
//第一次登陆
message.setFrom(name);
message.setInfo("我要登陆");
message.setMessage(MessageType.TYPE_LOGIN);
message.setTo(null);
objectOutputStream.writeObject(message);
objectOutputStream.flush();
//后续发消息
while(true) {
/**
* ????用原来的message就会一直发送旧的信息包,为什么
*/
Message MessageSend = new Message();
MessageSend.setFrom(name);
MessageSend.setMessage(MessageType.TYPE_SEND);
System.out.println("请输入目标名称");
MessageSend.setTo(scanner.nextLine());
System.out.println("请输入消息:");
MessageSend.setInfo(scanner.nextLine());
objectOutputStream.writeObject(MessageSend);
objectOutputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
objectOutputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class ReadThread implements Runnable {
Socket socket;
String name;
public ReadThread(Socket socket,String name) {
this.socket = socket;
this.name = name;
}
@Override
public void run() {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(socket.getInputStream());
while(true) {
try {
Message message = (Message) objectInputStream.readObject();
if(message.getFrom() != name && message.getInfo()!=null) {
System.out.println("["+message.getFrom()+"] : "+"["+message.getInfo()+"]" );
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException|ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端类实现逻辑很简单,主线程不断的给服务器端发消息, 另起一个线程不断读服务器发来的消息。
下来测试一下:
1.同时启动三个客户端和一个服务器端
2.输入客户端的名称,使之登录成功(这块可以继续加上功能:密码验证等)
服务器端一直在监听消息,现在状态是三个客户端都成功登录
2.现在用c给b和a分别发消息
功能是肯定实现了,不做过多的测试。
总结:这次做这个小练习有几点需要注意:
1.通过socket获取信息的时候不能放在线程的构造中,这样会阻塞服务器端。
2.通过对象流是不能完成局域网的聊天功能的,需要用josn和xml封装
3.代码中的问号,后续解决