Java网络编程——Socket用法解析

在客户/服务器通信模式中,客户端需要主动创建与服务器连接的Socket,服务器端收到了客户的连接请求,也会创建与客户连接的Socket。Socket可以被看作是通信连接两端的收发器,服务器与客户都通过套接字来收发数据。

1、构造Socket

Socket的构造方法有以下几种重载形式:

    Socket();
    Socket(InetAddress address, int port) throws UnknownHostException, IOException;
    Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException;
    Socket(String host, int port)throws UnknownHostException, IOException;
    Socket(String host, int port, InetAddress localAddr, int localPort)throws IOException;;
    Socket(Proxy proxy);

除了第1个不带参数的构造方法,其他构造方法都会试图建立与服务器的连接,如果连接成功,就返回Socket对象;如果因为某些原因连接失败,就会抛出IOException。

下面的PortScanner类能够扫描主机上从1到1024之间的端口,判断这些端口是否已经被服务器程序监听。PortScanner类的scan()方法在一个for循环中创建Socket对象,每次请求连接不同的端口,如果Socket对象创建成功,就表明在当前端口有服务器程序监听。

import java.io.IOException;
import java.net.Socket;

/**
 * @title PortScanner
 * @description 测试
 * @author: yangyongbing
 * @date: 2023/12/5 12:03
 */
public class PortScanner {
    public static void main(String[] args) {
        String host="localhost";
        if(args.length>0){
            host=args[0];
        }
        new PortScanner().scan(host);
    }

