创建ServerSocket对象

2024-03-27 08:08
文章标签 创建 对象 serversocket

本文主要是介绍创建ServerSocket对象,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ServerSocket类的构造方法有四种重载形式,它们的定义如下:

public ServerSocket() throws IOException
public ServerSocket(int port) throws IOException
public ServerSocket(int port, int backlog) throws IOException
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

        在上面的构造方法中涉及到了三个参数: portbacklogbindAddr。其中portServerSocket对象要绑定的端口,backlog是请求队列的长度,bindAddrServerSocket对象要绑定的IP地址。

一、通过构造方法绑定端口

通过构造方法绑定端口是创建ServerSocket对象最常用的方式。可以通过如下的构造方法来绑定端口:

public ServerSocket(int port) throws IOException

如果port参数所指定的端口已经被绑定,构造方法就会抛出IOException异常。但实际上抛出的异常是BindException。从图4.2异常类继承关系图可以看出,所有和网络有关的异常都是IOException类的子类。因此,为了ServerSocket构造方法还可以抛出其他的异常,就使用了IOException

如果port的值为0,系统就会随机选取一个端口号。但随机选取的端口意义不大,因为客户端在连接服务器时需要明确知道服务端程序的端口号。可以通过ServerSocket的toString方法输出ServerSocket对象相关的信息。下面的代码输入了和ServerSocket对象相关的信息。

ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=1320]

上面的输出结果中的addr是服务端绑定的IP地址,如果未绑定IP地址,这个值是0.0.0.0,在这种情况下,ServerSocket对象将监听服务端所有网络接口的所有IP地址。port永远是0。localport是ServerSocket绑定的端口,如果port值为0(不是输出结果的port,是ServerSocket构造方法的参数port),localport是一个随机选取的端口号。

        在操作系统中规定1 ~ 1023为系统使用的端口号。端口号的最小值是1,最大值是65535。在Windows中用户编写的程序可以绑定端口号小于1024的端口,但在Linux/Unix下必须使用root登录才可以绑定小于1024的端口。在前面的文章中曾使用Socket类来判断本机打开了哪些端口,其实使用ServerSocket类也可以达到同样的目的。基本原理是用ServerSocket来绑定本机的端口,如果绑定某个端口时抛出BindException异常,就说明这个端口已经打开,反之则这个端口未打开。
package server;import java.net.*;public class ScanPort
{public static void main(String[] args){if (args.length == 0)return;int minPort = 0, maxPort = 0;String ports[] = args[0].split("[-]");minPort = Integer.parseInt(ports[0]);maxPort = (ports.length > 1) ? Integer.parseInt(ports[1]) : minPort;for (int port = minPort; port <= maxPort; port++)try{ServerSocket serverSocket = new ServerSocket(port);serverSocket.close();}catch (Exception e){System.err.println(e.getClass());System.err.println("端口" + port + "已经打开!");}}
}

在上面的代码中输出了创建ServerSocket对象时抛出的异常类的信息。ScanPort通过命令行参数将待扫描的端口号范围传入程序,参数格式为:minPort-maxPort,如果只输入一个端口号,ScanPort程序只扫描这个端口号。

测试:

java server.ScanPort 1-1023
运行结果:

class java.net.BindException
端口80已经打开!
class java.net.BindException
端口135已经打开!

二、设置请求队列的长度

在编写服务端程序时,一般会通过多线程来同时处理多个客户端请求。也就是说,使用一个线程来接收客户端请求,当接到一个请求后(得到一个Socket对象),会创建一个新线程,将这个客户端请求交给这个新线程处理。而那个接收客户端请求的线程则继续接收客户端请求,这个过程的实现代码如下:

ServerSocket serverSocket = new ServerSocket(1234);   // 绑定端口
// 处理其他任务的代码
while(true)
{Socket socket = serverSocket.accept(); // 等待接收客户端请求// 处理其他任务的代码new ThreadClass(socket).start();   // 创建并运行处理客户端请求的线程
}
        上面代码中 ThreadClass 类是Thread 类的子类,这个类的构造方法有一个Socket 类型的参数,可以通过构造方法将Socket 对象传入ThreadClass 对象,并在ThreadClass 对象的run 方法中处理客户端请求。这段代码从表面上看好象是天衣无缝,无论有多少客户端请求,只要服务器的配置足够高,就都可以处理。但仔细思考上面的代码 ,我们可能会发现一些问题。如果在第2行和第6行有足够复杂的代码,执行时间也比较长,这就意味着服务端程序无法及时响应客户端的请求。

