• TCP通信实战案例-即时通信


    即时通信是什么含义,要实现怎么样的设计?

    即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。

    即时通信需要进行端口转发的设计思想。

    服务端需要把在线的Socket管道存储起来。

    一旦收到一个消息要推送给其他管道。

    即时通信-端口转发

     代码实现

    1. /**
    2. 拓展:即时通信
    3. 客户端:发消息的同时,随时有人发消息过来。
    4. 服务端:接收消息后,推送给其他所有的在线socket
    5. */
    6. public class ClientDemo1 {
    7. public static void main(String[] args) {
    8. try {
    9. System.out.println("====客户端启动===");
    10. // 1、创建Socket通信管道请求有服务端的连接
    11. // public Socket(String host, int port)
    12. // 参数一:服务端的IP地址
    13. // 参数二:服务端的端口
    14. Socket socket = new Socket("127.0.0.1", 6868);
    15. // 马上为客户端分配一个独立的线程负责读取它收到的消息
    16. new ClientReaderThread(socket).start();
    17. // 2、从socket通信管道中得到一个字节输出流 负责发送数据
    18. OutputStream os = socket.getOutputStream();
    19. // 3、把低级的字节流包装成打印流
    20. PrintStream ps = new PrintStream(os);
    21. Scanner sc = new Scanner(System.in);
    22. while (true) {
    23. System.out.println("请说:");
    24. String msg = sc.nextLine();
    25. // 4、发送消息
    26. ps.println(msg);
    27. ps.flush();
    28. }
    29. // 关闭资源。
    30. // socket.close();
    31. } catch (Exception e) {
    32. e.printStackTrace();
    33. }
    34. }
    35. }
    1. public class ClientReaderThread extends Thread{
    2. private Socket socket;
    3. public ClientReaderThread(Socket socket){
    4. this.socket = socket;
    5. }
    6. @Override
    7. public void run() {
    8. try {
    9. // 3、从socket通信管道中得到一个字节输入流
    10. InputStream is = socket.getInputStream();
    11. // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
    12. BufferedReader br = new BufferedReader(new InputStreamReader(is));
    13. // 5、按照行读取消息
    14. String msg;
    15. while ((msg = br.readLine()) != null){
    16. System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg);
    17. }
    18. } catch (Exception e) {
    19. System.out.println("服务端把你踢出去了~~");
    20. }
    21. }
    22. }
    1. /**
    2. 目标: 即时通信
    3. */
    4. public class ServerDemo2 {
    5. public static List onLineSockets = new ArrayList<>();
    6. public static void main(String[] args) {
    7. try {
    8. System.out.println("===服务端启动成功===");
    9. // 1、注册端口
    10. ServerSocket serverSocket = new ServerSocket(6868);
    11. // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
    12. while (true) {
    13. // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
    14. Socket socket = serverSocket.accept();
    15. System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
    16. // 把当前客户端管道Socket加入到在线集合中去
    17. onLineSockets.add(socket);
    18. // 3、开始创建独立线程处理socket
    19. new ServerReaderThread(socket).start();
    20. }
    21. } catch (Exception e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }
    1. public class ServerReaderThread extends Thread{
    2. private Socket socket;
    3. public ServerReaderThread(Socket socket){
    4. this.socket = socket;
    5. }
    6. @Override
    7. public void run() {
    8. try {
    9. // 3、从socket通信管道中得到一个字节输入流
    10. InputStream is = socket.getInputStream();
    11. // 4、把字节输入流包装成缓冲字符输入流进行消息的接收
    12. BufferedReader br = new BufferedReader(new InputStreamReader(is));
    13. // 5、按照行读取消息
    14. String msg;
    15. while ((msg = br.readLine()) != null){
    16. System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
    17. // 把这个消息发给当前所有在线socket
    18. sendMsgToAll(msg);
    19. }
    20. } catch (Exception e) {
    21. System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
    22. // 从在线集合中抹掉本客户端socket
    23. ServerDemo2.onLineSockets.remove(socket);
    24. }
    25. }
    26. private void sendMsgToAll(String msg) {
    27. try {
    28. // 遍历全部的在线 socket给他们发消息
    29. for (Socket onLineSocket : ServerDemo2.onLineSockets) {
    30. // 除了自己的socket,其他socket我都发!!
    31. if(onLineSocket != socket){
    32. PrintStream ps = new PrintStream(onLineSocket.getOutputStream());
    33. ps.println(msg);
    34. ps.flush();
    35. }
    36. }
    37. } catch (Exception e) {
    38. e.printStackTrace();
    39. }
    40. }
    41. }

    简易聊天系统代码

    1. package com.itheima.d9_chat.即时通信;
    2. import javax.swing.*;
    3. import java.awt.*;
    4. import java.awt.event.ActionEvent;
    5. import java.awt.event.ActionListener;
    6. import java.io.DataInputStream;
    7. import java.io.DataOutputStream;
    8. import java.net.Socket;
    9. /**
    10. * @Author xlei(徐磊)
    11. * 客户端界面
    12. */
    13. public class ClientChat implements ActionListener {
    14. /** 1.设计界面 */
    15. private JFrame win = new JFrame();
    16. /** 2.消息内容框架 */
    17. public JTextArea smsContent =new JTextArea(23 , 50);
    18. /** 3.发送消息的框 */
    19. private JTextArea smsSend = new JTextArea(4,40);
    20. /** 4.在线人数的区域 */
    21. /** 存放人的数据 */
    22. /** 展示在线人数的窗口 */
    23. public JList onLineUsers = new JList<>();
    24. // 是否私聊按钮
    25. private JCheckBox isPrivateBn = new JCheckBox("私聊");
    26. // 消息按钮
    27. private JButton sendBn = new JButton("发送");
    28. // 登录界面
    29. private JFrame loginView;
    30. private JTextField ipEt , nameEt , idEt;
    31. private Socket socket ;
    32. public static void main(String[] args) {
    33. new ClientChat().initView();
    34. }
    35. private void initView() {
    36. /** 初始化聊天窗口的界面 */
    37. win.setSize(650, 600);
    38. /** 展示登录界面 */
    39. displayLoginView();
    40. /** 展示聊天界面 */
    41. //displayChatView();
    42. }
    43. private void displayChatView() {
    44. JPanel bottomPanel = new JPanel(new BorderLayout());
    45. //-----------------------------------------------
    46. // 将消息框和按钮 添加到窗口的底端
    47. win.add(bottomPanel, BorderLayout.SOUTH);
    48. bottomPanel.add(smsSend);
    49. JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
    50. btns.add(sendBn);
    51. btns.add(isPrivateBn);
    52. bottomPanel.add(btns, BorderLayout.EAST);
    53. //-----------------------------------------------
    54. // 给发送消息按钮绑定点击事件监听器
    55. // 将展示消息区centerPanel添加到窗口的中间
    56. smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
    57. // 让展示消息区可以滚动。
    58. win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
    59. smsContent.setEditable(false);
    60. //-----------------------------------------------
    61. // 用户列表和是否私聊放到窗口的最右边
    62. Box rightBox = new Box(BoxLayout.Y_AXIS);
    63. onLineUsers.setFixedCellWidth(120);
    64. onLineUsers.setVisibleRowCount(13);
    65. rightBox.add(new JScrollPane(onLineUsers));
    66. win.add(rightBox, BorderLayout.EAST);
    67. //-----------------------------------------------
    68. // 关闭窗口退出当前程序
    69. win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    70. win.pack(); // swing 加上这句 就可以拥有关闭窗口的功能
    71. /** 设置窗口居中,显示出来 */
    72. setWindowCenter(win,650,600,true);
    73. // 发送按钮绑定点击事件
    74. sendBn.addActionListener(this);
    75. }
    76. private void displayLoginView(){
    77. /** 先让用户进行登录
    78. * 服务端ip
    79. * 用户名
    80. * id
    81. * */
    82. /** 显示一个qq的登录框 */
    83. loginView = new JFrame("登录");
    84. loginView.setLayout(new GridLayout(3, 1));
    85. loginView.setSize(400, 230);
    86. JPanel ip = new JPanel();
    87. JLabel label = new JLabel(" IP:");
    88. ip.add(label);
    89. ipEt = new JTextField(20);
    90. ip.add(ipEt);
    91. loginView.add(ip);
    92. JPanel name = new JPanel();
    93. JLabel label1 = new JLabel("姓名:");
    94. name.add(label1);
    95. nameEt = new JTextField(20);
    96. name.add(nameEt);
    97. loginView.add(name);
    98. JPanel btnView = new JPanel();
    99. JButton login = new JButton("登陆");
    100. btnView.add(login);
    101. JButton cancle = new JButton("取消");
    102. btnView.add(cancle);
    103. loginView.add(btnView);
    104. // 关闭窗口退出当前程序
    105. loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    106. setWindowCenter(loginView,400,260,true);
    107. /** 给登录和取消绑定点击事件 */
    108. login.addActionListener(this);
    109. cancle.addActionListener(this);
    110. }
    111. private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
    112. /** 得到所在系统所在屏幕的宽高 */
    113. Dimension ds = frame.getToolkit().getScreenSize();
    114. /** 拿到电脑的宽 */
    115. int width1 = ds.width;
    116. /** 高 */
    117. int height1 = ds.height ;
    118. System.out.println(width1 +"*" + height1);
    119. /** 设置窗口的左上角坐标 */
    120. frame.setLocation(width1/2 - width/2, height1/2 -height/2);
    121. frame.setVisible(flag);
    122. }
    123. @Override
    124. public void actionPerformed(ActionEvent e) {
    125. /** 得到点击的事件源 */
    126. JButton btn = (JButton) e.getSource();
    127. switch(btn.getText()){
    128. case "登陆":
    129. String ip = ipEt.getText().toString();
    130. String name = nameEt.getText().toString();
    131. // 校验参数是否为空
    132. // 错误提示
    133. String msg = "" ;
    134. // 12.1.2.0
    135. // \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
    136. if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
    137. msg = "请输入合法的服务端ip地址";
    138. }else if(name==null || !name.matches("\\S{1,}")){
    139. msg = "姓名必须1个字符以上";
    140. }
    141. if(!msg.equals("")){
    142. /** msg有内容说明参数有为空 */
    143. // 参数一:弹出放到哪个窗口里面
    144. JOptionPane.showMessageDialog(loginView, msg);
    145. }else{
    146. try {
    147. // 参数都合法了
    148. // 当前登录的用户,去服务端登陆
    149. /** 先把当前用户的名称展示到界面 */
    150. win.setTitle(name);
    151. // 去服务端登陆连接一个socket管道
    152. socket = new Socket(ip, Constants.PORT);
    153. //为客户端的socket分配一个线程 专门负责收消息
    154. new ClientReader(this,socket).start();
    155. // 带上用户信息过去
    156. DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
    157. dos.writeInt(1); // 登录消息
    158. dos.writeUTF(name.trim());
    159. dos.flush();
    160. // 关系当前窗口 弹出聊天界面
    161. loginView.dispose(); // 登录窗口销毁
    162. displayChatView(); // 展示了聊天窗口了
    163. } catch (Exception e1) {
    164. e1.printStackTrace();
    165. }
    166. }
    167. break;
    168. case "取消":
    169. /** 退出系统 */
    170. System.exit(0);
    171. break;
    172. case "发送":
    173. // 得到发送消息的内容
    174. String msgSend = smsSend.getText().toString();
    175. if(!msgSend.trim().equals("")){
    176. /** 发消息给服务端 */
    177. try {
    178. // 判断是否对谁发消息
    179. String selectName = onLineUsers.getSelectedValue();
    180. int flag = 2 ;// 群发 @消息
    181. if(selectName!=null&&!selectName.equals("")){
    182. msgSend =("@"+selectName+","+msgSend);
    183. /** 判断是否选中了私法 */
    184. if(isPrivateBn.isSelected()){
    185. /** 私法 */
    186. flag = 3 ;//私发消息
    187. }
    188. }
    189. DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
    190. dos.writeInt(flag); // 群发消息 发送给所有人
    191. dos.writeUTF(msgSend);
    192. if(flag == 3){
    193. // 告诉服务端我对谁私发
    194. dos.writeUTF(selectName.trim());
    195. }
    196. dos.flush();
    197. } catch (Exception e1) {
    198. e1.printStackTrace();
    199. }
    200. }
    201. smsSend.setText(null);
    202. break;
    203. }
    204. }
    205. }
    206. class ClientReader extends Thread {
    207. private Socket socket;
    208. private ClientChat clientChat ;
    209. public ClientReader(ClientChat clientChat, Socket socket) {
    210. this.clientChat = clientChat;
    211. this.socket = socket;
    212. }
    213. @Override
    214. public void run() {
    215. try {
    216. DataInputStream dis = new DataInputStream(socket.getInputStream());
    217. /** 循环一直等待客户端的消息 */
    218. while(true){
    219. /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
    220. int flag = dis.readInt();
    221. if(flag == 1){
    222. // 在线人数消息回来了
    223. String nameDatas = dis.readUTF();
    224. // 展示到在线人数的界面
    225. String[] names = nameDatas.split(Constants.SPILIT);
    226. clientChat.onLineUsers.setListData(names);
    227. }else if(flag == 2){
    228. // 群发消息
    229. String msg = dis.readUTF() ;
    230. clientChat.smsContent.append(msg);
    231. //滾動到底端
    232. clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
    233. }
    234. }
    235. } catch (Exception e) {
    236. e.printStackTrace();
    237. }
    238. }
    239. }
    1. package com.itheima.d9_chat.即时通信;
    2. public class Constants {
    3. /** 常量 */
    4. public static final int PORT = 7778 ;
    5. /** 协议分隔符 */
    6. public static final String SPILIT = "003197♣♣㏘♣④④♣";
    7. }
    1. package com.itheima.d9_chat.即时通信;
    2. import java.io.DataInputStream;
    3. import java.io.DataOutputStream;
    4. import java.net.ServerSocket;
    5. import java.net.Socket;
    6. import java.text.SimpleDateFormat;
    7. import java.util.Collection;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. import java.util.Set;
    11. /**
    12. * @Author
    13. * @Email dlei0009@163.com
    14. */
    15. public class ServerChat {
    16. /** 定义一个集合存放所有在线的socket */
    17. public static Map onLineSockets = new HashMap<>();
    18. public static void main(String[] args) {
    19. try {
    20. /** 注册端口 */
    21. ServerSocket serverSocket = new ServerSocket(Constants.PORT);
    22. /** 循环一直等待所有可能的客户端连接 */
    23. while(true){
    24. Socket socket = serverSocket.accept();
    25. /** 把客户端的socket管道单独配置一个线程来处理 */
    26. new ServerReader(socket).start();
    27. }
    28. } catch (Exception e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }
    33. class ServerReader extends Thread {
    34. private Socket socket;
    35. public ServerReader(Socket socket) {
    36. this.socket = socket;
    37. }
    38. @Override
    39. public void run() {
    40. DataInputStream dis = null;
    41. try {
    42. dis = new DataInputStream(socket.getInputStream());
    43. /** 循环一直等待客户端的消息 */
    44. while(true){
    45. /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
    46. int flag = dis.readInt();
    47. if(flag == 1){
    48. /** 先将当前登录的客户端socket存到在线人数的socket集合中 */
    49. String name = dis.readUTF() ;
    50. System.out.println(name+"---->"+socket.getRemoteSocketAddress());
    51. ServerChat.onLineSockets.put(socket, name);
    52. }
    53. writeMsg(flag,dis);
    54. }
    55. } catch (Exception e) {
    56. System.out.println("--有人下线了--");
    57. // 从在线人数中将当前socket移出去
    58. ServerChat.onLineSockets.remove(socket);
    59. try {
    60. // 从新更新在线人数并发给所有客户端
    61. writeMsg(1,dis);
    62. } catch (Exception e1) {
    63. e1.printStackTrace();
    64. }
    65. }
    66. }
    67. private void writeMsg(int flag, DataInputStream dis) throws Exception {
    68. // DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
    69. // 定义一个变量存放最终的消息形式
    70. String msg = null ;
    71. if(flag == 1){
    72. /** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
    73. /** onlineNames = [徐磊,zhangsan,李刚]*/
    74. StringBuilder rs = new StringBuilder();
    75. Collection onlineNames = ServerChat.onLineSockets.values();
    76. // 判断是否存在在线人数
    77. if(onlineNames != null && onlineNames.size() > 0){
    78. for(String name : onlineNames){
    79. rs.append(name+ Constants.SPILIT);
    80. }
    81. // 徐磊003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣李刚003197♣♣㏘♣④④♣
    82. // 去掉最后的一个分隔符
    83. msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));
    84. /** 将消息发送给所有的客户端 */
    85. sendMsgToAll(flag,msg);
    86. }
    87. }else if(flag == 2 || flag == 3){
    88. // 读到消息 群发的 或者 @消息
    89. String newMsg = dis.readUTF() ; // 消息
    90. // 得到发件人
    91. String sendName = ServerChat.onLineSockets.get(socket);
    92. // 李刚 时间
    93. // 内容--
    94. StringBuilder msgFinal = new StringBuilder();
    95. // 时间
    96. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
    97. if(flag == 2){
    98. msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
    99. msgFinal.append(" ").append(newMsg).append("\r\n");
    100. sendMsgToAll(flag,msgFinal.toString());
    101. }else if(flag == 3){
    102. msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n");
    103. msgFinal.append(" ").append(newMsg).append("\r\n");
    104. // 私发
    105. // 得到给谁私发
    106. String destName = dis.readUTF();
    107. sendMsgToOne(destName,msgFinal.toString());
    108. }
    109. }
    110. }
    111. /**
    112. * @param destName 对谁私发
    113. * @param msg 发的消息内容
    114. * @throws Exception
    115. */
    116. private void sendMsgToOne(String destName, String msg) throws Exception {
    117. // 拿到所有的在线socket管道 给这些管道写出消息
    118. Set allOnLineSockets = ServerChat.onLineSockets.keySet();
    119. for(Socket sk : allOnLineSockets){
    120. // 得到当前需要私发的socket
    121. // 只对这个名字对应的socket私发消息
    122. if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){
    123. DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
    124. dos.writeInt(2); // 消息类型
    125. dos.writeUTF(msg);
    126. dos.flush();
    127. }
    128. }
    129. }
    130. private void sendMsgToAll(int flag, String msg) throws Exception {
    131. // 拿到所有的在线socket管道 给这些管道写出消息
    132. Set allOnLineSockets = ServerChat.onLineSockets.keySet();
    133. for(Socket sk : allOnLineSockets){
    134. DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
    135. dos.writeInt(flag); // 消息类型
    136. dos.writeUTF(msg);
    137. dos.flush();
    138. }
    139. }
    140. }
    1. package com.itheima.d9_chat.即时通信;
    2. public class User {
    3. private Integer id ;
    4. private String name ;
    5. public User(Integer id, String name) {
    6. this.id = id;
    7. this.name = name;
    8. }
    9. public Integer getId() {
    10. return id;
    11. }
    12. public void setId(Integer id) {
    13. this.id = id;
    14. }
    15. public String getName() {
    16. return name;
    17. }
    18. public void setName(String name) {
    19. this.name = name;
    20. }
    21. @Override
    22. public String toString() {
    23. return "User [id=" + id + ", name=" + name + "]";
    24. }
    25. }
  • 相关阅读:
    美术如何创建 skybox 贴图资源?
    python+Vue大学生课堂答疑论坛交互系统django
    FlinkCDC基础篇章1-安装使用
    Zabbix邮箱报警
    在 Mac 客户端 C++ 代码中使用 breakpad
    Jetpack:014-Jetpack中的小红点
    有意识的神经网络之对比网络层和后意识层
    我对需求分析的理解
    Android使用ANativeWindow更新surfaceView内容最简Demo
    计算机毕业设计ssm社区基层党建管理信息系统s5738系统+程序+源码+lw+远程部署
  • 原文地址:https://blog.csdn.net/daqi1983/article/details/133988745