    public void scan(String host){
        Socket socket=null;
        for(int port=1;port<1024;port++){
            try {
                socket=new Socket(host,port);
                System.out.println("There is a server on port "+port);
            }catch (IOException e){
                System.out.println("Can't connect to port "+port);
            }finally {
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

在这里插入图片描述

1.1、设定等待建立连接的超时时间

当客户端的Socket构造方法请求与服务器连接时,可能要等待一段时间。在默认情况下,Socket构造方法会一直等待下去,直到连接成功,或者出现异常。Socket构造方法请求连接时,受底层网络的传输速度的影响,可能会处于长时间的等待状态。如果希望限定等待连接的时间,那么该如何做呢?此时就需要使用第1个不带参数的构造方法。
在这里插入图片描述
以上SocketAddress类表示一个套接字的地址,它同时包含了IP地址和端口信息。以上代码用于连接到本地机器上的监听8000端口的服务器程序,等待连接的最长时间为1分钟。如果在1分钟内连接成功,则connect()方法顺利返回;如果在1分钟内出现某种异常,则抛出该异常;如果在1分钟后既没有连接成功,也没有出现异常,那么会抛出SocketTimeoutException。Socket类的connect(SocketAddress endpoint,int timeout)方法负责连接服务器,参数endpoint是指定服务器的地址,参数timeout是设定的超时时间,以ms为单位。如果参数timeout被设为0,则表示永远不会超时。

不带参数的构造方法的另一个作用是在连接服务器之前,设置Socket的选项。

1.2、设定服务器的地址

除了第1个不带参数的构造方法,其他构造方法都需要在参数中设定服务器的地址,包括服务器的IP地址或者主机名,以及端口。
在这里插入图片描述
InetAddress类表示主机的IP地址,InetAddress类提供了一系列静态工厂方法,用于构造自身的实例,例如:
在这里插入图片描述

1.3、设定客户端的地址

在一个Socket对象中,既包含远程服务器的IP地址和端口信息,也包含本地客户端的IP地址和端口信息。在默认情况下,客户端的IP地址来自客户程序所在的主机,客户端的端口则由操作系统随机分配。Socket类还有两个构造方法允许显式地设置客户端的IP地址和端口。
在这里插入图片描述

如果一个主机同时属于两个以上的网络,它就可能拥有两个以上IP地址,例如一个主机在Internet网络中的IP地址为“222.67.1.34”,在一个局域网中的IP地址为“112.5.4.3”。假定这个主机上的客户程序希望和同一个局域网上的一个服务器程序(地址为:112.5.4.45:8000)通信,客户端可按照如下方式构造Socket对象。
在这里插入图片描述

1.4、客户连接服务器时可能抛出的异常

当Socket的构造方法请求连接服务器时,可能会抛出以下异常:

  • UnknownHostException:如果无法识别主机的名字或IP地址,就会抛出这种异常。
  • ConnectException:如果没有服务器进程监听指定的端口,或者服务器进程拒绝连接,就会抛出这种异常。
  • SocketTimeoutException:如果等待连接超时,就会抛出这种异常。

以上4种异常都是IOException的直接或间接子类,如下图所示:
在这里插入图片描述
下面以ConnectTester类为例,介绍抛出各种异常的原因。ConnectTester接收用户从命令行输入的主机名和端口,然后连接到该地址,如果连接成功,就会计算建立连接所花的时间;如果连接失败,就会捕获各种异常。下面是ConnectTester类的源程序。
在这里插入图片描述
在控制台中运行命令“java ConnectTester www.javathinker.net 80”,ConnectTester类的connect()方法会请求连接Internet网上www.javathinker.net主机的80端口,如果连接成功,就会打印如下结果:
在这里插入图片描述
以上打印结果表明客户与服务器建立连接花了49ms(毫秒),在不同的主机上运行该程序,会有不同的打印结果。

1.5、使用代理服务器

在实际应用中,有的客户程序会通过代理服务器来访问远程服务器。代理服务器有许多功能,比如能作为防火墙进行安全防范,或者提高访问速度,或者具有访问特定远程服务器的权限。

以下程序代码通过代理服务器来连接远程的www.javathinker.net服务器:
在这里插入图片描述
Proxy.Type类表示代理服务器的类型,有以下可选值:

  • Proxy.Type.SOCKS:在分层的网络结构中,Type.SOCKS是位于会话层的代理类型。
  • Proxy.Type.HTTP:在分层的网络结构中,Type.HTTP是位于应用层的代理类型。
  • Proxy.Type.DIRECT:不使用代理,直接连接远程服务器。

1.6、InetAddress地址类的用法

InetAddress类表示主机的IP地址,InetAddress类的静态工厂方法getByName()用于构造自身的实例,例如:
在这里插入图片描述
InetAddress的getByName()方法的参数可以是IP地址或主机名。一般说来,主机名比IP地址要固定许多,主机名通常不会发生变化,而IP地址可能会发生变动。所以,在通过Socket连接某个服务器时,应该优先考虑提供主机名。

以下程序代码打印www.javathinker.net的地址信息:
在这里插入图片描述

InetAddress还提供了获取相应的主机名的两种方法:

  • getHostname():首先从DNS缓存中查找与IP地址匹配的主机名,如果不存在,再通过DNS服务器查找,如果找到,则返回主机名,否则返回IP地址。
  • getCanonicalHostName():通过DNS服务器查找与IP地址匹配的主机名,如果找到,则返回主机名,否则返回IP地址。

以上两种方法的区别在于,getHostname()会先查找DNS缓存,减少查找DNS服务器的概率,这样做能提高查找性能。因为查找DNS服务器是很耗时的操作。而getCanonicalHostName()总是查找DNS服务器,确保获得当前最新版本的主机名。

以下程序代码打印本地主机的主机名:
在这里插入图片描述
InetAddress类还提供了两个测试能否从本地主机连接到特定主机的方法:

public boolean isReachable(int timeout)throws IOException;
public boolean isReachable(NetworkInterface interface,int ttl,int timeout)throws IOException;

如果远程主机在参数timout(以ms为单位)指定的时间内做出回应,以上方法返回true,否则返回false。如果出现网络错误则抛出IOException。第2种方法还允许从参数指定的本地网络接口建立连接,以及TTL生存时间。TTL( Time To Live)指IP数据包被丢弃前允许存在的时间。

以下程序代码测试本地主机是否能在10s内连接www.javathinker.net网站:
在这里插入图片描述
下面介绍一个运用InetAddress类的实用范例。很多服务器会监视垃圾邮件发送者(spammer)。Spamhaus是一家国际知名反垃圾邮件机构,作为一个国际性非营利组织,提供了这一项服务。它的官方网站收集了常见的垃圾邮件发送者的IP地址的列表。例如,如何判断IP地址“108.33.56.27”是否是垃圾邮件发送者的地址呢?其步骤为调用InetAddress的getByName()方法,该方法通过DNS服务器查找“27.56.33.108.sbl.spamhaus.org”,如果查找成功(更确切地说,是返回IP地址为“127.0.0.2”的InetAddress对象),就说明这是一个垃圾邮件发送者的地址。否则,getByName()方法抛出UnknownHostException异常,就说明这不是一个垃圾邮件发送者的地址。

下面的SpamCheck类演示了如何判断特定IP地址是否为垃圾邮件发送者的IP地址。
在这里插入图片描述
通过命令“java SpamCheck 108.33.56.27 45.78.123.5154.65.93.21”运行SpamCheck类,会得到以下打印结果。
在这里插入图片描述

1.7、NetworkInterface类的用法

NetworkInterface类表示物理上的网络接口,它有两种构造自身实例的静态工厂方法,这两种方法都声明抛出SocketException。

  • getByName(String name):参数name是指定网络接口的名字。如果不存在与名字对应的网络接口,就返回null。
  • getByInetAddress(InetAddress address):参数address是指定网络接口的IP地址。如果不存在与IP地址对应的网络接口,就返回null。

以下程序代码演示如何创建NetworkInterface对象:
在这里插入图片描述
NetworkInterface类的以下方法用于获取网络接口的信息:

  • public String getName():返回网络接口的名字。
  • public Enumeration getInetAddresses():返回和网络接口绑定的所有IP地址。返回值为Enumeration类型,里面存放了表示IP地址的InetAddress对象。

2、获取Socket的信息

在一个Socket对象中,同时包含了远程服务器的IP地址和端口信息,以及客户本地的IP地址和端口信息。此外,从Socket对象中还可以获得输出流和输入流,分别用于向服务器发送数据,以及接收从服务器端发来的数据。以下方法用于获取Socket的有关信息:

  • getInetAddress():获得远程被连接进程的IP地址。
  • getPort():获得远程被连接进程的端口。
  • getLocalAddress():获得本地的IP地址。
  • getLocalPort():获得本地的端口。
  • getInputStream():获得输入流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownInput()方法关闭输入流,那么此方法会抛出IOException。
  • getOutputStream():获得输出流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownOutput()方法关闭输出流,那么此方法会抛出IOException。

下面的HTTPClient类用于访问网页www.javathinker.net/index.jsp。该网页位于一个主机名(也叫域名)为“www.javathinker.net”的远程HTTP服务器上,它监听80端口。在HTTPClient类中,先创建了一个连接到该HTTP服务器的Socket对象,然后发送符合HTTP的请求,接着接收从HTTP服务器上发回的响应结果。
在这里插入图片描述
以上HTTPClient类在发送数据时,先把字符串形式的请求信息转换为字节数组(即字符串的编码),然后发送。
在这里插入图片描述
HTTPClient类在接收数据时,把接收到的字节写到一个ByteArrayOutputStream中,它具有一个容量能够自动增长的缓冲区。如果socketIn.read(buff)方法返回“-1”,则表示读到了输入流的末尾。
在这里插入图片描述
当运行HTTPClient程序时,会打印服务器端发送的HTTP响应结果。
在这里插入图片描述
HTTP响应结果包括响应头和响应正文,中间以空行隔开。以上响应正文部分是乱码。这是因为www.javathinker.net服务器在发送正文内容时,先把它压缩成为GZIP格式,客户端需要对压缩数据进行解压,才能得到正文内容。而本范例未对压缩的正文数据解压,就直接将它打印出来,所以会显示乱码。

3、关闭Socket

当客户与服务器的通信结束时,应该及时关闭Socket,以释放Socket占用的包括端口在内的各种资源。Socket的close()方法负责关闭Socket。如果一个Socket对象被关闭,就不能再通过它的输入流和输出流进行I/O操作,否则会导致IOException。

为了确保关闭Socket的操作总是被执行,可以把这个操作放在finally代码块中:
在这里插入图片描述
Socket类提供了3个状态测试方法:

  • isClosed():如果Socket没有关闭,则返回false,否则返回true。
  • isConnected():如果Socket曾经连接到远程主机,不管当前是否已经关闭,都返回true。如果Socket从未连接到远程主机,就返回false。
  • isBound():如果Socket已经与一个本地端口绑定,则返回true,否则返回false。

如果要判断一个Socket对象当前是否处于连接状态,可采用以下方式:
在这里插入图片描述

以下这段代码演示了isClosed()和isConnected()方法在各种场景中的取值:
在这里插入图片描述
提示:Socket和ServerSocket,以及ServerSocketChannel、SocketChannel、SSLServerSocket和SSLSocket等若都实现了java.lang.Auto Closable接口。这意味着如果在try代码块中打开或创建了这些类的实例,那么即使程序没有显式地关闭它们,Java虚拟机也会在退出try代码块时自动关闭它们,释放相关的资源。另一方面,尽管这些类具有自动关闭的功能,仍然建议在程序中及时显式地关闭它们,这样可以提高程序的健壮性并提高其性能。

4、半关闭Socket

进程A与进程B通过Socket通信,假定进程A输出数据,进程B读入数据。进程A如何告诉进程B所有数据已经输出完毕呢?有几种处理办法。

(1)如果进程A与进程B交换的是字符流,并且都一行一行地读写数据,那么可以事先约定以一个特殊的标志作为结束标志,例如以字符串“bye”作为结束标志。当进程A向进程B发送一行字符串“bye”,进程B读到这一行数据后,就停止读取数据。
在这里插入图片描述
(2)进程A先发送一个消息,告诉进程B所发送的正文的长度,然后发送正文。进程B先获知进程A将发送的正文的长度,接下来只要读取该长度的字符或者字节,就停止读取数据。

(3)进程A发完所有数据后,关闭Socket。当进程B读入了进程A发送的所有数据后,再次执行输入流的read()方法时,该方法返回“-1”,如果执行BufferedReader的readLine()方法,那么该方法返回null:
在这里插入图片描述
(4)当调用Socket的close()方法关闭Socket后,它的输出流和输入流也都被关闭。有的时候,可能仅仅希望关闭输出流或输入流之一。此时可以采用Socket类提供的半关闭方法:

  • shutdownInput():关闭输入流。
  • shutdownOutput():关闭输出流。

假定进程A执行以下代码,先向进程B发送一个字符串,等到进程B接收到这个字符串后,进程A再调用Socket的shutdownOutput()方法关闭输出流。接下来进程A不允许再输出数据,但是仍可以通过输入流读入数据:
在这里插入图片描述
进程B在读入数据时,如果进程A的输出流已经关闭,进程B读入所有数据后,就会读到输入流的末尾。

值得注意的是,先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等价于调用Socket的close()方法。在通信结束后,仍然要调用Socket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口等。

Socket类还提供了两种状态测试方法,用来判断输入流和输出流是否关闭:

  • public boolean isInputShutdown():如果输入流关闭,则返回true,否则返回false。
  • public boolean isOutputShutdown():如果输出流关闭,则返回true,否则返回false。

当客户与服务器通信时,如果有一方突然结束程序,或者关闭了Socket,或者单独关闭了输入流或输出流,对另一方会造成什么影响呢?以下就用Sender类和Receiver类来演示。Sender表示发送数据的客户程序,它每隔500ms发送一行字符串,共发送20行字符串。Receiver表示接收数据的服务器程序,它每隔1s接收一行字符串,共接收20行字符串。
在这里插入图片描述

在这里插入图片描述
Sender类和Receiver类的stopWay成员变量用来指定结束通信的方式。stopWay变量的默认值为1,表示自然结束通信,此外,用户可以通过命令行参数来设置stopWay变量的值。

1.自然结束Sender和Receiver的通信

先运行“java Receiver”,再运行“java Sender”,Sender会发送20行字符串,然后自然结束运行,Receiver会接收20行字符串,然后也自然结束运行。

2.提前终止Receiver

先运行“java Receiver 2”,或者“java Receiver 3”,或者“java Receiver 4”,或者“java Receiver 5”,然后运行“java Sender”。Receiver接收了3行字符串后,就结束运行。但是Sender仍然会发送完20行字符串后,才自然结束运行。之所以会出现这种情况,是因为尽管Receiver已经结束运行,但底层的Socket并没有立即释放本地端口,操作系统探测到还有发送给该Socket的数据,会使底层Socket继续占用本地端口一段时间。

3.突然终止Sender

先运行“java Receiver”,再运行“java Sender 2”,Sender发送了3行字符串后,在没有关闭Socket的情况下,就结束运行。Receiver在第4次执行BufferedReader的readLine()方法时会抛出异常。
在这里插入图片描述

4.关闭或者半关闭Sender的Socket

先运行“java Receiver”,再运行“java Sender 3”,或者运行“java Sender 4”。Sender发送了3行字符串后,会关闭Socket(运行“java Sender 3”),或者关闭Socket的输出流(运行“java Sender 4”),然后结束运行。Receiver在第4次执行BufferedReader的readLine()方法时读到输入流的末尾,因此readLine()方法返回null。

5、设置Socket的选项

Socket的选项如下所示:

  • TCP_NODELAY:表示立即发送数据。
  • SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址。
  • SO_TIMEOUT:表示接收数据时的等待超时时间。
  • SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket。
  • SO_SNDBUF:表示发送数据的缓冲区的大小。
  • SO_RCVBUF:表示接收数据的缓冲区的大小。
  • SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动把它关闭。
  • OOBINLINE:表示是否支持发送1字节的TCP紧急数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/226053.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

毕业设计全流程!

先看一眼时间线&#xff1a; 1

nodejs+vue+elementui+express青少年编程课程在线考试系统

针对传统线下考试存在的老师阅卷工作量较大&#xff0c;统计成绩数据时间长等问题&#xff0c;实现一套高效、灵活、功能强大的管理系统是非常必要的。该系统可以迅速完成随机组卷&#xff0c;及时阅卷、统计考试成绩排名的效果。该考试系统要求&#xff1a;该系统将采用B/S结构…

【数电笔记】53-与非门构成的基本RS触发器

目录 说明&#xff1a; 1. 电路组成 2. 逻辑功能 3. 特性表 4. 特性方程 5. 状态转换图 6. 驱动表 7. 例题 例1 例2 说明&#xff1a; 笔记配套视频来源&#xff1a;B站&#xff1b;本系列笔记并未记录所有章节&#xff0c;只对个人认为重要章节做了笔记&#xff1b…

高中教师工资多少钱一个月

身为一个高中老师&#xff0c;经常被问到的问题就是&#xff1a;“老师&#xff0c;你一个月到底挣多少钱&#xff1f;”今天就为大家揭秘一下&#xff01; 其实&#xff0c;教师工资并不是一成不变的&#xff0c;它会受到多种因素的影响&#xff0c;比如地区、学校类型、教龄、…

使用ApexSQLLog工具恢复数据库

目录 前言 一、ApexSQLLog是什么&#xff1f; 二、使用步骤 1.连接你要恢复的数据库 2.选择你要恢复的时间点的数据 3.恢复指定操作的数据 4.恢复指定的表 5.输出结果方式 6.输出结果方式 7.生成还原的sql语句 总结 前言 我们在操作数据库的时候可能误操作把数据修…

Maven-高效的Java项目构建与管理工具(含Maven详细安装与配置过程)

Maven 什么是Maven&#xff1f; 正如题目所说&#xff0c;Maven就是一款高效的Java项目构建与管理工具&#xff0c;基于项目对象模型&#xff08;POM&#xff09;概念&#xff0c;利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。是Apache软件基金会的一个开源…

JavaScript基础(23)_DOM增删改

DOM增加对象 createElement(): 含义&#xff1a;创建一个元素节点对象。 用法&#xff1a;它需要一个标签名作为参数&#xff0c;将会根据该标签名创建元素节点对象&#xff0c;并将创建好的对象作为返回值返回。比如&#xff1a;var li document.createElement("li&quo…

时间序列预测实战(二十五)PyTorch实现Seq2Seq进行多元和单元预测(附代码+数据集+完整解析)

一、本文介绍 本文给大家带来的时间序列模型是Seq2Seq&#xff0c;这个概念相信大家都不陌生了&#xff0c;网上的讲解已经满天飞了&#xff0c;但是本文给大家带来的是我在Seq2Seq思想上开发的一个模型和新的架构&#xff0c;架构前面的文章已经说过很多次了&#xff0c;其是…

基于LSTM和N-gram序列的英文文本生成(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

opencv的图像直方图处理

1 opencv的直方图 1.1 什么是直方图 直方图是对数据进行统计的一种方法&#xff0c;用于显示数据中各个数值或数值范围的分布情况。它将数据划分为一系列的区间&#xff08;也称为“箱子”或“bin”&#xff09;&#xff0c;然后统计每个区间中数据出现的频次&#xff08;或频…

Jvm常见问题

1. 为什么用元空间替换永久代 避免OOM异常&#xff1a;永久代中存放了很多JVM需要的类信息&#xff0c;这些数据大多数是不会被清理的&#xff0c;所以Full GC往往无法回收多少空间。而永久代的空间是有限的&#xff0c;如果经常加载新的类进来或者频繁的创建和删除类&#xf…

1-2算法基础-常用库函数

1.排序 sort(first,last,cmp) first指向要排序范围的第一个元素&#xff0c;从0起 last指向要排序范围的最后一个元素的下一个位置 cmp&#xff08;可选&#xff09;&#xff0c;自定义函数&#xff0c;默认从小到大 评测系统 #include <iostream> #include<algorith…

数据库备份脚本

#!/bin/bash #数据库备份 #工具&#xff1a;xtrabackupif [ ! -d /xtrabackup/ ];thenmkdir /xtrabackup/{full,inter,diff} -p fito_mail15191876750163.com db_userroot db_passwdAren123 basedir/xtrabackup/full/ baseinter/xtrabackup/inter/ basediff/xtrabackup/diff/ f…

Java程序员,你掌握了多线程吗?(文末送书)

目录 01、多线程对于Java的意义02、为什么Java工程师必须掌握多线程03、Java多线程使用方式04、如何学好Java多线程送书规则 摘要&#xff1a;互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流…

房产报备小程序开发方案详解

房产客户报备小程序&#xff0c;php语言&#xff0c;uniapp前端。 房产公司合作的一些渠道商&#xff0c;渠道商在报备端报备客户&#xff0c;房产公司的人在确客端确认报备。报备的客户是以手机号为判断&#xff0c;已经报备过的手机号无法再次报备&#xff0c;有效时间到期后…

Python-基本的输入和输出(input函数和print函数用法)

输入 输出 控制台:一种人和计算机交互最基础的方式 什么是控制台&#xff1f;下面这个东西就是控制台。 但是生活中常用的是图形化界面&#xff0c;最初始的计算机使用的就是这样的控制台进行输入和输出的。 所以原来会使用计算机的都是科学家或者高级知识分子。 直到图形化界…

动手学深度学习笔记

1. 深度学习基础与MLP 1.1 框架&#xff1a; 线性回归&#xff1b; Softmax回归&#xff08;实际上用于分类问题&#xff09;&#xff1b; 感知机与多层感知机&#xff1b; 模型选择&#xff1b; 权重衰退&#xff08;weight decay&#xff09;&#xff1b; 丢弃法&…

使用VS Code远程开发MENJA小游戏并通过内网穿透分享本地游戏到公网

文章目录 前言1. 编写MENJA小游戏2. 安装cpolar内网穿透3. 配置MENJA小游戏公网访问地址4. 实现公网访问MENJA小游戏5. 固定MENJA小游戏公网地址 推荐一个人工智能学习网站 点击跳转学习 前言 本篇教程&#xff0c;我们将通过VS Code实现远程开发MENJA小游戏&#xff0c;并通…

hibernate实现ID序列自增,异常处理

目录 一、问题描述 二、问题解析 一、问题描述 明明数据库表设置了id自增&#xff0c;为啥添加数据时&#xff0c;会抛出异常呢&#xff1f; 具体内容如下&#xff1a; &#xff08;1&#xff09;控制台异常打印如下 org.hibernate.id.IdentifierGenerationException: ids f…

vue.js el-table 动态单元格列合并

一、业务需求&#xff1a; 一个展示列表&#xff0c;表格中有一部分列是根据后端接口动态展示&#xff0c;对于不同类型的数据展示效果不一样。如果接口返回数据是’类型1‘的&#xff0c;则正常展示&#xff0c;如果是’类型2‘的数据&#xff0c;则合并当前数据的动态表格。…
最新文章