博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java网络编程从入门到精通(25):创建ServerSocket对象
阅读量:5870 次
发布时间:2019-06-19

本文共 7551 字,大约阅读时间需要 25 分钟。

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
    
在上面的构造方法中涉及到了三个参数:port
backlog
bindAddr
。其中port
ServerSocket
对象要绑定的端口,backlog
是请求队列的长度,bindAddr
ServerSocket
对象要绑定的IP
地址。
一、
通过构造方法绑定端口
通过构造方法绑定端口是创建ServerSocket
对象最常用的方式。可以通过如下的构造方法来绑定端口:
public
 ServerSocket(
int
 port) 
throws
 IOException
如果port
参数所指定的端口已经被绑定,构造方法就会抛出IOException
异常。但实际上抛出的异常是BindException
。从图4.2
异常类继
承关系图可以看出,所有和网络有关的异常都是IOException
类的子类。因此,为了ServerSocket
构造方法还可以抛出其他的异常,就使用了IOException
如果port
的值为0
,系统就会随机选取一个端口号。但随机选取的端口意义不大,因为客户端在连接服务器时需要明确知道服务端程序的端口号。可以通过ServerSocket
toString
方法输出和ServerSocket
对象相关的信息。下面的代码输入了和ServerSocket
对象相关的信息。
ServerSocket serverSocket 
=
 
new
 ServerSocket(
1320
);
System.out.println(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 error
                       at 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
类的构造方法绑定端口外,还可以用ServerSocket
bind
方法来完成构造方法所做的工作。要想使用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
方法比构造方法更灵活。
 本文转自 androidguy 51CTO博客,原文链接:
http://blog.51cto.com/androidguy/214408
,如需转载请自行联系原作者
你可能感兴趣的文章
sql_recover_2017/11/1
查看>>
[转载] Discovery——看穿读心术 01 微表情解读
查看>>
解决hash冲突的方法
查看>>
【转】一步一步学Linq to sql(三):增删改
查看>>
Java并发(一):多线程干货总结
查看>>
SCOI2019酱油记
查看>>
noip车站分级 拓扑排序
查看>>
简单回溯,最少步数
查看>>
ckeditor4.7配置图片上传
查看>>
数据库封装函数
查看>>
元-博客园博客管理思路整合
查看>>
界面出现卡死状态右滑恢复正常
查看>>
线程处理的具体应用情况
查看>>
【ZZ】各类程序开发语言概述 | 菜鸟教程
查看>>
【ZZ】终于有人把云计算、大数据和人工智能讲明白了!
查看>>
Linux Kernel之flush_cache_all在ARM平台下是如何实现的【转】
查看>>
AF_INET域与AF_UNIX域socket通信原理对比【转】
查看>>
Linux 中open系统调用实现原理【转】
查看>>
SSH中调用另一action的方法(chain,redirect)
查看>>
RabbitMQ指南之三:发布/订阅模式(Publish/Subscribe)
查看>>