最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

Java Socket编程|环球聚看点

来源:博客园

一:Socket介绍

Socket(中文:套接字)编程是网络常用的编程,我们通过在网络中创建Socket关键字来实现网络间的通信;要想解释清楚Socket,首先要知道TCP,要想知道TCP,那就得对TCP/IP的体系结构以及每一层的大概工作有所了解,那么我们就先来说说TCP/IP的分层。

1:ISO/OSI和TCP/IP模型

其实模型一共分为2种:ISO/OSI模型:即开放式通信系统互联参考模型(Open System Interconnection Reference Model),是国际标准化组织(ISO)提出的一个试图   使各种计算机在世界范围内互连为网络的标准框架,简称OSI。TCP/IP协议模型:即包含了一系列构成互联网基础的网络协议(Transmission Control Protocol/Internet Protocol),是Internet的核心协议,  通过20多年的发展已日渐成熟,并被广泛应用于局域网和广域网中,目前已成为事实上的国际标准。TCP/IP协议簇是一组不同层次上的  多个协议的组合,通常被认为是一个四层协议系统,与OSI的七层模型相对应。
具体的TCP/IP四层模型,有兴趣可以看看:应用层:应用层决定了向用户提供应用服务时通信的活动。应用层负责处理特定的应用程序细节。TCP/IP 协议族内预存了各类通用的应用服务。  比如:FTP(File Transfer Protocol,文件传输协议)和 DNS(Domain Name System,域名系统)服务就是其中两类。   HTTP 协议也处于该层。传输层:传输层对上层应用层提供处于网络连接中的两台计算机之间的数据 传输。  在传输层有两个性质不同的协议:        TCP(Transmission Control Protocol传输控制协议)        UDP(User Data Protocol用户数据报协议)  这两个协议主要为两台主机上的应用程序提供端到端的通信。  在TCP/IP协议族中,有两个互不相同的传输协议:TCP(传输控制协议)和UDP(用户数据报协议)。  TCP:为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层,确认接收到的  分组,设置发送最后确认分组的超时时钟等。由于运输层提供了高可靠性的端到端的通信,因此应用层可以忽略所有这些细节。为了提供  可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。  UDP:则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。  一个数据报是指从发送方传输到接收方的一个信息单元(例如,发送方指定的一定字节数的信息)。UDP协议任何必需的可靠性必须由应  用层来提供。网络层:网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计  算机,并把数据包传送给对方。与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多的选项内选择一  条传输路线。也称作互联网层(在图中为网络层),处理分组在网络中的活动,例如分组的选路。在TCP/IP协议族中,网络层协议  包括IP协议(网络协议),ICMP协议(Internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)。  IP:是一种网络层协议,提供的是一种不可靠的服务,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。    同时被TCP和UDP使用。TCP和UDP的每组数据都通过端系统和每个中间路由器中的IP层在互联网中进行传输。  ICMP:是IP协议的附属协议。IP层用它来与其它主机或路由器交换错误报文和其它重要信息。  IGMP:是Internet组管理协议。它用来把一个UDP数据报多播到多个主机。链路层:用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱动、NIC(Network Interface Card,网络适配器,即网卡),及  光纤等物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴均在链路层的作用范围之内。也称作数据链路层或网络接口  层(在第一个图中为网络接口层和硬件层),通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆  (或其他任何传输媒介)的物理接口细节。ARP(地址解析协议)和RARP(逆地址解析协议)是某些网络接口(如以太网和令牌环网)  使用的特殊协议,用来转换IP层和网络接口层使用的地址。

2:总结

链路层:对0和1进行分组,定义数据帧,确认主机的物理地址,传输数据;网络层:定义IP地址,确认主机所在的网络位置,并通过IP进行MAC寻址,对外网数据包进行路由转发;传输层:定义端口,确认主机上应用程序的身份,并将数据包交给对应的应用程序;应用层:定义数据格式,并按照对应的格式解读数据。把每层模型的职责串联起来,用一句通俗易懂的话讲就是:当你输入一个网址并按下回车键的时候,首先,应用层协议对该请求包做了格式定义;紧接着传输层协议加上了双方的端口号,确认了    双方通信的应用程序;然后网络协议加上了双方的IP地址,确认了双方的网络位置;最后链路层协议加上了双方的MAC地址,确认了双    方的物理位置,同时将数据进行分组,形成数据帧,采用广播方式,通过传输介质发送给对方主机。而对于不同网段,该数据包首先会    转发给网关路由器,经过多次转发后,最终被发送到目标主机。目标机接收到数据包后,采用对应的协议,对帧数据进行组装,然后再    通过一层一层的协议进行解析,最终被应用层的协议解析并交给服务器处理。

二:JavaSE实现Socket网络编程

1:基本梳理

