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类的所有方法:
boolean | equals(Object obj) 将此对象与指定对象进行比较。 |
byte[] | getAddress() 返回此 |
static InetAddress[] | getAllByName(String host) 给定主机的名称,根据系统上配置的名称服务返回其IP地址数组。 |
static InetAddress | getByAddress(byte[] addr) 给出原始IP地址的 |
static InetAddress | getByAddress(String host, byte[] addr) 根据提供的主机名和IP地址创建InetAddress。 |
static InetAddress | getByName(String host) 确定主机名称的IP地址。 |
String | getCanonicalHostName() 获取此IP地址的完全限定域名。 |
String | getHostAddress() 返回文本显示中的IP地址字符串。 |
String | getHostName() 获取此IP地址的主机名。 |
static InetAddress | getLocalHost() 返回本地主机的地址。 |
static InetAddress | getLoopbackAddress() 返回回送地址。 |
int | hashCode() 返回此IP地址的哈希码。 |
boolean | isAnyLocalAddress() 检查通配符地址中的InetAddress的实用程序。 |
boolean | isLinkLocalAddress() 检查InetAddress是否是链接本地地址的实用程序。 |
boolean | isLoopbackAddress() 检查InetAddress是否是一个环回地址的实用程序。 |
boolean | isMCGlobal() 检查多播地址是否具有全局范围的实用程序。 |
boolean | isMCLinkLocal() 检查组播地址是否具有链路范围的实用程序。 |
boolean | isMCNodeLocal() 检查多播地址是否具有节点范围的实用程序。 |
boolean | isMCOrgLocal() 检查组播地址是否具有组织范围的实用程序。 |
boolean | isMCSiteLocal() 检查多播地址是否具有站点范围的实用程序。 |
boolean | isMulticastAddress() 检查InetAddress是否是IP组播地址的实用程序。 |
boolean | isReachable(int timeout) 测试该地址是否可达。 |
boolean | isReachable(NetworkInterface netif, int ttl, int timeout) 测试该地址是否可达。 |
boolean | isSiteLocalAddress() 检查InetAddress是否是站点本地地址的实用程序。 |
String | toString() 将此IP地址转换为 |
InetAddress类的常用方法:
static InetAddress | getByName(String host) 确定主机名称的IP地址。 |
static InetAddress | getLocalHost() 返回本地主机的地址。 |
String | getHostName() 获取此IP地址的主机名。 |
String | getHostAddress() 返回文本显示中的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类的所有方法:
Socket | accept() 侦听要连接到此套接字并接受它。 |
void | bind(SocketAddress endpoint) 将 |
void | bind(SocketAddress endpoint, int backlog) 将 |
void | close() 关闭此套接字。 |
ServerSocketChannel | getChannel() 返回与此套接字相关联的唯一的 |
InetAddress | getInetAddress() 返回此服务器套接字的本地地址。 |
int | getLocalPort() 返回此套接字正在侦听的端口号。 |
SocketAddress | getLocalSocketAddress() 返回此套接字绑定到的端点的地址。 |
int | getReceiveBufferSize() 获取此 |
boolean | getReuseAddress() 测试是否启用了 |
int | getSoTimeout() 检索 |
protected void | implAccept(Socket s) ServerSocket的子类使用这个方法来覆盖accept()来返回自己的套接字子类。 |
boolean | isBound() 返回ServerSocket的绑定状态。 |
boolean | isClosed() 返回ServerSocket的关闭状态。 |
void | setPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此ServerSocket的性能首选项。 |
void | setReceiveBufferSize(int size) 设置从 |
void | setReuseAddress(boolean on) 启用/禁用 |
static void | setSocketFactory(SocketImplFactory fac) 设置应用程序的服务器套接字实现工厂。 |
void | setSoTimeout(int timeout) 启用/禁用 |
String | toString() 将该套接字的实现地址和实现端口返回为 |
ServerSocket类的常用方法:
Socket | accept() 侦听要连接到此套接字并接受它。 |
boolean | isBound() 返回ServerSocket的绑定状态。 |
InetAddress | getInetAddress() 返回此服务器套接字的本地地址。 |
boolean | isClosed() 返回ServerSocket的关闭状态。 |
void | close() 关闭此套接字。 |
void | bind(SocketAddress endpoint) 将 |
int | getLocalPort() 返回此套接字正在侦听的端口号。 |
调用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异常。此时客户端和服务端建立了连接。