即时通信是什么含义,要实现怎么样的设计?
即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。
即时通信需要进行端口转发的设计思想。
服务端需要把在线的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;}@Overridepublic 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<Socket> 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、开始创建独立线程处理socketnew ServerReaderThread(socket).start();}} catch (Exception e) {e.printStackTrace();}}
}
public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic 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);// 把这个消息发给当前所有在线socketsendMsgToAll(msg);}} catch (Exception e) {System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");// 从在线集合中抹掉本客户端socketServerDemo2.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<String> 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);}@Overridepublic 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;}@Overridepublic 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<Socket, String> 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;}@Overridepublic 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<String> 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<Socket> 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<Socket> 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;}@Overridepublic String toString() {return "User [id=" + id + ", name=" + name + "]";}
}