InetAddress类:表示Internet协议(IP)地址。可以通过此类获取IP地址对象    其直接实现子类:Inet4Address(IPv4)、Inet6Address(IPv6)    常用方法及属性:        static InetAddress getLocalHost():返回本地主机的地址。        String getHostName():获取此IP地址的主机名。     //获取指定IP地址    InetAddress id = InetAddress.getByName("49.235.99.193");    System.out.println("获取当前指定IP的名称:" + id.getHostName());    // 获取当前指定IP的名称:49.235.99.193    System.out.println("获取当前本机的IP对象:" + InetAddress.getLocalHost());    // 获取当前本机的IP对象:DESKTOP-EHHFE8S/192.168.0.101 UDP操作:DatagramSocket类:此类表示用于发送和接收数据报数据包的套接字。    DatagramSocket() :构造数据报套接字并将其绑定到本地主机上的任何可用端口。     DatagramSocket(int port) :构造数据报套接字并将其绑定到本地主机上的指定端口。     DatagramSocket(int port, InetAddress laddr) :创建一个数据报套接字,绑定到指定的本地地址。     receive(DatagramPacket p) :从此套接字接收数据报包。     send(DatagramPacket p) :从此套接字发送数据报包。 DatagramPacket类:该类表示数据报包。 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) :    构造用于发送长度的分组数据报包length具有偏移 ioffset指定主机上到指定的端口号。          byte[] buf:数据包数据        int offset:数据包数据偏移量        int length:数据包数据长度        InetAddress address:目的地地址        int port:目标端口号    InetAddress getAddress() :返回该数据报发送或接收数据报的计算机的IP地址。      byte[] getData() :返回数据缓冲区。      int getLength() :返回要发送的数据的长度或接收到的数据的长度。      int getOffset() :返回要发送的数据的偏移量或接收到的数据的偏移量。      int getPort() :返回发送数据报的远程主机上的端口号,或从中接收数据报的端口号。 MulticastSocket类:组播数据报套接字类对发送和接收IP组播数据包很有用。void joinGroup(InetAddress mcastaddr) :加入组播组TCP操作:Socket类:该类实现客户端套接字(也称为“套接字”)。套接字是两台机器之间通讯的端点。 ServerSocket类:这个类实现了服务器套接字。 服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,    然后可能将结果返回给请求者。 

2:Java实现UDP通信(单播)

UDP是面向无连接通信协议,这种协议可以单播、组播、群播这3种方式;发送速度快,但是一次性只能发送最多64K大小,而且数据发送也不安全,容易丢失数据。

