Java网络编程(三)—— Socket

850 阅读5分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

在客户/服务器通信模式中,客户端需要主动创建与服务器连接的 Socket,服务器端收到了客户端的连接请求,也会创建与客户连接的Socket。Socket可看做是通信连接两端的收发器,服务器与客户端都通过 Socket来收发数据。

构造Socket

Socket构造方法有以下几种形式:

(1) public Socket()
(2) public Socket(String host, int port) throws UnknownHostException, IOException
(3) public Socket(InetAddress address,int port) throws IOException
(4) public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
(5) public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
(6) public Socket(Proxy proxy)

上面6种构造方法中除了第一种是不带参的构造方法,其他构造方法都会试图与服务器进行连接,如果成功连接返回Socket对象,否则抛出异常。其中第(4)种和第(5)种会指定本地客户端的IP地址和端口信息,在默认情况下,客户端的IP地址来自客户程序所在的主机,客户端的端口则由操作系统随机分配,但是一台主机有可能同时拥有两个及以上的IP地址,这个时候就可以指定IP地址。

public static void main(String[] args) throws Exception {
        InetAddress remote = InetAddress.getByName("www.baidu.com");
        InetAddress local = InetAddress.getByName("localhost");
        
        Socket socket = new Socket(remote,8000,local,2345);
}

下面这段代码是判断本机端口1到1024中,哪些端口被程序监听,如果能成功建立socket,那么表示这个端口正在被程序所监听,否则没有被监听。

public static void main(String[] args) throws Exception {
        String host = "localhost";
        Socket socket = null;
        for (int port=1 ; port<=1024 ; port++)
        {
            try {
                socket = new Socket(host, port);
                System.out.println("本地端口 "+" "+port+" 被监听");
            }catch (IOException e)
            {
                System.out.println("本地端口 "+" "+port+" 没有被监听");
            }finally {
                if(socket!=null)
                    socket.close();
            }
        }
}

设定等待建立连接的超时时间

当客户端的Socket构造方法请求与服务器连接时,可能要等待一段时间。默认情况下,Socket构造方法会一直等待下去,直到连接成功,或者出现异常。Socket构造方法请求连接时,受底层网络的传输速度的影响,可能会处于长时间的等待状态。如果希望限定等待连接的时间的话,可以先使用一个不带参数的构造方法,然后调用connect函数设定等待时间进行连接:

public static void main(String[] args) throws Exception {
        Socket socket = new Socket();
        SocketAddress socketAddress = new InetSocketAddress("localhost", 8000);
        socket.connect(socketAddress,60000);
    }

以上代码用于连接到本地机器上的监听8000端口的服务器程序,等待连接的最长时间为1分钟。如果在1分钟内连接成功,则connect()方法顺利返回;如果在1分钟内出现某种异常,则抛出该异常;如果超过了1分钟后,既没有连接成功,也没有出现其他异常,那么会抛出SocketTimeoutException。Socket类的 connect(SocketAddres sendpoint,int timeout)方法负责连接服务器,参数endpoint 指定服务器的地址,参数timeout设定超时时间,以毫秒为单位。如果参数timeout设为0,表示永远不会超时。

获取Socket信息

在一个Socket对象中同时包含了远程服务器的IP地址和端口信息,以及客户本地的IP地址和端口信息。此外,从Socket对象中还可以获得输出流和输入流,分别用于向服务器发送数据,以及接收从服务器端发来的数据。以下方法用于获取Socket的有关信息:

获得远程服务器的IP地址。
getInetAddress()

获得远程服务器的端口。
getPort()

获得客户本地的IP地址。
getLocalAddress()

获得客户本地的端口。
getLocalPort()

获得输入流。如果 Socket还没有连接,或者已经关闭,或者已经通过shutdownInput()方法关闭输入流,那么此方法会抛出IOException。
getInputStream()

获得输出流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownOutput()方法关闭输出流,那么此方法会抛出IOException。
getOutputStream()

关闭Socket

当客户与服务器的通信结束,应该及时关闭Socket,以释放Socket占用的包括端口在内的各种资源。Socket 的close()方法负责关闭Socket。当一个Socket对象被关闭,就不能再通过它的输入流和输出流进行IO操作,否则会导致IOException。

为了确保关闭Socket的操作总是被执行,强烈建议把这个操作放在finally代码块中,类似于下面这种方式

public static void main(String[] args) throws Exception {
        Socket socket = null;
        try{
            socket = new Socket("localhost", 8000);
            //执行操作
            /*
            ......
             */
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(socket!=null)
                    socket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

此外,Socket还提供了三个状态测试的方法:

isClosed() :如果Socket已经连接到远程主机,并且还没有关闭,则返回true,否则返回falseisConnected() :如果Socket曾经连接到远程主机,则返回true,否则返回falseisBound() :如果 Socket已经与一个本地端口绑定,则返回true,否则返回false

半关闭

当调用Socket 的 close()方法关闭Socket 时,它的输出流和输入流也都被关闭。有的时候,可能仅仅希望关闭输出流或输入流之一。此时可以采用Socket类提供的半关闭方法:

shutdownInput():关闭输入流。
shutdownOutput():关闭输出流。

值得注意的是,先后调用Socket的 shutdownInput()和 shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等价于调用Socket 的 close()方法。在通信结束后,仍然要调用Socket的close()方法,因为只有该方法才会释放Socket占用的资源,如占用的本地端口等。