网络编程
一 网络编程概述
1 网络编程中有三个主要的问题:
- 问题1:如何准确地定位网络上一台或多台主机
- 问题2:如何定位主机上的特定的应用
- 问题3:找到主机后,如何可靠、高效地进行数据传输
2 实现网络传输的三个要素:(对应解决三个问题)
- 使用IP地址(准确地定位网络上一台或多台主机)
- 使用端口号(如何定位主机上的特定的应用)
- 规范网络通信协议(可靠、高效地进行数据传输)
3 通信要素1:IP地址
-
作用:IP地址用来给网络中的一台计算机设备做唯一的编号
-
IP地址分类:
方式1:IPv4(占用4个字节)
IPv6(占用6个字节)
方式2:公网地址( 万维网使用)和 私有地址( 局域网使用)。192.168.开头的就是私有地址
-
本地回路地址:127.0.0.1
-
域名:便捷的记录IP地址
www.baidu.com
www.bilibili.com
www.mi.com
4 通信要素2:端口号
- 可以唯一标识主机中的进程(应用程序)
- 不同的进程分配不同的端口号
- 范围:0-65535
5 通信要素3:通信协议
-
网络通信协议目的:为了实现可靠而高效的数据传输
-
网络参考模型:
OSI参考模型:将网络分为7层,过于理想化,没有实施起来。
TCP/IP参考模型:将网络分为4层:应用层、传输层、网络层、物理+数据链路层,事实上使用的标准。
6 InetAddress的使用
-
作用: InetAddress类的一个实例就代表一个具体的IP地址
-
实例化方法:
InetAddress getByName(String host):获取指定IP对应的InetAddress的实例。
InetAddress getLocalHost():获取本地IP对应的InetAddress的实例。
3.常用方法:
getHostName()
getHostAddress()
4 举例:
package Test3;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressTest {
public static void main(String[] args) {
try{
//1.实例化
//InetAddress getByName(String host):获取指定IP对应的InetAddress的实例。
InetAddress inet1 = InetAddress.getByName("192.168.23.31");
System.out.println(inet1);///192.168.23.31
InetAddress inet2 = InetAddress.getByName("www.mi.com");
System.out.println(inet2);//www.mi.com/113.240.98.43
//InetAddress getLocalHost():获取本地IP对应的InetAddress的实例。
InetAddress inet3 = InetAddress.getLocalHost();
System.out.println(inet3);//DESKTOP-2OS0G9O/172.17.110.17
InetAddress inet4 = InetAddress.getByName("127.0.0.1");
System.out.println(inet4);///127.0.0.1
//2.两个常用的方法
System.out.println(inet2.getHostName());//www.mi.com
System.out.println(inet2.getHostAddress());//113.240.98.42
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
二 谈传输层协议:TCP与UDP协议
java.net
包中提供了两种常见的网络协议的支持:
- UDP:用户数据报协议(User Datagram Protocol)。
- TCP:传输控制协议 (Transmission Control Protocol)。
1 TCP协议与UDP协议
TCP协议:
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 使用TCP协议前,须先
建立TCP连接
,形成基于字节流的传输数据通道 - 传输前,采用“三次握手”方式,点对点通信,是
可靠的
- TCP协议使用
重发机制
,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。
- TCP协议使用
- 在连接中可进行
大数据量的传输
- 传输完毕,需
释放已建立的连接,效率低
UDP协议:
- UDP协议进行通信的两个应用进程:发送端、接收端。
- 将数据、源、目的封装成数据包(传输的基本单位),
不需要建立连接
- 发送不管对方是否准备好,接收方收到也不确认,不能保证数据的完整性,故是
不可靠的
- 每个数据报的大小限制在
64K
内 - 发送数据结束时
无需释放资源,开销小,通信效率高
- 适用场景:音频、视频和普通数据的传输。例如视频会议
TCP生活案例:打电话
UDP生活案例:发送短信、发电报
2 三次握手
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
- 第一次握手,客户端向服务器端发起TCP连接的请求
- 第二次握手,服务器端发送针对客户端TCP连接请求的确认
- 第三次握手,客户端发送确认的确认
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
3 四次挥手
TCP协议中,在发送数据结束后,释放连接时需要经过四次挥手。
- 第一次挥手:客户端向服务器端提出结束连接,
让服务器做最后的准备工作
。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。 - 第二次挥手:服务器接收到客户端释放连接的请求后,
会将最后的数据发给客户端
。并告知上层的应用进程不再接收数据。 - 第三次挥手:服务器发送完数据后,会给客户端
发送一个释放连接的报文
。那么客户端接收后就知道可以正式释放连接了。 - 第四次挥手:客户端接收到服务器最后的释放连接报文后,要
回复一个彻底断开的报文
。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL后,没有收到,那么彻底断开。
三 Socket类
-
网络上具有唯一标识的IP地址和端口号组合在一起构成唯一能识别的标识符套接字(Socket)。
-
利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。网络通信其实就是Socket间的通信。
-
通信的两端都要有Socket,是两台机器间通信的端点。
-
Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
-
一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
-
Socket分类:
- 流套接字(stream socket):使用TCP提供可依赖的字节流服务
- ServerSocket:此类实现TCP服务器套接字。服务器套接字等待请求通过网络传入。
- Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
- 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
- DatagramSocket:此类表示用来发送和接收UDP数据报包的套接字。
- 流套接字(stream socket):使用TCP提供可依赖的字节流服务
Socket相关类API
ServerSocket类
ServerSocket类的构造方法:
- ServerSocket(int port) :创建绑定到特定端口的服务器套接字。
ServerSocket类的常用方法:
- Socket accept():侦听并接受到此套接字的连接。
Socket类
Socket类的常用构造方法:
- public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
- public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket类的常用方法:
- public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
- public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消息
- public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
- public InetAddress getLocalAddress():获取套接字绑定的本地地址。
- public int getPort():此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
- public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回 -1。
- public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。
- public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
- public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
注意:先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。
四 TCP网络编程
开发步骤
客户端程序包含以下四个基本的步骤 :
- 创建 Socket :根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
- 打开连接到 Socket 的输入/ 出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
- 按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线路。
- 关闭 Socket :断开客户端到服务器的连接,释放线路
服务器端程序包含以下四个基本的 步骤:
- 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
- 调用 accept() :监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
- 调用 该Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。
- 关闭Socket 对象:客户端访问结束,关闭通信套接字。
举例:
package Test3;
//客户端发送内容给服务端,服务端将内容打印在控制台上
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest {
@Test
//客户端
public void client(){
Socket socket = null;
OutputStream os = null;
try{
//创建一个Socket
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");//声明一个IP地址
int port = 8989;//声明端口号
socket= new Socket(inetAddress,port);
//发送数据
os = socket.getOutputStream();
os.write("我是客户端".getBytes());
}catch(IOException e) {
e.printStackTrace();
}finally {
//关闭
try {
if(socket != null){
socket.close();
}
}catch(IOException e) {
e.printStackTrace();
}
try{
if(os != null){
os.close();
}
}catch (IOException e1){
e1.printStackTrace();
}
}
}
@Test
//服务端
public void server() {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
try {
int port = 8989;
ServerSocket serversocket = new ServerSocket(port);
socket = serversocket.accept();//阻塞式方法
System.out.println("服务器端已开启");
is = socket.getInputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
String str = new String(buffer, 0, length);
System.out.println(str);
System.out.println("\n数据接收完毕");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五 UDP网络编程
UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报大小限制在64K以下。
1 开发步骤
发送端程序包含以下四个基本的步骤:
- 创建DatagramSocket :默认使用系统随机分配端口号。
- 创建DatagramPacket:将要发送的数据用字节数组表示,并指定要发送的数据长度,接收方的IP地址和端口号。
- 调用 该DatagramSocket 类对象的 send方法 :发送数据报DatagramPacket对象。
- 关闭DatagramSocket 对象:发送端程序结束,关闭通信套接字。
接收端程序包含以下四个基本的步骤 :
- 创建DatagramSocket :指定监听的端口号。
- 创建DatagramPacket:指定接收数据用的字节数组,起到临时数据缓冲区的效果,并指定最大可以接收的数据长度。
- 调用 该DatagramSocket 类对象的receive方法 :接收数据报DatagramPacket对象。。
- 关闭DatagramSocket :接收端程序结束,关闭通信套接字。
2 DatagramSocket类
DatagramSocket 类的常用方法:
- public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
- public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
- public void close()关闭此数据报套接字。
- public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
- public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
- public InetAddress getLocalAddress()获取套接字绑定的本地地址。
- public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
- public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接,则返回 null。
- public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。
3 DatagramPacket类
DatagramPacket类的常用方法:
- public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
- public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
- public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
- public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
public byte[] getData()
返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。public int getLength()
返回将要发送或接收到的数据的长度。
4 举例:
package Test3;
import org.testng.annotations.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPTest {
//发送端
@Test
public void sender() throws Exception {
DatagramSocket ds = new DatagramSocket();
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port = 9090;
byte[] bytes = "我是发送端".getBytes("utf-8");
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,inetAddress,port);
ds.send(packet);
ds.close();
}
//接收端
@Test
public void receiver() throws IOException {
int port = 9090;
DatagramSocket ds = new DatagramSocket(port);
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
ds.receive(packet);
String str = new String(packet.getData(),0,packet.getLength());
System.out.println(str);
ds.close();
}
}
六 URL编程
1 URL类
-
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
-
通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
-
URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
例如:
http://192.168.21.107:8080/examples/abcd.jpg?name=Tom
2 URL类常用方法
一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
-
public String getProtocol( ) 获取该URL的协议名
-
public String getHost( ) 获取该URL的主机名
-
public String getPort( ) 获取该URL的端口号
-
public String getPath( ) 获取该URL的文件路径
-
public String getFile( ) 获取该URL的文件名
-
public String getQuery( ) 获取该URL的查询名
举例:
package Test3;
import java.net.MalformedURLException;
import java.net.URL;
public class URLTest {
public static void main(String[] args) {
String str = "http://192.168.21.107:8080/examples/abcd.jpg?name=Tom";
URL url;
{
try {
url = new URL(str);
System.out.println(url.getProtocol());//获取该URL的协议名
System.out.println(url.getHost());//获取该URL的主机名
System.out.println(url.getPort());//获取该URL的端口名
System.out.println(url.getPath());//获取该URL的文件路径
System.out.println(url.getFile());//获取该URL的文件名
System.out.println(url.getQuery());//获取该URL的查询名
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
}
//运行结果:
//http
//192.168.21.107
//8080
///examples/abcd.jpg
///examples/abcd.jpg?name=Tom
//name=Tom
3 针对HTTP协议的URLConnection类
- URL的方法 openStream():能从网络上读取数据
- 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用 URLConnection 。
- URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生IOException.
- URL netchinaren = new URL (“http://192.168.21.107:8080/examples/abcd.jpg?name=Tom”);
- URLConnectonn u = netchinaren.openConnection( );
举例:
package com.test2;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class Download {
public static void main(String[] args) throws Exception {
URL url = new URL("https://dl.360nate.com/setup.exe");
URLConnection con = url.openConnection();
con.connect();
try (FileOutputStream fos = new FileOutputStream("D:\\360.exe"); InputStream is = con.getInputStream()) {
int len = -1;
byte[] bt = new byte[20480];
while ((len = is.read(bt)) != -1) {
fos.write(bt, 0, len);
}
fos.flush();
System.out.println("下载完成...");
}
}
}