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

一、构造函数
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的子类),如下所示:

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端运行结果如下:

- 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端运行结果如下:

如果将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端运行结果如下:

Cient端运行结果如下:

三、构造函数绑定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):

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());
}
}
运行结果如下所示:


浙公网安备 33010602011771号