即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。
即时通信需要进行端口转发的设计思想。
服务端需要把在线的Socket管道存储起来。
一旦收到一个消息要推送给其他管道。
- /**
- 拓展:即时通信
- 客户端:发消息的同时,随时有人发消息过来。
- 服务端:接收消息后,推送给其他所有的在线socket
- */
- public class ClientDemo1 {
- public static void main(String[] args) {
- try {
- System.out.println("====客户端启动===");
- // 1、创建Socket通信管道请求有服务端的连接
- // public Socket(String host, int port)
- // 参数一:服务端的IP地址
- // 参数二:服务端的端口
- Socket socket = new Socket("127.0.0.1", 6868);
-
- // 马上为客户端分配一个独立的线程负责读取它收到的消息
- new ClientReaderThread(socket).start();
-
- // 2、从socket通信管道中得到一个字节输出流 负责发送数据
- OutputStream os = socket.getOutputStream();
-
- // 3、把低级的字节流包装成打印流
- PrintStream ps = new PrintStream(os);
-
- Scanner sc = new Scanner(System.in);
- while (true) {
- System.out.println("请说:");
- String msg = sc.nextLine();
- // 4、发送消息
- ps.println(msg);
- ps.flush();
- }
- // 关闭资源。
- // socket.close();
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- public class ClientReaderThread extends Thread{
- private Socket socket;
- public ClientReaderThread(Socket socket){
- this.socket = socket;
- }
- @Override
- public void run() {
- try {
- // 3、从socket通信管道中得到一个字节输入流
- InputStream is = socket.getInputStream();
- // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
- BufferedReader br = new BufferedReader(new InputStreamReader(is));
- // 5、按照行读取消息
- String msg;
- while ((msg = br.readLine()) != null){
- System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg);
- }
- } catch (Exception e) {
- System.out.println("服务端把你踢出去了~~");
- }
- }
- }
- /**
- 目标: 即时通信
- */
- public class ServerDemo2 {
-
- public static List
onLineSockets = new ArrayList<>(); -
- public static void main(String[] args) {
- try {
- System.out.println("===服务端启动成功===");
- // 1、注册端口
- ServerSocket serverSocket = new ServerSocket(6868);
- // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
- while (true) {
- // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
- Socket socket = serverSocket.accept();
- System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
- // 把当前客户端管道Socket加入到在线集合中去
- onLineSockets.add(socket);
-
- // 3、开始创建独立线程处理socket
- new ServerReaderThread(socket).start();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- public class ServerReaderThread extends Thread{
- private Socket socket;
- public ServerReaderThread(Socket socket){
- this.socket = socket;
- }
- @Override
- public void run() {
- try {
- // 3、从socket通信管道中得到一个字节输入流
- InputStream is = socket.getInputStream();
- // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
- BufferedReader br = new BufferedReader(new InputStreamReader(is));
- // 5、按照行读取消息
- String msg;
- while ((msg = br.readLine()) != null){
- System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
- // 把这个消息发给当前所有在线socket
- sendMsgToAll(msg);
- }
- } catch (Exception e) {
- System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
- // 从在线集合中抹掉本客户端socket
- ServerDemo2.onLineSockets.remove(socket);
- }
- }
-
- private void sendMsgToAll(String msg) {
- try {
- // 遍历全部的在线 socket给他们发消息
- for (Socket onLineSocket : ServerDemo2.onLineSockets) {
- // 除了自己的socket,其他socket我都发!!
- if(onLineSocket != socket){
- PrintStream ps = new PrintStream(onLineSocket.getOutputStream());
- ps.println(msg);
- ps.flush();
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- package com.itheima.d9_chat.即时通信;
-
-
- import javax.swing.*;
- import java.awt.*;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.net.Socket;
-
- /**
- * @Author xlei(徐磊)
- * 客户端界面
- */
- public class ClientChat implements ActionListener {
- /** 1.设计界面 */
- private JFrame win = new JFrame();
- /** 2.消息内容框架 */
- public JTextArea smsContent =new JTextArea(23 , 50);
- /** 3.发送消息的框 */
- private JTextArea smsSend = new JTextArea(4,40);
- /** 4.在线人数的区域 */
- /** 存放人的数据 */
- /** 展示在线人数的窗口 */
- public JList
onLineUsers = new JList<>(); -
- // 是否私聊按钮
- private JCheckBox isPrivateBn = new JCheckBox("私聊");
- // 消息按钮
- private JButton sendBn = new JButton("发送");
-
- // 登录界面
- private JFrame loginView;
-
- private JTextField ipEt , nameEt , idEt;
-
- private Socket socket ;
-
- public static void main(String[] args) {
- new ClientChat().initView();
-
- }
-
- private void initView() {
- /** 初始化聊天窗口的界面 */
- win.setSize(650, 600);
-
- /** 展示登录界面 */
- displayLoginView();
-
- /** 展示聊天界面 */
- //displayChatView();
-
-
- }
-
- private void displayChatView() {
-
- JPanel bottomPanel = new JPanel(new BorderLayout());
- //-----------------------------------------------
- // 将消息框和按钮 添加到窗口的底端
- win.add(bottomPanel, BorderLayout.SOUTH);
- bottomPanel.add(smsSend);
- JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
- btns.add(sendBn);
- btns.add(isPrivateBn);
- bottomPanel.add(btns, BorderLayout.EAST);
- //-----------------------------------------------
- // 给发送消息按钮绑定点击事件监听器
- // 将展示消息区centerPanel添加到窗口的中间
- smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
- // 让展示消息区可以滚动。
- win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
- smsContent.setEditable(false);
- //-----------------------------------------------
- // 用户列表和是否私聊放到窗口的最右边
- Box rightBox = new Box(BoxLayout.Y_AXIS);
- onLineUsers.setFixedCellWidth(120);
- onLineUsers.setVisibleRowCount(13);
- rightBox.add(new JScrollPane(onLineUsers));
- win.add(rightBox, BorderLayout.EAST);
- //-----------------------------------------------
- // 关闭窗口退出当前程序
- win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- win.pack(); // swing 加上这句 就可以拥有关闭窗口的功能
- /** 设置窗口居中,显示出来 */
- setWindowCenter(win,650,600,true);
- // 发送按钮绑定点击事件
- sendBn.addActionListener(this);
- }
-
- private void displayLoginView(){
-
- /** 先让用户进行登录
- * 服务端ip
- * 用户名
- * id
- * */
- /** 显示一个qq的登录框 */
- loginView = new JFrame("登录");
- loginView.setLayout(new GridLayout(3, 1));
- loginView.setSize(400, 230);
-
- JPanel ip = new JPanel();
- JLabel label = new JLabel(" IP:");
- ip.add(label);
- ipEt = new JTextField(20);
- ip.add(ipEt);
- loginView.add(ip);
-
- JPanel name = new JPanel();
- JLabel label1 = new JLabel("姓名:");
- name.add(label1);
- nameEt = new JTextField(20);
- name.add(nameEt);
- loginView.add(name);
-
- JPanel btnView = new JPanel();
- JButton login = new JButton("登陆");
- btnView.add(login);
- JButton cancle = new JButton("取消");
- btnView.add(cancle);
- loginView.add(btnView);
- // 关闭窗口退出当前程序
- loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- setWindowCenter(loginView,400,260,true);
-
- /** 给登录和取消绑定点击事件 */
- login.addActionListener(this);
- cancle.addActionListener(this);
-
- }
-
- private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
- /** 得到所在系统所在屏幕的宽高 */
- Dimension ds = frame.getToolkit().getScreenSize();
-
- /** 拿到电脑的宽 */
- int width1 = ds.width;
- /** 高 */
- int height1 = ds.height ;
-
- System.out.println(width1 +"*" + height1);
- /** 设置窗口的左上角坐标 */
- frame.setLocation(width1/2 - width/2, height1/2 -height/2);
- frame.setVisible(flag);
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- /** 得到点击的事件源 */
- JButton btn = (JButton) e.getSource();
- switch(btn.getText()){
- case "登陆":
- String ip = ipEt.getText().toString();
- String name = nameEt.getText().toString();
- // 校验参数是否为空
- // 错误提示
- String msg = "" ;
- // 12.1.2.0
- // \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
- if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
- msg = "请输入合法的服务端ip地址";
- }else if(name==null || !name.matches("\\S{1,}")){
- msg = "姓名必须1个字符以上";
- }
-
- if(!msg.equals("")){
- /** msg有内容说明参数有为空 */
- // 参数一:弹出放到哪个窗口里面
- JOptionPane.showMessageDialog(loginView, msg);
- }else{
- try {
- // 参数都合法了
- // 当前登录的用户,去服务端登陆
- /** 先把当前用户的名称展示到界面 */
- win.setTitle(name);
- // 去服务端登陆连接一个socket管道
- socket = new Socket(ip, Constants.PORT);
-
- //为客户端的socket分配一个线程 专门负责收消息
- new ClientReader(this,socket).start();
-
- // 带上用户信息过去
- DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
- dos.writeInt(1); // 登录消息
- dos.writeUTF(name.trim());
- dos.flush();
-
- // 关系当前窗口 弹出聊天界面
- loginView.dispose(); // 登录窗口销毁
- displayChatView(); // 展示了聊天窗口了
-
-
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
- break;
- case "取消":
- /** 退出系统 */
- System.exit(0);
- break;
- case "发送":
- // 得到发送消息的内容
- String msgSend = smsSend.getText().toString();
- if(!msgSend.trim().equals("")){
- /** 发消息给服务端 */
- try {
- // 判断是否对谁发消息
- String selectName = onLineUsers.getSelectedValue();
- int flag = 2 ;// 群发 @消息
- if(selectName!=null&&!selectName.equals("")){
- msgSend =("@"+selectName+","+msgSend);
- /** 判断是否选中了私法 */
- if(isPrivateBn.isSelected()){
- /** 私法 */
- flag = 3 ;//私发消息
- }
-
- }
-
- DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
- dos.writeInt(flag); // 群发消息 发送给所有人
- dos.writeUTF(msgSend);
- if(flag == 3){
- // 告诉服务端我对谁私发
- dos.writeUTF(selectName.trim());
- }
- dos.flush();
-
- } catch (Exception e1) {
- e1.printStackTrace();
- }
-
- }
- smsSend.setText(null);
- break;
-
- }
-
- }
- }
-
- class ClientReader extends Thread {
-
- private Socket socket;
- private ClientChat clientChat ;
-
- public ClientReader(ClientChat clientChat, Socket socket) {
- this.clientChat = clientChat;
- this.socket = socket;
- }
-
- @Override
- public void run() {
- try {
- DataInputStream dis = new DataInputStream(socket.getInputStream());
- /** 循环一直等待客户端的消息 */
- while(true){
- /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
- int flag = dis.readInt();
- if(flag == 1){
- // 在线人数消息回来了
- String nameDatas = dis.readUTF();
- // 展示到在线人数的界面
- String[] names = nameDatas.split(Constants.SPILIT);
-
- clientChat.onLineUsers.setListData(names);
- }else if(flag == 2){
- // 群发消息
- String msg = dis.readUTF() ;
- clientChat.smsContent.append(msg);
- //滾動到底端
- clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- }
- }
- package com.itheima.d9_chat.即时通信;
-
- public class Constants {
- /** 常量 */
- public static final int PORT = 7778 ;
-
- /** 协议分隔符 */
- public static final String SPILIT = "003197♣♣㏘♣④④♣";
- }
- package com.itheima.d9_chat.即时通信;
-
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.text.SimpleDateFormat;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
-
- /**
- * @Author
- * @Email dlei0009@163.com
- */
- public class ServerChat {
-
- /** 定义一个集合存放所有在线的socket */
- public static Map
onLineSockets = new HashMap<>(); -
- public static void main(String[] args) {
- try {
- /** 注册端口 */
- ServerSocket serverSocket = new ServerSocket(Constants.PORT);
-
- /** 循环一直等待所有可能的客户端连接 */
- while(true){
- Socket socket = serverSocket.accept();
- /** 把客户端的socket管道单独配置一个线程来处理 */
- new ServerReader(socket).start();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- class ServerReader extends Thread {
-
- private Socket socket;
-
- public ServerReader(Socket socket) {
- this.socket = socket;
- }
-
- @Override
- public void run() {
- DataInputStream dis = null;
- try {
- dis = new DataInputStream(socket.getInputStream());
- /** 循环一直等待客户端的消息 */
- while(true){
- /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
- int flag = dis.readInt();
- if(flag == 1){
- /** 先将当前登录的客户端socket存到在线人数的socket集合中 */
- String name = dis.readUTF() ;
- System.out.println(name+"---->"+socket.getRemoteSocketAddress());
- ServerChat.onLineSockets.put(socket, name);
- }
- writeMsg(flag,dis);
- }
- } catch (Exception e) {
- System.out.println("--有人下线了--");
- // 从在线人数中将当前socket移出去
- ServerChat.onLineSockets.remove(socket);
- try {
- // 从新更新在线人数并发给所有客户端
- writeMsg(1,dis);
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
-
- }
-
- private void writeMsg(int flag, DataInputStream dis) throws Exception {
- // DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
- // 定义一个变量存放最终的消息形式
- String msg = null ;
- if(flag == 1){
- /** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
- /** onlineNames = [徐磊,zhangsan,李刚]*/
- StringBuilder rs = new StringBuilder();
- Collection
onlineNames = ServerChat.onLineSockets.values(); - // 判断是否存在在线人数
- if(onlineNames != null && onlineNames.size() > 0){
- for(String name : onlineNames){
- rs.append(name+ Constants.SPILIT);
- }
- // 徐磊003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣李刚003197♣♣㏘♣④④♣
- // 去掉最后的一个分隔符
- msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));
-
- /** 将消息发送给所有的客户端 */
- sendMsgToAll(flag,msg);
- }
- }else if(flag == 2 || flag == 3){
- // 读到消息 群发的 或者 @消息
- String newMsg = dis.readUTF() ; // 消息
- // 得到发件人
- String sendName = ServerChat.onLineSockets.get(socket);
-
- // 李刚 时间
- // 内容--
- StringBuilder msgFinal = new StringBuilder();
- // 时间
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
- if(flag == 2){
- msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
- msgFinal.append(" ").append(newMsg).append("\r\n");
- sendMsgToAll(flag,msgFinal.toString());
- }else if(flag == 3){
- msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n");
- msgFinal.append(" ").append(newMsg).append("\r\n");
- // 私发
- // 得到给谁私发
- String destName = dis.readUTF();
- sendMsgToOne(destName,msgFinal.toString());
- }
- }
- }
- /**
- * @param destName 对谁私发
- * @param msg 发的消息内容
- * @throws Exception
- */
- private void sendMsgToOne(String destName, String msg) throws Exception {
- // 拿到所有的在线socket管道 给这些管道写出消息
- Set
allOnLineSockets = ServerChat.onLineSockets.keySet(); - for(Socket sk : allOnLineSockets){
- // 得到当前需要私发的socket
- // 只对这个名字对应的socket私发消息
- if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){
- DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
- dos.writeInt(2); // 消息类型
- dos.writeUTF(msg);
- dos.flush();
- }
- }
-
- }
-
- private void sendMsgToAll(int flag, String msg) throws Exception {
- // 拿到所有的在线socket管道 给这些管道写出消息
- Set
allOnLineSockets = ServerChat.onLineSockets.keySet(); - for(Socket sk : allOnLineSockets){
- DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
- dos.writeInt(flag); // 消息类型
- dos.writeUTF(msg);
- dos.flush();
- }
- }
- }
- package com.itheima.d9_chat.即时通信;
-
- public class User {
- private Integer id ;
- private String name ;
-
- public User(Integer id, String name) {
- this.id = id;
- this.name = name;
- }
-
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "User [id=" + id + ", name=" + name + "]";
- }
- }