ServerSocket类的构造方法有四种重载形式它们的定义如下
publicServerSocket()throwsIOException
publicServerSocket(intport)throwsIOException
publicServerSocket(intportintbacklog)throwsIOException
publicServerSocket(intportintbacklogInetAddressbindAddr)throwsIOException
在上面的构造方法中涉及到了三个参数portbacklog和bindAddr其中port是ServerSocket对象要绑定的端口backlog是请求队列的长度bindAddr是ServerSocket对象要绑定的IP地址
一通过构造方法绑定端口
通过构造方法绑定端口是创建ServerSocket对象最常用的方式可以通过如下的构造方法来绑定端口
publicServerSocket(intport)throwsIOException
如果port参数所指定的端口已经被绑定构造方法就会抛出IOException异常但实际上抛出的异常是BindException从图的异常类继承关系图可以看出所有和网络有关的异常都是IOException类的子类因此为了ServerSocket构造方法还可以抛出其他的异常就使用了IOException
如果port的值为系统就会随机选取一个端口号但随机选取的端口意义不大因为客户端在连接服务器时需要明确知道服务端程序的端口号可以通过ServerSocket的toString方法输出和ServerSocket对象相关的信息下面的代码输入了和ServerSocket对象相关的信息
ServerSocketserverSocket=newServerSocket();
Systemoutprintln(serverSocket);
运行结果
ServerSocket[addr=/port=localport=]
上面的输出结果中的addr是服务端绑定的IP地址如果未绑定IP地址这个值是在这种情况下ServerSocket对象将监听服务端所有网络接口的所有IP地址port永远是localport是ServerSocket绑定的端口如果port值为(不是输出结果的port是ServerSocket构造方法的参数port)localport是一个随机选取的端口号
在操作系统中规定 ~ 为系统使用的端口号端口号的最小值是最大值是在Windows中用户编写的程序可以绑定端口号小于的端口但在Linux/Unix下必须使用root登录才可以绑定小于的端口在前面的文章中曾使用Socket类来判断本机打开了哪些端口其实使用ServerSocket类也可以达到同样的目的基本原理是用ServerSocket来绑定本机的端口如果绑定某个端口时抛出BindException异常就说明这个端口已经打开反之则这个端口未打开
package server;
import*;
publicclassScanPort
{
publicstaticvoidmain(String[]args)
{
if(argslength==)
return;
intminPort=maxPort=;
Stringports[]=args[]split([]);
minPort=IntegerparseInt(ports[]);
maxPort=(portslength>)?IntegerparseInt(ports[]):minPort;
for(intport=minPort;port<=maxPort;port++)
try
{
ServerSocketserverSocket=newServerSocket(port);
serverSocketclose();
}
catch(Exceptione)
{
Systemerrprintln(egetClass());
Systemerrprintln(端口+port+已经打开!);
}
}
}
在上面的代码中输出了创建ServerSocket对象时抛出的异常类的信息ScanPort通过命令行参数将待扫描的端口号范围传入程序参数格式为minPortmaxPort如果只输入一个端口号ScanPort程序只扫描这个端口号
测试
javaserverScanPort
运行结果
classBindException
端口已经打开!
classBindException
端口已经打开!
二设置请求队列的长度
在编写服务端程序时一般会通过多线程来同时处理多个客户端请求也就是说使用一个线程来接收客户端请求当接到一个请求后(得到一个Socket对象)会创建一个新线程将这个客户端请求交给这个新线程处理而那个接收客户端请求的线程则继续接收客户端请求这个过程的实现代码如下
ServerSocketserverSocket=newServerSocket();//绑定端口
//处理其他任务的代码
while(true)
{
Socketsocket=serverSocketaccept();//等待接收客户端请求
//处理其他任务的代码
newThreadClass(socket)start();//创建并运行处理客户端请求的线程
}
上面代码中的ThreadClass类是Thread类的子类这个类的构造方法有一个Socket类型的参数可以通过构造方法将Socket对象传入ThreadClass对象并在ThreadClass对象的run方法中处理客户端请求这段代码从表面上看好象是天衣无缝无论有多少客户端请求只要服务器的配置足够高就都可以处理但仔细思考上面的代码我们可能会发现一些问题如果在第行和第行有足够复杂的代码执行时间也比较长这就意味着服务端程序无法及时响应客户端的请求
假设第行和第行的代码是Threadsleep()这将使程序延迟秒那么在这秒内程序不会执行accept方法因此这段程序只是将端口绑定到了上并未开始接收客户端请求如果在这时一个客户端向端口发来了一个请求从理论上讲客户端应该出现拒绝连接错误但客户端却显示连接成功究其原因就是这节要讨论的请求队列在起作用
在使用ServerSocket对象绑定一个端口后操作系统就会为这个端口分配一个先进先出的队列(这个队列长度的默认值一般是)这个队列用于保存未处理的客户端请求因此叫请求队列而ServerSocket类的accept方法负责从这个队列中读取未处理的客户端请求如果请求队列为空accept则处于阻塞状态每当客户端向服务端发来一个请求服务端会首先将这个客户端请求保存在请求队列中然后accept再从请求队列中读取这也可以很好地解释为什么上面的代码在还未执行到accept方法时仍然可以接收一定数量的客户端请求如果请求队列中的客户端请求数达到请求队列的最大容量时服务端将无法再接收客户端请求如果这时客户端再向服务端发请求客户端将会抛出一个SocketException异常
ServerSocket类有两个构造方法可以使用backlog参数重新设置请求队列的长度在以下几种情况仍然会采用操作系统限定的请求队列的最大长度
● backlog的值小于等于
● backlog的值大于操作系统限定的请求队列的最大长度
● 在ServerSocket构造方法中未设置backlog参数
下面积代码演示了请求队列的一些特性请求队列长度通过命令行参数传入SetRequestQueue
package server;
import*;
classTestRequestQueue
{
publicstaticvoidmain(String[]args)throwsException
{
for(inti=;i<;i++)
{
Socketsocket=newSocket(localhost);
socketgetOutputStream()write();
Systemoutprintln(已经成功创建第+StringvalueOf(i+)+个客户端连接!);
}
}
}
publicclassSetRequestQueue
{
publicstaticvoidmain(String[]args)throwsException
{
if(argslength==)
return;
intqueueLength=IntegerparseInt(args[]);
ServerSocketserverSocket=newServerSocket(queueLength);
Systemoutprintln(端口()已经绑定请按回车键开始处理客户端请求!);
Systeminread();
intn=;
while(true)
{
Systemoutprintln(<准备接收第+(++n)+个客户端请求!);
Socketsocket=serverSocketaccept();
Systemoutprintln(正在处理第+n+个客户端请求);
Threadsleep();
Systemoutprintln(第+n+个客户端请求已经处理完毕!>);
}
}
}
测试(按着以下步骤操作)
执行如下命令(在执行这条命令后先不要按回车键)
java serverSetRequestQueue
运行结果
端口()已经绑定请按回车键开始处理客户端请求!
执行如下命令
javaserverTestRequestQueue
运行结果
已经成功创建第个客户端连接!
已经成功创建第个客户端连接!
ExceptioninthreadmainSocketException:Connectionresetbypeer:socketwriteerror
atSocketOutputStreamsocketWrite(NativeMethod)
atSocketOutputStreamsocketWrite(SocketOutputStreamjava:)
atSocketOutputStreamwrite(SocketOutputStreamjava:)
at serverTestRequestQueuemain(SetRequestQueuejava:)
按回车键继续执行SetRequestQueue后运行结果如下
端口()已经绑定请按回车键开始处理客户端请求!
<准备接收第个客户端请求!
正在处理第个客户端请求
第个客户端请求已经处理完毕!>
<准备接收第个客户端请求!
正在处理第个客户端请求
第个客户端请求已经处理完毕!>
<准备接收第个客户端请求!
从第二步的运行结果可以看出当TestRequestQueue创建两个Socket连接之后服务端的请求队列已满并且服务端暂时无法继续执行(由于Systeminread()的原因而暂停程序的执行等待用户的输入)因此服务端程序无法再接收客户端请求这时TestRequestQueue抛出了一个SocketException异常在TestRequestQueue已经创建成功的两个Socket连接已经保存在服务端的请求队列中在这时按任意键继续执行SetRequestQueueaccept方法就会从请求队列中将这两个客户端请求队列中依次读出来从第三步的运行结果可以看出服务端处理完这两个请求后(一个<…>包含的就是一个处理过程)请求队列为空这时accept处理阻塞状态等待接收第三个客户端请求如果这时再运行TestRequestQueue服务端会接收几个客户端请求呢?如果将请求队列的长度设为大于的数TestRequestQueue的运行结果会是什么呢?读者可以自己做一下这些实验看看和自己认为的结果是否一致
三绑定IP地址
在有多个网络接口或多个IP地址的计算机上可以使用如下的构造方法将服务端绑定在某一个IP地址上
publicServerSocket(intportintbacklogInetAddressbindAddr)throwsIOException
bindAddr参数就是要绑定的IP地址如果将服务端绑定到某一个IP地址上就只有可以访问这个IP地址的客户端才能连接到服务器上如一台机器上有两块网卡一块网卡连接内网另一块连接外网如果用Java实现一个Email服务器并且只想让内网的用户使用它就可以使用这个构造方法将ServerSocket对象绑定到连接内网的IP地址上这样外网就无法访问Email服务器了可以使用如下代码来绑定IP地址
ServerSocketserverSocket=new
ServerSocket(InetAddressgetByName());
上面的代码将IP地址绑定到了上因此服务端程序只能使用绑定了这个IP地址的网络接口进行通讯
四默认构造方法的使用
除了使用ServerSocket类的构造方法绑定端口外还可以用ServerSocket的bind方法来完成构造方法所做的工作要想使用bind方法必须得用ServerSocket类的默认构造方法(没有参数的构造方法)来创建ServerSocket对象bind方法有两个重载形式它们的定义如下
publicvoidbind(SocketAddressendpoint)throwsIOException
publicvoidbind(SocketAddressendpointintbacklog)throwsIOException
bind方法不仅可以绑定端口也可以设置请求队列的长度以及绑定IP地址bind方法的作用是为了在建立ServerSocket对象后设置ServerSocket类的一些选项而这些选项必须在绑定端口之前设置一但绑定了端口后再设置这些选项将不再起作用下面的代码演示了bind方法的使用及如何设置ServerSocket类的选项
ServerSocketserverSocket=newServerSocket();
serverSocketsetReuseAddress(true);
serverSocketbind(newInetSocketAddress());
ServerSocketserverSocket=newServerSocket();
serverSocketsetReuseAddress(true);
serverSocketbind(newInetSocketAddress());
ServerSocketserverSocket=newServerSocket();
serverSocketsetReuseAddress(true);
serverSocketbind(newInetSocketAddress());
在上面的代码中设置了SO_REUSEADDR 选项(这个选项将在后面的文章中详细讨论)如果使用下面的代码这个选项将不起作用
ServerSocketserverSocket=newServerSocket();
serverSocketsetReuseAddress(true);
在第行绑定了IP地址和端口使用构造方法是无法得到这个组合的(想绑定IP地址必须得设置backlog参数)因此bind方法比构造方法更灵活