1、ServerSocket简单介绍

  ServerSocket.class实现了服务器套接字的功能。服务器套接字会等待通过网络传来的请求。它会根据该请求执行一些操作,然后可能向请求者返回结果。ServerSocket.class的UML关系图如下所示:
image

一、构造函数

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;
ServerSocket(SocketImpl impl);

1、int port指定服务器要绑定的端口(即服务器要监听的端口),如果运行时无法绑定1~65536之间的某个端口号,会抛出BindException(它是IOException的子类),如下所示:
image
BindException一般是由以下2种原因造成的:
①、端口已经被其他服务器进程占用。
②、在某些操作系统中,如果没有以超级用户的身份来运行服务器程序,那么操作系统不允许服务器绑定到1~1023之间的端口。
2、int backlog指定客户连接请求队列的长度,如果没有指定,默认值是50;
3、InetAddress bindAddr指定服务器要绑定的IP地址;

二、客户连接请求队列的长度

  管理客户连接请求的任务是由操作系统来完成的。操作系统把这些连接请求存储在一个先进先出的队列中。许多操作系统都限定了队列的最大长度,一般为50。当队列中的连接请求达到了队列的最大长度时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的accept()函数从队列中取出连接请求,使队列腾出空位,队列才能继续加入新的连接请求。如下代码所示:

  • Server端
package com.xxx.serverSocket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
   private ServerSocket serverSocket = null;

   public Server() throws IOException {
      this.serverSocket = new ServerSocket(8080,3);//端口号为8080,请求连接的最大长度为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 {
            if (socket != null) {
               try {
                  socket.close();//无论如何,最终都会关闭这个Socket
               } catch (IOException e) {
                  e.printStackTrace();
               }
            }
         }
      }
   }

   public static void main(String[] args) throws Exception {
      Server server = new Server();
      Thread.sleep(600 * 1000);
//    server.service();
   }
}

Server端运行结果如下:
image

  • Cient端
package com.xxx.serverSocket;
import java.net.Socket;
public class Client {
   public static void main(String[] args) throws Exception {
      Socket[] sockets = new Socket[100];
      for (int i = 0; i < sockets.length; i++) {
         sockets[i] = new Socket("127.0.0.1", 8080);
         System.out.println("第" + (i + 1) + "次连接成功");
      }
      Thread.sleep(3000);
      for (int i = 0; i < sockets.length; i++) {
         sockets[i].close();
      }
   }
}

Cient端运行结果如下:
image

如果将Server端的代码修改如下(只修改了main函数中的内容,调用service()函数来处理Client端的连接Socket,并且在finally块中关闭Client端的连接Socket):

package com.xxx.serverSocket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
   private ServerSocket serverSocket = null;

   public Server() throws IOException {
      this.serverSocket = new ServerSocket(8080,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 {
            if (socket != null) {
               try {
                  socket.close();
               } catch (IOException e) {
                  e.printStackTrace();
               }
            }
         }
      }
   }

   public static void main(String[] args) throws Exception {
      Server server = new Server();
      server.service();
   }
}

Server端运行结果如下:
image

Cient端运行结果如下:
clipboard

三、构造函数绑定IP和bind()函数绑定port

3.1、构造函数绑定IP

  如果主机只有一个IP地址,那么在默认情况下,服务器程序就与该IP地址绑定。下面这个构造函数就是绑定IP、port、和连接队列长度的构造函数:

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

InetAddress bindAddr用来指定IP地址。该构造方法适用于具有多个IP地址的主机。假定一个主机有两个网卡,一个网卡用于连接到Internet,IP地址为222.67.5.94,另一个网卡用于连接到本地局域网,IP地址为192.168.3.4。如果服务器仅仅被本地局域网中的客户访问,那么可以按如下方式创建

ServerSocket serverSocket = new ServerSocket(8080,3,InetAddress.getByName("192.168.3.4"));
3.2、bind()函数绑定port

  ServerSocket有一个不带参数的默认构造方法。通过该方法创建的ServerSocket不与任何端口绑定,需要通过bind()函数,如下所示:

ServerSocket serverSocket = new ServerSocket();
//需要先把SO_REUSEADDR选项设为true(SO_REUSEADDR在setReuseAddress()函数的源码里),才能进行port绑定
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(8000));//与8000端日进行绑定

如果把以上程序代码改为如下所示,则会失效(因为SO_REUSEADDR选项必须在服务器绑定端口之前设置才有效):

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

四、close()函数、isBound()函数、isClosed()函数

  ServerSocket的close()函数使服务器释放占用的端口,并且断开与所有客户的连接。当一个服务器程序运行结束时,即使没有执行ServerSocket的close()函数,操作系统也会释放这个服务器占用的端口。因此,服务器程序并不一定要在结束之前执行ServerSocket的close()函数。
在某些情况下,如果希望及时释放服务器的端口,以便让其他程序能占用该端口,则可以显式地调用ServerSocket的close()方法。例如以下代码用于扫描1~65535之间的端口号。如果ServerSocket成功创建,则意味着该端口未被其他服务器进程绑定,否则说明该端口已经被其他进程占用,如下代码所示(个人认为这种方式不好,不如执行shell命令再解析shell命令返回的字符串高效,windows系统查看端口号的shell命令为netstat -ano):
clipboard

  ServerSocket的isBound()函数判断ServerSocket是否已经与一个端口绑定,只要ServerSocket已经与一个端口绑定,即使它已经被关闭,isBound()函数也会返回true。
  如果需要判断一个ServerSocket是否已经与特定端口绑定,并且还没有被关闭,则可以采用以下方式:

boolean isOpen = serverSocket.isBound() && !serverSocket.isClosed();

五、getInetAddress()函数和getLocalPort()函数

  getInetAddress()函数用于获得服务器绑定的IP地址:和getLocalPort()函数用于获得服务器绑定的port,如果把port设为0,那么将由操作系统为服务器分配一个port(称为匿名port)。如下代码所示:

package com.xxx.serverSocket;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
public class ServerSocketTest {
   public static void main(String[] args) throws IOException {
       //把port设为0,请求连接的最大长度为3,IP设为127.0.0.1
      ServerSocket serverSocket = new ServerSocket(0,3, InetAddress.getByName("127.0.0.1"));
      System.out.println(serverSocket.getInetAddress());
      System.out.println(serverSocket.getLocalPort());
   }
}

运行结果如下所示:
clipboard

posted @ 2026-01-26 11:19  Carey_ccl  阅读(4)  评论(0)    收藏  举报