Java网络编程从入门到精通(14):多种多样的建立网络连接的方式

简介: 本文为原创,如需转载,请注明作者和出处,谢谢! 上一篇:Java网络编程从入门到精通(13):使用Socket类接收和发送数据 在上一篇文章中我们讨论了Socket类的基本用法,并给出的例子中使用Socket类连接服务器时使用了一种最简单的连接方式,也就是通过IP和端口号来连接服务器。

本文为原创,如需转载,请注明作者和出处,谢谢!

上一篇:Java网络编程从入门到精通(13):使用Socket类接收和发送数据

在上一篇文章中我们讨论了Socket类的基本用法,并给出的例子中使用Socket类连接服务器时使用了一种最简单的连接方式,也就是通过IP和端口号来连接服务器。而为了使连接服务器的方式更灵活,Socket类不仅可以通过自身的构造方法连接服务器,而且也可以通过connect方法来连接数据库。

一、通过构造方法连接服务器

    我们可以通过6个重载构造函数以不同的方式来连接服务器。这6个重载的构造函数可以分为两类:

1.
自动选择IP

    这种方式是最常用的。所谓自动选择
IP,是指当本机有多块网卡或者在一个网卡上绑定了多个IP时,Socket类会自动为我们选择一个可用的IP。在上述6个构造方法中有4个是使用这种方法来连接服务器的。

(1)    public Socket(String host, int port)

这是最常用的构造方法,在前面的例子中就是使用的这个构造方法。在使用时只需要提供一个字符串类型的IP或域名以及一个整型的端口号即可。在这个构造方法中可能会抛出两个错误:UnknownHostExceptionIOException。发生第一个错误的原因是我们提供的host并不存在或不合法,而其它的错误被归为IO错误。因此,这个构造方法的完整定义是:

public  Socket(String host,  int  port)  throws  UnknownHostException, IOException

(2) public Socket(InetAddress inetaddress, int port)

这个构造方法和第一种构造方法类似只是将字符串形式的host改为InetAddress对象类型了。在这个构造方法中之所以要使用InetAddress类主要是因为考虑到在程序中可能需要使用Socket类多次连接同一个IP或域名,这样使用InetAddress类的效率比较高。另外,在使用字符串类型的host连接服务器时,可能会发生两个错误,但使用InetAddress对象来描述host,只会发生IOException错误,这是因为当你将IP或域名传给InetAddress时,InetAddress会自动检查这个IP或域名,如果这个IP或域名无效,那么InetAddress就会抛出UnknownHostException错误,而不会由Socket类的构造方法抛出。因此,这个构造方法的完整定义是:

public  Socket(InetAddress inetaddress,  int  port)  throws  IOException

(3) public Socket(String host, int port, boolean stream)

这个构造方法和第一种构造方法差不多,只是多了一个boolean类型的stream参数。如果这个streamtrue,那么这个构造方法和第一种构造方法完全一样。如果streamfalse,则使用UDP协议建立一个UDP连接(UDP将在下面的章节详细讨论,在这里只要知道它和TCP最大的区别是UDP是面向无连接的,而TCP是面向有连接的),也许是当初Sun的开发人员在编写Socket类时还未考虑编写处理UDP连接的DatagramSocket类,所以才将建立UDP连接的功能加入到Socket类中,不过Sun在后来的JDK中加入了DatagramSocket类,所以,这个构造方法就没什么用了,因此,Sun将其设为了Deprecated标记,也就是说,这个构造方法在以后的JDK版本中可以会被删除。其于以上原因,在使用Java编写网络程序时,尽量不要使用这个构造方法来建立UDP连接。

(4) public Socket(InetAddress inetaddress, int port, boolean flag)

这个构造方法和第三种构造方法的flag标记的含义一样,也是不建议使用的。

    下面的代码演示
上述4种构造方法的使用:

package mysocket;

import  java.net. * ;
import  java.io. * ;

public   class MoreConnection
{
    
private   static   void  closeSocket(Socket socket)
    {
        
if  (socket  !=   null )
            
try
            {
                socket.close();
            }
            
catch  (Exception e) { }
    }

    
public   static   void  main(String[] args)
    {
        Socket socket1 
=   null , socket2  =   null , socket3  =   null , socket4  =   null ;
        
try
        {
            
//  如果将www.ptpress.com.cn改成其它不存在的域名,将抛出UnknownHostException错误
            
//  测试public Socket(String host, int port)
            socket1  =   new  Socket( " www.ptpress.com.cn " 80 );
            System.out.println(
" socket1连接成功! " );
            
//  测试public Socket(InetAddress inetaddress, int port)
            socket2  =   new  Socket(InetAddress.getByName( " www.ptpress.com.cn " ), 80 );
            System.out.println(
" socket2连接成功! " );

            
//  下面的两种建立连接的方式并不建议使用
            
//  测试public Socket(String host, int port, boolean stream)
            socket3  =   new  Socket( " www.ptpress.com.cn " 80 false );
            System.out.println(
" socket3连接成功! " );
            
//  测试public Socket(InetAddress inetaddress, int i, boolean flag)
            socket4  =   new  Socket(InetAddress.getByName( " www.ptpress.com.cn " ), 80 false );
            System.out.println(
" socket4连接成功! " );
        }
        
catch  (UnknownHostException e)
        {
            System.out.println(
" UnknownHostException 被抛出! " );
        }
        
catch  (IOException e)
        {
            System.out.println(
" IOException 被抛出! " );
        }
        
finally
        {
            closeSocket(socket1);
            closeSocket(socket2);
            closeSocket(socket3);
            closeSocket(socket4);
        }
    }
}

在上面代码中的最后通过finally关闭了被打开的Socket连接,这是一个好习惯。因为只有在将关闭Socket连接的代码写在finally里,无论是否出错,都会执行这些代码。但要注意,在关闭Socket连接之前,必须检查Socket对象是否为null,这是因为错误很可能在建立连接时发生,这样Socket对象就没有建立成功,也就用不着关闭了。

1.       手动绑定IP

当本机有多个IP时(这些IP可能是多块网卡上的,也可能是一块网卡上绑定的多个IP),在连接服务器时需要由客户端确定需要使用哪个IP。这样就必须使用Socket类的另外两个构方法来处理。下面让我们来看看这两个构造方法是如何来使用特定的IP来连接服务器的。

public Socket(String host, int port, InetAddress inetaddress, int localPort)

这个构造方法的参数分为两部分,第一部分为前两个参数:hostport,它们分别表示要连接的服务器的IP和端口号。第二部分为后两个参数:inetaddresslocalPort。其中inetaddress则表示要使用的本地的IP,而localPort则表示要绑定的本地端口号。这个localPort这以设置为本机的任何未被绑定的端口号。如果将localPort的值设为0java将在102465,535之间随即选择一个未绑定的端口号。因此,在一般情况下将localPort设为0

public Socket(InetAddress inetaddress, int port, InetAddress inetaddress1, int localPort)

这个构造方法和第一个构造方法基本相同,只是将第一个参数host换成了inetaddress。其它的使用方法和第一个构造方法类似。

下面的代码中将使用这两个构造方法来做一个实验。我们假设有两台计算机:PC1PC2PC1PC2各有一块网卡。PC1绑定有两个IP192.168.18.252200.200.200.200PC2绑定有一个IP200.200.200.4PC1PC2的子网掩码都是255.255.255.0。而PC1的默认网关为:192.168.28.254。下面的代码需要在PC1上运行。

package mysocket;

import  java.net. * ;

public   class MoreConnection1
{
    
public   static   void  main(String[] args)
    {
        
try
        {
            InetAddress localAddress1 
=  InetAddress.getByName( " 200.200.200.200 " );
            InetAddress localAddress2 
=  InetAddress.getByName( " 192.168.18.252 " );
            // 如果将localAddress1改成localAddress2,socket1无法连接成功
            Socket socket1  =   new  Socket( " 200.200.200.4 " 80 , localAddress1,  0 );
            System.out.println(
" socket1连接成功! " );
            Socket socket2 
=   new  Socket( " www.ptpress.com.cn " 80 , localAddress2,  0 );
            System.out.println(
" socket2连接成功! " );
            
//  下面的语句将抛出一个IOException错误
            Socket socket3  =   new  Socket( " www.ptpress.com.cn " 80 , localAddress1,  0 );
            System.out.println(
" socket3连接成功! " );
            socket1.close();
            socket2.close();
            socket3.close();
        }
        
catch  (Exception e)
        {
            System.out.println(e.getMessage());
        }
    }
}

运行上面代码的输出结果如下:

socket1连接成功 !
socket2连接成功
!
Connection timed out: connect

从上面的输出结果可以看出socket1socket2已经连接成功socket3并未连接成功。从例程4-8可以看出,socket1在连接时使用localAddress1绑定到了200.200.200.200上,而PC2IP200.200.200.4,因此,socket1所使用的IPPC2IP在同一个网段,所以socket1可以连接成功。如果将localAddress1改成localAddress2后,socket1将无法连接成功。另外两个Socket连接socket2socket3是通过Internet连接www.ptpress.com.cn。它们所不同的是socket2绑定的是192.168.18.252,而socket3绑定的是200.200.200.200。它们执行的结果是socket2可以连接成功,而socket3连接失败。这是因为socket2所绑定的IPPC1的默认网关192.168.18.254在同一个网段,因此,socket2可以连接到Internet。而socket3所绑定的IPPC1IP不在同一个网段,因此,socket3将无法连接到Internet

二、通过connect方法连接服务器

Socket类不仅可以通过构造方法直接连接服务器,而且还可以建立未连接的Socket对象,并通过connect方法来连接服务器。Socket类的connect方法有两个重载形式:

1. public void connect(SocketAddress endpoint) throws IOException

Socket类的connect方法和它的构造方法在描述服务器信息IP和端口上有一些差异。在connect方法中并未象构造方法中以字符串形式的host和整数形式的port作为参数,而是直接将IP和端口封装在了SocketAddress类的子类InetSocketAddress中。可按如下形式使用这个connect方法:

Socket socket  =   new  Socket();
socket.connect(
new  InetSocketAddress(host, port));

2.  public void connect(SocketAddress endpoint, int timeout) throws IOException

    这个connect方法和第一个connect类似,只是多了一个timeout参数。这个参数表示连接的超时时间,单位是毫秒。使用timeout设为0,则使用默认的超时时间。

在使用Socket类的构造方法连接服务器时可以直接通过构造方法绑定本地IP,而connect方法可以通过Socket类的bind方法来绑定本地IP。例程4-9演示如何使用connect方法和bind方法。

package mysocket;

import  java.net. * ;

public   class MoreConnection2
{
    
public   static   void  main(String[] args)
    {
        
try
        {
            Socket socket1 
=   new  Socket();
            Socket socket2 
=   new  Socket();
            Socket socket3 
=   new  Socket();
            socket1.connect(
new  InetSocketAddress( " 200.200.200.4 " 80 ));
            socket1.close();
            System.out.println(
" socket1连接成功! " );             
            
/*
               将socket2绑定到192.168.18.252将产生一个IOException错误  
            socket2.bind(new InetSocketAddress("192.168.18.252", 0));
            
*/
            socket2.bind(
new  InetSocketAddress( " 200.200.200.200 " 0 ));
            socket2.connect(
new  InetSocketAddress( " 200.200.200.4 " 80 ));
             
            socket2.close();
            System.out.println(
" socket2连接成功! " );

            socket3.bind(
new  InetSocketAddress( " 192.168.18.252 " 0 ));
            socket3.connect(
new  InetSocketAddress( " 200.200.200.4 " 80 ),  2000 );            
            socket3.close();
            System.out.println(
" socket3连接成功! " );
        }
        
catch  (Exception e)
        {
            System.out.println(e.getMessage());
        }
    }
}

上面的代码的输出结果为:


socket1连接成功 !
socket2连接成功
!
Connection timed out: connect

在上面代码中的socket3连接服务器时为其设置了超时时间(2000毫秒),因此,socket3在非常短的时间就抛出了IOException错误。

下一篇:
Java网络编程从入门到精通(15):为什么要使用SocketAddress来管理网络地址



国内最棒的Google Android技术社区(eoeandroid),欢迎访问!

《银河系列原创教程》发布

《Java Web开发速学宝典》出版,欢迎定购

相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
目录
相关文章
|
25天前
|
网络协议 Shell Linux
【Shell 命令集合 网络通讯 】Linux 创建网络连接 nc命令 使用教程
【Shell 命令集合 网络通讯 】Linux 创建网络连接 nc命令 使用教程
35 1
|
29天前
|
网络协议 算法 Java
|
25天前
|
网络协议 Shell Linux
【Shell 命令集合 网络通讯 】⭐⭐⭐Linux 测试与目标主机之间的网络连接ping 命令 使用指南
【Shell 命令集合 网络通讯 】⭐⭐⭐Linux 测试与目标主机之间的网络连接ping 命令 使用指南
39 1
|
25天前
|
监控 网络协议 Linux
【Shell 命令集合 网络通讯 】Linux 显示网络 连接、路由表和网络接口信息 netstat命令 使用指南
【Shell 命令集合 网络通讯 】Linux 显示网络 连接、路由表和网络接口信息 netstat命令 使用指南
44 1
|
17天前
|
关系型数据库 Java 开发工具
Java入门高频考查基础知识9(15问万字参考答案)
本文探讨了Spring Cloud的工作原理,包括注册中心的心跳机制、服务发现机制,以及Eureka默认的负载均衡策略。同时,概述了Spring Boot中常用的注解及其实现方式,并深入讨论了Spring事务的注解、回滚条件、传播性和隔离级别。文章还介绍了MySQL的存储引擎及其区别,特别关注了InnoDB如何实现MySQL的事务处理。此外,本文还详细探讨了MySQL索引,包括B+树的原理和设计索引的方法。最后,比较了Git和SVN的区别,并介绍了Git命令的底层原理及流程。
28 0
Java入门高频考查基础知识9(15问万字参考答案)
|
17天前
|
存储 缓存 算法
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
最重要的是保持自信和冷静。提前准备,并对自己的知识和经验有自信,这样您就能在面试中展现出最佳的表现。祝您面试顺利!Java 是一种广泛使用的面向对象编程语言,在软件开发领域有着重要的地位。Java 提供了丰富的库和强大的特性,适用于多种应用场景,包括企业应用、移动应用、嵌入式系统等。下是几个面试技巧:复习核心概念、熟悉常见问题、编码实践、项目经验准备、注意优缺点、积极参与互动、准备好问题问对方和知其所以然等,多准备最好轻松能举一反三。
46 0
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
|
17天前
|
存储 算法 JavaScript
Java入门高频考查算法逻辑基础知识3-编程篇(超详细18题1.8万字参考编程实现)
解决这类问题时,建议采取下面的步骤: 理解数学原理:确保你懂得基本的数学公式和法则,这对于制定解决方案至关重要。 优化算法:了解时间复杂度和空间复杂度,并寻找优化的机会。特别注意避免不必要的重复计算。 代码实践:多编写实践代码,并确保你的代码是高效、清晰且稳健的。 错误检查和测试:要为你的代码编写测试案例,测试标准的、边缘情况以及异常输入。 进行复杂问题简化:面对复杂的问题时,先尝试简化问题,然后逐步分析和解决。 沟通和解释:在编写代码的时候清晰地沟通你的思路,不仅要写出正确的代码,还要能向面试官解释你的
32 0
|
17天前
|
存储 Java 编译器
Java入门高频考查基础知识2(超详细28题2.5万字答案)
多态是面向对象编程中的一个重要概念,它允许不同类的对象对同一消息作出不同的响应。在具体实现上,多态允许一个父类的引用指向其子类的对象,并根据实际指向的对象的类型来调用相应的方法。在 Java 中,多态可以通过以下几种方式实现:在同一个类中,方法名相同,但形参列表不同,实现了多态。子类可以重写(覆盖)其父类的方法,实现多态。在父类引用中调用该方法时,根据实际指向的子类对象的类型来调用相应的方法实现。
38 0
|
26天前
|
网络协议 安全 Java
Java网络编程
Java网络编程
|
1月前
|
算法 Java
Java必刷入门递归题×5(内附详细递归解析图)
Java必刷入门递归题×5(内附详细递归解析图)
20 1