假设第2行和第6行的代码是Thread.sleep(3000),这将使程序延迟3秒。那么在这3秒内,程序不会执行accept方法,因此,这段程序只是将端口绑定到了1234上,并未开始接收客户端请求。如果在这时一个客户端向端口1234发来了一个请求,从理论上讲,客户端应该出现拒绝连接错误,但客户端却显示连接成功。究其原因,就是这节要讨论的请求队列在起作用。

        在使用 ServerSocket对象绑定一个端口后,操作系统就会为这个端口分配一个先进先出的队列(这个队列长度的默认值一般是50),这个队列用于保存未处理的客户端请求,因此叫请求队列。而ServerSocket类的accept方法负责从这个队列中读取未处理的客户端请求。如果请求队列为空,accept 则处于阻塞状态。每当客户端向服务端发来一个请求,服务端会首先将这个客户端请求保存在请求队列中,然后accept 再从请求队列中读取。这也可以很好地解释为什么上面的代码在还未执行到accept 方法时,仍然可以接收一定数量的客户端请求。如果请求队列中的客户端请求数达到请求队列的最大容量时,服务端将无法再接收客户端请求。如果这时客户端再向服务端发请求,客户端将会抛出一个 SocketException 异常

        ServerSocket类有两个构造方法可以使用backlog参数重新设置请求队列的长度。在以下几种情况,仍然会采用操作系统限定的请求队列的最大长度:

  • backlog的值小于等于0
  • backlog的值大于操作系统限定的请求队列的最大长度
  • ServerSocket构造方法中未设置backlog参数

        下面的代码演示了请求队列的一些特性,请求队列长度通过命令行参数传入SetRequestQueue

package server;import java.net.*;class TestRequestQueue
{public static void main(String[] args) throws Exception{for (int i = 0; i < 10; i++){Socket socket = new Socket("localhost", 1234);socket.getOutputStream().write(1);System.out.println("已经成功创建第" + String.valueOf(i + 1) + "个客户端连接!");}}
}
public class SetRequestQueue
{public static void main(String[] args) throws Exception{if (args.length == 0)return;int queueLength = Integer.parseInt(args[0]);ServerSocket serverSocket = new ServerSocket(1234, queueLength);System.out.println("端口(1234)已经绑定,请按回车键开始处理客户端请求!");System.in.read();int n = 0;while (true){System.out.println("<准备接收第" + (++n) + "个客户端请求!");Socket socket = serverSocket.accept();System.out.println("正在处理第" + n + "个客户端请求");Thread.sleep(3000);System.out.println("第" + n + "个客户端请求已经处理完毕!>");}}
}
测试(按以下步骤操作)

        1、 执行如下命令(在执行这条命令后,先不要按回车键):

java server.SetRequestQueue 2
运行结果:

端口(1234)已经绑定,请按回车键开始处理客户端请求!
        2、 执行如下命令:

java server.TestRequestQueue
运行结果:


已经成功创建第1个客户端连接!
已经成功创建第2个客户端连接!
Exception in thread "main" java.net.SocketException: Connection reset by peer: socket write errorat java.net.SocketOutputStream.socketWrite0(Native Method)at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)at java.net.SocketOutputStream.write(SocketOutputStream.java:115)at server.TestRequestQueue.main(SetRequestQueue.java:12)

        3、按回车键继续执行SetRequestQueue后,运行结果如下:

端口(1234)已经绑定,请按回车键开始处理客户端请求!
<准备接收第1个客户端请求!
正在处理第1个客户端请求
第1个客户端请求已经处理完毕!>
<准备接收第2个客户端请求!
正在处理第2个客户端请求
第2个客户端请求已经处理完毕!>
<准备接收第3个客户端请求!
        从第二步的运行结果可以看出,当TestRequestQueue 创建两个Socket 连接之后,服务端的请求队列已满,并且服务端暂时无法继续执行(由于System.in.read() 的原因而暂停程序的执行,等待用户的输入)。因此,服务端程序无法再接收客户端请求。这时TestRequestQueue 抛出了一个SocketException 异常。在TestRequestQueue 已经创建成功的两个Socket 连接已经保存在服务端的请求队列中。在这时按任意键继续执行SetRequestQueue accept 方法就会从请求队列中将这两个客户端请求队列中依次读出来。从第三步的运行结果可以看出,服务端处理完这两个请求后(一个<…> 包含的就是一个处理过程),请求队列为空,这时accept 处理阻塞状态,等待接收第三个客户端请求。如果这时再运行TestRequestQueue ,服务端会接收几个客户端请求呢?如果将请求队列的长度设为大于10 的数,TestRequestQueue 的运行结果会是什么呢?读者可以自己做一下这些实验,看看和自己认为的结果是否一致。

三、绑定IP地址

在有多个网络接口或多个IP地址的计算机上可以使用如下的构造方法将服务端绑定在某一个IP地址上:

public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
        bindaddr参数就是要绑定的 IP地址。如果将服务端绑定到某一个IP地址上,就只有可以访问这个IP地址的客户端才能连接到服务器上。如一台机器上有两块网卡,一块网卡连接内网,另一块连接外网。如果用Java实现一个Email服务器,并且只想让内网的用户使用它。就可以使用这个构造方法将ServerSocket对象绑定到连接内网的IP地址上。这样外网就无法访问Email服务器了。可以使用如下代码来绑  定IP地址:
