多线程服务器
原理
- ServerSocket+Thread
-
总框架
-
服务器利用多线程并发实现服务器同时处理多个客户端的效果
服务器和单一客户机交互图
-
客户端与服务器通过套接字输入输出流传递信息,服务器将接收的信息再传递给其他已登录客户端
服务端代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
//服务器代码
public class Server {
private HashSet<Socket> allSocket;//创建保存所有客户端的集合
private ServerSocket server;//创建服务器套接字
//创建构造方法
public Server() {
try {
server=new ServerSocket(5214);//实例化服务器套接字
System.out.println("服务器就绪···");
} catch (IOException e) {
e.printStackTrace();
}
allSocket=new HashSet<>();//实例化HashSet
}
//服务器启动方法
public void startServer() throws IOException {
//循环获取客户端连接Socket
while(true) {
Socket socket=server.accept();//获取连接
System.out.println("用户进入聊天室");
allSocket.add(socket);//将对象放入集合
ServerThread t=new ServerThread(socket);//创建线程
t.start();//开始线程
}
}
//线程内部类,读取用户发送的消息再将信息发送给所有客户端
private class ServerThread extends Thread{
Socket socket;//声明客户端套接字
//线程构造方法
public ServerThread(Socket socket) {
this.socket=socket;
}
public void run() {
//读取用户发送的消息
BufferedReader br=null;//声明字符输入流
try {
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取客户端输入字符流
while(true) {
String str=br.readLine();
if(str.contains("EXIT")) {//如果客户端输入的字符串("%EXIT%:用户名%")中有“EXIT”,则客户端退出聊天室
String temp=str.split(":")[1]+"退出聊天室";
sendMessageToAllClient(temp);
allSocket.remove(socket);//删除推出的套接字
socket.close();//关闭套接字
System.out.println(temp);
break;//停止线程
}
sendMessageToAllClient(str);//将信息发送给所有客户端
}
//若服务器中没有客户端,则关闭程序
if(allSocket.isEmpty()) {
System.exit(0);//关闭程序
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//将信息发送给所有客户端
private void sendMessageToAllClient(String str) {
for (Socket socket : allSocket) {
try {
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//获取输出字符流
bw.write(str);//写入字符串
bw.newLine();//换行
bw.flush();//刷新
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Server s=new Server();
try {
s.startServer();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
客户端代码
定义客户端套接字
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
//客户端套接字
public class Client {
Socket socket;//套接字对象
BufferedWriter bw;//输出字符流
BufferedReader br;//输入字符流
public Client(String ip,int port) {
try {
socket=new Socket(ip, port);//创建套接字
bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//创建该套接字下的输出字符流
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//创建该套接字下的输入字符流
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//客户端发消息
public void sendMessage(String message) {
try {
bw.write(message);//写出message
bw.newLine();//换行
bw.flush();//刷新
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//客户端收消息
public String receiveMessage() {
String message=null;
try {
message=br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return message;
}
//关闭客户端
public void closeClient() {
try {
socket.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
连接服务器窗体
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
/*
* 连接服务器窗体
* */
@SuppressWarnings("serial")
public class LinkServerFrame extends JFrame{
private JPanel contentPane;//面板
private JLabel IP=new JLabel("IP地址");//IP地址标签
private JLabel user=new JLabel("用户名");//用户名标签
private JTextField Ipaddress=new JTextField("127.0.0.1", 20);//IP地址文本框
private JTextField userName=new JTextField(20);//用户名文本框
private JButton jb=new JButton("确定");//确定按钮
public LinkServerFrame() {
setTitle("连接服务器");
setResizable(false);//窗口大小不可变
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(400,400,300,150);
//面板
contentPane=new JPanel();
contentPane.setLayout(new FlowLayout(1, 10, 10));//使用流布局
setContentPane(contentPane);
//IP地址
IP.setSize(60, 20);
contentPane.add(IP);
//IP地址文本框
contentPane.add(Ipaddress);
//用户名
user.setSize(40,20);
contentPane.add(user);
//用户名文本框
contentPane.add(userName);
//确定按钮
contentPane.add(jb);
//添加按钮事件监听器
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
do_Link_actionPerformed(e);
}
});
setVisible(true);
}
protected void do_Link_actionPerformed(ActionEvent e) {
if(!Ipaddress.getText().equals("")&&!userName.getText().equals("")) {//当文本框内内容不为空时
dispose();//销毁当前窗体
new ClientFrame(Ipaddress.getText().trim(),userName.getText().trim());
}else {
JOptionPane.showMessageDialog(null, "文本框内容不能为空","警告",JOptionPane.WARNING_MESSAGE);
}
}
public static void main(String[] args) {
LinkServerFrame link=new LinkServerFrame();
}
}
窗口展示
用户发送接收消息窗口
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.*;
@SuppressWarnings("serial")
public class ClientFrame extends JFrame{
private String uername;//用户名
private JPanel contentPane;//面板
private JTextArea text;;//文本域
private JTextField message;//文本框
private JButton send;//发送按钮
private JLabel jl;//用户名标签
Client client;//client对象
public ClientFrame(String ip, String uer) {
this.uername=uer;
init();//添加窗体初始化内容
addListener();//添加监听
client=new Client(ip, 5214);//创建client对象
ReadMessageThread t=new ReadMessageThread();
t.start();//开始收消息线程
}
//收消息线程
private class ReadMessageThread extends Thread{
public void run() {
while(true) {
String str=client.receiveMessage();//客户端收消息
text.append(str+"\n");//将消息加入文本域
}
}
}
private void addListener() {
//发送按钮事件监听
send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Date date=new Date();
SimpleDateFormat sf=new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
client.sendMessage(uername+" "+sf.format(date)+"\n"+message.getText());//客户端发消息
message.setText(" ");//清空文本框
}
});
//窗口事件监听
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent atg0) {
int op=JOptionPane.showConfirmDialog(ClientFrame.this, "确定要退出聊天室吗?","确定",JOptionPane.WARNING_MESSAGE);
if(op==JOptionPane.YES_OPTION) {
client.sendMessage("%EXIT%:"+uername);
//客户端关闭停顿(给服务器缓冲时间)
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
client.closeClient();//关闭客户端
System.exit(0);//关闭程序
}
}
});
}
private void init() {
setTitle("客户端");
setResizable(false);
setBounds(400, 400, 450, 296);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//面板
contentPane=new JPanel();
contentPane.setLayout(new FlowLayout(1, 5, 5));//使用流布局
setContentPane(contentPane);
//文本域
text=new JTextArea(12,40);
JScrollPane sp=new JScrollPane(text);
contentPane.add(sp);
//用户名标签
jl=new JLabel(uername);
jl.setFont(new Font("微软雅黑", Font.PLAIN,18));
contentPane.add(jl);
//文本框
message=new JTextField(25);
contentPane.add(message);
//发送按钮
send=new JButton("发送");
send.setSize(40, 20);
contentPane.add(send);
setVisible(true);
}
}
窗口展示
结果展示