一、概述
上一期我们了解了如何进行双端通信,本期更近一步,我们通过socket模型实现多客户端通信。
二、单客户端的缺点
在单个客户端接入时,我们直接通过Socket循环监听去捕捉客户端于服务端的连接。因此一个服务端只能服务一个客户端,并且如果我们想实现信息交流,并向客户端发送消息,我们只能从服务端发送,这样服务端的功能就会被模糊掉。因此我们需要一个新的模型,将服务端作为客户端的中转站并且需要使多个客户端进行交流,那么我们就需要用到多客户端连接。
三、多客户端连接
1、结构:我们的目标是将服务端作为信息中转站,并能够从一个客户端向另一个客户端发送消息因此接下来我们将实现服务端连接多个客户端
2、功能实现:
(1)、服务端创建
在这里服务端与上期的主要区别是,多客户端连接时,我们需要一个数据结构来保存连接到该服务端的客户端信息,由于客户端连接到服务端是以socket形式连接的,因此我们需要使用一个socket结构的数组来保存
public class ServerHost {
//创建一个Socket结构的数组用于保存连接到该服务端的客户端
ArrayList<Socket> socketArrayList = new ArrayList<>();
//服务端初始化
ServerSocket serverSocket;
int port = 25000;
//服务端启动
public void serverStart(){
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
}
为了能够实时监听客户端的连接以及客户端消息发送的情况,我们需要在服务器启动时初始化两个线程,分别监听客户端连接情况以及消息收发情况
客户端连接监听器
public class ClientListenerThread extends Thread{
//这里通过数组来保存连接的客户端
ArrayList<Socket> socketArrayList = new ArrayList<>();
ServerSocket serverSocket;
//构造函数,用于接收服务端的数据
public ClientListenerThread(ArrayList<Socket> socketArrayList , ServerSocket serverSocket){
this.socketArrayList = socketArrayList;
this.serverSocket = serverSocket;
}
@Override
public void run() {
//循环监听客户端的连接情况
while (true){
Socket accept = null;
try {
accept = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
//有客户端连接时将连接信息存入List中
socketArrayList.add(accept);
System.out.println("客户端连接成功" + accept.getRemoteSocketAddress());
System.out.println("客户端连接数量为:" + socketArrayList.size());
}
}
}
客户端消息监听器
public class ClientMsgListenerThread extends Thread{
ArrayList<Socket> socketArrayList = new ArrayList<>();
ServerSocket serverSocket;
public ClientMsgListenerThread(ArrayList<Socket> socketArrayList , ServerSocket serverSocket) {
this.socketArrayList = socketArrayList;
this.serverSocket = serverSocket;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//从客户端List中将连接到服务端的客户端取出,并接收消息
for (int i = 0; i < socketArrayList.size(); i++) {
try {
//通过InputStream;接收各个客户端的消息
Socket socket = socketArrayList.get(i);
InputStream in = socket.getInputStream();
int msg = in.read();
byte [] bytes = new byte[msg];
in.read(bytes);
String newMsg = new String(bytes);
System.out.println(newMsg);
t.messageDisplay(newMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上述两个线程在服务端启动时一并初始化
public void serverStart(){
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
ClientListenerThread clientListenerThread = new ClientListenerThread(socketArrayList , serverSocket);
clientListenerThread.start();
System.out.println("客户端连接监听器启动");
ClientMsgListenerThread clientMsgListenerThread = new ClientMsgListenerThread(socketArrayList , serverSocket);
clientMsgListenerThread.start();
System.out.println("客户端消息接收器启动");
}
(2)、客户端创建
要想通信,首先客户端需要连接到服务端
public class Client {
//设置连接信息
String serverHostLocation = "127.0.0.1";
int port = 25000;
Socket socket;
//连接到服务端
public Socket connect(){
try {
socket = new Socket(serverHostLocation , port);
return socket;
} catch (IOException e) {
throw new RuntimeException();
}
}
其次时客户端需要有收发消息的功能
public void sendMessage(String message){
OutputStream os = null;
try {
os = socket.getOutputStream();
String text = message;
byte b[] = text.getBytes();
os.write(b);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void reciveMessage() throws IOException{
InputStream in = socket.getInputStream();
System.out.println(in.read());
}
将客户端可视化,并将上述功能整合至界面中
public class Client extends JFrame {
String serverHostLocation = "127.0.0.1";
int port = 25000;
Socket socket;
JButton send;
JTextField textField;
int clientNum;
public void setWindows(){
this.setSize(800 , 400);
this.setTitle("client");
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(3);
this.setVisible(true);
}
public void layOut(){
//消息发送面板
JPanel southPanel = new JPanel();
southPanel.setPreferredSize(new Dimension(510 , 120));
textField = new JTextField(20);
textField.setPreferredSize(new Dimension(300 , 45));
southPanel.add(textField);
send = new JButton("发送");
send.setPreferredSize(new Dimension(80 , 30));
southPanel.add(send);
this.add(southPanel , BorderLayout.NORTH);
}
public void setClientNum(){
Random r = new Random();
clientNum = r.nextInt(500);
}
public void buttonAction(){
send.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String msg = clientNum + ":" + textField.getText();
sendMessage(msg);
}
});
}
public Socket connect(){
try {
socket = new Socket(serverHostLocation , port);
return socket;
} catch (IOException e) {
throw new RuntimeException();
}
}
public void sendMessage(String message){
OutputStream os = null;
try {
os = socket.getOutputStream();
String text = message;
byte b[] = text.getBytes();
os.write(b);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void reciveMessage() throws IOException{
InputStream in = socket.getInputStream();
System.out.println(in.read());
}
public void init(){
setWindows();
layOut();
connect();
setClientNum();
buttonAction();
try {
reciveMessage();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Client().init();
}
}
四、运行效果
首先启动服务端
接着启动客户端
这里如果要启动个客户端的话需要设置允许多个实例运行
启动多个客户端后
发送消息
五、总结
本章讲解了如何使多个客户端连接到服务端,但我们的服务端分类任然未实现,因此下期我们讲解如何进行客户端与服务端分离,并且实现服务端之间的消息收发