ServerSocket serverSocket = new
ServerSocket(1234, 0, InetAddress.getByName("192.168.18.10"));

        上面的代码将IP地址绑定到了192.168.18.10上,因此,服务端程序只能使用绑定了这个IP地址的网络接口进行通讯。


四、默认构造方法的使用

        除了使用ServerSocket类的构造方法绑定端口外,还可以用ServerSocketbind方法来完成构造方法所做的工作。要想使用bind方法,必须得用ServerSocket类的默认构造方法(没有参数的构造方法)来创建ServerSocket对象。bind方法有两个重载形式,它们的定义如下:

public void bind(SocketAddress endpoint) throws IOException
public void bind(SocketAddress endpoint, int backlog) throws IOException 

        bind方法不仅可以绑定端口,也可以设置请求队列的长度以及绑定IP地址。bind方法的作用是为了在建立ServerSocket对象后设置ServerSocket类的一些选项。而这些选项必须在绑定端口之前设置,一旦绑定了端口后,再设置这些选项将不再起作用。下面的代码演示了bind方法的使用及如何设置ServerSocket类的选项。

ServerSocket serverSocket1 = new ServerSocket();
serverSocket1.setReuseAddress(true);
serverSocket1.bind(new InetSocketAddress(1234));
ServerSocket serverSocket2 = new ServerSocket();
serverSocket2.setReuseAddress(true);
serverSocket2.bind(new InetSocketAddress("192.168.18.10", 1234));
ServerSocket serverSocket3 = new ServerSocket();
serverSocket3.setReuseAddress(true);
serverSocket3.bind(new InetSocketAddress("192.168.18.10", 1234), 30);    

        在上面的代码中设置了SO_REUSEADDR选项(这个选项将在后面的文章中详细讨论)。如果使用下面的代码,这个选项将不起作用。

ServerSocket serverSocket3 = new ServerSocket(1234);
serverSocket3.setReuseAddress(true);
        在第6 行绑定了IP 地址和端口。使用构造方法是无法得到这个组合的(想绑定IP 地址,必须得设置backlog 参数),因此,bind 方法比构造方法更灵活。


这篇关于创建ServerSocket对象的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/851453

相关文章

Spring创建Bean的八种主要方式详解

《Spring创建Bean的八种主要方式详解》Spring(尤其是SpringBoot)提供了多种方式来让容器创建和管理Bean,@Component、@Configuration+@Bean、@En... 目录引言一、Spring 创建 Bean 的 8 种主要方式1. @Component 及其衍生注解

使用Java读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

MySQL 数据库表操作完全指南:创建、读取、更新与删除实战

《MySQL数据库表操作完全指南:创建、读取、更新与删除实战》本文系统讲解MySQL表的增删查改(CURD)操作,涵盖创建、更新、查询、删除及插入查询结果,也是贯穿各类项目开发全流程的基础数据交互原... 目录mysql系列前言一、Create(创建)并插入数据1.1 单行数据 + 全列插入1.2 多行数据

MySQL 临时表创建与使用详细说明

《MySQL临时表创建与使用详细说明》MySQL临时表是存储在内存或磁盘的临时数据表,会话结束时自动销毁,适合存储中间计算结果或临时数据集,其名称以#开头(如#TempTable),本文给大家介绍M... 目录mysql 临时表详细说明1.定义2.核心特性3.创建与使用4.典型应用场景5.生命周期管理6.注

MySQL的触发器全解析(创建、查看触发器)

《MySQL的触发器全解析(创建、查看触发器)》MySQL触发器是与表关联的存储程序,当INSERT/UPDATE/DELETE事件发生时自动执行,用于维护数据一致性、日志记录和校验,优点包括自动执行... 目录触发器的概念:创建触www.chinasem.cn发器:查看触发器:查看当前数据库的所有触发器的定

创建springBoot模块没有目录结构的解决方案

《创建springBoot模块没有目录结构的解决方案》2023版IntelliJIDEA创建模块时可能出现目录结构识别错误,导致文件显示异常,解决方法为选择模块后点击确认,重新校准项目结构设置,确保源... 目录创建spChina编程ringBoot模块没有目录结构解决方案总结创建springBoot模块没有目录

javaSE类和对象进阶用法举例详解

《javaSE类和对象进阶用法举例详解》JavaSE的面向对象编程是软件开发中的基石,它通过类和对象的概念,实现了代码的模块化、可复用性和灵活性,:本文主要介绍javaSE类和对象进阶用法的相关资... 目录前言一、封装1.访问限定符2.包2.1包的概念2.2导入包2.3自定义包2.4常见的包二、stati

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与