TCP协议,一个服务器(ServerSocket)只服务于一个客户端(Socket),那么能够经过ServerSocket+Thread的方式,实现一个服务器服务于多个客户端。java
多线程服务器实现原理——多线程并发机制
一、建立服务器ServerSocket,开启监听.accept(),当有客户端链接,返回一个Socket对象。
二、把Socket对象须要处理的事务,交给Thread,此线程会到一边默默地执行,那么服务器监听就会空闲出来,等待另一个客户端链接。
三、另外一个客户端链接成功,新的Socket对象又交给另外一个Thread,以此类推。服务器
【多线程聊天室---群聊】先运行服务器,在运行客户端(2个客户端则运行两次)多线程
import java.io.*; 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(4567);//实例化服务器套接字,设定端口4567 } catch (IOException e) { e.printStackTrace(); } allSocket = new HashSet<>();//实例化服务器套接字集合 } public void startServer() {//启动服务器,循环获取服务器监听 while (true) { Socket socket = null; try { socket = server.accept();//服务器监听是否有客户端接入 } catch (IOException e) { e.printStackTrace(); } 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();//读取字符串 //若字符串中有EXIT内容(有用户退出聊天室),提醒其余人 if (str.contains("EXIT")) {//用户退出聊天室,给服务器发送"%EXIT%:用户名"(自定义规则) String tmp = str.split(":")[1] + "用户退出聊天室";//依据:分隔成两个元素,取第2个元素(用户名) sendMessageToAllClient(tmp);//告诉全部人,他退出了 allSocket.remove(socket);//移除离开的客户端 socket.close();//关闭客户端 return;//中止线程 } sendMessageToAllClient(str);//若是没有EXIT内容,直接发送给全部客户端。调用自定义方法 } } catch (IOException e) { e.printStackTrace(); } } } private void sendMessageToAllClient(String str) {//发给全部人,参数str是发送的内容 for (Socket s : allSocket) {//遍历全部接入的客户端 try { BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//客户端输出流 bw.write(str);//输出str bw.newLine();//换行 bw.flush();//强制将输出流缓冲区的数据送出,清空缓冲区(通常用在IO中) } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { Server s = new Server(); s.startServer(); } }
import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; //登录界面、获取用户名与IP public class LinkServerFrame extends JFrame { private JPanel contentPane; private JLabel lblIP; private JLabel lblUserName; private JTextField tfIP; private JTextField tfUserName; private JButton btnLink; public LinkServerFrame(){//构造方法 setTitle("链接服务器"); setResizable(false); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setBounds(100,100,390,150); contentPane=new JPanel(); contentPane.setBorder(new EmptyBorder(5,5,5,5)); contentPane.setLayout(null); setContentPane(contentPane); lblIP=new JLabel("服务器IP地址:"); lblIP.setFont(new Font("微软雅黑",Font.PLAIN,14)); lblIP.setBounds(20,15,100,15); contentPane.add(lblIP); tfIP=new JTextField("127.0.0.1");//指定IP内容,本地服务器 tfIP.setBounds(121,13,242,21); contentPane.add(tfIP); tfIP.setColumns(10); lblUserName=new JLabel("用户名"); lblUserName.setFont(new Font("微软雅黑",Font.PLAIN,14)); lblUserName.setBounds(60,40,60,15); contentPane.add(lblUserName); tfUserName=new JTextField(); tfUserName.setBounds(121,42,242,21); contentPane.add(tfUserName); tfUserName.setColumns(10); btnLink=new JButton("链接服务器");//登陆按钮 btnLink.addActionListener(new ActionListener() {//实现登陆,显示客户端窗体,关闭登陆窗体 public void actionPerformed(ActionEvent e) { do_btnLink_actionPerformed(e); } }); btnLink.setFont(new Font("微软雅黑",Font.PLAIN,14)); btnLink.setBounds(140,80,120,23); contentPane.add(btnLink); } public static void main(String[] args) { LinkServerFrame linkServerFrame=new LinkServerFrame();//建立窗体对象 linkServerFrame.setVisible(true);//显示窗体 } protected void do_btnLink_actionPerformed(ActionEvent e){ if(!tfIP.getText().equals("")&&!tfUserName.getText().equals("")){//文本框内容不为空 dispose();//关闭窗体,释放资源 //获取文本并除去空格,.trim()的做用是:去掉字符串左右的空格 ClientFrame clientFrame=new ClientFrame(tfIP.getText().trim(),tfUserName.getText().trim());//实例化登陆窗口,并赋值IP、用户名 clientFrame.setVisible(true);//显示客户端窗体 }else { JOptionPane.showMessageDialog(null,"内容不能为空!","警告",JOptionPane.WARNING_MESSAGE); } } }
import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.event.*; import java.text.SimpleDateFormat; import java.util.Date; //客户端窗体、共享消息在文本域中、用户退出提醒 public class ClientFrame extends JFrame { private JPanel contentPane; private JLabel lblUserName;//显示用户名 private JTextField tfMessage;//信息输入文本框 private JButton btnSend;//发送按钮 private JTextArea textArea;//信息接收文本域 private String userName;//用户名称 Client client;//调用自定义类(在Client.java中) public ClientFrame(String ip,String userName){//ip与userName由登陆窗体得到(LinkServerFrame.java中) this.userName=userName; init();//窗体初始化,自定义方法 addListener();//调用发送按钮监听,自定义方法 client=new Client(ip,4567);//赋值形参。实例化自定义类(在Client.java中) ReadMessageThread t=new ReadMessageThread();//显示消息在文本域中 t.start(); } private void init(){//窗体属性(显示与否,由LinkServerFrame.java控制) setTitle("客户端"); setResizable(false); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); setBounds(100,100,450,300); contentPane=new JPanel(); contentPane.setBorder(new EmptyBorder(5,5,5,5)); contentPane.setLayout(null); setContentPane(contentPane); JScrollPane scrollPane=new JScrollPane(); scrollPane.setBounds(5,5,434,229); contentPane.add(scrollPane); textArea=new JTextArea(); //添加滚动条,或JScrollPane scrollPane=new JScrollPane(textArea); scrollPane.setViewportView(textArea); textArea.setEditable(false);//文本域不可编辑 JPanel panel=new JPanel(); panel.setBounds(5,235,434,32); contentPane.add(panel); panel.setLayout(null); lblUserName=new JLabel(userName); lblUserName.setHorizontalAlignment(SwingConstants.TRAILING); lblUserName.setBounds(2,4,55,22); panel.add(lblUserName); tfMessage=new JTextField(); tfMessage.setBounds(62,5,274,22); tfMessage.setColumns(10); panel.add(tfMessage); btnSend=new JButton("发送"); btnSend.setBounds(336,4,93,23); panel.add(btnSend); tfMessage.validate();//验证容器中的组件,即刷新。 } //显示消息在文本域中 private class ReadMessageThread extends Thread{//内部类,收消息线程类 public void run() { while (true) { String str=client.receiveMessage();//调用自定义方法(在Client.java中) textArea.append(str+"\n");//将消息显示在文本域中 } } } private void addListener(){ btnSend.addActionListener(new ActionListener() {//发送按钮的动做监听 public void actionPerformed(ActionEvent e) { Date date=new Date(); SimpleDateFormat sf=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); client.sendMessage(userName+" "+sf.format(date)+":\n"+tfMessage.getText()); tfMessage.setText("");//消息发送后,文本框清空 } }); //开启窗口监听,单击窗口“X”,弹出确认对话框。“是”则关闭客户端窗体 this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent atg0){//窗口关闭时 int op=JOptionPane.showConfirmDialog(ClientFrame.this,"肯定要退出聊天室吗?","肯定",JOptionPane.YES_NO_OPTION); if(op==JOptionPane.YES_OPTION){//若是选择“是” client.sendMessage("%EXIT%:"+userName);//若是退出,发送"%EXIT%:用户名"给服务器 try { Thread.sleep(200);//200ms后,关闭客户端 } catch (InterruptedException e) { e.printStackTrace(); } client.close();//关闭客户端套接字,调用Client类中的close方法(Client.java中) System.exit(0);//关闭程序 } } }); } }
import java.io.*; import java.net.Socket; //供ClientFrame.java调用的各类方法,消息输入、输出,关闭套接字 public class Client { Socket socket;//客户端套接字 BufferedWriter bw;//发数据 BufferedReader br;//收数据 public Client(String ip,int port){//带参数构造方法,参数值由ClientFrame.java赋予 try { socket=new Socket(ip,port);//实例化客户端套接字,链接服务器 bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//客户端输出流,输出到服务器 br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//客户端输入流,服务器反馈给客户端 } catch (IOException e) { e.printStackTrace(); } } public void sendMessage(String message){//发消息 try { bw.write(message);//发消息 bw.newLine();//换行 bw.flush();//强制将输出流缓冲区的数据送出,清空缓冲区(通常用在IO中) } catch (IOException e) { e.printStackTrace(); } } public String receiveMessage(){//收消息 String message=null; try { message=br.readLine();//收消息 } catch (IOException e) { e.printStackTrace(); } return message;//返回消息结果 } public void close(){//关闭客户端套接字 try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }