05 ServerSocket用法详解(一)


在客户端服务端通信模式中,服务器端需要创建监听特定端口的ServerSocket,ServerSocket负责接收客户端的连接请求。

1 构造ServerSocket

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

ServerSocket的构造方法有以下几种重载形式:
ServerSocket()throws IOException 
/***
	port: 服务器要监听的端口号
*/
ServerSocket(int port) throws IOException 
/***
	port: 服务器要监听的端口号
	backlog: 指定客户连接请求队列的长度
*/
ServerSocket(int port, int backlog) throws IOException
/***
	port: 服务器要监听的端口号
	backlog: 指定客户连接请求队列的长度
	bindAddr: 指定服务器要绑定的IP地址
*/
ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException    

1.1 绑定端口

通过构造函数的port参数绑定监听的端口,会抛出IOException,更确切的说,是抛出BindException,它是IOException的子类,一般由于一下原因造成:

  1. 端口已经被占用
  2. 个别操作系统,需要超级管理员用户的身份才可以绑定到1~1023之间的端口

另外,如果把端口设置为0,则表示系统有操作系统随机分配一个可用的端口。也被称为匿名端口。

@Test
public void testBindPort() throws IOException {
    // 设置为匿名端口
    ServerSocket serverSocket = new ServerSocket(0);
    // 获取分配的端口
    int localPort = serverSocket.getLocalPort();
    System.out.println("port: " + localPort);
}

多数服务器会监听固定的端口,这样才便于客户程序访问服务器。匿名端口一般适用与客户端之间的临时通信,通信结束后就断开连接,并且serversocket绑定的临时端口也会被释放

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

当服务器运行时,可能会同时监听到多个客户端的连接请求,管理客户端连接请求的任务是由于操作系统来完成的,操作系统把这些请求存储在一个FIFO(先进先出)的队列中。许多操作系统都限定了队列的最大长度,一般为50。当队列满了,服务器进程就会拒绝新的连接请求,这个时候客户端会抛出ConnectionException

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

  • backlog参数的值大于操作系统限定的队列的最大长度。
  • backlog参数的值小于或等于0。
  • 在ServerSocket构造方法中没有设置backlog参数。
new ServerSocket(80,2);

验证一下系统的默认值

  1. 客户端,模拟发送100个请求
import java.io.IOException;
import java.net.Socket;
public class Client {
    public static void main(String[] args) throws IOException, InterruptedException {
        final int port = 9999;
        final String host = "localhost";
        final int len = 100;
        // 尝试建立100次连接
        Socket[] sockets = new Socket[len];
        for (int i = 0; i < len; i++) {
            sockets[i] = new Socket(host, port);
            System.out.println("第" + (i + 1) + "次数" + "连接成功");
        }

        // 关闭连接
        for (int i = 0; i < len; i++) {
            sockets[i].close();
        }
    }
}
  1. 服务端
import java.io.IOException;
import java.net.ServerSocket;

public class Server {
    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务启动");
        // 为了服务不挂掉
       System.in.read();
    }
}

启动服务端和客户端,控制台输出

1次数连接成功
第2次数连接成功
第3次数连接成功
....49次数连接成功
第50次数连接成功
Exception in thread "main" java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)

可见默认是50次

设置最大连接数为3:

public class Server {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 设置最大连接数为3
        ServerSocket serverSocket = new ServerSocket(9999,3);
        System.out.println("服务启动");
        // 为了服务不挂掉
       System.in.read();
    }
}

重新测试:

1次数连接成功
第2次数连接成功
第3次数连接成功
Exception in thread "main" java.net.ConnectException: Connection refused: connect
  1. 修改服务端代码,之前的服务端没有处理请求,现在进行处理
public class Server {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 设置最大连接数为3
        ServerSocket serverSocket = new ServerSocket(9999,3);
        System.out.println("服务启动");
        while (true){
            Socket socket = null;
            try {
                // 获取客户端连接, 就是从连接请求队列中获取一个连接
                socket = serverSocket.accept();
                System.out.println("new connection from" + socket.getInetAddress() + ":"+ socket.getPort());
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(socket!=null) socket.close();
            }
        }
    }
}

再次测试,由于这次每来一个请求,服务端都及时处理,没有出现队列满的情况:

1次数连接成功
第2次数连接成功
第3次数连接成功
第4次数连接成功
...99次数连接成功
第100次数连接成功

1.3 绑定ip

如果主机只有一个ip,默认情况下,服务器程序就与该ip绑定。但是也可以通过构造方法绑定指定的ip。

/***
	port: 服务器要监听的端口号
	backlog: 指定客户连接请求队列的长度
	bindAddr: 指定服务器要绑定的IP地址
*/
ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException   

1.4 无参构造

通过该方法创建的ServerSocket不与任何端口绑定,接下来还需要通过bind()方法与特定端口绑定。

主要的作用是,在绑定端口之前,先对ServerSocket进行设置,因为有些设置,在绑定之后就无法设置了

2 accept方法

这个方法就是从连接请求队列中取出一个客户端请求,然后创建与客户端连接的Socket对象并返回,如果队列里没有请求,这个方法就会一直等待,直到收到了连接请求

Socket  socket = serverSocket.accept();

接下来获取输入流和输出流就可以和客户端交互了。当服务器正在进行发送数据操作时候,如何客户端断开了连接,那么服务端会抛出一个异常IOException的子类SocketException

服务端这种异常应该捕获掉,不应该因为这些异常导致服务端挂掉,导致其他客户端不能和服务端通信。

  while (true) {
    Socket socket=null;
    try {
      socket = serverSocket.accept();  //从连接请求队列中取出一个连接
      System.out.println("New connection accepted " +
      socket.getInetAddress() + ":" +socket.getPort());
      //接收和发送数据}catch (IOException e) {
      //这只是与单个客户通信时遇到的异常,可能是由于客户端过早断开连接引起的
      //这种异常不应该中断整个while循环
       e.printStackTrace();
    }finally {
       try{
         if(socket!=null)socket.close();  //与一个客户通信结束后,要关闭Socket
       }catch (IOException e) {e.printStackTrace();}
    }
  }

所以上面的代码,被trycatch包起来了,不期望在和某一个客户端交互的是发生异常,而导致服务端挂掉

3 关闭ServerSocket

close()方法会释放服务器程序占用的端口,断开和所有客户端的连接。

4 获取ServerSocket的信息

ServerSocket的以下两个get方法分别获得服务器绑定的IP地址,以及绑定的端口:

public InetAddress getInetAddress()
public int getLocalPort()

4 ServerSocket的配置项

  • SO_TIMEOUT:表示等待客户连接的超时时间。
  • SO_REUSEADDR:表示是否允许重用服务器所绑定的地址。
  • SO_RCVBUF:表示接收数据的缓冲区的大小

4.1 SO_TIMEOUT

等待客户连接的超时时间。以毫秒为单位。如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认值。

当服务器执行ServerSocket的accept()方法时,如果连接请求队列为空,服务器就会一直等待,直到接收到了客户连接才从accept()方法返回。如果设定了超时时间,那么当服务器等待的时间超过了超时时间,就会抛出SocketTimeoutException,它是InterruptedException的子类。

InterruptedException可以看出底层用的wait方法

// 设置该选项:
public void setSoTimeout(int timeout) throws SocketException
// 读取该选项:
public int getSoTimeout () throws IOException

4.2 SO_REUSEADDR选项

这个选项与Socket的SO_REUSEADDR选项相同,用于决定如果网络上仍然有数据向旧的ServerSocket传输,是否允许新的ServerSocket绑定到与旧的ServerSocket同样的端口。他的默认值和操作系统有关,有的系统允许重用端口,有的则不允许。

当serversocket关闭时,如果网络上还有发送到这个serversocket的数据,那么这个serversocket不会立刻释放端口,而是会等待一段时间,确保接收到了网络上发送过来的数据,然后释放端口。

为了确保一个进程关闭了ServerSocket后,即使操作系统还没释放端口,同一个主机上的其他进程还可以立刻重用该端口,可以调用ServerSocket的setResuseAddress(true)方法。

并且需要在绑定端口之前设置:

 ServerSocket serverSocket = new ServerSocket();
 serverSocket.setReuseAddress(true);
 serverSocket.bind(new InetSocketAddress(8000));

4.3 O_RCVBUF选项

SO_RCVBUF表示服务器端的用于接收数据的缓冲区的大小,以字节为单位。一般说来,传输大的连续的数据块(比如基于HTTP或FTP协议的数据传输)可以使用较大的缓冲区,这可以减少传输数据的次数,从而提高传输数据的效率。而对于交互式的通信(比如Telnet和网络游戏),则应该采用小的缓冲区,确保能及时把小批量的数据发送给对方。

// 设置该选项:
public void setReceiveBufferSize(int size) throws SocketException
// 读取该选项:
public int getReceiveBufferSize() throws SocketException

建议在绑定端口前进行设置,如果设置的大小超过了64k的缓冲区,就必须在绑定之前设置,才有效。

4.4 设定连接时间、延迟和带宽的相对重要性

该方法的作用与Socket的setPerformancePreferences()方法的作用相同,用于设定连接时间、延迟和带宽的相对重要性。

public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值