逻辑分析:
- 首先通过TCP通信创建一个客户端和一个服务端,服务端仅用来和客户端建立连接。
- 并且在服务端创建一个List集合,List集合用来记录已经连接的客户端
- 然后创建一个服务端线程(ServerReaderThread),并通过字节输入流(socket.getInputStream())读取客户端传递过来的消息,并读取List集合中的记录,把消息发送给每一个客户端。
- 创建一个客户端线程,用来不断的读取发送过来的数据。这样就实现了群里功能。
为什么要分别再创建一个服务端线程和客户端线程?
这是因为在TCP通信中,客户端和服务端建立的连接是一对一的,而我们可以让服务端仅用来建立连接,而操作部分交给线程来操作。线程和线程运行是不会相互有影响的,也就是说,当客户端和服务端建立连接后,数据操作部分就可以正常运行,而又不影响接下来其他的客户端来和服务端建立连接。
同理,因为客户端中的主线程是一直运行着的,所以就会一直收到其他客户端传递过来的消息,也就是说,我们读取其他客户端传递过来的消息也需要一直运行。因此这里也创建了一个客户端线程来实现。
然后当时我在想,为什么不把读取其他客户端传递消息的过程放在客户端的主线程里面,因为主线程也是一直运行着的,也可以一直读取其他客户端传递过来的消息。
后来想了一下,因为主线程一直运行是用来发送消息的,需要用户手动来输入,所以我们不能放在主线程里面。
客户端:
package WebTest5;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 目标:完成TCP通信快速入门-客户端开发:实现多个客户端像服务端进行通信
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的链接
Socket socket = new Socket("127.0.0.1", 8888);
//创建一个独立的线程,负责随时从socket中接收服务端发送过来的消息
new ClentReaderThread(socket).start();
//2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
OutputStream os = socket.getOutputStream();
//3、把低级的字节输出流,包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入内容:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("退出成功");
dos.close();
socket.close();//释放连接资源
break;
}
//4、开始写数据出去
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端:
package WebTest5;
import WebTest5.ServerReaderThread;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 目标:完成TCP通信快速入门-服务端开发:实现多个客户端像服务端进行通信
*/
public class Server {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("========服务端启动成功=======");
//1、创建一个ServerSocket的对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
onLineSockets.add(socket);
System.out.println("有人上线了"+socket.getRemoteSocketAddress());
//3、把这个客户端对应的socket通信管道,交给一个独立的线程进行处理
new ServerReaderThread(socket).start();
}
}
}
ServerReaderThread:
package WebTest5;
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
//把这个消息分发给全部客户端进行接收
senMsgAll(msg);
} catch (IOException e) {
System.out.println("有人下线了"+socket.getRemoteSocketAddress());
Server.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//把这个消息分发给全部客户端进行接收
private void senMsgAll(String msg) throws Exception {
//发送给全部在线的socket管道接收
for (Socket onLineSocket : Server.onLineSockets) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
ClentReaderThread:
package WebTest5;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ClentReaderThread extends Thread{
private Socket socket;
public ClentReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (IOException e) {
System.out.println("有人下线了"+socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}