Fundación JAVA-Capítulo 20 Programación de red

contenido principal

Arquitectura de software
Comunicación de red CS / BS Tres elementos
Comunicación TCP
Socket Socket
ServerSocket

objetivos de enseñanza

Capaz de distinguir las características de los protocolos UDP y TCP.
Capaz de nombrar dos clases de uso común bajo el protocolo TCP.
Capaz de escribir programas de transmisión de datos de cadena
bajo el protocolo TCP.
Capaz de comprender el caso de carga de archivos bajo el protocolo TCP. Capaz de comprender el caso 2 bajo el protocolo TCP.

Capítulo 1 Introducción a la programación de red

1.1 Estructura del software

C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。

Estructura B / S: el nombre completo es Estructura navegador / servidor, que se refiere a la estructura del navegador y del servidor. Los navegadores comunes incluyen Google, Firefox, etc.

Ambas arquitecturas tienen sus propias ventajas, pero no importa qué arquitectura, son inseparables del soporte de la red. La programación en red es un programa que realiza la comunicación entre dos computadoras bajo un protocolo determinado.

1.2 Protocolo de comunicación de red

Protocolo de comunicación en red: El protocolo de comunicación son las reglas que deben cumplir las computadoras. Solo cumpliendo con estas reglas, las computadoras pueden comunicarse. Esto es como si un automóvil que circula por la carretera debe cumplir con las normas de tráfico. El acuerdo tiene disposiciones unificadas sobre el formato de transmisión de datos, la velocidad de transmisión y los pasos de transmisión. Las dos partes deben cumplir con él al mismo tiempo para completar el intercambio de datos.

TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是
Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它
的内部包含一系列的用于处理数据通信的协议,并采用了 4 层的分层模型,每一层都呼叫它的下一层所提供的
协议来完成自己的需求。

1.3 Clasificación de protocolo

El protocolo de comunicación es bastante complicado, las clases e interfaces contenidas en el paquete java.net proporcionan detalles de comunicación de bajo nivel. Podemos utilizar directamente
estas clases e interfaces para centrarnos en el desarrollo de programas de red sin considerar los detalles de la comunicación.

java.net 包中提供了两种常见的网络协议的支持:
TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,
在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可
靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。
Una vez que se completa el protocolo de enlace de tres vías y se establece la conexión, el cliente y el servidor pueden iniciar la transmisión de datos. Debido a esta función orientada a la conexión, el protocolo TCP puede
Para garantizar la seguridad de los datos transmitidos, se utiliza ampliamente, como descargar archivos y navegar por páginas web.
UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需
要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个
数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应
用中,例如视频会议、QQ聊天等。

1.4 Tres elementos de la programación de red

protocolo

Protocolo: Las reglas que deben seguirse en la comunicación de redes informáticas ya se han introducido y no se repetirán.

dirección IP

IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设
备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

Clasificación de direcciones IP

IPv4:是一个 32 位的二进制数,通常被分为 4 个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其
中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示 42 亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。
有资料显示,全球IPv4地址在 2011 年 2 月分配完毕。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用 128 位地址长度,每 16 个字节一组,分成 8 组十六进
制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网
址,这样就解决了网络地址资源数量不够的问题。

Comandos comunes

Verifique la dirección IP de la máquina, ingrese en la consola:
Para comprobar si la red está conectada, escriba en la consola:
Dirección IP especial
本机IP地址:127.0.0.1、localhost 。

El número de puerto

La comunicación en red es esencialmente una comunicación entre dos procesos (aplicaciones). Cada computadora tiene muchos procesos, entonces, ¿cómo distinguir entre la comunicación de red?
¿Y estos procesos?
Si la dirección IP puede identificar de forma única el dispositivo en la red, entonces el número de puerto puede identificar de forma única el proceso (aplicación) en el dispositivo.
Número de puerto: un número entero representado por dos bytes y su rango de valores es 0 65535. Entre ellos, el número de puerto entre 0 y 1023 se usa en algunas redes conocidas
Los servicios y aplicaciones de red, las aplicaciones normales deben utilizar números de puerto superiores a 1024. Si el número de puerto está ocupado por otro servicio o aplicación,
Hacer que el programa actual no se inicie.
Usando la combinación triple de protocolo + dirección IP + número de puerto, puede identificar el proceso en la red, luego la comunicación entre procesos puede usar esta identificación y
Procesa de forma interactiva.

Capítulo 2 Programa de comunicación TCP

2.1 Resumen

La comunicación TCP puede realizar el intercambio de datos entre dos computadoras.Los dos extremos de la comunicación deben dividirse estrictamente en el cliente y el servidor.