/** * @author Anhui OuYang * @version 1.0 * UDP接收端(先启动)**/public class UDPReceive {        public static void main(String[] args) throws UnknownHostException {        //此类表示用于发送和接收数据报数据包的套接字。        DatagramSocket datagramSocket = null;        try {            //绑定到10086端口,方便从10086端口接收数据(此时我们编写的是接收端)            datagramSocket = new DatagramSocket(10086);            //用来接收消息的包            byte[] bytes = new byte[1024];            DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);            //接收数据            datagramSocket.receive(datagramPacket);            byte[] data = datagramPacket.getData();     // 获取数据            int length = datagramPacket.getLength();    // 获取数据长度            InetAddress address = datagramPacket.getAddress();  // 接收到哪个ip发来的数据            int port = datagramPacket.getPort();    // 对方使用哪个端口发送的            String str = new String(data, 0, length, StandardCharsets.UTF_8);            System.out.println("接收数据:");            System.out.println(str);            System.out.println("从哪个ip发送来的数据:" + address + " 对方使用哪个端口发送数据:" + port);            //打印消息:            //  接收数据:            //  一个简单下消息:            //  您好朋友            //  从哪个ip发送来的数据:/192.168.0.101 对方使用哪个端口发送数据8081        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            //关闭Socket对象            if (datagramSocket != null) {                datagramSocket.close();            }        }    }}
/** * @author Anhui OuYang * @version 1.0 * UDP发送端**/public class UDPSend {        public static void main(String[] args) throws UnknownHostException {        //此类表示用于发送和接收数据报数据包的套接字。        DatagramSocket datagramSocket = null;        try {            //获取指定IP地址            InetAddress id = InetAddress.getByName("192.168.0.101");            //绑定8081端口,从8081端口发送数据(此时我们编写的是发送端)            datagramSocket = new DatagramSocket(8081);            //把要发送的消息打包            byte[] bytes = "一个简单下消息:\r\n您好朋友".getBytes(StandardCharsets.UTF_8);            DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, id, 10086);            //发送消息            datagramSocket.send(datagramPacket);        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            //关闭Socket对象            if (datagramSocket != null) {                datagramSocket.close();            }        }    }}

3:Java实现UDP通信(组播、广播)

组播实现:借助 MulticastSocket 类来实现,这个类是一个(UDP)DatagramSocket,具有加入互联网上其他组播主机的“组”的附加功能。     组播组由D类IP地址和标准UDP端口号指定。D类IP地址范围为224.0.0.0至239.255.255.255(含)。地址224.0.0.0是保留的,    不应该使用。    一个可以通过首先创建具有所需端口的MulticastSocket来加入多播组,然后调用joinGroup(InetAddress groupAddr)方法:
/** * @author Anhui OuYang * @version 1.0 * 组播接收者A **/public class UDPMulticastReceiveA {    public static void main(String[] args) throws IOException {        MulticastSocket multicastSocket = null;        try {            //创建组播接收者(这里绑定10086端口用来接收数据)            multicastSocket = new MulticastSocket(10086);            //将当前本机添加到224.0.0.1的这一组中(这样就可以接收到组发来的数据)            InetAddress byName = InetAddress.getByName("224.0.0.1");            multicastSocket.joinGroup(byName);            //用来接收消息的包            byte[] bytes = new byte[1024];            DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);            //接收数据            multicastSocket.receive(datagramPacket);            //打印数据            byte[] data = datagramPacket.getData();     // 获取组播数据            int length = datagramPacket.getLength();    // 获取组播数据长度            InetAddress address = datagramPacket.getAddress();  // 接收到哪个ip发来的组播数据            int port = datagramPacket.getPort();    // 对方使用哪个端口发送的            String str = new String(data, 0, length, StandardCharsets.UTF_8);            System.out.println("接收A:接收组播数据:");            System.out.println(str);            System.out.println("从哪个ip发送来的组播数据:" + address + " 对方使用哪个端口发送数据:" + port);        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            if (multicastSocket != null) {                //关闭                multicastSocket.close();            }        }    }}
组播接收者A(UDPMulticastReceiveA)
/** * @author Anhui OuYang * @version 1.0 * 组播接收者B **/public class UDPMulticastReceiveB {    public static void main(String[] args) throws IOException {        MulticastSocket multicastSocket = null;        try {            //创建组播接收者(这里绑定10086端口用来接收数据)            multicastSocket = new MulticastSocket(10086);            //将当前本机添加到224.0.0.1的这一组中(这样就可以接收到组发来的数据)            InetAddress byName = InetAddress.getByName("224.0.0.1");            multicastSocket.joinGroup(byName);            //用来接收消息的包            byte[] bytes = new byte[1024];            DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);            //接收数据            multicastSocket.receive(datagramPacket);            //打印数据            byte[] data = datagramPacket.getData();     // 获取组播数据            int length = datagramPacket.getLength();    // 获取组播数据长度            InetAddress address = datagramPacket.getAddress();  // 接收到哪个ip发来的组播数据            int port = datagramPacket.getPort();    // 对方使用哪个端口发送的            String str = new String(data, 0, length, StandardCharsets.UTF_8);            System.out.println("接收B:接收组播数据:");            System.out.println(str);            System.out.println("从哪个ip发送来的组播数据:" + address + " 对方使用哪个端口发送数据:" + port);        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            if (multicastSocket != null) {                //关闭                multicastSocket.close();            }        }    }}
组播接收者B(UDPMulticastReceiveB)
/** * @author Anhui OuYang * @version 1.0 * 组播发送者 **/public class UDPMulticastSend {    public static void main(String[] args) {        MulticastSocket multicastSocket = null;        try {            //创建组播Socket            multicastSocket = new MulticastSocket();            //将当前本机添加到224.0.0.1的这一组中(发送数据到这一组)            // IP地址范围为224.0.0.0(特殊不可用)至239.255.255.255(包含)            InetAddress groupId = InetAddress.getByName("224.0.0.1");            multicastSocket.joinGroup(groupId);            //把要发送的消息打包            byte[] bytes = "一个简单下消息:\r\n您好朋友".getBytes(StandardCharsets.UTF_8);            DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, groupId, 10086);            //发送消息            multicastSocket.send(datagramPacket);        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            if (multicastSocket != null) {                //关闭                multicastSocket.close();            }        }    }}
组播发送者(UDPMulticastSend)

广播实现:广播实现是最简单的,在单播的基础上把发送端的IP改为255.255.255.255,这时候则会对当前局域网内的所有ip发送数据


【资料图】

4:Java实现TCP通信(发送接收应答)

TCP协议是面向连接的通信协议。速度慢,没有大小限制,数据安全

/** * @author Anhui OuYang * @version 1.0 *TCP服务端(先启动服务端)**/public class TCPDemoService {    public static void main(String[] args) {        try {            //TCP服务端(监听10086端口,等待客户端发送数据到这个端口)            ServerSocket serverSocket = new ServerSocket(10086);            //接收消息            Socket accept = serverSocket.accept();            //服务的获取流,并接收数据(客户端传的是文本)            System.out.println("开始接收数据!");            BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));            String str = "";            while ((str = br.readLine()) != null) {                System.out.println("打印数据:" + str);            }            //告知客户端,服务端接收数据完成            accept.shutdownInput();            System.out.println("数据接收完成,准备断开连接");            //发送数据告知客户端,服务端已经处理本次消息            OutputStream outputStream = accept.getOutputStream();            byte[] bytes = "服务器处理完成".getBytes(StandardCharsets.UTF_8);            outputStream.write(bytes, 0, bytes.length);            //告知客户端,服务端写出的数据完成(注:不这么写会报:java.net.SocketException: Connection reset)            //因为客户端会一直读服务端返回的数据,此时没有这个方法,则服务端执行close直接关闭了,那么客户端执            //行inputStream.read()就会出现问题,因为服务端都关闭了,你们客户端执行read方法肯定报错            accept.shutdownOutput();            //关闭Socket连接            serverSocket.close();        } catch (IOException e) {            throw new RuntimeException(e);        }    }}
/** * @author Anhui OuYang * @version 1.0 * TCP客户端**/public class TCPDemoClient {    public static void main(String[] args) {        try {            //创建Socket,并且连接服务器127.0.0.0:10086的服务器上            Socket socket = new Socket("127.0.0.1", 10086);            //获取网络输出流            OutputStream outputStream = socket.getOutputStream();            //发送数据给服务端            byte[] bytes = "今天真漂亮\r\n啦啦啦".getBytes(StandardCharsets.UTF_8);            outputStream.write(bytes, 0, bytes.length);            //告知服务器,数据发送结束            socket.shutdownOutput();            //等待获取服务器发送过来的处理成功消息            InputStream inputStream = socket.getInputStream();            byte[] bytes1 = new byte[1024];            int s;            while ((s = inputStream.read(bytes1)) != -1) {                System.out.print(new String(bytes1, 0, s, StandardCharsets.UTF_8));            }            //关闭Socket,关闭这个系统会默认先关闭outputStream            socket.close();        } catch (IOException e) {            throw new RuntimeException(e);        }    }}

5:Java实现TCP通信(文件上传)

/** * @author Anhui OuYang * @version 1.0 * 文件上传服务端(先启动)**/public class TCPUploadFileService {    public static void main(String[] args) {        try {            //创建服务端的Socket,并监听指定端口            ServerSocket serverSocket = new ServerSocket(10086);            //监听指定端口(等待数据的发来)            Socket socket = serverSocket.accept();            //从Socket里获取网络输入流(这个流有用户传来的数据信息)            BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());            //获取服务器的输出流,用来写到服务器的资源文件夹下            //获取资源路径(后面用来存储文件的)            URL url = TCPUploadFileClient.class.getResource("/file");            assert url != null; //断言明确肯定url不为null            String fileName = UUID.randomUUID() + ".zip";            FileOutputStream fileOutputStream = new FileOutputStream(url.getFile() + "/" + fileName);            //包装一层缓冲流            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);            //写入资源到服务器资源下            byte[] bytes = new byte[1024];            int index = 0;            while ((index = bufferedInputStream.read(bytes)) != -1) {                bufferedOutputStream.write(bytes, 0, index);            }            //告知服务端接收文件成功(写出文件信息)(其实下载也是一个样子,只不过是流的信息是颠倒的)            socket.shutdownInput();            //写出成功信息            BufferedWriter bufferedWriter =           new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));            bufferedWriter.write("文件上传成功,上传位置:" + url.getFile() + "/" + fileName);            bufferedWriter.flush();            bufferedWriter.newLine();            bufferedWriter.close();            //关闭Socket还得关闭我们自己创建的流            socket.close();            bufferedOutputStream.close();            bufferedWriter.close();        } catch (IOException e) {            throw new RuntimeException(e);        }    }}
/** * @author Anhui OuYang * @version 1.0 * 文件上传客户端(上传文件到客户端) **/public class TCPUploadFileClient {    public static void main(String[] args) {        try {            //创建Socket,并连接到指定的服务器            Socket socket = new Socket("192.168.0.100", 10086);            //获取项目下指定文件的路径资源流(file/testUser/jdk.zip需要提前在resources资源目录下定义好)            //发送jdk.zip给服务端            InputStream inputStream = TCPUploadFileClient.class                    .getResourceAsStream("/file/testUser/jdk.zip");            //获取Socket的网络流(输出)            OutputStream outputStream = socket.getOutputStream();            //写出jdk.zip数据发送到客户端            byte[] bytes = new byte[1024];            int index = 0;            while ((index = inputStream.read(bytes)) != -1) {                outputStream.write(bytes, 0, index);            }            //告知服务端写出数据结束            socket.shutdownOutput();            //接收服务端返回来的信息            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));            System.out.println("服务端返回:" + bufferedReader.readLine());            socket.shutdownInput();            //关闭Socket还得关闭我们自己创建的流            socket.close();            inputStream.close();            bufferedReader.close();        } catch (IOException e) {            throw new RuntimeException(e);        }    }}

使用Java自带的Socket完成了基本的TCP和UDP的案例,但是企业中大部分都是使用Web方式或者SpringBoot集成的方式完成,后面会详细介绍。

三:WebApp方式实现Socket网络编程

在这一节将使用普通的WebSocket的方式来完成基本的聊天功能的实现,这里将不详细介绍,具体的在SpringBoot集成的那节介绍;所以这里直接上代码和示例:

    4.0.0    org.example    websocket    war    1.0-SNAPSHOT                                        jakarta.websocket            jakarta.websocket-api            2.1.0            provided                                    org.apache.tomcat            tomcat-websocket            9.0.74                                    org.projectlombok            lombok            1.18.26                                    com.alibaba.fastjson2            fastjson2            2.0.29                                            org.apache.logging.log4j            log4j-api            2.17.1                                    org.apache.logging.log4j            log4j-core            2.17.1                                    org.slf4j            slf4j-api            1.7.35                                    org.apache.logging.log4j            log4j-slf4j-impl            2.17.1                                                                        org.apache.maven.plugins                maven-compiler-plugin                3.8.1                                    1.8                    1.8                    UTF-8                    true                    false                    false                    false                    128M                    512M                                        
pom.xml坐标文件
                                        ./logs                                                                                                        ${myPattern}                                                                                    
log4j2.xml日志配置文件
import com.alibaba.fastjson2.JSONObject;import lombok.extern.slf4j.Slf4j;import javax.websocket.*;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * @author Anhui OuYang * @version 1.0 * 注:WebSocket是多例对象,每次一个用户请求都会创建一个新的WebSocket供本次操作 * WebSocket文件 **/@Slf4j@ServerEndpoint("/test/{name}")public class WebSocket {    //当前客户端连接数    private static Integer onlineCount = 0;    //用来记录处于活跃的Socket连接    private static Map clients = new ConcurrentHashMap();    //用来记录当前连接登录的Session    private Session session;    //用来记录当前登录人信息    private String userMessage;    /***     * 创建连接时触发(有客户端请求时打印)     * @param username 代表地址参数中的{name}信息,用于接收URL传递的参数(这里代表是谁提交的信息)     * @param session 当前建立的连接     */    @OnOpen    public void onOpen(@PathParam("name") String username, Session session) {        log.info("{}触发 >>> 创建连接触发onOpen()方法设置信息!并设置记录信息", username);        this.session = session;        this.userMessage = username;        clients.put(username, this);    // 当前Socket存起来        addOnlineCount();               // 当前连接数加1    }    /***     * 接收到信息时触发;用于接收客户端发送来的消息,(具体按照业务编写内部代码)     * 如两个人聊天:应该是看接收到的消息解析后,看看具体发送给谁的,然后转发给另外一个人     * 比如传来的message可以是如下格式:{"to":"jack","message","您好"};这时解析后就知道是发送给jack     * 这时我只需要在 “clients” 集合里找到具体的人转发即可     * @param session 当前用户连接的Session对象     * @param message 当前用户发送来的消息(一般为Json数据,好解析)     */    @OnMessage    public void onMessage(Session session, String message) {        log.info("{}触发 >>> 接收到信息触发onMessage()方法,处理信息!并返回结果", this.userMessage);        //解析Json(并获取消息和消息发给谁)        JSONObject jsonObject = JSONObject.parseObject(message);        String msgData = jsonObject.getString("toMessage");        String toName = jsonObject.getString("toName");        //根据信息名称去集合查询用户信息        WebSocket webSocket = getClients().get(toName);        //判断当前的WebSocket是否存在0        if (webSocket != null) {            //获取在集合中查询到的Session用户信息,并进行远程调用            RemoteEndpoint.Async asyncRemote = webSocket.getSession().getAsyncRemote();            //拼接数据            String str = "【" + this.userMessage + "】在[" +                    new SimpleDateFormat("HH:mm:ss").format(new Date()) + "]:" + msgData;            asyncRemote.sendText(str);        } else {            session.getAsyncRemote().sendText("当前用户不在线,请稍后联系....");        }    }    /***     * 通讯异常时触发     * @param session 当前用户连接的Session对象     * @param e 异常信息     */    @OnError    public void onError(Session session, Throwable e) {        log.info("{}触发 >>> 通讯异常触发onError()方法,异常信息为:{}", this.userMessage, e.getMessage());    }    /***     * 连接断开时触发     * @param session 当前用户连接的Session对象     */    @OnClose    public void onClose(Session session) {        log.info("{}触发 >>> 连接断开触发onClose()方法,结束连接,关闭:{} 的连接", this.userMessage, this.userMessage);        clients.remove(this.userMessage);        subOnlineCount();    }    /***     * 获取当前客户端连接数     * @return Integer     */    public static synchronized Integer getOnlineCount() {        return onlineCount;    }    /***     * 当前客户端连接数+1     */    public static synchronized void addOnlineCount() {        WebSocket.onlineCount++;    }    /***     * 当前客户端连接数-1     */    public static synchronized void subOnlineCount() {        WebSocket.onlineCount--;    }    /***     * 获取session信息     * @return Session     */    public Session getSession() {        return session;    }    /***     * 获取当前全部活跃的连接     * @return 活跃集合     */    public static synchronized Map getClients() {        return clients;    }}
                聊天页面    



显示Websocket请求信息:

<script type="text/javascript"> //定义webSocket对象 let websocket = null; //定义发送函数 function connection() { //定义谁连接Socket let username = document.getElementById("username").value; //判断当前浏览器是否支持WebSocket if ("WebSocket" in window) { websocket = new WebSocket("ws://192.168.0.106:8080/websocket/test/" + username); } else if ("MozWebSocket" in window) { websocket = new MozWebSocket("ws://192.168.0.106:8080/websocket/test/" + username); } else { websocket = new SockJS("192.168.0.106:8080/websocket/test/" + username); } //连接发生错误的回调方法 websocket.onerror = function () { let element = document.createElement("p"); element.textContent = "WebSocket连接发生错误..." document.getElementById("wsMsg").appendChild(element) } //连接成功建立的回调方法 websocket.onopen = function () { let element = document.createElement("p"); element.textContent = "WebSocket连接成功..." + username document.getElementById("wsMsg").appendChild(element) } //接收到消息的回调方法 websocket.onmessage = function (event) { let element = document.createElement("p"); element.textContent = "数据接收成功..." + event.data document.getElementById("wsMsg").appendChild(element) //写出到信息栏 let element1 = document.createElement("p"); element1.textContent = event.data document.getElementById("dv").appendChild(element1) } //连接关闭的回调方法 websocket.onclose = function () { let element = document.createElement("p"); element.textContent = "WebSocket连接关闭..." document.getElementById("wsMsg").appendChild(element) } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } } //定义发送ws函数 function sendMessage() { //获取信息 let username = document.getElementById("username").value; let toName = document.getElementById("toName").value let toMessage = document.getElementById("toMessage").value //定义发送的数据 // let sendJson = "{"toName":"+"toName"+"}" let sendJson = `{"toName":"${toName}","toMessage":"${toMessage}"}`; if (websocket != null) { websocket.send(sendJson) //写出到信息栏 let element = document.createElement("p"); element.textContent = "【" + username + "】:" + toMessage document.getElementById("dv").appendChild(element) } } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); }</script>

效果图:

四:使用SpringBoot集成WebSocket原生方式

其实使用这种方式和上面的使用WebApp的方式基本上都是一样,在这里只需要把WebSocket交给Spring容器管理即可,其它的代码和上面的一样,这里我就简单把不一样的代码写出,其实这种我更推荐下面一种方式,使用SpringBoot来处理封装WebSocket的方式。

    4.0.0            org.springframework.boot        spring-boot-starter-parent        2.7.11        cn.xw    SpringBootWebSocket    0.0.1-SNAPSHOT    SpringBootWebSocket    SpringBootWebSocket                                org.springframework.boot            spring-boot-starter                                                            org.springframework.boot                    spring-boot-starter-logging                                                                org.springframework.boot            spring-boot-starter-log4j2                                    org.springframework.boot            spring-boot-starter-web                                    org.projectlombok            lombok            1.18.26                                    org.springframework.boot            spring-boot-starter-websocket                                    com.alibaba.fastjson2            fastjson2            2.0.31                                                                org.apache.maven.plugins                maven-compiler-plugin                3.8.1                                    1.8                    1.8                    UTF-8                    true                    false                    false                    false                    128M                    512M                                        
pom.xml坐标文件代码
## 核心代码,把WebSocket交给Spring处理;剩下的代码就和上面的案例一样的,可以任意位置编写/** * @author Anhui OuYang * @version 1.0 **/@SpringBootConfigurationpublic class WebSocketConfig {    /***     * 向Spring容器中注入这个ServerEndpointExporter对象,配置WebSocket的服务器端点导出器     * 该实例将会自动扫描Spring容器中所有标注了@ServerEndpoint注解的类,并将其添加到WebSocket服务器中。     * 同时,它还会管理WebSocket服务器的生命周期,如开启和关闭等。     * @return WebSocket的服务器端点     */    @Bean(value = "serverEndpointExporter")    public ServerEndpointExporter serverEndpointExporter() {        return new ServerEndpointExporter();    }}

五:SpringBoot集成WebSocket(重点)

这种方式的集成和上面的几种方式不一样,这种SpringBoot集成的方式是对其作了封装,使我们有更多的灵活性,比如可以编写拦截器等等操作;还需要导入指定的坐标,如下:

                    org.springframework.boot            spring-boot-starter-websocket        

1:WebSocket配置类

WebSocketHandlerRegistry:它是Spring Framework中用于注册并管理WebSocket处理程序的类。它是WebSocketConfigurer接口的一部分,    它定义了一个方法registerWebSocketHandlers(WebSocketHandlerRegistry registry),    该方法用于注册和配置WebSocket处理程序实例。    WebSocketHandlerRegistry提供了一些方法,可以用于注册WebSocket处理程序、设置跨域访问规则,设置拦截器等。其主要方法如下:       ①:addHandler(WebSocketHandler handler, String... paths):注册WebSocket处理程序,并指定处理程序可访问的路径;       ②:setAllowedOrigins(String... origins):设置允许跨域访问的域名列表;       ③:addInterceptors(HandshakeInterceptor... interceptors):添加WebSocket握手拦截器;        ④:setHandshakeHandler(HandshakeHandler handshakeHandler):设置WebSocket握手处理程序;       ⑤:setTaskScheduler(TaskScheduler taskScheduler):设置任务调度程序。(注:需要SpringBoot 2.7.11版本以上)通过WebSocketHandlerRegistry,开发人员可以灵活地配置WebSocket服务器,以满足特定的业务需求和安全要求。
@Configurable       //代表当前是一个配置类@EnableWebSocket    // 开启WebSocket的自动配置public class WebSocketConfig implements WebSocketConfigurer {    /***     * registerWebSocketHandlers方法用于注册WebSocket处理程序     * @param registry 配置信息类     */    @Override    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {        registry.addHandler(null, "/websocket/**")  // 注册WebSocket处理程序                .setAllowedOrigins("*")             // 设置允许跨域访问                .addInterceptors(null)              // 添加WebSocket握手拦截器(后面需要实现)                .setHandshakeHandler(null);         // 设置WebSocket握手处理程序(后面需要实现)    }}

2:WebSocket拦截器

其实拦截器有两种,具体看业务上的选择,这里我选择Http方式的拦截器

HandshakeInterceptor接口分别有两个实现类:HttpSessionHandshakeInterceptor:是基于HTTP会话的WebSocket握手拦截器。在握手之前,它会基于当前HTTP请求的会话信息来添加 WebSocket 握手的请求头;        在握手之后,它会通过检查握手请求头来确定是否要创建或关闭会话以及报告任何错误。   WebSocketHandshakeInterceptor:是基于WebSocket协议的握手拦截器。在握手之前,它会检查WebSocket握手请求和响应头,并在需要时添加或删除必要的消息头;        在握手之后,它会通过检查握手请求和响应标头来确保协议交换已成功,如果不成功,它会关闭WebSocket连接并报告任何错误。
/** * WebSocket握手拦截器,检查握手请求和响应,对WebSocketHandler传递属性,用于区别WebSocket * * @author Anhui OuYang * @version 1.0 **/@Slf4jpublic class MyWebSocketInterceptorextends HttpSessionHandshakeInterceptor {    /***     * 握手之前被调用     * @param request 请求信息     * @param response 响应信息     * @param wsHandler 用于处理WebSocket通信过程中的各种事件和消息     * @param attributes 如果需要,可以使用setAttribute方法添加属性,这些属性可以在后续处理中使用     * @return 返回true表示继续握手,或者返回false以终止握手     * @throws Exception 异常信息     */    @Override    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,                                   WebSocketHandler wsHandler, Map attributes) throws Exception {        log.info("请求被拦截器拦截,当前请求地址为:{}", request.getURI());        //在拦截器解析信息,并设置到 attributes 中,后续程序可以在这里面取数据        // 示例请求地址:http://localhost:8080/websocket/tom  因为这个restFul风格的地址,所以那个tom我需要拿到        String path = request.getURI().getPath();        String name = path.substring(path.lastIndexOf("/") + 1);        attributes.put("loginName", name);        log.info("请求握手成功,当前的登录人为:{}", attributes.get("loginName"));        return super.beforeHandshake(request, response, wsHandler, attributes);    }    /***     * 握手成功之后或者失败之后被调用;可以利用这个方法去清理任何未完成的状态并记录异常。     */    @Override    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,                               WebSocketHandler wsHandler, Exception ex) {        log.info("请求被拦截器拦截放行。。。");        super.afterHandshake(request, response, wsHandler, ex);    }}

3:WebSocket消息处理类

继承:TextWebSocketHandler或者继承BinaryWebSocketHandler 是Spring WebSocket框架为我们提供的一个处理WebSocket消息的默认处理器,它可以方便地处理文本消息。    ①:继承TextWebSocketHandler:        我们需要重写handleTextMessage方法,以便在处理到达的文本消息时调用自己的处理程序。但除了这个还有好多方法    ②:继承BinaryWebSocketHandler:        可以处理来自非文本类型的消息以TextWebSocketHandler来说(包含如下方法):    ①:handleTextMessage():处理WebSocket文本消息。    ②:afterConnectionEstablished():在WebSocket连接建立后调用。    ③:afterConnectionClosed():在WebSocket连接关闭后调用。    ④:handleTransportError():在WebSocket传输错误时调用。    ⑤:supportsPartialMessages():返回是否支持部分消息。以BinaryWebSocketHandler来说(包含如下方法):    ①:handleBinaryMessage():处理WebSocket二进制消息。    ②:afterConnectionClosed():在WebSocket连接关闭后调用。    ③:afterConnectionEstablished():在WebSocket连接建立后调用。    ④:handleTransportError():在WebSocket传输错误时调用。    ⑤:supportsPartialMessages():返回是否支持部分消息。
/** * WebSocket消息处理,这里以TextWebSocketHandler文本的处理方式 * * @author Anhui OuYang * @version 1.0 **/@Slf4j@Componentpublic class SimpleWebSocketMessageHandler extends TextWebSocketHandler {    //当前客户端连接数    private static Integer onlineCount = 0;    //用来记录处于活跃的Socket连接    private static Map allClients = new HashMap<>();    /***     * 在WebSocket连接建立后调用     */    @Override    public void afterConnectionEstablished(WebSocketSession session) throws Exception {        log.info("afterConnectionEstablished() 方法执行,在WebSocket连接建立后调用....");        //保存当前连接Socket的信息        String name = String.valueOf(session.getAttributes().get("loginName"));        if (name != null) {            allClients.put(name, session);      // 把当前用户存起来            addOnlineCount();                   // 当前连接数加1        }    }    /***     * 处理WebSocket文本消息(若处理二进制非文本数据则使用handleMessage()方法或者继承BinaryWebSocketHandler类)     */    @Override    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {        log.info("handleTextMessage() 方法执行,处理WebSocket文本消息.... 调用人:{}"                , session.getAttributes().get("loginName"));        //解析Json(并获取消息和消息发给谁)        JSONObject jsonObject = JSONObject.parseObject(message.getPayload());        String msgData = jsonObject.getString("toMessage");        String toName = jsonObject.getString("toName");        //根据信息名称去集合查询用户信息        WebSocketSession webSocketSession = getAllClients().get(toName);        //判断当前的WebSocket是否存在        if (webSocketSession != null && webSocketSession.isOpen()) {            //拼接发送的消息和包装消息对象            String str = "【" + session.getAttributes().get("loginName") + "】在[" +                    new SimpleDateFormat("HH:mm:ss").format(new Date()) + "]:" + msgData;            TextMessage textMessage = new TextMessage(str);            //获取在集合中查询到的WebSocketSession用户信息,并进行远程调用            webSocketSession.sendMessage(textMessage);        }    }    /***     * 在WebSocket传输错误时调用     */    @Override    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {        log.info("handleTransportError()  方法执行,在WebSocket传输错误时调用....");        super.handleTransportError(session, exception);    }    /***     * 在WebSocket连接关闭后调用     */    @Override    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {        log.info("afterConnectionClosed() 方法执行,在WebSocket连接关闭后调用....");        super.afterConnectionClosed(session, status);    }    /***     * 返回是否支持部分消息     */    @Override    public boolean supportsPartialMessages() {        log.info("supportsPartialMessages() 方法执行,方法返回是否支持部分消息....");        return super.supportsPartialMessages();    }    /***     * 获取当前客户端连接数     * @return Integer     */    public static synchronized Integer getOnlineCount() {        return onlineCount;    }    /***     * 当前客户端连接数+1     */    public static synchronized void addOnlineCount() {        SimpleWebSocketMessageHandler.onlineCount++;    }    /***     * 当前客户端连接数-1     */    public static synchronized void subOnlineCount() {        SimpleWebSocketMessageHandler.onlineCount--;    }    /***     * 获取当前全部活跃的连接     * @return 活跃集合     */    public static Map getAllClients() {        return allClients;    }}

4:最后一步(修改配置和前端页面)

@SpringBootConfiguration       //代表当前是一个配置类@EnableWebSocket    // 开启WebSocket的自动配置public class WebSocketConfig implements WebSocketConfigurer {    //注入对象    private final SimpleWebSocketMessageHandler simpleWebSocketMessageHandler;    @Autowired    public WebSocketConfig(SimpleWebSocketMessageHandler simpleWebSocketMessageHandler) {        this.simpleWebSocketMessageHandler = simpleWebSocketMessageHandler;    }    /***     * registerWebSocketHandlers方法用于注册WebSocket处理程序     * @param registry 配置信息类     */    @Override    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {        registry.addHandler(simpleWebSocketMessageHandler, "/websocket/**")  // 注册WebSocket处理程序                .setAllowedOrigins("*")                             // 设置允许跨域访问                .addInterceptors(new MyWebSocketInterceptor())      // 添加WebSocket握手拦截器(后面需要实现)                .setHandshakeHandler(null);                         // 设置WebSocket握手处理程序(后面需要实现)    }}
                聊天页面    



显示Websocket请求信息:

<script type="text/javascript"> //定义webSocket对象 let websocket = null; //定义发送函数 function connection() { //定义谁连接Socket let username = document.getElementById("username").value; //判断当前浏览器是否支持WebSocket if ("WebSocket" in window) { websocket = new WebSocket("ws://localhost:8080/websocket/" + username); } else if ("MozWebSocket" in window) { websocket = new MozWebSocket("ws://localhost:8080/websocket/" + username); } else { websocket = new SockJS("localhost:8080/websocket/" + username); } //连接发生错误的回调方法 websocket.onerror = function () { let element = document.createElement("p"); element.textContent = "WebSocket连接发生错误..." document.getElementById("wsMsg").appendChild(element) } //连接成功建立的回调方法 websocket.onopen = function () { let element = document.createElement("p"); element.textContent = "WebSocket连接成功..." + username document.getElementById("wsMsg").appendChild(element) } //接收到消息的回调方法 websocket.onmessage = function (event) { let element = document.createElement("p"); element.textContent = "数据接收成功..." + event.data document.getElementById("wsMsg").appendChild(element) //写出到信息栏 let element1 = document.createElement("p"); element1.textContent = event.data document.getElementById("dv").appendChild(element1) } //连接关闭的回调方法 websocket.onclose = function () { let element = document.createElement("p"); element.textContent = "WebSocket连接关闭..." document.getElementById("wsMsg").appendChild(element) } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } } //定义发送ws函数 function sendMessage() { //获取信息 let username = document.getElementById("username").value; let toName = document.getElementById("toName").value let toMessage = document.getElementById("toMessage").value //定义发送的数据 // let sendJson = "{"toName":"+"toName"+"}" let sendJson = `{"toName":"${toName}","toMessage":"${toMessage}"}`; if (websocket != null) { websocket.send(sendJson) //写出到信息栏 let element = document.createElement("p"); element.textContent = "【" + username + "】:" + toMessage document.getElementById("dv").appendChild(element) } } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); }</script>
前端页面(还是和之前的一样,前端页面可以放在任意地方,到时候可以运行就行

具体的运行效果和上面的WebApp方式的运行一样的,一样的前端,只不过后端实现不同。

关键词: