1、网络相关概念
1.1、网络
编辑
1.2、网络模型
编辑
编辑
编辑
编辑
1.3、网络三要素
1.3.1、IP地址
处于网络中的通信设备(电脑、手机、电视等),都会分配一个ip地址。这些设备在通信之前,首先要接入到网络中。通过通信设备的网卡。而这个网卡属于一个硬件,并且所有的网卡在出厂的时候,都有一个固定的网卡地址。并且这个网卡地址(MAC地址)全球唯一。
在dos窗口中输入 ipconfig /all 可以查询ip(mac地址)相关的详细信息
编辑
所有的网卡都有一个唯一的物理地址,但是这个地址非常的不好记。于是给每个网卡有分配了一个逻辑(IP)地址。
这个逻辑地址就是ip地址。通信的过程中,就可以根据这个逻辑地址找到处于网络中的这台设备。
IP地址也是对处于网络中的某个通信终端的标识。然后通信的另外一方,就可以根据这个ip地址找到对方,和对方进行通信。
由于IP地址也不容易记忆,因此又给每个ip绑定了一个名称。这个名称被称为(域名)。
通信设备只要连接到网络中,网络就会给这个设备分配一个IP地址。要进行网络通信,必须使用的IP地址。
通信设备能够联网,就必须要有网卡,每个网卡有一个物理地址(MAC地址,是唯一的)。
但是每个网卡在出厂的时候,就已经绑定死了一个IP地址,本地回环地址(127.0.0.1)。
小常识:经常使用ping命令,来检测网卡的好坏。
1.3.2、域名解析
访问网络的时候,真正是需要通过IP地址访问。但是由于IP地址也不容易记忆。因此又给网络中的每个IP地址起名字,这个名称就叫做域名。
当我们在浏览器等可以访问网络的软件中输入了某个域名之后,需要将这个域名解析(解释)成对应的IP地址。这个过程就叫域名解析。
域名解析分成2步:
- 本地解析:
在我们的操作系统中,有一个hosts文件,当输入域名之后,首先会在hosts文件中找有没有当前的域名对应的IP地址,有就会使用这个IP地址。
C:\Windows\System32\drivers\etc\hosts
- DNS服务器解析:
如果第一步解析失败了,会到网络中的DNS服务器上进行解析。DNS服务器中会将全球所有的域名和IP设置在其中。如果DNS服务器解析失败,说明当前的域名有问题的。
1.3.3、协议
协议:通信双方通信的时候需要遵守的通信的规则。后期我们会遇到很多协议(高级协议,应用协议:http、ftp、https )。
目前我们网络编程介绍的协议属于底层协议,所有的高级协议都是基于底层协议。
有ip可以找到网络中的那个设备,设备之间要通信,还要使用相同的通信协议。
传输层协议:
TCP:传输控制协议。Transmission Control Protocol 传输控制协议
TCP协议在通信的时候,要求通信的双方先建立起连接(面向有连接的协议)。在建立连接的过程中需要经过三次握手才能完成连接的建立。
编辑
由于TCP协议需要经过三次握手,才能建立连接,因此TCP协议传输数据的时候比较安全,可靠。但是效率较低。
因此在互联网中很多对安全要求较高的程序底层全部使用TCP协议。
UDP:用户数据报文包协议。UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议。它是面向无连接的协议。发送一方,不用关心接受一方是否在线。就直接发送数据。如果对方在就可以接受数据,如果对应不在,这时数据就自动的被丢弃。
UDP协议不安全,不可靠,但是效率高。不能传输大数据。
1.3.4、端口
我们通过ip可以找到网络中具体那个通信终端的设备。而具体需要访问这个设备中的哪个资源(软件)信息,这时由于设备中运行的资源(软件)肯定很多,这时每个资源(软件)必须再给一个唯一的编号(标识)。通过这个标识才能保证我们可以没有错误的访问到指定ip地址的具体那个资源(软件)上。
端口:是一台设备(电脑、手机登)上某个应用程序的唯一标识。设备中运行的任何一个软件,只要启动,就肯定会有一个唯一的编号其整个软件绑定。
端口从0开始到65535之间。0到1024之间的端口数字已经分配给本机的操作系统的应用程序占用,因此后期我们书写程序如果需要绑定端口,这时必须大于1024.
总结:ip 是网络中某个设备的标识。端口是设备中某个应用程序的标识,协议是设备之间通信的规则。
2、网络编程
2.1、IP对象
在网络中需要连接到另外一个的设备,然后才能进行通信。而处于网络中的任何一个设备都需要唯一的数字地址标识(IP)。
IP是网络中设备中存在的一类特殊的数据,那么Java针对IP整个特殊的数据,也给出了一个类。
Java中的网络编程相关的类保存在java.net包下:
编辑
InetAddress类没有对外提供构造函数,其中提供静态的方法可以根据指定的主机(域名)找到对应的IP,然后把IP封装成InetAddress对象。
编辑
/*
* 演示IP对象
*/
public class IPDemo {
public static void main(String[] args) throws UnknownHostException {
/*
* InetAddress类没有提供工作方法,需要使用静态方法获取对象
* getByName(String host)
* String host:参数可以是字符串形式的ip地址,也可以域名,也可以电脑(主机)的名字
*/
//InetAddress inet = InetAddress.getByName("欣心");
InetAddress inet = InetAddress.getByName("www.qq.com");
String ip = inet.getHostAddress();
System.out.println(ip);
/*
* getAllByName(String host) :
* 有时一个域名下面对应的是多个IP地址。getAllByName方法可以获取到指定的域名下面的多个ip地址对象。
*/
InetAddress[] inets = InetAddress.getAllByName("www.baidu.com");
for (InetAddress ia : inets) {
System.out.println( "IP = "+ia.getHostAddress() );
}
}
}
2.2、通信的端点介绍
要进行网络通信,需要在网络最少有2个端点(连接到网络的设备)。
每个端点都称为一个Socket(网络套接字)对象。而通信的两端(socket)之间,是通过网络在进行连接。
编辑
2.3、UDP编程
如果要进行网络编程,前提必须先确定具体使用哪个协议进行通信。使用UDP,还是TCP协议。Java针对两种协议,给出了不同的类,可以完成网络编程。
网络编程的那些类在java.net包下:
编辑
编辑
DatagramSocket:它的底层使用的就是UDP协议,它表示的基于UDP协议的通信的端点对象。
基于UDP在两端之间通信的数据被称为数据报包(DatagramPacket)。
编辑
编辑
2.3.1、发送方
/*
* 演示基于UDP协议的编程:
*/
public class UdpSend {
public static void main(String[] args) throws IOException {
/*
* 创建通信的端点对象
* DatagramSocket() :
* 创建出来的端点对象,JVM会根据操作系统目前可以分配的端口号给当前端点一个端口号。
*/
DatagramSocket ds = new DatagramSocket( );
/*
* 需要创建负责对数据打包的对象
* DatagramPacket:需要创建的是用来发送数据的包裹
* DatagramPacket(byte[] buf, int length, InetAddress address, int port)
* byte[] buf : 需要被打包的字节数据
* int length : 需要打包的字节个数
* InetAddress address:是接收方的IP对象
* int port : 是接收方的端口号
*/
byte[] buf = "udp通信!!!".getBytes();
int length = buf.length;
InetAddress inet = InetAddress.getByName("192.168.31.8");
int port = 6789;
DatagramPacket dp = new DatagramPacket( buf , length , inet , port );
/*
* 发送数据
*/
ds.send(dp);
// 关闭端点对象
ds.close();
}
}
2.3.2、接收方
/*
* UDP的接收端
*/
public class UdpReceive {
public static void main(String[] args) throws IOException {
/*
* 创建端点对象:
*/
DatagramSocket ds = new DatagramSocket( 6789 );
/*
* 创建拆包的对象
* DatagramPacket(byte[] buf, int length)
* byte[] buf : 创建拆包对象的时候构造方法中的数组,是用来存储拆包之后的数据。
* int length : 可以接受的字节的长度,不能超过数组的长度
*/
DatagramPacket dp = new DatagramPacket( new byte[1024] , 1024 );
/*
* 接收数据
*/
ds.receive(dp); // 如果接受不到数据,就会停留在此。阻塞式方法
/*
* 需要获取拆包之后的数据:拆包对象自己肯定知道具体接受了多少个字节的数据,
* 以及数据来自哪个IP对象,和哪个端口
*/
// 获取数据
byte[] bs = dp.getData();
// 获取数据长度
int len = dp.getLength();
// 获取发送数据方的IP地址
String ip = dp.getAddress().getHostAddress();
// 获取发送端的端口号
int port = dp.getPort();
System.out.println( "数据来自 :" + ip + ", 发送数据的端口号是:" + port +" 发送的数据是: " + new String( bs , 0 , len ) );
// 关闭接收端
ds.close();
}
}
2.4、TCP编程
TCP进行通信之前,必须先建立双方的通信通道。
TCP编程中的对象:
客户端clinet:主要是需要和服务器建立连接,然后客户端就可以给服务端发送数据,或者接受服务端的数据。
客户端永远是主动的去连接服务端,而服务端不会主动的找客户端。
服务端server:它是给各个客户端提供服务的。它是在被动的等待不同的客户端连接,只要每个客户端连接到服务端之后,
服务端就会为这个连接上的客户端建立它们两个之间的通信的通道。
如果还有其他的客户端也连接到了服务端,那么服务端会与每个客户端有各自的通信通道。彼此不会影响。
不管是客户端,还是服务端(服务器)它们只要有一方将通道给断开了,那么彼此的通信立刻结束。
Java针对TCP的客户端对象:Socket;服务端对象:ServerSocket。
2.4.1、基于TCP的客户端
编辑
编辑
/*
* 演示基于TCP协议的客户端实现
*/
public class TcpClientDemo {
public static void main(String[] args) throws IOException {
/*
* Socket(String host, int port)
* String host : 客户端需要连接的服务端的ip地址
* int port : 客户端需要连接的服务端的端口号
*
* 当我们创建好Socket对象之后,Socket就会尝试连接到指定的IP和端口上。
*/
Socket s = new Socket( "192.168.31.8" , 6789 );
/*
* TCP的Socket对象,客户端给服务端发送数据,需要使用字节输出流
* 如果客户端接收服务端发来的数据,需要使用字节输入流
*/
// 基于客户端和服务端的通信的通道获取流对象,然后和服务端之间发送和接收数据
OutputStream out = s.getOutputStream();
// 写数据
out.write("TCP简单通信!!!".getBytes());
// 关闭客户端
s.close();
}
}
2.4.2、基于TCP的服务端
编辑
编辑
/*
* 基于TCP协议的服务端实现
*/
public class TcpServerDemo {
public static void main(String[] args) throws IOException {
/*
* ServerSocket(int port)
* int port : TCP服务端的端口号
*/
ServerSocket ss = new ServerSocket( 6789 );
/*
* 当创建好服务端对象之后,需要调用服务端中的accept方法,
* 来接收某个通过网络连接到服务端的那个客户端。
* 并在服务端的内部会得到连接上来的那个客户端对象。
*/
Socket s = ss.accept();
/*
* 在服务端的内部可以这个连接上来的这个客户端进行通信
*
* 需要在服务端的内部使用连接到的这个客户端对象
* 和真正的客户端进行通信
*/
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = 0 ;
// 这里没有使用while循环读取数据,这里使用数组只读取了一次数据
len = in.read(buf);
// 打印数据
System.out.println( new String( buf , 0 , len ) );
// 关闭客户端
s.close();
// 关闭服务端
ss.close();
}
}
2.4.3、TCP协议两端解释
编辑
TCP的服务端ServerSocket其实它仅仅只是在调用accept方法获取连接的Socket对象,然后在服务端内部依然在使用Socket获取流对象,和客户端的Socket进行数据交互。
2.4.4、TCP服务端开启多线程
/*
* 基于TCP协议的服务端实现 , 将服务端改造成多线程,为每个客户端都可以提供服务
*/
public class TCPServer2 {
public static void main(String[] args) throws Exception {
// 创建服务端对象
ServerSocket ss = new ServerSocket( 8989 );
/*
* 服务端开启死循环的目的是让服务端永远都无法停止,
* 这样可以保证只要有客户端连接进来,
* 服务端就会立刻给这个客户端开启线程并进行服务
*/
while( true ){
/*
* 创建好服务端对象之后,等待客户端的连接,
* 只要有一个客户端连接,就开启一个线程,
* 让服务端中的这个线程和当前这个客户端进行数据交互
*/
Socket s = ss.accept();
System.err.println("有一个客户端连接到服务端了.............");
new Thread( new Runnable(){
public void run(){
try {
// 在run方法中书写当前需要给客户端提供的服务代码
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
// 这里我们先不用循环读取
len = is.read(buf);
// 打印数据
String data = new String( buf , 0 , len );
System.err.println( data );
// 在服务端关闭和当前这个客户端的连接
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
// 关闭服务端
//ss.close();
}
}
}
2.5、TCP的练习
编辑
2.5.1、客户端实现
/*
* 上传文件的客户端,同时需要获取服务端发来的数据
*/
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建客户端对象
Socket s = new Socket( "192.168.31.8" ,6799 );
// 创建字节输入流,读取本地的图片数据
FileInputStream fis = new FileInputStream("e:/1.jpg");
// 获取输出流,给服务端发图片
OutputStream out = s.getOutputStream();
System.out.println("客户端准备给服务端发送数据......");
byte[] buf = new byte[1024];
int len = 0;
/*
* 客户端在使用循环读取本地的图片,然后将数据写给服务端
* 但是由于客户端的循环读取到文件的末尾,循环就会停止
* 但是文件已经读取结束的信息并不会发送给服务端
*/
while( ( len = fis.read(buf) ) != -1 ){
out.write(buf, 0, len);
}
// 程序到这里说明已经将图片数据读写完成。
/*
* 需要人为的告诉服务端文件数据发送完成了
* 使用Socket类中的shutdownOutput,禁用输出流
* 本质是在告诉接收方数据写完了,不要在继续等待读取数据
*/
s.shutdownOutput();
// 需要关闭自己new的流对象
fis.close();
System.out.println("客户端将数据全部发送给服务端......");
// 获取服务端发来的数据
InputStream in = s.getInputStream();
byte[] b = new byte[1024];
int length = 0;
/*
* 下面的read方法在读取服务端发来的数据,如果读取不到数据,
* 客户端的程序就会卡在read的地方,卡住原因是因为没有读取到数据
*/
length = in.read(b);
System.out.println( new String( b , 0 , length ));
// 关闭客户端
s.close();
}
}
2.5.2、服务端实现
/*
* 上次文件的服务端
*/
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建服务端对象
ServerSocket ss = new ServerSocket(6799);
// 获取连接到服务端的客户端对象
Socket s = ss.accept();
System.out.println("有客户端连接到服务端.........");
// 获取输入流读取图片数据
InputStream in = s.getInputStream();
// 创建输出流
FileOutputStream fos = new FileOutputStream("e:/upload/1.jpg");
byte[] buf = new byte[1024];
int len = 0;
/*
* 服务端使用了循环在读取客户端发来的图片数据,由于没有读取到结束的标记,
* 导致这个循环无法停止。
* 在字节输入或字符输入流中,read方法如果读取到末尾返回的都是-1
* 现在循环无法停止,说明read就没有读取到-1.
*/
while( (len = in.read(buf)) != -1 ){
fos.write(buf, 0, len);
}
System.out.println("服务端将图片保存完成了.....");
// 程序到这里,说明服务端已经将图片写到文件中
fos.close();
// 给客户端发送数据
OutputStream out = s.getOutputStream();
out.write("图片上传成功!!!".getBytes());
// 关闭客户端
s.close();
// 关闭服务端
ss.close();
}
}
2.5.3、解决并发上传问题
public class UploadPicServer2 {
public static void main(String[] args) throws IOException {
// 创建服务器端对象
ServerSocket ss = new ServerSocket(9999);
while (true) {
// 获取客户端对象
Socket s = ss.accept();
new Thread(new Service(s)).start();
}
}
}
class Service implements Runnable {
Socket s = null;
public Service(Socket s) {
this.s = s;
}
public void run() {
try {
// 获取连接的客户的IP
String ip = s.getInetAddress().getHostAddress();
// 从客户端获取输入流
InputStream in = s.getInputStream();
// 创建输出的路径对象
File dir = new File("e:\img");
if (!dir.exists()) {
dir.mkdir();
}
int count = 1;
// 创建输出的文件对象
File filePic = new File(dir, ip + "" + count + ".jpg");
while (filePic.exists()) {
count++;
filePic = new File(dir, ip + "" + count + ".jpg");
}
// 创建文件输出流对象
FileOutputStream fis = new FileOutputStream(filePic);
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
fis.write(buf, 0, len);
}
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
fis.close();
s.close();
} catch (IOException e) {
}
}
}
3、网络编程相关类
3.1、URL和URI
3.3.1、URL类
编辑
URL:统一资源定位符。当我们需要访问网络中某个设备中的具体某个资源(文件、图片、音频、视频等等)的时候,需要输入这个资源所在的设备的ip、端口、协议、资源路径等信息才能够访问。
例如:访问京东的某个图片:
img11.360buyimg.com:80/da/jfs/t116…
上面的这个路径就被称为一个URL对象。URL就是浏览器地址栏中输入的东西。
当有一个URL存在的时候,我们就可以知道这个资源具体在哪个IP的设备上的某个端口下,并且可以获取到这个资源的全部真实的路径。
我们可以使用URL去访问这个资源。
一个URL所表示的资源有下面几部分组成:
协议:https/http ,这个超文本传输协议,属于应用级别的协议(底层依然在使用TCP协议)
主机:img11.360buyimg.com ,其实就域名,在DNS解析(域名解析)之后对应就是IP地址。
端口:80 ,底层需要使用TCP连接服务端时使用的端口号。
资源和资源所在的路径:jfs/t11689/182/937825045/142913/f6207e5a/59fc0cbaNe5148bb2.jpg
编辑
/*
* 演示URL使用
*/
public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
// 创建URL对象 key=value&key=value
URL url = new URL("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=url&rsv_pq=ed0e930100017019&rsv_t=45b82LvipAe4VamiI2yEmFS8BfzDEkLmvXSX5QKXeW2QuINdHFk5FncbZi8&rqlang=cn&rsv_enter=1&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100");
System.out.println("getDefaultPort = " + url.getDefaultPort() );
System.out.println("getPort = " + url.getPort() );
System.out.println("getProtocol = " + url.getProtocol() );
System.out.println("getHost = " + url.getHost() );
System.out.println("getFile = " + url.getFile() );
System.out.println("getPath = " + url.getPath() );
System.out.println("getQuery = " + url.getQuery() );
}
}
3.1.2、URI类
编辑
URL:定位符,URI标识符。两个都是用来表示网络中的资源的。如果URL确定了,那么资源就是唯一的。而URI仅仅表示资源,但是资源具体在哪里,是不确定的。
确定的URL:img11.360buyimg.com/da/jfs/t116…
不确定的URI:59fc0cbaNe5148bb2.jpg ,它表示就是一个资源,但是资源在网络中的什么地方,无法确定。
jfs/t11689/182/937825045/142913 这个资源也不确定
URI其实就是URL中的某一部分。但是URI所表示的范围比URL大。
3.2、URLConnection
编辑
URLConnection类:它表示的URL所代表的资源(服务端)与客户端之间的连接的通道。
要想得到URLConnection对象,必须使用URL类中的openConnection方法:
编辑
当程序中得到URLConnection对象之后,就表示得到客户端和服务端之间的连接通道,就可以通过流可以完成客户端和服务端之间的数据读写操作。
编辑
/*
* 演示使用URL去访问网络中的资源
*/
public class URLConnectionDemo {
public static void main(String[] args) throws IOException {
// 创建URL对象
URL url = new URL("https://img11.360buyimg.com/da/jfs/t11689/182/937825045/142913/f6207e5a/59fc0cbaNe5148bb2.jpg");
// 打开和URL所表示的资源所在的服务器之间的连接
URLConnection conn = url.openConnection();
// 获取输入流
InputStream in = conn.getInputStream();
// 创建输出流
FileOutputStream fos = new FileOutputStream("e:/jd.jpg");
// 读取数据
byte[] buf = new byte[1024];
int len = 0 ;
while( ( len = in.read(buf) ) != -1 ){
// 写数据
fos.write(buf, 0, len);
}
// 关流
fos.close();
}
}