一、Java 网络编程
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
TCP:TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
UDP:UDP (User Datagram Protocol,用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。
1、Socket 编程
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现: 1、服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。 2、服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。 3、服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。 4、Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够 与服务器进行通信。 5、在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。
Socket,实际上是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。
传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。socket是对端口通信开发的工具,更底层一些。
- Socket整体流程
- Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。
2、ServerSocket 类的方法
方法 | 方法描述 |
public ServerSocket(int port) throws IOException | 创建绑定到特定端口的服务器套接字。 |
public ServerSocket(int port, int backlog) throws IOException | 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
public ServerSocket(int port, int backlog, InetAddress address) throws IOException | 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。 |
public ServerSocket() throws IOException | 创建非绑定服务器套接字。 |
服务器应用程序通过使用java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有以上四个构造方法。
创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket 类的常用方法:
方法 | 方法描述 |
public int getLocalPort() | 返回此套接字在其上侦听的端口。 |
public Socket accept() throws IOException | 侦听并接受到此套接字的连接。 |
public void setSoTimeout(int timeout) | 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
public void bind(SocketAddress host, int backlog) | 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
3、Socket 类的方法
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
Socket 类有五个构造方法.
方法 | 方法描述 |
public Socket(String host, int port) throws UnknownHostException, IOException. | 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
public Socket(InetAddress host, int port) throws IOException | 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException. | 创建一个套接字并将其连接到指定远程主机上的指定远程端口。 |
public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException. | 创建一个套接字并将其连接到指定远程地址上的指定远程端口。 |
public Socket() | 通过系统默认类型的 SocketImpl 创建未连接套接字 |
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
方法 | 方法描述 |
public void connect(SocketAddress host, int timeout) throws IOException | 将此套接字连接到服务器,并指定一个超时值。 |
public InetAddress getInetAddress() | 返回套接字连接的地址。 |
public int getPort() | 返回此套接字连接到的远程端口。 |
public int getLocalPort() | 返回此套接字绑定到的本地端口。 |
public SocketAddress getRemoteSocketAddress() | 返回此套接字连接的端点的地址,如果未连接则返回 null。 |
public InputStream getInputStream() throws IOException | 返回此套接字的输入流。 |
public OutputStream getOutputStream() throws IOException | 返回此套接字的输出流。 |
public void close() throws IOException | 关闭此套接字。 |
4、InetAddress 类的方法
方法 | 方法描述 |
static InetAddress getByAddress(byte[] addr) | 在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
static InetAddress getByAddress(String host, byte[] addr) | 根据提供的主机名和 IP 地址创建 InetAddress。 |
static InetAddress getByName(String host) | 在给定主机名的情况下确定主机的 IP 地址。 |
String getHostAddress() | 返回 IP 地址字符串(以文本表现形式)。 |
String getHostName() | 获取此 IP 地址的主机名。 |
static InetAddress getLocalHost() | 返回本地主机。 |
String toString() | 将此 IP 地址转换为 String。 |
这个类表示互联网协议(IP)地址。上面列出了 Socket 编程时比较有用的方法:
5、Socket 客户端实例
如下的 GreetingClient 是一个客户端程序,该程序通过 socket 连接到服务器并发送一个请求,然后等待一个响应。
import java.net.*; import java.io.*; public class GreetingClient { public static void main(String [] args) { String serverName = args[0]; int port = Integer.parseInt(args[1]); try { System.out.println("连接到主机:" + serverName + " ,端口号:" + port); Socket client = new Socket(serverName, port); System.out.println("远程主机地址:" + client.getRemoteSocketAddress()); OutputStream outToServer = client.getOutputStream(); DataOutputStream out = new DataOutputStream(outToServer); out.writeUTF("Hello from " + client.getLocalSocketAddress()); InputStream inFromServer = client.getInputStream(); DataInputStream in = new DataInputStream(inFromServer); System.out.println("服务器响应: " + in.readUTF()); client.close(); }catch(IOException e) { e.printStackTrace(); } } } |
6、Socket 服务端实例
如下的GreetingServer 程序是一个服务器端应用程序,使用 Socket 来监听一个指定的端口。
import java.net.*; import java.io.*; public class GreetingServer extends Thread { private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException { serverSocket = new ServerSocket(port); serverSocket.setSoTimeout(10000); } public void run() { while(true) { try { System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "..."); Socket server = serverSocket.accept(); System.out.println("远程主机地址:" + server.getRemoteSocketAddress()); DataInputStream in = new DataInputStream(server.getInputStream()); System.out.println(in.readUTF()); DataOutputStream out = new DataOutputStream(server.getOutputStream()); out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!"); server.close(); }catch(SocketTimeoutException s) { System.out.println("Socket timed out!"); break; }catch(IOException e) { e.printStackTrace(); break; } } } public static void main(String [] args) { int port = Integer.parseInt(args[0]); try { Thread t = new GreetingServer(port); t.run(); }catch(IOException e) { e.printStackTrace(); } } } |
反射
一、反射概念
Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,先了解两个概念,编译期和运行期。
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。
Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。例如,下面的示例代码:
Class demo = new Demo01().getClass();
利用 Class 类的对象 demo 可以访问 Demo01 对象的描述信息、Demo01 类的信息以及基类 Object 的信息。下表列出了通过反射可以访问的信息。
类型 | 访问方法 | 返回值类型 | 说明 |
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获取该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 | |
方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 | |
成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 | |
内部类 | getClasses() | Class 型数组 | 获取所有权限为 public 的内部类 |
getDeclaredClasses() | Class 型数组 | 获取所有内部类 | |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
如上表所示,在调用 getFields() 和 getMethods() 方法时将会依次获取权限为 public 的字段和变量,然后将包含从超类中继承到的成员变量和方法。而通过 getDeclareFields() 和 getDeclareMethod() 只是获取在本类中定义的成员变量和方法。
Java 反射机制的优缺点
优点: 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。 与 Java 动态编译相结合,可以实现无比强大的功能。 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射; 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用。
二、反射机制API
实现 Java 反射机制的类都位于 java.lang.reflect 包中,java.lang.Class 类是 Java 反射机制 API 中的核心类。
1、java.lang.Class 类
java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。
程序代码中获得 Class 实例可以通过如下代码实现:
// 1. 通过类型class静态变量
Class clz1 = String.class;
String str = "Hello";
// 2. 通过对象的getClass()方法
Class clz2 = str.getClass();
每一种类型包括类和接口等,都有一个 class 静态变量可以获得 Class 实例。另外,每一个对象都有 getClass() 方法可以获得 Class 实例,该方法是由 Object 类提供的实例方法。
注:int 是基本数据类型,所以输出结果为 true;Integer 是类,是引用数据类型,所以输出结果为 false。
2、java.lang.reflect 包
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
Constructor 类:提供类的构造方法信息。
Field 类:提供类或接口中成员变量信息。
Method 类:提供类或接口成员方法信息。
Array 类:提供了动态创建和访问 Java 数组的方法。
Modifier 类:提供类和成员访问修饰符信息。
示例代码:
public class ReflectionTest02 { public static void main(String[] args) {
try { // 动态加载xx类的运行时对象 Class c = Class.forName("java.lang.String"); // 获取成员方法集合 Method[] methods = c.getDeclaredMethods(); // 遍历成员方法集合 for (Method method : methods) { // 打印权限修饰符,如public、protected、private System.out.print(Modifier.toString(method.getModifiers())); // 打印返回值类型名称 System.out.print(" " + method.getReturnType().getName() + " "); // 打印方法名称 System.out.println(method.getName() + "();"); } } catch (ClassNotFoundException e) { System.out.println("找不到指定类"); } } } |
三、通过反射访问构造方法
为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor 类型的对象或者数组。
创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
public class Demo07 {
public static void main(String[] args) {
Class cls = LinkedList.class;
// 获取当前类对象的所有方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名字:" + method.getName());
System.out.println("获取方法返回值类型:" + method.getReturnType());
System.out.println("获取方法的访问权限:" + Modifier.toString(method.getModifiers()));
System.out.print("获取方法的参数列表:");
Class<?>[] parems = method.getParameterTypes();
for (Class<?> parem : parems) {
System.out.print(parem + ", ");
}
System.out.println();
// 获取方法可能抛出的异常信息
Class<?>[] exceptions = method.getExceptionTypes();
System.out.print("该方法抛出的异常类型为:");
for (Class<?> exception : exceptions) {
System.out.print(exception + ", ");
}
System.out.println();
System.out.println("该方法是否支持可变参数:" + method.isVarArgs());
System.out.println("**********************************************************");
}
}
}
通过反射执行方法
要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 Method 类型的对象或者数组。
如果是访问指定的构造方法,需要根据该方法的入口参数的类型来访问。
public static void main(String[] args) {
try {
// 创建Student类的class对象
Class cls = Class.forName("com.zpark.reflect.Student");
// 获取当前class对象的属性
Field[] fie = cls.getDeclaredFields();
for (Field field : fie) {
System.out.println("获取属性名字:" + field.getName());
System.out.println("获取变量的类型:" + field.getType());
System.out.println("获取属性的访问权限:" + Modifier.toString(field.getModifiers()));
System.out.println("---------------------------------------------------");
}
// 创建类对象
Student stu = (Student) cls.newInstance();
// 设置属性值
int coun = 0;
for (Field field : fie) {
// 设置忽略私有权限的值
field.setAccessible(true);
coun ++;
if(coun == 4){
break;
}
// 为所有属性设置值
field.set(stu, "大锤");
}
// 获取属性值
for (Field field : fie) {
System.out.println(field.getName() + ": " + field.get(stu));
}
System.out.println("******************************************");
System.out.println(stu.toString());
} catch (ClassNotFoundException e) {
System.out.println("com.zpark.reflect.Student类不存在异常");
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
通过反射访问成员变量
// 动态代理演示
InvocationHandler ih = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
String name = method.getName();
System.out.println(name);
System.out.println(Arrays.toString(args));
return null;
}
};
Hello h = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 的动态代理对象类加载器
new Class[]{Hello.class}, // 动态代理的class对象
ih // 传入处理调用方法的InvocationHandler
);
// 调用接口方法
h.morning("大锤");
}
}