Java网络编程精解之ServerSocket用法详解一1

在客户/服务器通信模式中,服务器端需要创建监听特定端口的ServerSocket,ServerSocket负责接收客户连接请求。本章首先介绍ServerSocket类的各个构造方法,以及成员方法的用法,接着介绍服务器如何用多线程来处理与多个客户的通信任务。

本章提供线程池的一种实现方式。线程池包括一个工作队列和若干工作线程。服务器程序向工作队列中加入与客户通信的任务,工作线程不断从工作队列中取 出任务并执行它。本章还介绍了java.util.concurrent包中的线程池类的用法,在服务器程序中可以直接使用它们。

3.1  构造ServerSocket

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

◆ServerSocket()throws IOException 

◆ServerSocket(int port) throws IOException 

◆ServerSocket(int port, int backlog) throws IOException

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

在以上构造方法中,参数port指定服务器要绑定的端口(服务器要监听的端口),参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。 

3.1.1  绑定端口

除了第一个不带参数的构造方法以外,其他构造方法都会使服务器与特定端口绑定,该端口由参数port指定。例如,以下代码创建了一个与80端口绑定的服务器:

ServerSocket serverSocket=new ServerSocket(80);

如果运行时无法绑定到80端口,以上代码会抛出IOException,更确切地说,是抛出BindException,它是IOException的子类。BindException一般是由以下原因造成的:

◆端口已经被其他服务器进程占用;

◆在某些操作系统中,如果没有以超级用户的身份来运行服务器程序,那么操作系统不允许服务器绑定到1~1023之间的端口。

如果把参数port设为0,表示由操作系统来为服务器分配一个任意可用的端口。由操作系统分配的端口也称为匿名端口。对于多数服务器,会使用明确的 端口,而不会使用匿名端口,因为客户程序需要事先知道服务器的端口,才能方便地访问服务器。在某些场合,匿名端口有着特殊的用途,本章3.4节会对此作介 绍。

3.1.2  设定客户连接请求队列的长度

当服务器进程运行时,可能会同时监听到多个客户的连接请求。例如,每当一个客户进程执行以下代码:

Socket socket=new Socket(www.javathinker.org,80);

就意味着在远程www.javathinker.org主 机的80端口上,监听到了一个客户的连接请求。管理客户连接请求的任务是由操作系统来完成的。操作系统把这些连接请求存储在一个先进先出的队列中。许多操 作系统限定了队列的最大长度,一般为50。当队列中的连接请求达到了队列的最大容量时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过 ServerSocket的accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。

对于客户进程,如果它发出的连接请求被加入到服务器的队列中,就意味着客户与服务器的连接建立成功,客户进程从Socket构造方法中正常返回。如果客户进程发出的连接请求被服务器拒绝,Socket构造方法就会抛出ConnectionException。

ServerSocket构造方法的backlog参数用来显式设置连接请求队列的长度,它将覆盖操作系统限定的队列的最大长度。值得注意的是,在以下几种情况中,仍然会采用操作系统限定的队列的最大长度:

◆backlog参数的值大于操作系统限定的队列的最大长度;

◆backlog参数的值小于或等于0;

◆在ServerSocket构造方法中没有设置backlog参数。

以下例程3-1的Client.java和例程3-2的Server.java用来演示服务器的连接请求队列的特性。

例程3-1  Client.java

import java.net.*;

public class Client {

public static void main(String args[])throws Exception{

final int length=100;

String host="localhost";

int port=8000;

Socket[] sockets=new Socket[length];

for(int i=0;i
sockets[i]=new Socket(host, port);

System.out.println("第"+(i+1)+"次连接成功");

}

Thread.sleep(3000);

for(int i=0;i
sockets[i].close();      //断开连接



}

}

例程3-2  Server.java

import java.io.*;

import java.net.*;

public class Server {

private int port=8000;

private ServerSocket serverSocket;

public Server() throws IOException {

serverSocket = new ServerSocket(port,3);    //连接请求队列的长度为3

System.out.println("服务器启动");

}

public void service() {

while (true) {

Socket socket=null;

try {

socket = serverSocket.accept();     //从连接请求队列中取出一个

连接

System.out.println("New connection accepted " +

socket.getInetAddress() + ":" +socket.getPort());

}catch (IOException e) {

e.printStackTrace();

}finally {

try{

if(socket!=null)socket.close();

}catch (IOException e) {e.printStackTrace();}

}

}

}

public static void main(String args[])throws Exception {

Server server=new Server();

Thread.sleep(60000*10);      //睡眠10分钟

//server.service();

}

}

Client试图与Server进行100次连接。在Server类中,把连接请求队列的长度设为3。这意味着当队列中有了3个连接请求时,如果Client再请求连接,就会被Server拒绝。下面按照以下步骤运行Server和Client程序。

(1)把Server类的main()方法中的“server.service();”这行程序代码注释掉。这使得服务器与8000端口绑定后,永 远不会执行serverSocket.accept()方法。这意味着队列中的连接请求永远不会被取出。先运行Server程序,然后再运行Client 程序,Client程序的打印结果如下:

第1次连接成功

第2次连接成功

第3次连接成功

Exception in thread "main" java.net.ConnectException: Connection refused: connect

at java.net.PlainSocketImpl.socketConnect(Native Method)

at java.net.PlainSocketImpl.doConnect(Unknown Source)

at java.net.PlainSocketImpl.connectToAddress(Unknown Source)

at java.net.PlainSocketImpl.connect(Unknown Source)

at java.net.SocksSocketImpl.connect(Unknown Source)

at java.net.Socket.connect(Unknown Source)

at java.net.Socket.connect(Unknown Source)

at java.net.Socket. (Unknown Source)

at java.net.Socket. (Unknown Source)

at Client.main(Client.java:10)

从以上打印结果可以看出,Client与Server在成功地建立了3个连接后,就无法再创建其余的连接了,因为服务器的队列已经满了。

(2)把Server类的main()方法按如下方式修改:

public static void main(String args[])throws Exception {

Server server=new Server();

//Thread.sleep(60000*10);  //睡眠10分钟

server.service();

}

作了以上修改,服务器与8 000端口绑定后,就会在一个while循环中不断执行serverSocket.accept()方法,该方法从队列 中取出连接请求,使得队列能及时腾出空位,以容纳新的连接请求。先运行Server程序,然后再运行Client程序,Client程序的打印结果如下:

第1次连接成功

第2次连接成功

第3次连接成功



第100次连接成功

从以上打印结果可以看出,此时Client能顺利与Server建立100次连接。

3.1.3  设定绑定的IP地址

如果主机只有一个IP地址,那么默认情况下,服务器程序就与该IP地址绑定。ServerSocket的第4个构造方法ServerSocket (int port, int backlog, InetAddress bindAddr)有一个bindAddr参数,它显式指定服务器要绑定的 IP地址,该构造方法适用于具有多个IP地址的主机。假定一个主机有两个网卡,一个网卡用于连接到Internet, IP地址为 222.67.5.94,还有一个网卡用于连接到本地局域网,IP地址为192.168.3.4。如果服务器仅仅被本地局域网中的客户访问,那么可以按如 下方式创建ServerSocket:

ServerSocket serverSocket=new ServerSocket(8000,10,InetAddress.getByName ("192.168.3.4"));

3.1.4  默认构造方法的作用

ServerSocket有一个不带参数的默认构造方法。通过该方法创建的ServerSocket不与任何端口绑定,接下来还需要通过bind()方法与特定端口绑定。

这个默认构造方法的用途是,允许服务器在绑定到特定端口之前,先设置ServerSocket的一些选项。因为一旦服务器与特定端口绑定,有些选项就不能再改变了。

在以下代码中,先把ServerSocket的SO_REUSEADDR选项设为true,然后再把它与8000端口绑定:

ServerSocket serverSocket=new ServerSocket();

serverSocket.setReuseAddress(true);      //设置ServerSocket的选项

serverSocket.bind(new InetSocketAddress(8000));   //与8000端口绑定

如果把以上程序代码改为:

ServerSocket serverSocket=new ServerSocket(8000);

serverSocket.setReuseAddress(true);      //设置ServerSocket的选项

那么serverSocket.setReuseAddress(true)方法就不起任何作用了,因为SO_ REUSEADDR选项必须在服务器绑定端口之前设置才有效。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值