Java网络通信Socket

1、网络编程基础

网络编程是指编写与其他计算机进行通信的程序。Java已经将网络程序所需要的东西封装成不同的类。只要创建这些类的对象,使用相应的方法,即使设计人员不具备有关网络知识,也可以编写高质量的网络通信程序。

网络的三个条件:服务器,网络,客户机。

1.1 客户机

请求信息的计算机或程序。

1.2 服务器

提供信息的计算机或程序。

1.3 网络

网络用于连接服务器与客户机,实现两者相互通信。但有时在某个网络中很难将服务器与客户机区分开。我们通常所说的“局域网”(Local Area Network,LAN),就是一群通过一定形式连接起来的计算机。它可以由两台计算机组成,也可以由同一区域内的上千台计算机组成。由LAN延伸到更大的范围,这样的网络称为“广域网”(Wide Area Network,WAN)。我们熟悉的因特网(Internet),则是由无数的LAN和WAN组成。LAN是由特定类型的传输媒体(光纤、电缆、无线媒体)和网络适配器(网卡)连在一起的计算机,并受网络操作系统监控的网络系统。
1.3.1 网络协议

网络协议规定了计算机之间连接的物理、机械(网线与网卡的连接规定)、电气(有效的电平范围)等特征以及计算机之间的相互寻址规则、数据发送冲突的解决、长的数据如何分段传送与接收等。就像不同的国家有不同的法律一样,目前网络协议也有多种,下面简单地介绍几个常用的网络协议。

IP协议:互联网协议(nternet Protocol)。

TCP协议:传输控制协议(Transmission Control Protocol)。

UDP协议:用户数据报协议(User Datagram Protocol)。
1.3.2 端口和套接字

一般而言,一台计算机只有单一的连到网络的“物理连接”(Physical Connection),所有的数据都通过此连接对内、对外送达特定的计算机。这就是端口。网络程序设计中的端口(port)并非真实的物理存在,而是一个假想的连接装置。端口被规定为一个在0~65535之间的整数。HTTP服务一般使用80端口,FTP服务使用21端口。假如一台计算机提供了HTTP、FTP等多种服务,那么客户机通过不同的端口来确定连接到服务器的哪项服务上。

网络程序中套接字(Socket)用于将应用程序与端口连接起来。套接字是一个假想的连接装置,就像插插头的设备“插座”,用于连接电器与电线。Java将套接字抽象化为类,程序设计者只需创建Socket类对象,即可使用套接字。
2、TCP程序设计基础

TCP程序设计是指利用Socket类编写通信程序。利用TCP协议进行通信的两个应用程序时有主次之分的,一个称为服务器程序,另一个称为客户机程序,两者的功能和编写方法大不一样。
2.1 InetAddress类

java.net包中InetAddress类是与IP地址相关的类,利用该类可以获取IP地址、主机地址等信息。

InetAddress类的所有方法:

booleanequals(Object obj)

将此对象与指定对象进行比较。

byte[]getAddress()

返回此 InetAddress对象的原始IP地址。

static InetAddress[]getAllByName(String host)

给定主机的名称,根据系统上配置的名称服务返回其IP地址数组。

static InetAddressgetByAddress(byte[] addr)

给出原始IP地址的 InetAddress对象。

static InetAddressgetByAddress(String host, byte[] addr)

根据提供的主机名和IP地址创建InetAddress。

static InetAddressgetByName(String host)

确定主机名称的IP地址。

StringgetCanonicalHostName()

获取此IP地址的完全限定域名。

StringgetHostAddress()

返回文本显示中的IP地址字符串。

StringgetHostName()

获取此IP地址的主机名。

static InetAddressgetLocalHost()

返回本地主机的地址。

static InetAddressgetLoopbackAddress()

返回回送地址。

inthashCode()

返回此IP地址的哈希码。

booleanisAnyLocalAddress()

检查通配符地址中的InetAddress的实用程序。

booleanisLinkLocalAddress()

检查InetAddress是否是链接本地地址的实用程序。

booleanisLoopbackAddress()

检查InetAddress是否是一个环回地址的实用程序。

booleanisMCGlobal()

检查多播地址是否具有全局范围的实用程序。

booleanisMCLinkLocal()