Pasos cuando ambos extremos se comunican:

1. 服务端程序,需要事先启动,等待客户端的连接。
2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

En Java, se proporcionan dos clases para implementar programas de comunicación TCP:

1. 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建
立连接开始通信。
2. 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端
的连接。

2.2 Clase de enchufe

Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

Método de construcción

ipconfig
ping 空格 IP地址
ping 220.181.57.
public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指
定的host是null ,则相当于指定地址为回送地址。
小贴士:回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本
地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

Por ejemplo, el código es el siguiente:

Método de miembro

public InputStream getInputStream() : 返回此套接字的输入流。
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream() : 返回此套接字的输出流。
如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
关闭生成的OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。
一旦一个socket被关闭,它不可再使用。
关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput() : 禁用此套接字的输出流。
任何先前写出的数据将被发送,随后终止输出流。

2.3 clase ServerSocket

ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。

Método de construcción

public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指
定的端口号上,参数port就是端口号。

Por ejemplo, el código es el siguiente:

Método de miembro

public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法
会一直阻塞直到建立连接。

2.4 Programa de red TCP simple

Diagrama de análisis de comunicación TCP

1. 【服务端】启动,创建ServerSocket对象,等待连接。
Socket client = new Socket("127.0.0.1",  6666 );
ServerSocket server = new ServerSocket( 6666 );
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
到此,客户端向服务端发送数据成功。
Desde entonces, el servidor vuelve a escribir datos en el cliente.
6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。

El cliente envía datos al servidor

Implementación del lado del servidor:
public^ class^ ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket ss = new ServerSocket( 6666 );
// 2.接收连接 accept 方法, 返回 socket 对象.
Socket server = ss.accept();
// 3.通过socket 获取输入流
InputStream is = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] b = new byte[ 1024 ];
// 4.2 据读取到字节数组中.
int len = is.read(b);
// 4.3 解析数组,打印字符串信息
String msg = new String(b,  0 , len);
System.out.println(msg);
//5.关闭资源.
is.close();
server.close();
    }
}
Implementación del cliente:

El servidor escribe datos al cliente.

Implementación del lado del servidor:
Implementación del cliente:
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客户端 发送数据");
// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
Socket client = new Socket("localhost",  6666 );
// 2.获取流对象 . 输出流
OutputStream os = client.getOutputStream();
// 3.写出数据.
os.write("你好么? tcp ,我来了".getBytes());
// 4. 关闭资源 .
os.close();
client.close();
}
}
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket ss = new ServerSocket( 6666 );
// 2.接收连接 accept 方法, 返回 socket 对象.
Socket server = ss.accept();
// 3.通过socket 获取输入流
InputStream is = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] b = new byte[ 1024 ];
// 4.2 据读取到字节数组中.
int len = is.read(b);
// 4.3 解析数组,打印字符串信息
String msg = new String(b,  0 , len);
System.out.println(msg);
// =================回写数据=======================
// 5. 通过 socket 获取输出流
OutputStream out = server.getOutputStream();
// 6. 回写数据
out.write("我很好,谢谢你".getBytes());
// 7.关闭资源.
out.close();
is.close();
server.close();
    }
}

Capítulo 3 Caso completo

3.1 Caso de carga de archivos

Diagrama de análisis de carga de archivos

1. [Cliente] Flujo de entrada, leer datos de archivo desde el disco duro al programa.
2. [Cliente] Flujo de salida, escriba los datos del archivo en el servidor.
3. [Servidor] Flujo de entrada, leer datos de archivo en el programa del servidor.
4. [Servidor] Flujo de salida, escriba los datos del archivo en el disco duro del servidor.

Realización básica

public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客户端 发送数据");
// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
Socket client = new Socket("localhost",  6666 );
// 2.通过Scoket,获取输出流对象
OutputStream os = client.getOutputStream();
// 3.写出数据.
os.write("你好么? tcp ,我来了".getBytes());
// ==============解析回写=========================
// 4. 通过Scoket,获取 输入流对象
InputStream in = client.getInputStream();
// 5. 读取数据数据
byte[] b = new byte[ 100 ];
int len = in.read(b);
System.out.println(new String(b,  0 , len));
// 6. 关闭资源 .
in.close();
os.close();
client.close();
}
}
Implementación del lado del servidor:
Implementación del cliente:
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动.....  ");
// 1. 创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket( 6666 );
// 2. 建立连接
Socket accept = serverSocket.accept();
// 3. 创建流对象
// 3.1 获取输入流,读取文件数据
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
// 3.2 创建输出流,保存到本地 .
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));
// 4. 读写数据
byte[] b = new byte[ 1024  *  8 ];
int len;
while ((len = bis.read(b)) != ‐ 1 ) {
bos.write(b,  0 , len);
        }
//5. 关闭 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
    }
}
public class FileUPload_Client {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 创建输入流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 创建输出流,写到服务端
Socket socket = new Socket("localhost",  6666 );
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.写出数据.
byte[] b = new byte[ 1024  *  8  ];
int len ;
while (( len = bis.read(b))!=‐ 1 ) {
bos.write(b,  0 , len);
bos.flush();
        }
System.out.println("文件发送完毕");
// 3.释放资源
bos.close();
socket.close();
bis.close();

Análisis de optimización de carga de archivos

1. El nombre del archivo es difícil de escribir.
En el lado del servidor, si el nombre del archivo guardado está codificado, eventualmente lo llevará al disco duro del servidor y solo se retendrá un archivo. Se recomienda utilizar la optimización de tiempo del sistema
Para asegurarse de que el nombre del archivo sea único, el código es el siguiente:
2. El problema de la recepción circular
En el lado del servidor, significa que después de guardar un archivo, se cierra y los usuarios posteriores ya no pueden cargarlo. Esto no está en línea con la realidad. El uso de la mejora cíclica puede continuar
Para recibir archivos de diferentes usuarios, el código es el siguiente:
3. Problemas de eficiencia
En el lado del servidor, cuando se reciben archivos de gran tamaño, puede tardar unos segundos. En este momento, no puede recibir cargas de otros usuarios. Por lo tanto, es mejor utilizar la tecnología multi-threading.
El código es el siguiente:

Implementación optimizada

System.out.println("文件上传完毕 ");
}
}
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);
// 每次接收新的连接,创建一个Socket
while(true){
Socket accept = serverSocket.accept();
    ......
}
while(true){
Socket accept = serverSocket.accept();
// accept 交给子线程处理.
new Thread(() ‐> {
......
InputStream bis = accept.getInputStream();
......
    }).start();
}
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动.....  ");
// 1. 创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket( 6666 );
// 2. 循环接收,建立连接
while (true) {
Socket accept = serverSocket.accept();
/*
  1. El objeto socket se entrega al subproceso secundario para operaciones de procesamiento, lectura y escritura.

Diagrama de análisis de escritura diferida de información

Los primeros cuatro pasos son consistentes con la carga de archivos básica.
5. [Servidor] Obtenga el flujo de salida y vuelva a escribir los datos.
6. [Cliente] Obtenga el flujo de entrada y analice los datos de escritura diferida.

Implementación de escritura diferida

Runnable接口中,只有一个run方法,使用lambda表达式简化格式
*/
new Thread(() ‐> {
try (
//3.1 获取输入流对象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 创建输出流对象, 保存到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() +
".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);) {
// 3.3 读写数据
byte[] b = new byte[ 1024  *  8 ];
int len;
while ((len = bis.read(b)) != ‐ 1 ) {
bos.write(b,  0 , len);
                    }
//4. 关闭 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
                } catch (IOException e) {
e.printStackTrace();
                }
            }).start();
        }
    }
}
Implementación del cliente:
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动.....  ");
// 1. 创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket( 6666 );
// 2. 循环接收,建立连接
while (true) {
Socket accept = serverSocket.accept();
/*
  1. El objeto de socket se entrega al subproceso secundario para su procesamiento y operaciones de lectura y escritura. En la
    interfaz Runnable, solo hay un método de ejecución, que usa expresiones lambda para simplificar el formato.
    * /
    New Thread (() -> { try ( //3.1 Obtener el objeto de flujo de entrada BufferedInputStream bis = new BufferedInputStream (accept.getInputStream ()); //3.2 Cree un objeto de flujo de salida y guárdelo localmente. FileOutputStream fis = new FileOutputStream (System.currentTimeMillis () + “.jpg”); BufferedOutputStream bos = new BufferedOutputStream (fis); ) // 3.3 Leer y escribir el byte de datos [] b = new byte [1024 * 8]; int len; while ((len = bis.read (b))! = - 1) { bos.write (b, 0, len) ; }













// 4.=======信息回写===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传成功".getBytes());
out.close();
//================================
//5. 关闭 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
                } catch (IOException e) {
e.printStackTrace();
                }
            }).start();
        }
    }
}
public class FileUpload_Client {
public static void main(String[] args) throws IOException {

3.2 Simular servidor B \ S

Simule el servidor web, utilice el navegador para acceder al programa del servidor escrito por usted mismo y compruebe el efecto de la página web.

analisis de CASO

1. 准备页面数据,web文件夹。
复制到我们Module中,比如复制到day08中
2. 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问
// 1. Crea un objeto de flujo
// 1.1 Crear flujo de entrada y leer archivo local
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 创建输出流,写到服务端
Socket socket = new Socket("localhost",  6666 );
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.写出数据.
byte[] b = new byte[ 1024  *  8  ];
int len ;
while (( len = bis.read(b))!=‐ 1 ) {
bos.write(b,  0 , len);
        }
// 关闭输出流,通知服务端,写出数据完毕
socket.shutdownOutput();
System.out.println("文件发送完毕");
// 3. =====解析回写============
InputStream in = socket.getInputStream();
byte[] back = new byte[ 20 ];
in.read(back);
System.out.println(new String(back));
in.close();
// ============================
// 4.释放资源
socket.close();
bis.close();
    }
}
3. El flujo de entrada de bytes en el programa del servidor puede leer la información de solicitud enviada por el navegador

GET / web / index.html HTTP / 1.1 es el mensaje de solicitud del navegador. /web/index.html es el recurso del lado del servidor que el navegador desea solicitar,
y el recurso solicitado se obtiene cortando cadenas.

Realización de casos

Implementación del lado del servidor:
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket( 8000 );
Socket socket = server.accept();
InputStream in = socket.getInputStream();
byte[] bytes = new byte[ 1024 ];
int len = in.read(bytes);
System.out.println(new String(bytes, 0 ,len));
socket.close();
server.close();
}
// Convierta la transmisión, lea la primera línea de la solicitud del navegador
BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出请求资源的路径
String[] strArr = requst.split(" ");
//去掉web前面的/
String path = strArr[ 1 ].substring( 1 );
System.out.println(path);
public class SerDemo {
public static void main(String[] args) throws IOException {
System.out.println("服务端 启动 , 等待连接 .... ");
// 创建ServerSocket 对象
ServerSocket server = new ServerSocket( 8888 );
Socket socket = server.accept();
// 转换流读取浏览器的请求消息
BufferedReader readWb = new

Efecto visita

Firefox
Sugerencias: los distintos navegadores tienen distintos núcleos y el efecto de análisis puede ser diferente.
Se encuentra que hay muchas bifurcaciones en el navegador, lo que indica que el navegador no leyó la información de la imagen.
El principio de funcionamiento del navegador es que iniciará un hilo para el acceso por separado cuando encuentre imágenes, por lo que la tecnología de hilo se agrega en el lado del servidor.
BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
// 取出请求资源的路径
String[] strArr = requst.split(" ");
// 去掉web前面的/
String path = strArr[ 1 ].substring( 1 );
// 读取客户端请求的资源文件
FileInputStream fis = new FileInputStream(path);
byte[] bytes= new byte[ 1024 ];
int len =  0  ;
// 字节输出流,将文件写会客户端
OutputStream out = socket.getOutputStream();
// 写入HTTP协议响应头,固定写法
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content‐Type:text/html\r\n".getBytes());
// 必须要写入空行,否则浏览器不解析
out.write("\r\n".getBytes());
while((len = fis.read(bytes))!=‐ 1 ){
out.write(bytes, 0 ,len);
        }
fis.close();
out.close();
readWb.close();
socket.close();
server.close();
    }
}
Efecto visita:
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket( 8888 );
while(true){
Socket socket = server.accept();
new Thread(new Web(socket)).start();
        }
    }
static class Web implements Runnable{
private Socket socket;
public Web(Socket socket){
this.socket=socket;
        }
public void run() {
try{
//转换流,读取浏览器请求第一行
BufferedReader readWb = new
BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出请求资源的路径
String[] strArr = requst.split(" ");
System.out.println(Arrays.toString(strArr));
String path = strArr[ 1 ].substring( 1 );
System.out.println(path);
FileInputStream fis = new FileInputStream(path);
System.out.println(fis);
byte[] bytes= new byte[ 1024 ];
int len =  0  ;
//向浏览器 回写数据
OutputStream out = socket.getOutputStream();
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content‐Type:text/html\r\n".getBytes());
out.write("\r\n".getBytes());
while((len = fis.read(bytes))!=‐ 1 ){
out.write(bytes, 0 ,len);
                }
fis.close();
out.close();
readWb.close();
socket.close();
            }catch(Exception ex){
            }
        }
    }
}
Ilustración:

Supongo que te gusta

Origin blog.csdn.net/weixin_43419256/article/details/108230771
Recomendado
Clasificación