JavaDay7
网络编程
网络通信三要素为:ip地址
端口号
协议
而网络编程主要应用为TCP协议编程和UDP协议编程
无论是TCP或是UDP,每个端都要创建对应的socket对象
UDP协议
- 发送端
/*
1:建立udp的socket服务
UDP协议发送端的Socket类叫做:DatagramSocket
2:将要发送的数据封装成数据包
3:通过udp的socket服务,将数据包发送出
4:关闭资源
*/
public class SendDemo {
public static void main(String[] args) throws Exception{
// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口。
DatagramSocket ds = new DatagramSocket();
//将要发送的数据封装成数据包 DatagramPacket
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
byte[] bytes = "欢迎观看!".getBytes(); // 发送数据的字节数组形式
int length = bytes.length; // 数据内容字节数组的长度
InetAddress address = InetAddress.getByName("xxx.xxx.xxx.xxx"); // 目标ip地址的InetAddress对象
int port = 10086;
DatagramPacket datagramPacket = new DatagramPacket(bytes, length, address, port);
//3:通过udp的socket服务,将数据包发送出
//DatagramPacket
ds.send(datagramPacket);
// 关闭资源
ds.close();
}
}
- 接收端
/*
1:建立udp的socket服务.
2:通过receive方法接收数据
3:将收到的数据存储到数据包对象中
4:通过数据包对象的功能来完成对接收到数据进行解析.
5:可以对资源进行关闭
*/
public class ReceiveDemo {
public static void main(String[] args) throws Exception{
//DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket ds = new DatagramSocket(10086);
//创建一个空的数据包
//DatagramPacket(byte[] buf, int length) 构造一个 DatagramPacket用于接收长度的数据包 length 。指定包的大小和长度
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
//void receive(DatagramPacket p)
ds.receive(datagramPacket); // 发生阻塞,直到有数据过来被接收
//接收到的数据在数据包中,需要解析数据包中的数据
byte[] data = datagramPacket.getData();
//获取实际接收到的数据长度
int length = datagramPacket.getLength();
String info = new String(data, 0, length);
InetAddress address = datagramPacket.getAddress();
String ip = address.getHostAddress();
String hostName = address.getHostName();
System.out.println("主机名:"+hostName+" ip地址:"+ip+"发来一条消息:");
System.out.println(info);
//关闭资源
ds.close();
}
}
以上为一次性发送和一次性接收信息
可改进为一直手动发送信息和一直接收信息
//发送端:
public class SendDemo {
public static void main(String[] args) throws Exception{
// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口。
DatagramSocket ds = new DatagramSocket();
//创建键盘录入对象
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("请输入要发送的内容:");
String info = sc.next();
if("exit".equals(info)){
System.out.println("----------------------");
System.out.println("发送端已关闭........");
break;
}
//将要发送的数据封装成数据包 DatagramPacket
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
byte[] bytes = info.getBytes(); // 发送数据的字节数组形式
int length = bytes.length; // 数据内容字节数组的长度
InetAddress address = InetAddress.getByName("xxx.xxx.xxx.xxx"); // 目标ip地址的InetAddress对象
int port = 10086;
DatagramPacket datagramPacket = new DatagramPacket(bytes, length, address, port);
//3:通过udp的socket服务,将数据包发送出
//DatagramPacket
ds.send(datagramPacket);
}
// 关闭资源
ds.close();
}
}
//接收端:
public class ReceiveDemo {
public static void main(String[] args) throws Exception{
//DatagramSocket(int port) 构造数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket ds = new DatagramSocket(10086);
//创建一个空的数据包
//DatagramPacket(byte[] buf, int length) 构造一个 DatagramPacket用于接收长度的数据包 length 。指定包的大小和长度
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
while (true){
//void receive(DatagramPacket p)
ds.receive(datagramPacket); // 发生阻塞,直到有数据过来被接收
//接收到的数据在数据包中,需要解析数据包中的数据
byte[] data = datagramPacket.getData();
//获取实际接收到的数据长度
int length = datagramPacket.getLength();
String info = new String(data, 0, length);
InetAddress address = datagramPacket.getAddress();
String ip = address.getHostAddress();
String hostName = address.getHostName();
System.out.println("主机名:"+hostName+" ip地址:"+ip+"发来一条消息:");
System.out.println(info);
System.out.println();
}
//关闭资源
ds.close();
}
}
需要注意的是:
UDP协议,发送的数据的时候,虽然发送端将数据封装在了数据包中,但是实际发送过程中是将数据解析出来进行传输,所以到达接收的端的时候,需要接收端自己创建一个空的数据包进行接收数据,接收了之后,再对数据包中的数据进行解析
TCP协议
- 客户端(传输端):
/*
1:建立客户端的Socket服务,并明确要连接的服务器。
2:如果连接建立成功,就表明,已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入.该通道称为Socket流,Socket流中既有读取流,也有写入流.
3:通过Socket对象的方法,可以获取这两个流
4:通过流的对象可以对数据进行传输
5:如果传输数据完毕,关闭资源
*/
public class ClientDemo {
public static void main(String[] args) throws Exception{
//创建客户端Socket对象,需要指定连接的服务器地址和端口号
//Socket(String host, int port)
//创建流套接字并将其连接到指定主机上的指定端口号
Socket socket = new Socket("xxx.xxx.xxx.xxx", 12345);
//获取通道中的输出流对象
//返回值类型是OutputStream,本质上是字节输出流
OutputStream os = socket.getOutputStream();
os.write("欢迎观看!".getBytes());
os.flush(); // 刷
//如果传输数据完毕,关闭资源
socket.close();
}
}
- 服务端
/*
1:建立服务器端的socket服务,需要一个端口
2:服务端没有直接流的操作,而是通过accept方法获取客户端对象,在通过获取到的客户端对象的流和客户端进行通信
3:通过客户端的获取流对象的方法,读取数据或者写入数据
4:如果服务完成,需要关闭客户端,然后关闭服务器,但是,一般会关闭客户端,不会关闭服务器,因为服务端是一直提供服务的
*/
public class ServerDemo {
public static void main(String[] args) throws Exception {
//建立服务器端的socket服务,需要一个端口
ServerSocket ss = new ServerSocket(12345);
//调用方法监听客户端的连接
Socket s = ss.accept(); // 服务器运行到这一步的时候,发送阻塞,等待一个客户端的连接
//获取通道中的输入流对象,获取客户端发送的数据
InputStream inputStream = s.getInputStream();
byte[] bytes = new byte[1024];
int length = inputStream.read(bytes);
String info = new String(bytes, 0, length);
System.out.println("服务器端收到信息: " + info);
// 关闭与客户端的连接
s.close();
// 释放资源,一般不关,因为服务器除特殊情况外一般不会关闭
if(xxxxxx){
ss.close();
}
ss.close();
}
}
注意:
目前的第一版tcp协议编程有些缺点:
1、服务器端没有给客户端一个反馈
2、改进成一个手动输入内容的版本
3、就目前代码而言,服务器端只监听一次,也就是说只允许一个客户端进行连接
解决方案:服务端使用多线程的方式改进
- 服务器端没有给客户端一个反馈【服务端修改】
//解决如下:
public class ServerDemo {
public static void main(String[] args) throws Exception {
//建立服务器端的socket服务,需要一个端口
ServerSocket ss = new ServerSocket(12345);
//调用方法监听客户端的连接
Socket s = ss.accept(); // 服务器运行到这一步的时候,发送阻塞,等待一个客户端的连接
//获取通道中的输入流对象,获取客户端发送的数据
InputStream inputStream = s.getInputStream();
//获取通道中的输出流对象,给客户端写反馈
OutputStream outputStream = s.getOutputStream();
//在服务器端获取客户端的信息
InetAddress inetAddress = s.getInetAddress();
String hostName = inetAddress.getHostName();
String ip = inetAddress.getHostAddress();
try {
while (true){
byte[] bytes = new byte[1024];
int length = inputStream.read(bytes);
String time = DateUtil.getTime(System.currentTimeMillis());
String info = new String(bytes, 0, length);
System.out.println(time);
System.out.println("ip地址为: "+ip+"发来一条消息:");
System.out.println(info);
System.out.println();
outputStream.write("服务器已收到!".getBytes());
outputStream.flush();
}
}catch (Exception e){
System.out.println("-----------客户端已下线.....------------");
}
// 关闭与客户端的连接
s.close();
// 释放资源,一般不关,因为服务器除特殊情况外一般不会关闭
if(xxxxxx){
ss.close();
}
ss.close();
}
}
- 改进成一个手动输入内容的版本【客户端修改】
//解决如下:
public class ClientDemo {
public static void main(String[] args) throws Exception{
//创建客户端Socket对象,需要指定连接的服务器地址和端口号
//Socket(String host, int port)
//创建流套接字并将其连接到指定主机上的指定端口号
Socket socket = new Socket("192.168.1.20", 12345);
Scanner sc = new Scanner(System.in);
//获取通道中的输出流对象
//返回值类型是OutputStream,本质上是字节输出流
OutputStream os = socket.getOutputStream();
//将通道中的字节输出流包装成字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
//获取通道中的输入流对象
//返回值类型是InputStream,本质上是字节输入流
InputStream is = socket.getInputStream();
while (true){
System.out.println("请输入要发送的内容:");
String info = sc.next();
if("exit".equals(info)){
break;
}
bw.write(info);
bw.flush();
byte[] bytes = new byte[1024];
int length = is.read(bytes);
String res = new String(bytes, 0, length);
System.out.println("服务器反馈:"+res);
System.out.println();
}
is.close();
bw.close();
//如果传输数据完毕,关闭资源
socket.close();
}
}
- 就目前代码而言,服务器端只监听一次,也就是说只允许一个客户端进行连接【服务端修改,加入线程】
//线程建立
//重写run()方法
//解决如下:
public class ClientThread extends Thread{
private Socket s;
public ClientThread(Socket socket) {
this.s = socket;
}
@Override
public void run() {
//在服务器端获取客户端的信息
InetAddress inetAddress = s.getInetAddress();
String hostName = inetAddress.getHostName();
String ip = inetAddress.getHostAddress();
System.out.println("用户: "+hostName+"已上线!!");
try {
//获取通道中的输入流对象,获取客户端发送的数据
InputStream inputStream = s.getInputStream();
//获取通道中的输出流对象,给客户端写反馈
OutputStream outputStream = s.getOutputStream();
while (true){
byte[] bytes = new byte[1024];
int length = inputStream.read(bytes);
String time = DateUtil.getTime(System.currentTimeMillis());
String info = new String(bytes, 0, length);
System.out.println(time);
System.out.println(hostName+": "+info);
System.out.println();
outputStream.write("服务器已收到!".getBytes());
outputStream.flush();
}
}catch (Exception e){
System.out.println("------------------------------");
System.out.println("用户: "+hostName+"已下线.....");
System.out.println("------------------------------");
}
}
}
//重新编写服务端程序:
public class ServerDemo1 {
public static void main(String[] args) throws Exception {
//建立服务器端的socket服务,需要一个端口
ServerSocket ss = new ServerSocket(12345);
while (true){
//使用while true的方式将监听进行包装,这样一直监听,可以有多个客户端进行连接
//调用方法监听客户端的连接
Socket s = ss.accept(); // 服务器运行到这一步的时候,发送阻塞,等待一个客户端的连接
//创建用户线程对象
ClientThread clientThread = new ClientThread(s);
clientThread.start();
}
// 关闭与客户端的连接
s.close();
// 释放资源,一般不关,因为服务器除特殊情况外一般不会关闭
if(xxxxxx){
ss.close();
}
ss.close();
}
}
注意:
tcp协议中,客户端不能先启动,要先启动服务器端,让服务器端绑定一个端口号,然后再让客户端做连接。
类加载器和反射
类加载器
反射的前奏,首先学习如何获取一个类的Class对象
我们首先创建一个类Student
public class Student {
private int id;
protected String name;
int age;
public String address;
private Student(){
}
private Student(String name){
this.name = name;
}
public Student(int id){
this.id = id;
}
protected Student(int id, String name){
this.id = id;
this.name = name;
}
public String fun1(){
System.out.println("这是无参公共的方法fun1");
return "hello world";
}
public void fun2(String s){
System.out.println("这是有参公共的方法fun2");
}
private void fun3(){
System.out.println("这是私有的方法fun3");
}
private void fun4(String s){
System.out.println("这是私有的方法fun4");
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
我们可以看见该类中并没有setter和getter
且存在两个成员变量id、name和两个方法fun3()、fun4()是私有的无法直接获取
那么我们该如何获取其中的元素呢?
获取Student类的Class对象
public class ClassDemo1 {
public static void main(String[] args) throws Exception{
//方式1:在已经有该类的对象前提下获取 getClass();
Student s1 = new Student(1001);
Class<? extends Student> c1 = s1.getClass();
Student s2 = new Student(1002);
Class<? extends Student> c2 = s2.getClass();
System.out.println(s1==s2); // 不同的对象地址值不一样
System.out.println(c1==c2); // 同一个类的Class对象在内存中只有一个
System.out.println("------------------------------------");
//方式2:直接通过类名.class获取
Class<Student> c3 = Student.class;
System.out.println(c1==c3); // 同一个类的Class对象在内存中只有一个
System.out.println(c2==c3); // 同一个类的Class对象在内存中只有一个
System.out.println("-------------------------------------");
//方式3:通过Class类中的静态方法forName()
Class<?> c4 = Class.forName("shujia.day18.Student");
System.out.println(c1==c4);
System.out.println(c2==c4);
//对于这个案例来说,都是获取Student类class文件在内存中的Class对象
}
}
在了解完如何获取类中Class对象,我们仍不能对Student类中的成员变量和方法进行赋值和调用
那么接下来可以使用反射来进行赋值和调用
反射
通过反射获取Student中的构造方法并使用
public class ClassDemo2 {
public static void main(String[] args) throws Exception{
Class<?> studentClass = Class.forName("Student");
//获取公共public的构造方法
//Constructor<T> getConstructor(Class<?>... parameterTypes)
//获取某一个构造方法,根据参数列表来获取
//获取一个参数列表为一个int类型的构造方法
Constructor<?> c1 = studentClass.getConstructor(int.class);
System.out.println("c1: "+c1);
//获取一个无参的构造方法
Constructor<?> c2 = studentClass.getConstructor(int.class, String.class);
System.out.println("c2: "+c2);
Constructor<?> c2 = studentClass.getDeclaredConstructor();
System.out.println("c2: "+c2);
Constructor<?> c3 = studentClass.getDeclaredConstructor(int.class, String.class);
System.out.println("c3: "+c3);
Constructor<?> c4 = studentClass.getDeclaredConstructor(int.class);
System.out.println("c4: "+c4);
//获取任意修饰符的构造方法
//public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
//Constructor类中有一个方法可以根据一个构造方法,创建对应类的对象
Object o1 = c4.newInstance(1001);
System.out.println(o1);
c2.setAccessible(true); // 暴力访问
Object o2 = c2.newInstance();
System.out.println(o2);
//获取类中所有的公共的public修饰的构造方法
Constructor<?>[] constructors = studentClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
//获取类中所有的构造方法
Constructor<?>[] declaredConstructors = studentClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}
}
通过反射获取Student类中的成员变量并使用
public class ClassDemo3 {
public static void main(String[] args) throws Exception {
Class<?> studentClass = Class.forName("Student");
//public Field getField(String name) 只能获取公共的public修饰的成员变量
// 根据成员变量的名字获取对应的成员变量的对象
Field id = studentClass.getField("id");
System.out.println("id: " + id);
//public Field getDeclaredField(String name) 获取任意修饰符修饰的成员变量
Field id = studentClass.getDeclaredField("id");
System.out.println("id: "+id);
Field name = studentClass.getDeclaredField("name");
System.out.println("name: "+name);
Field age = studentClass.getDeclaredField("age");
System.out.println("age: "+age);
Field address = studentClass.getDeclaredField("address");
System.out.println("address: "+address);
//如何使用成员变量
//之前的思想:给某一个对象中的某一个成员变量进行赋值
//反射的思想:哪一个对象的成员变量要赋什么值
//在使用成员变量之前,先知道给哪一个对象的该成员变量赋值
Constructor<?> c1 = studentClass.getDeclaredConstructor();
c1.setAccessible(true);
Student s = (Student) c1.newInstance();
System.out.println("s: "+s);
//给学生对象s中的address成员变量进行赋值
address.set(s, "安徽合肥");
System.out.println("s: "+s);
id.setAccessible(true);
id.set(s, 1001);
System.out.println("s: "+s);
name.setAccessible(true);
name.set(s,"张三");
System.out.println("s: "+s);
age.setAccessible(true);
age.set(s, 18);
System.out.println("s: "+s);
Field[] fields = studentClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
Field[] declaredFields = studentClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
}
}
通过反射获取Student类中的成员方法并使用
public class ClassDemo4 {
public static void main(String[] args) throws Exception {
Class<?> studentClass = Class.forName("Student");
//public Method getMethod(String name, Class<?>... parameterTypes) 获取某一个公共的成员方法
// 获取一个无参的方法,方法名叫做fun1
Method fun1 = studentClass.getMethod("fun1");
System.out.println("fun1: " + fun1 );
// 获取一个有一个参数为String类型的方法,方法名叫做fun2
Method fun2 = studentClass.getMethod("fun2", String.class);
System.out.println("fun2: " + fun2);
Method fun3 = studentClass.getMethod("fun3");
System.out.println("fun3: " + fun3);
//public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取任意权限修饰符的成员方法
Method fun3 = studentClass.getDeclaredMethod("fun3");
System.out.println("fun3: " + fun3);
//根据参数的类型和方法名获取对应的方法,将其封装成Method对象进行返回
Method fun4 = studentClass.getDeclaredMethod("fun4", String.class);
System.out.println("fun4: " + fun4);
//如何使用获取到的成员方法
//之前写代码的思想:对象是固定的,调用对象中的某一个方法
//反射中的思想:方法是固定的,是哪一个对象的该方法
Constructor<?> c1 = studentClass.getDeclaredConstructor();
c1.setAccessible(true);
Student s = (Student) c1.newInstance();
// 调用对象s中的fun1方法
//Method类中有一个方法invoke'
//public Object invoke(Object obj, Object... args)
Object o = fun1.invoke(s);
System.out.println(o);
//调用对象s中的fun4方法
fun4.setAccessible(true);
fun4.invoke(s, "shujia");
//getMethods 获取自己本类或父类所有的public修饰的方法
Method[] methods = studentClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
//getDeclaredMethods 只能获取本类中的所有方法,父亲的不管
Method[] declaredMethods = studentClass.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
}
}
个人认为对于一个已知构造类,频繁利用反射进行对象的创建的话,代码量十分繁杂并且操作麻烦。并且,拿成员变量举例,在反射出成员变量之后,同样要对新建的对象进行类的转型。所以对于已知构造类,我们可以利用new一个对象对非私有成员变量和成员方法进行操作,而对于那些私有且无set、get方法的成员变量和成员方法,则利用反射进行操作
如下展示:
- 首先我们还是创建一个Student类,但不同的是类中存在部分成员变量有Set和Get方法:
public class Student {
private int id;
protected String name;
String gender;
int age;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
- 而后便可以利用new对象和反射结合来对对象进行操作
public class Fanshe {
public static void main(String[] args){
// try{
// Class<?> c1 = Class.forName("HomeWork.Jan17.Student");
//
// Constructor<?> constructor = c1.getConstructor(int.class,String.class);
// constructor.setAccessible(true);
// System.out.println(constructor);
// }catch (Exception e){
// e.printStackTrace();
// }
//new对象
Student student = new Student();
student.setAge(16);
student.setGender("男");
student.setName("查镕贤");
//反射
try{
Class<?> c2 = Class.forName("HomeWork.Jan17.Student");
Field id = c2.getDeclaredField("id");
id.setAccessible(true);
id.set(student,13);
System.out.println(student);
}catch (Exception e){
e.printStackTrace();
}
}
}
来源链接:https://www.cnblogs.com/Roxan-bd/p/18677443
没有回复内容