检查组播地址是否具有链路范围的实用程序。

booleanisMCNodeLocal()

检查多播地址是否具有节点范围的实用程序。

booleanisMCOrgLocal()

检查组播地址是否具有组织范围的实用程序。

booleanisMCSiteLocal()

检查多播地址是否具有站点范围的实用程序。

booleanisMulticastAddress()

检查InetAddress是否是IP组播地址的实用程序。

booleanisReachable(int timeout)

测试该地址是否可达。

booleanisReachable(NetworkInterface netif, int ttl, int timeout)

测试该地址是否可达。

booleanisSiteLocalAddress()

检查InetAddress是否是站点本地地址的实用程序。

StringtoString()

将此IP地址转换为 String

InetAddress类的常用方法:

static InetAddressgetByName(String host)

确定主机名称的IP地址。

static InetAddressgetLocalHost()

返回本地主机的地址。

StringgetHostName()

获取此IP地址的主机名。

StringgetHostAddress()

返回文本显示中的IP地址字符串。

示例:使用InetAddress对象获取本地主机的本机名、本机IP地址。

测试:

测试内容:使用InetAddress类获取本地主机的主机名、本地的IP地址

代码如下:

package com.Attacking.demo.Socket;

import java.net.*;

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/7 17:05
 */
public class TestSocket2 {
    public static void main(String[] args) {
        /**
         * 使用方式1:获取本地的IP地址以及指定域名的IP地址
         */
        try{
            InetAddress address = InetAddress.getLocalHost();
            System.out.println("本机IP地址为:" + address.getHostAddress() + ", 主机名为:" + address.getHostName());
            InetAddress address1 = InetAddress.getByName("10.1.60.105");
            System.out.println("远程IP地址为:" + address1.getHostAddress() + ", 主机名为:" + address1.getHostName());
            InetAddress address2 = InetAddress.getByName("www.baidu.com");
            System.out.println("域名IP地址为:" + address2.getHostAddress() + ", 主机名为:" + address2.getHostName());
        }catch (UnknownHostException e){
            e.printStackTrace();
        }
    }
}

测试结果:

本机IP地址为:10.4.4.83, 主机名为:lenovo-PC
远程IP地址为:10.1.60.105, 主机名为:10.1.60.105
域名IP地址为:39.156.66.14, 主机名为:www.baidu.com

Process finished with exit code 0

测试结论:

1.InetAddress类的方法会抛出UnKnownHostException异常,所以处理的时候要么在方法上面抛出异常throws UnKnownHostException或者使用try{}catch(UnKnownHostException e){}方法抛出异常
2.使用InetAddress类的getLocalHost()可以得到本地的IP和主机名,使用getByName(param)可以访问远程服务器的IP(param可以为远程服务器IP也可以为域名)和域名

 

2.2 ServerSocket类

java.net包中ServerSocket类用于表示服务器套接字,其主要功能是等待来自网络上的“请求”,它可通过指定的端口来等待连接的套接字。服务器套接字一次可以与一个套接字连接。如果多台客户机同时提出连接请求,服务器套接字会将请求连接的客户机存入列队中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数大于最大容纳数,则多出的连接请求被拒绝。队列的默认大小是50。

ServerSocket类的构造方法都抛出IOException异常,分别有以下几种形式。

ServerSocket()

 

创建未绑定的服务器套接字。
ServerSocket(int port)

 

创建绑定到指定端口的服务器套接字。
ServerSocket(int port, int backlog)

 

创建服务器套接字并将其绑定到指定的本地端口号,并指定了积压。
ServerSocket(int port, int backlog, InetAddress bindAddr)

 

创建一个具有指定端口的服务器,侦听backlog和本地IP地址绑定。

该构造适用于计算机有多网卡和多IP的情况,可以明确规定在哪块网卡或IP地址上等待客户的连接请求

ServerSocket类的所有方法:

Socketaccept()

侦听要连接到此套接字并接受它。

voidbind(SocketAddress endpoint)

ServerSocket绑定到特定地址(IP地址和端口号)。

voidbind(SocketAddress endpoint, int backlog)

ServerSocket绑定到特定地址(IP地址和端口号)。

voidclose()

关闭此套接字。

ServerSocketChannelgetChannel()

返回与此套接字相关联的唯一的ServerSocketChannel对象(如果有)。

InetAddressgetInetAddress()

返回此服务器套接字的本地地址。

intgetLocalPort()

返回此套接字正在侦听的端口号。

SocketAddressgetLocalSocketAddress()

返回此套接字绑定到的端点的地址。

intgetReceiveBufferSize()

获取此 ServerSocketSO_RCVBUF选项的值,即将用于从该 ServerSocket接受的套接字的建议缓冲区大小。

booleangetReuseAddress()

测试是否启用了 SO_REUSEADDR

intgetSoTimeout()

检索 SO_TIMEOUT设置。

protected voidimplAccept(Socket s)

ServerSocket的子类使用这个方法来覆盖accept()来返回自己的套接字子类。

booleanisBound()

返回ServerSocket的绑定状态。

booleanisClosed()

返回ServerSocket的关闭状态。

voidsetPerformancePreferences(int connectionTime, int latency, int bandwidth)

设置此ServerSocket的性能首选项。

voidsetReceiveBufferSize(int size)

设置从 ServerSocket接受的套接字的 SO_RCVBUF选项的默认建议值。

voidsetReuseAddress(boolean on)

启用/禁用 SO_REUSEADDR套接字选项。

static voidsetSocketFactory(SocketImplFactory fac)

设置应用程序的服务器套接字实现工厂。

voidsetSoTimeout(int timeout)

启用/禁用 SO_TIMEOUT带有指定超时,以毫秒为单位。

StringtoString()

将该套接字的实现地址和实现端口返回为 String

ServerSocket类的常用方法:

Socketaccept()

侦听要连接到此套接字并接受它。

booleanisBound()

返回ServerSocket的绑定状态。

InetAddressgetInetAddress()

返回此服务器套接字的本地地址。

booleanisClosed()

返回ServerSocket的关闭状态。

voidclose()

关闭此套接字。

voidbind(SocketAddress endpoint)

ServerSocket绑定到特定地址(IP地址和端口号)。

intgetLocalPort()

返回此套接字正在侦听的端口号。

调用ServerSocket类的accept()方法会返回一个和客户端Socket对象连接的Socket对象,服务器端的Socket对象使用getOutputStream()方法获得的输出流将指向客户端Socket对象使用的getInputStream()方法获得的那个输入流;同样,服务器端的Socket对象使用的getInputStream()方法获得的输入流将指向客户端Socket对象使用的getOutputStream()方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之亦然。

注意:

accept()方法会阻塞线程的继续执行,直到接受到客户的呼叫。如果没有客户呼叫服务器,那么该方法下面的程序都不会执行。语句如果没有客户请求,accept()方法没有发生阻塞,肯定是程序出现了问题。通常是使用了一个还在被其它程序占用的端口号,ServerSocket绑定没有成功。

2.3 TCP网络程序

在网络编程中如果只要求客户机向服务器发送消息,不用服务器向客户机发送消息,称为单向通信。客户机套接字和服务器套接字连接成功后,客户机通过输出流发送数据,而服务器会使用输入流接收数据。

测试:

实现简单的单向通信功能。

(1)服务端Server,接收客户端请求并响应

package com.Attacking.demo.Socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 14:31
 */
public class ServiceSocket {
    public static void main(String[] args) throws IOException {
        //创建服务端socket,监听18976端口
        ServerSocket serverSocket = new ServerSocket(18976);
        //等待客户端连接
        Socket socket = serverSocket.accept();
        //获取客户端的输入流
        InputStream inputStream = socket.getInputStream();
        //将字节流转化为字符流
        InputStreamReader reader = new InputStreamReader(inputStream);
        //将字符流加入缓冲区
        BufferedReader bufferedReader = new BufferedReader(reader);
        String string = null;
        //打印客户端输入内容
        while ((string = bufferedReader.readLine()) != null){
            System.out.println("客户端传入信息为:" + string);
        }
        //关闭输入流
        socket.shutdownInput();
        //创建输出流
        OutputStream outputStream = socket.getOutputStream();
        //写入数据
        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.write("谢谢大家关注我的blog");
        printWriter.flush();

        //关闭所有资源
        printWriter.close();
        outputStream.close();
        bufferedReader.close();
        reader.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
    }
}

   (2)客户端Client:发送信息至服务器端并获取响应内容。

package com.Attacking.demo.Socket;

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

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 14:32
 */
public class ClientSocket {
    public static void main(String[] args) {
        try {
            //创建客户端socket
            Socket socket = new Socket("localhost", 18976);
            //创建输出流
            OutputStream outputStream = socket.getOutputStream();
            //写入数据
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write("我是AttackingApe,请大家关注我的blog");
            printWriter.flush();
            //关闭输出流
            socket.shutdownOutput();
            //获取服务端的输入流
            InputStream inputStream = socket.getInputStream();
            //将字节流转化为字符流
            InputStreamReader reader = new InputStreamReader(inputStream);
            //将字符流加入缓冲区
            BufferedReader bufferedReader = new BufferedReader(reader);
            String string = null;
            //打印内容
            while ((string = bufferedReader.readLine()) != null){
                System.out.println("服务端返回信息为:" + string);
            }

            //关闭所有资源
            bufferedReader.close();
            reader.close();
            inputStream.close();
            printWriter.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            System.out.println("客户端与服务端交互失败");
            e.printStackTrace();
        }
    }
}

  启动服务端和客户端,得到测试结果:

服务端:

客户端传入信息为:我是AttackingApe,请大家关注我的blog

Process finished with exit code 0

客户端:

服务端返回信息为:谢谢大家关注我的blog

Process finished with exit code 0

3、UDP程序设计基础

用户数据报协议(UDP)是网络信息传输的另一种形式。基于UDP的通信和基于TCP的通信不同,基于UDP的信息传递更快,但不提供可靠的保证。使用UDP传递数据时,用户无法知道数据能否正确的到达主机,也不能确定到达目的地顺序是否和发送的顺序相同。虽然UDP是一种不可靠的协议,但如果需要较快地传递信息,并能容忍小的错误,可以考虑使用UDP。

基于UDP通信的基本模式如下:

(1)将数据打包(称为数据报),然后将数据报发往目的地。

(2)接受别人发来的数据包,然后查看数据包。

下面总结的UDP程序的步骤。

发送数据包:

(1)使用DatagramSocket()创建一个数据包套接字。

(2)使用DatagramPacket(byte[] buf , int length , InetAddress address , int port)创建要发送的数据包。

(3)使用DatagramSocket类的send(DatagramPacket p)方法发送数据包。

接受数据包:

(1)使用DatagramSocket(int port)创建数据包套接字,绑定都指定的端口。

(2)使用DatagramPacket(byte[] buf , int length)创建字节数组来接受数据包。

(3)使用DatagramSocket类的receive(DatagramPacket p)方法接收UDP包。

注意:DatagramSocket类的receive()方法接收数据时,如果还没有可以接受的数据,在正常情况下receive()方法将阻塞,一直等到网络上有数据传来,receive()方法接受该数据并返回。如果网络上没有数据发送过来,receive()方法也没有阻塞,肯定是程序有问题,大多数是使用了一个被其它程序占用的端口。
3.1 DatagramPacket类

java.net包的DatagramPacket类用来表示数据包。DatagramPacket类的构造函数有:

DatagramPacket(byte[] buf , int length)。

DatagramPacket(byte[] buf , int length , InetAddress address , int port)。

第一种构造函数创建DatagramPacket对象,指定了数据包的内存空间和大小。第二种构造函数不仅指定了数据包的内存空间和大小,而且指定了数据包的目标地址和端口。在发送数据时,必须指定接收方的Socket地址和端口号,因此使用第二种构造函数可创建发送数据的DatagramPacket对象。
3.2 DatagramSocket类

java.net包中的DatagramSocket类用于表示发送和接收数据包的套接字。该类的构造函数有:

DatagramSocket()。

DatagramSocket(int port)。

DatagramSocket(int port , InetAddress addr)。

第一种构造函数创建DatagramSocket对象,构造数据报套接字并将其绑定到本地主机上任何可用的端口。使用第二种构造函数创建DatagramSocket对象,创建数据报套接字并将其绑定到本地主机上的指定端口。第三种构造函数创建DatagramSocket对象,创建数据报套接字,将其绑定到指定的本地地址。第三种构造函数适用于有多块网卡和多个IP的情况。
3.3 UDP网络程序

根据前面所讲的网络编程的基本知识,以及UDP网络编程的特点,下面创建一个广播数据报程序。广播数据报是一种较新的技术,类似于广播电台,广播电台需要在指定的波段和频率上广播信息,收听者也要将收音机调到指定的波段、频率才可以收听广播内容。

测试:

测试内容:主机不断地重复播出节目预报,可以保证加入到同一组的主机随时可接收到广播信息。接收者将正在接收的信息放在一个文本域中,并将接收的全部信息放在另一个文本域中。

测试:

测试内容:
1.广播程序不断地向外播出信息
2.接收广播程序,单击"开始接收"按钮,系统开始获取广播信息,单击"停止接收"按钮,系统会停止获取广播信息

(1)广播主机程序

  代码如下:

package com.Attacking.demo.Socket;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 16:22
 */
public class SendBroadCast extends Thread{
    String content = "疫情期间,请带好口罩,减少出门";
    //监听端口
    int port = 18966;
    //创建InetAddress对象
    InetAddress inetAddress = null;
    //创建多播套接字对象
    MulticastSocket socket = null;

    public SendBroadCast(){
        try{
            //指定连接地址,IP地址只能为224.0.0.0至224.255.255.255返回内(不代表某个特定的主机位置)
            inetAddress = InetAddress.getByName("224.224.255.224");
            //实例化广播套接字
            socket = new MulticastSocket(port);
            //指定发送范围为本地网络
            socket.setTimeToLive(1);
            //将链路加入到广播组
            socket.joinGroup(inetAddress);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true){
            //创建DatagramPacket广播数据对象
            DatagramPacket datagramPacket = null;
            //将广播内容转化为字符数组
            byte[] data = content.getBytes();
            //将数据就行打包
            datagramPacket = new DatagramPacket(data, data.length, inetAddress, port);
            try {
                //发送数据
                socket.send(datagramPacket);
                //线程休眠2s
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        SendBroadCast sendBroadCast = new SendBroadCast();
        sendBroadCast.start();
    }
}

(2)接收广播程序

代码如下:

package com.Attacking.demo.Socket;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 16:22
 */
public class ReceiveBroadCast extends JFrame implements Runnable, ActionListener {
    //监听端口号
    int port = 18966;
    //声明InetAddress对象
    InetAddress inetAddress = null;
    //声明多点广播套接字对象
    MulticastSocket socket = null;
    //接收按钮
    JButton receiveJB = new JButton("开始接收");
    //停止接收按钮
    JButton stopJB = new JButton("停止接收");
    //接收广播的文本域
    JTextArea receiveJT = new JTextArea(10, 10);
    JTextArea stopJT = new JTextArea(10, 10);
    Thread thread;
    boolean b = false;

    public ReceiveBroadCast(){
        super("广播报");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        thread = new Thread(this);
        //绑定接收按钮的单击事件
        receiveJB.addActionListener(this);
        //绑定停止接收按钮的单击事件
        stopJB.addActionListener(this);
        //指定文本域中文字颜色
        receiveJT.setForeground(Color.RED);
        //创建按钮面板
        JPanel northJP = new JPanel();
        //将按钮添加到面板上
        northJP.add(receiveJB);
        northJP.add(stopJB);
        //将northJP面板放置在窗体的上部
        add(northJP, BorderLayout.NORTH);
        //创建中心面板
        JPanel centerJP = new JPanel();
        //设置面板布局
        centerJP.setLayout(new GridLayout(1, 2));
        //将文本域添加到面板上
        centerJP.add(receiveJT);

        //定义滚动
        final JScrollPane scrollPane = new JScrollPane();
        //将滚动放在centerJP面板
        centerJP.add(scrollPane);
        scrollPane.setViewportView(stopJT);
        //设置面板布局
        add(centerJP, BorderLayout.CENTER);
        //刷新
        validate();

        try {
            //指定接收地址,IP地址只能为224.0.0.0至224.255.255.255返回内(不代表某个特定的主机位置)
            inetAddress = InetAddress.getByName("224.224.255.224");
            //绑定多点广播套接字
            socket = new MulticastSocket(port);
            //加入广播组
            socket.joinGroup(inetAddress);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //设置布局
        setBounds(100, 50, 360, 380);
        //将窗体设置为显示状态
        setVisible(true);
        setLocationRelativeTo(null);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        //单击接收按钮触发的事件
        if(e.getSource() == receiveJB){
            //设置按钮颜色
            receiveJB.setBackground(Color.blue);
            stopJB.setBackground(Color.CYAN);
            //如线程不处于"新建状态"
            if(!thread.isAlive()){
                //实例化Thread对象
                thread = new Thread(this);
            }
            //启动线程
            thread.start();
            b = false;
        }

        //单击停止接收按钮触发的事件
        if(e.getSource() == stopJB){
            //设置按钮颜色
            receiveJB.setBackground(Color.CYAN);
            stopJB.setBackground(Color.blue);
            b = true;
        }
    }

    @Override
    public void run() {
        while (true){
            //创建byte数组
            byte[] bytes = new byte[1024];
            //待接收的数据包
            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, inetAddress, port);
            try {
                //接收数据包
                socket.receive(datagramPacket);
                //获取数据包中内容
                String info = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
                //将接收内容显示在文本域中
                receiveJT.setText("正在接收以下内容:\n" + info);
                //每条信息为一行
                stopJT.append(info + "\n");
            } catch (Exception e) {
                e.printStackTrace();
            }

            //当变量等于true时,退出循环
            if(b){
                break;
            }
        }
    }

    public static void main(String[] args) {
        ReceiveBroadCast receiveBroadCast = new ReceiveBroadCast();
        //设置窗体大小
        receiveBroadCast.setSize(460, 300);
    }
}

测试结果:

注意:

要广播或接收广播的主机地址必须加入到一个组内,地址范围为224.0.0.0至224.255.255.255,这类地址并不代表某个特定主机的位置。加入到同一个组的主机可以在某个端口上广播消息,也可以在某个端口上接收信息。

4、Socket超时时间设置

在Java中设置Socket的超时时间有两种方式:

       ①Socket socket = new Socket(); socket.connect(new InetSocketAddress(ip,port),timeout);

       ②Socket socket = new Socket(ip,port); socket.setSoTimeout(timeout);

4.1 connect设置超时时间

测试1:

测试内容:设置一个未知的IP,超时时间为10s,查看结果

代码如下:

package com.Attacking.demo.Socket;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

/**
 *   设置连接到某个IP和Port的超时时间的两种方式(使用socket.connect(socketAddress, timeout);使用socket.setSoTimeout(timeout))
 *   第一种方式(socket.connect(socketAddress, timeout)):如果IP是一个未知的IP,则socket会一直尝试连接此IP直到超时;如果IP是一个已知的IP,未知端口,则socket会立即报错
 *   第二种方式(socket.setSoTimeout(timeout))
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 10:59
 */
public class SetTimeout {
    public static void main(String[] args) {
        Socket socket = new Socket();
        SocketAddress address = new InetSocketAddress("29.212.19.251", 18080);
        try {
            socket.connect(address, 10000);
            System.out.println("连接成功");
        } catch (IOException e) {
            System.out.println("连接超时");
            e.printStackTrace();
        }

        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试结果:

连接超时
java.net.SocketTimeoutException: connect timed out
	at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at com.Attacking.demo.Socket.SetTimeout.main(SetTimeout.java:22)

Process finished with exit code 0

测试2:

测试内容:设置一个已知的IP,未监听端口,超时时间为10s,查看结果

代码如下:

package com.Attacking.demo.Socket;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

/**
 *   设置连接到某个IP和Port的超时时间的两种方式(使用socket.connect(socketAddress, timeout);使用socket.setSoTimeout(timeout))
 *   第一种方式(socket.connect(socketAddress, timeout)):如果IP是一个未知的IP,则socket会一直尝试连接此IP直到超时;如果IP是一个已知的IP,未知端口,则socket会立即报错
 *   第二种方式(socket.setSoTimeout(timeout))
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 10:59
 */
public class SetTimeout {
    public static void main(String[] args) {
        Socket socket = new Socket();
        SocketAddress address = new InetSocketAddress("10.1.62.34", 18215);
        try {
            socket.connect(address, 10000);
            System.out.println("连接成功");
        } catch (IOException e) {
            System.out.println("连接超时");
            e.printStackTrace();
        }

        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试结果:

连接超时
java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at com.Attacking.demo.Socket.SetTimeout.main(SetTimeout.java:21)

Process finished with exit code 0

综合测试1,2:

1.从测试1可以知道,当IP为1个未知IP的时候,socket在10s内已知尝试连接直到超时,并抛出java.net.SocketTimeoutException: connect timed out异常。
2.从测试2可以知道,当IP已知PORT未知的时候,socket会立即报错不会等待超时时间,并抛出java.net.ConnectException: Connection refused: connect异常。
3.综合1和2可知:使用connect方法设置超时时间。如果IP未知,则socket会一直连接直到超时,并抛出java.net.SocketTimeoutException: connect timed out异常;如果PORT未知,则socket立即抛出java.net.ConnectException: Connection refused: connect异常。

4.2 setSoTimeout设置超时时间

测试:

测试内容:模拟服务端和客户端,客户端设置socket的超时时间为8s,服务端线程沉睡10s,查看客户端和服务端的执行情况。

代码如下:

①客户端:

package com.Attacking.demo.Socket;

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

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 14:32
 */
public class ClientSocket {
    public static void main(String[] args) {
        try {
            //创建客户端socket
            Socket socket = new Socket("localhost", 18976);
            socket.setSoTimeout(8000);
            //创建输出流
            OutputStream outputStream = socket.getOutputStream();
            //写入数据
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write("我是AttackingApe,请大家关注我的blog");
            printWriter.flush();
            //关闭输出流
            socket.shutdownOutput();
            //获取服务端的输入流
            InputStream inputStream = socket.getInputStream();
            //将字节流转化为字符流
            InputStreamReader reader = new InputStreamReader(inputStream);
            //将字符流加入缓冲区
            BufferedReader bufferedReader = new BufferedReader(reader);
            String string = null;
            //打印内容
            while ((string = bufferedReader.readLine()) != null){
                System.out.println("服务端返回信息为:" + string);
            }

            //关闭所有资源
            bufferedReader.close();
            reader.close();
            inputStream.close();
            printWriter.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            System.out.println("客户端与服务端交互失败");
            e.printStackTrace();
        }
    }
}

②服务端:

package com.Attacking.demo.Socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author PengPan
 * @version 1.0
 * @date 2020/7/8 14:31
 */
public class ServiceSocket {
    public static void main(String[] args) throws IOException, InterruptedException {
        //创建服务端socket,监听18976端口
        ServerSocket serverSocket = new ServerSocket(18976);
        //等待客户端连接
        Socket socket = serverSocket.accept();

        System.out.println("开始睡眠,10s结束");
        Thread.sleep(10000);
        System.out.println("睡眠结束");

        OutputStream outputStream = socket.getOutputStream();
        //写入数据
        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.write("我是AttackingApe,请大家关注我的blog");
        printWriter.flush();

        //关闭所有资源
        printWriter.close();
        outputStream.close();
        socket.close();
        serverSocket.close();
    }
}

测试结果:

①客户端

客户端与服务端交互失败
java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.Attacking.demo.Socket.ClientSocket.main(ClientSocket.java:33)

Process finished with exit code 0

②服务端

开始睡眠,10s结束
睡眠结束

Process finished with exit code 0

测试结论:

1.当客户端使用setSoTimeout设置超时时间,超时时间达到后,客户端会抛出java.net.SocketTimeoutException: Read timed out异常;但是服务端后继续向下执行。

综上,设置socket超时的两种方式的差异:

1.connect是客户端和服务端进行连接的超时时间,如果超时时间内简历连接失败抛出 java.net.SocketTimeoutException: connect timed out异常。此时客户端和服务端未建立连接
2.setSoTimeout方式是设置InputStream.read()方法的阻塞时间(客户端发出请求后等待服务端返回响应的等待时长),超过这个时间将会抛出java.net.SocketTimeoutException: Read timed out异常。此时客户端和服务端建立了连接。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值