GUI
- GUI:Graphical User Interface(用户图形接口)
- Eclipse就是Java写的
- 桌面应用一般用C++,C#或Delphi
- Java的GUI操作类都在java.awt和java.swing中
- awt:abstract window toolkit(抽象窗口工具包)。调用本地系统方法实现功能,显示效果为系统图形效果。重量级控件
- swing:在awt基础上建立的图形界面系统,提供了更多组件,完全由java实现。增强了移植性。轻量级控件。
- 继承关系图
- Component:组件
- Container:容器(能添加组件的组件)
- Window:独立存在的一个页面
- Frame:框架
- Dialog:对话框
- FileDialog:文件对话框
- Panel:窗体中的一个一个部分
- Window:独立存在的一个页面
- Button:按钮
- Label:标签
- Checkbox:复选框
- TextComponent:文本组件
- TextArea:文本域
- TextField:文本框
- Container:容器(能添加组件的组件)
- 容器中的布局管理器
- FlowLayout(流式布局管理器)
- 从左向右的顺序,放不下就下一行
- Panel默认布局管理器
- BorderLayout(边界布局管理器)
- 东南西北中
- Frame默认的布局管理器
- 没有指定东南西北,直接界面填充,下边的覆盖上边的
- GridLayout(网格布局管理器)
- 规矩的矩阵
- CardLayout(卡片布局管理器)
- 选项卡
- GridBagLayout(网格包布局管理器)
- 非规则的矩阵,可以占多个格子
- 坐标式布局
- 可以拖动到任何位置
- FlowLayout(流式布局管理器)
- JFrame
- 特点
- 用于创建一个框架实例。
- 构造方法
JFrame() 创建一个最初不可见的窗体 默认为边界布局。 JFrame(窗口标题) 给窗体放一个标题
- 常用方法
setVisiable(boolean) 设置窗体是否可见 setSize(int, int) 设置窗口宽高(px) setLocation(int, int) 设置窗口具体左上角的距离 setBounds(int, int, int, int) 设置距离和宽高 setlayout(new FlowLayout()) 设置布局 Component add(Component) 将组件添加到窗体中
- 事件监听机制
- 组成
- 事件源(组件)
- 事件(Event)
- 监听器(Listener)
- 事件处理(引发事件后的处理方式)
- 处理流程
- 事件源可以添加监听器来监听事件。通过addXxx方法
- WindowListener监听WindowEvent。前缀有对应关系
- WindowListener抽象方法太多,其子类WindowAdapter实现了所有其方法,但只实现为空。方便创建监听器对象,不必实现所有方法。
- WindowAdapter是抽象类,但没有抽象方法,也就是不能创建对象,但是方便该接口创建对象
- 使用匿名内部类即可。
- 组成
- 特点
- JButton
- 特点
- 用于创建一个按钮。
- 按钮添加MouseListener和ActionListener,ActionListener先触发
- 包解释
- awt:图形界面组件的包
- event:监听事件或适配器的包
- 小知识
- 键盘的F1的F是function
- 有些大型机的F到F24
- 构造方法
JButton() 创建一个按钮 JButton(标题) 给按钮放一个标题
- 简单窗体+监听器
// 构造一个最初不可见的框架 JFrame f = new JFrame(); // f.setSize(500, 400); // f.setLocation(400, 300); f.setBounds(400, 200, 500, 400); f.setLayout(new FlowLayout()); JButton button = new JButton("按钮"); f.add(button); // 给窗体添加一个窗体监听器,监听器方法超过2个,所以有适配器来避免手动实现所有接口方法 f.addWindowListener(new WindowAdapter() { // 当点击了窗体的退出按钮,自动调用该方法。并将事件传递给e public void windowClosing(WindowEvent e) { // 退出虚拟机 System.exit(0); } }); // 给按钮加一个活动监听器,因为监听器只有1个方法,所以没有适配器 button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); f.setVisible(true);
- 键盘+鼠标监听器的演示
public class JFrameTest{ // 将图形化界面涉及的所有组件用成员变量定义 // 用来明确需要多少组件 // 创建方法的时候再对组件初始化 private JFrame f; private JTextField tf; private JButton but; public JFrameTest() { init(); } /** * 组件初始化 */ private void init() { f = new JFrame("鼠标键盘监听"); f.setBounds(400, 200, 500, 400); f.setLayout(new FlowLayout()); tf = new JTextField(15); but = new JButton("一个按钮"); f.add(tf); f.add(but); myEvent(); f.setVisible(true); } private void myEvent() { // 窗体监听事件 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // 给按钮添加一个鼠标监听事件 but.addMouseListener(new MouseAdapter() { private int count = 1; public void mouseEntered(MouseEvent e) { tf.setText("" + count); count ++; } public void mouseClicked(MouseEvent e) { // 可以通过MouseEvent的各种方法来获取鼠标事件的信息 if (e.getClickCount() == 2) { System.out.println("double click"); } // System.out.println("1"); } }); // 给按钮添加活动事件,活动事件比鼠标监听事件先触发 but.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("2"); } }); // 给文本框添加键盘监听事件 tf.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { // 获取键代码的名称 System.out.println(KeyEvent.getKeyText(e.getKeyCode())); if (!(e.getKeyCode() >= KeyEvent.VK_0 && e.getKeyCode() <= KeyEvent.VK_9)) { // 消耗掉该数据,不进行默认操作 e.consume(); } // 用来判断CTRL + 回车 if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_ENTER) { System.out.println("CTRL + enter"); } } }); } public static void main(String[] args) throws IOException { new JFrameTest(); } }
-
JFileChooser 文件对话框
-
JMenuBar 菜单条
-
JDialog 对话框
-
JScrollPane 添加带滚动条的面板。可以继续向里边添加组件。
- 特点
网络编程
- 网络模型
- OSI:Open System Interconnection 开放系统互联参考模型
- TCP/IP参考模型
- 物理层:网线是物理层介质。
- 数据链路层:将物理层接收的数据进行MAC地址封装与解封装。该层数据叫帧。设备是交换机,交换机每个口都有MAC地址。
- 网络层:对数据链路层数据进行IP地址封装。该层数据是数据包。设备是路由器。
- 传输层:定义传输协议和端口号。发送数据规则。TCP,UDP等。该层数据叫段。
- 会话层:建立会话
- 表示层:进行数据的解析,加密解密,压缩解压
- 应用层:计算机的应用软件
- 网络通讯要素
- IP地址
- 127.0.0.1 本地回环地址。访问本机使用
- 192.168.1.100 局域网内可以互相访问
- 端口号
- 同一个计算机的不同应用程序标识。标识进程的逻辑地址
- 端口号在0-65535.0-1024通常为保留端口,不要用。
- 防火墙:禁用进来和出去的端口就无法进行数据交互了。
- 传输协议
- 传输协议就是传输规则
- 传输层的两种协议UDP(数据报文协议),TCP(传输控制协议)
- UDP将数据,源地址,目的地址封装到数据包。不需要建立连接,速度快,不可靠协议,无法知道是否到达。数据包大小限制在64K。类似快递。对讲机,QQ,在线视频使用UDP,可能丢失一些数据,但是速度快。
- TCP建立连接后才数据通讯。通过三次握手完成连接。是可靠协议,对方断开不传输数据,不丢包。必须建立连接。效率低。类似打电话。下载用的就是TCP。
- UDP可能丢包,但是速度快。TCP速度较慢,但是不会丢包。
- IP地址
- Java网络编程的包是net包
-
InetAddress
-
特点
-
表示网络的IP地址。在网际层。
-
没有构造函数,使用静态方法返回对象。
-
-
小知识
-
DNS:(Domain Name System)域名解析服务器,进行域名与IP对应关系的记录
-
先请求域名,域名从DNS服务器中找到对应IP返回自己的主机,自己根据该IP访问域名所属网站。
-
域名解析顺序:本地hosts->互联网
-
-
局域网的某台机器装一个DNS服务器软件,配置好域名解析,然后本地DNS指向该机器的IP,访问网址的时候就会自动从这台DNS服务器进行域名查询。
-
cmd输入start可以开一个新窗口
-
C类地址,P前三字段都是网络位,IP的第四个字段为0是网络位,相当于网段,ip地址从1-254有效,255是广播字段,会把消息广播到网段中所有IP中。可用于群聊
-
-
常用方法
InetAddress getLocalHost() 返回本地主机ip地址对象 InetAddress getByName("DESKTOP-G8GJS0I") 根据主机名获取ip地址对象 InetAddress getByName("192.168.120.124") 根据IP获取ip地址对象 InetAddress getByName("www.baidu.com") 根据域名获取ip地址对象 InetAddress[] getAllByName() 获取主机的IP地址数组。有的主机IP地址不唯一。可能有服务器集群。 String getHostAddress() 获取主机地址 String getHostName() 获取主机名。找不到主机名就返回IP
-
- UDP传输流程
- DatagramSocket创建数据包套接字对象
- DatagramPacket创建发送或接收数据包对象
- 使用DatagramSocket发送或接收数据包
- 关闭socket流
- TCP传输流程
- Socket
- 实现客户端套接字
- 术语叫做套接字,是为网络服务提供的一种机制
- 可以理解为通讯的端点,数据在Socket中进行IO传递。需要通信两端都有Socket
- TCP客户端创建的过程
- 创建Socket对象,并指定要连接的IP与Port,明确目的主机
- 如果连接建立成功,就形成了一个数据传输通道(socket流)。可以从该Socket对象获取输入输出流对象进行数据传输。
- 使用输出流写出数据或使用输入流写入数据
- 关闭Socket流
- 这里可以不用关闭,服务器端会统一关闭,但是需要给服务器端发送结束标记,否则服务器端也不会关。
- ServerSocket
- 实现服务端套接字
- TCP服务端创建的过程
- 创建ServerSocket对象
- 给服务端提供一个监听端口,否则客户端无法连接
- 获取连接过来的客户端Socket对象,并获取其输入流或输出流对象
- 使用输出流写出数据或使用输入流写入数据
- 关闭资源
- 需要关闭客户端,服务端如果需要一直进行服务提供则可以不用关闭。
- Socket
- DatagramSocket
- 特点
- 用于表示发送和接收数据报包的套接字
- 构造方法
DatagramSocket() 创建一个数据报包套接字对象。用于发送数据包,默认绑定任意可用端口 DatagramSocket(int) 创建一个带端口的数据报包套接字对象。用于接收数据包
- 常用方法
close() 关闭套接字 send(DatagramPacket) 发送数据报包 receive(DatagramPacket) 接收数据报包。阻塞式方法。接收到数据就可以走他下边的代码了。
- 特点
- DatagramPacket
- 特点
- 表示数据报包,实现无连接的投递服务。不对包投递做出保证。
- 包含包中的数据,目的地址,源地址。所以封装对象更方便。
- 构造方法
DatagramPacket(byte[], int, InetAddress, int) 创建一个发送数据报包套接字对象,需要指定字节数组,发送长度,IP和端口 发送包有目的地址,接收包不需要地址
- 常用方法
byte[] getData() 获取接收的数据 int getPort() 获取发送端端口 InetAddress getAddress() 获取发送端IP对象 int getLength() 获取发送数据的长度
- 发送接收端小示例(可以使用多线程来进行同时发送和接收)
// 发送端代码 public void udpSend() throws IOException { /** * 1 创建UDP的socket * 2 组包 * 3 发包 * 4 关服务 */ System.out.println("发送端启动"); DatagramSocket ds = new DatagramSocket(); String sendData = "UDP传输演示"; byte[] sendDataBinary = sendData.getBytes(); InetAddress ip = InetAddress.getByName("127.0.0.1"); DatagramPacket dp = new DatagramPacket(sendDataBinary, sendDataBinary.length, ip, 10000); ds.send(dp); ds.close(); } // 接收端代码 public void updReceive() throws IOException { /** * 1 建立UDP的socket * 2 接收UDP的包。使用数据包接收 * 3 解包查看 * 4 关闭socket */ System.out.println("接收端启动"); DatagramSocket ds = new DatagramSocket(10000); byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf, buf.length); ds.receive(dp); System.out.println(new String(dp.getData(), 0, dp.getLength())); System.out.println(dp.getPort()); System.out.println(dp.getAddress().getHostAddress()); ds.close(); }
- 使用多线程开启聊天室
// 线程开启 public static void chatroomDemo() throws SocketException { DatagramSocket senderSocket = new DatagramSocket(10001); DatagramSocket receiverSocket = new DatagramSocket(10002); ChatSender sender = new ChatSender(senderSocket); ChatReceiver receiver = new ChatReceiver(receiverSocket); new Thread(sender).start(); new Thread(receiver).start(); } // ChatSender import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class ChatSender implements Runnable{ private DatagramSocket ds; public ChatSender(DatagramSocket ds) { this.ds = ds; } public void run() { try { System.out.println("发送端启动"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; while ((line = br.readLine()) != null) { byte[] sendDataBinary = line.getBytes(); // InetAddress ip = InetAddress.getByName("127.0.0.255"); 255可以广播消息到该网段所有IP InetAddress ip = InetAddress.getByName("127.0.0.1"); DatagramPacket dp = new DatagramPacket(sendDataBinary, sendDataBinary.length, ip, 10002); ds.send(dp); if ("over".equals(line)) break; } ds.close(); } catch (Exception e) { } } } //ChatReceiver import java.net.DatagramPacket; import java.net.DatagramSocket; public class ChatReceiver implements Runnable { private DatagramSocket ds; public ChatReceiver(DatagramSocket ds) { this.ds = ds; } public void run() { try { System.out.println("接收端启动"); while (true) { byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf, buf.length); ds.receive(dp); String ip = dp.getAddress().getHostAddress() ; String port = "" + dp.getPort(); String message = new String(dp.getData(), 0, dp.getLength(), "UTF-8"); System.out.println(ip + ":" + port + ":" + message); if (message.equals("over")) System.out.println(ip + "退出聊天室"); } } catch (Exception e) { } } }
- 特点
- Socket
- 构造方法
Socket() 创建一个客户端未连接的套接字。可以使用connect方法连接 Socket(InetAddress, port) 创建一个流套接字并连接到指定IP和端口号 Socket(String, port) 使用字符串来指定IP
- 常用方法
connect() 连接该socket到一个套接字地址 InputStream getInputStream() 获取输入流对象,用于读数据 OutputStream getOutputStream() 获取输出流对象,用于写数据 shutdownInput() 给套接字的输入流置一个结束标记 shutdownOutput() 给套接字的输出流置一个结束标记
-
客户端给服务端发送数据,并接收服务端返回数据
Socket socket = new Socket("127.0.0.1", 10001); OutputStream out = socket.getOutputStream(); out.write("TCP演示".getBytes()); InputStream input = socket.getInputStream(); byte[] buf = new byte[1024]; int len = input.read(buf); String text = new String(buf, 0, len); System.out.println(text); // 这里的关闭链接就相当于断开socket流连接。不关的话服务器关掉也行 socket.close();
- 构造方法
-
SocketAddress
- 特点
- 封装了IP + Port
- 特点
- ServerSocket
- 构造方法
ServerSocket() 创建一个服务端未连接的套接字 Socket(port) 创建服务端套接字的时候初始化监听端口
- 常用方法
accept() 获取客户端连接的Socket对象
- 服务端接收客户端发送的数据,并给客户端返回数据
ServerSocket ss = new ServerSocket(10001); Socket socket = ss.accept();// 阻塞式方法。如果没有进行Socket连接,这里一直等待 InputStream input = socket.getInputStream(); byte[] buf = new byte[1024]; int len = input.read(buf); String ip = socket.getInetAddress().getHostAddress(); String text = new String(buf, 0, len); System.out.println(ip + ":" + text); OutputStream out = socket.getOutputStream(); out.write("收到".getBytes()); socket.close(); ss.close();
- 创建英文大写转换服务器。
// TransClient import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class TransClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", 10001); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; // 用PrintWriter直接输出字符流,加true自动刷新 PrintWriter pw = new PrintWriter(socket.getOutputStream()); BufferedReader result = new BufferedReader(new InputStreamReader(socket.getInputStream())); while ((line = br.readLine()) != null) { if ("over".equals(line)) break; // 将字节流输出,并加上换行 pw.print(line); // 读入服务器返回的一行数据 System.out.println(result.readLine()); } // 客户端进行了close,就会在流中植入结束标记-1,所以服务端判断没有数据,也会自动关闭 // 最好给服务端发送一个结束标记,让服务端来关闭。 socket.close(); } } // TransServer import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class TransServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10001); Socket socket = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream()); String line = null; while((line = br.readLine()) != null) { System.out.println(line); String newLine = line.toUpperCase(); pw.print(newLine); } socket.close(); ss.close(); } }
-
TCP传输过程两端都在等,接收不到数据
-
有阻塞式方法。数据留在了缓冲区,没有发出去。
-
需要进行数据刷新
-
-
没有读取到数据结束标记
-
例如服务端使用readLine()读取客户端发送的数据,没有发送换行符,所以一直进行数据等待
-
-
-
上传文本文件
// UploadClient import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class UploadClient { public static void main(String[] args) throws UnknownHostException, IOException { System.out.println("client start"); Socket socket = new Socket("127.0.0.1", 10001); BufferedReader br = new BufferedReader(new FileReader("Worker.java")); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); String line = null; // 客户端可以发送时间戳来当作结束标记。先发一个时间戳,服务端接收到存储好。然后客户端发送完再发相同的时间戳即可。 // 也可以使用Socket的shutdownInput说shutdownOutput方法 while ((line = br.readLine()) != null) { pw.println(line); } // 传输给服务端结束标记 socket.shutdownOutput(); pw.println("over"); BufferedReader result = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println(line = result.readLine()); br.close(); socket.close(); } } // UploadServer import java.io.BufferedReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class UploadServer { public static void main(String[] args) throws IOException { System.out.println("server start"); ServerSocket ss = new ServerSocket(10001); Socket socket = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(new FileWriter("server.txt"), true); String line = null; // 服务端需要使用结束标记来判断接收数据结束。 // 或者客户端使用Socket的shutdownInput说shutdownOutput方法 while ((line = br.readLine()) != null) { pw.println(line); } PrintWriter returnStr = new PrintWriter(socket.getOutputStream()); returnStr.println("success"); System.out.println("server success"); returnStr.flush(); pw.close(); socket.close(); ss.close(); } }
-
服务端多线程上传图片
// UploadPicClient import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.net.UnknownHostException; public class UploadPicClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("127.0.0.1", 10001); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] buf = new byte[1024]; int len = 0; System.out.println("client start"); while ((len = bis.read(buf)) != -1) { bos.write(buf, 0, len); bos.flush(); } socket.shutdownOutput(); InputStream is = socket.getInputStream(); byte[] result = new byte[1024]; int resultLength = is.read(result); System.out.println(new String(result, 0, resultLength)); System.out.println("client finish"); bis.close(); socket.close(); } } // UploadPicServer // 使用多线程是为了解决客户端请求服务端需要排队的问题。如果请求时间过长,会超时。 import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class UploadPicServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10001); System.out.println("server start"); while (true) { Socket socket = ss.accept(); new Thread(new UploadTask(socket)).start();; } // ss.close(); } } // UploadTask import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.Socket; public class UploadTask implements Runnable { private Socket socket; public UploadTask(Socket socket) { this.socket = socket; } public void run() { BufferedOutputStream bos = null; int count = 0; try { File dir = new File("C:\\pic"); if (!dir.exists()) { dir.mkdirs(); } File pic = new File(dir, socket.getInetAddress().getHostAddress() + ".png"); while (pic.exists()) { pic = new File(dir, socket.getInetAddress().getHostAddress() + "(" + (++count) + ").png"); } BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); bos = new BufferedOutputStream(new FileOutputStream(pic)); byte[] buf = new byte[1024]; int len = 0; while ((len = bis.read(buf)) != -1) { bos.write(buf, 0, len); bos.flush(); } PrintWriter pw = new PrintWriter(socket.getOutputStream()); pw.print("success"); pw.flush(); System.out.println("server finish"); } catch (IOException e) { } finally { try { bos.close(); } catch (IOException e) { } try { socket.close(); } catch (IOException e) { } } } }
- 小知识
- 无限循环不要怕,主要是因为循环中有阻塞方法,资源没进来就进行等待。不耗费CPU
- 如果判断一次,使用if,如果判断多次,使用while
- HTTP是应用层规则。是一种通讯协议,通讯规则。
- 192.168.1.100(自己的主机地址)和127.0.0.1都可以访问本地主机
- C/S需要做客户端和服务端
- 开发成本较高,维护比较麻烦。
- 可以在本地分担一部分运算。例如杀毒就不适合使用B/S
- 客户端游戏速度更快,效果更好。
- B/S不需要做客户端,直接有浏览器就行
- 开发成本较低,维护较简单。
- 所有运算都在浏览器
- 服务器端原理
// 服务端多线程伪代码 ServerSocket ss = new ServerSocket(); while (true) { // 服务器端接收到一个客户端 Socket socket = ss.accept(); // 将客户端封装为一个线程执行任务 new Thread(new Task(socket)); // 主线程任务执行完,返回while重新执行,重新进行accept()获取。这样就可以并发处理多个客户端请求了。 // 如果是单线程,原客户端任务没有执行完,新的客户端无法进行accept()获取,只能等待 }
- 常见客户端和服务端
-
客户端
-
浏览器
-
早期浏览器都是IE内核,改了下外观。
-
IE是单窗口的,一个页面一个窗口。遨游将IE改为单窗口,多标签形式,切换更方便
-
基于IE内核可能收费,所以后期基于webkit(开源免费)
-
-
-
服务端
-
服务器:可以对外提供服务的机器。
-
Oracle:数据库服务器
-
Tomcat:Web资源访问服务器。处理请求并给予应答。
-
服务端的处理都需要实现Servlet(Server Applet)接口
-
用于访问web应用(webapp)
-
-
网络硬盘:存储服务器
-
-
-
-
客户端和服务端原理
- 服务端
-
可以使用ServerSocket写一个服务端,浏览器访问该服务端,接收返回信息并解析。
-
浏览器服务端小例子
ServerSocket ss = new ServerSocket(10001); Socket socket = ss.accept(); String ip = socket.getInetAddress().getHostAddress(); System.out.println(ip); InputStream is = socket.getInputStream(); byte[] buf = new byte[1024]; int len = is.read(buf); System.out.println(new String(buf, 0, len)); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); // 需要加上响应行,浏览器才可以解析 pw.println("HTTP/1.1 200 OK"); pw.println(); // 响应体前边加一个空行 pw.println("hello KnowServer"); socket.close(); ss.close();
-
服务端发送的应答消息的含义
HTTP/1.1 200 OK 应答行:HTTP协议版本 应答状态码 应答状态描述信息 Accept-Ranges: bytes (从这里开始都是应答消息头,键值对形式) ETag: W/"5-1597909615505" Last-Modified: Thu, 20 Aug 2020 07:46:55 GMT 资源最后修改时间。如果本地有缓存,请求的时候带上缓存中的这个键值对,服务端判断时间一致,返回一个状态码,不再返回响应体。速度会更快。 Content-Type: text/html 返回数据类型。 Content-Length: 5 返回数据的字节长度 Date: Thu, 20 Aug 2020 07:47:54 GMT Connection: close 连接关闭 hello 应答体和应答消息头之间也需要有空行
-
常用状态码
- 200 OK 请求成功
-
404 not found 资源没有找到(输入不存在的网页也找不到)
-
-
客户端
-
使用Socket写一个客户端,使其访问Tomcat,接收返回信息并解析。
-
浏览器客户端小例子
Socket socket = new Socket("127.0.0.1", 8080); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); // 添加请求行 pw.println("GET / HTTP/1.1"); // 添加请求头 pw.println("Accept: */*"); pw.println("Connection: close"); pw.println("Host: 127.0.0.1:8080"); // 这里没有请求体,有的话下边的换行是必须的 pw.println(); pw.println(); InputStream is = socket.getInputStream(); byte[] buf = new byte[1024]; int len = 0; while ((len = is.read(buf)) != -1) { System.out.println(new String(buf, 0, len)); } socket.close();
-
浏览器发送的请求消息的含义
GET / HTTP/1.1 请求行:请求方式 请求路径(这里是根目录) http协议版本。请求行是最主要的。 GET请求的请求参数会在请求行获取到 /?user=qwe&pass=123 POST请求的请求参数会在请求体中获取到 user=qwe&pass=123 Host: 127.0.0.1:10001 获取主机名(从这里向下都是请求消息头,格式都是键值对。用于告诉服务器都允许什么应用程序解析) Connection: keep-alive 连接状态:保持存活 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 用来获取浏览器版本 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 表示浏览器都支持解析什么数据。 Sec-Fetch-Site: cross-site Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br 表示浏览器支持的压缩方式。如果网页比较大,服务端会将网页压缩再发给浏览器。用于提高传输效率。 Accept-Language: zh-CN,zh;q=0.9 表示浏览器都支持什么语言 页面数据 浏览器发送的请求体,请求体和请求头由空行隔开。HTTP请求规则。
- GET提交和POST提交的区别
- GET
- 数据封装在请求行
- 提交大量数据,地址栏有限,放不下
- POST
- 数据封装在请求体
- 可以提交大量数据
- GET
-
- 服务端
- 构造方法
- URL
- 特点
- Uniform Resource Locator,统一资源定位符
- 指向互联网资源的指针,资源可以是简单的文件或目录
- 这是一个URL:http://www.lightly.com/project/index.html.可以参考。
- 构造方法
URL(String) 创建一个带URL地址的URL对象 URL(String protocol, String host, int port, String file) 指定协议,主机,端口,文件路径来创建URL对象
- 常用方法
String getProtocol() 获取协议名称 String getHost() 获取主机名称 int getPort() 获取端口 String getPath() 获取路径名 String getFile() 获取文件名,会有参数信息 String getQuery() 获取查询部分 InputStream openStream() 打开URL连接,返回从连接读入的InputStream,获取服务端返回的数据。只返回应答体。 连上就相当于建立了Socket。 底层调用了openConnection.getInputStream(); URLConnection openConnection() 获得URL连接器对象。可以连到统一资源定位符指向的资源。
- 特点
- URI
- 特点
- Uniform Resource Identifier,统一资源标识符
- 包括URL和URN
- 这是一个URI:http://www.lightly.com/project/index.html#intro.可以参考。
- 特点
- URLConnection
- 特点
- Java中内置的可以解析具体协议的对象+Socket
- 只能获取该对象,不能进行实现。
- 常用方法
String getHeaderField(String) 用键获取消息头对应键的值 InputStream getInputStream() 获取输入流 OutputStream getOutputStream() 获取输出流
- 特点
反射
- 含义
- 运行状态中,对任意一个类(class文件)都能知道这个类的所有属性和方法
- 对任意一个对象,都能够调用它的任意一个方法和属性
- 这种动态获取的信息及动态调用对象的方法的功能叫做java的反射机制。
- 可以理解为对类的解剖。
- 应用场景
- 对指定名称的字节码文件加载并获取其内容进行调用
- 例如Tomcat中自己实现了Servlet,需要Tomcat运行自己的程序,只需要把自己的程序配置到web.xml,Tomcat就可以执行了。Tomcat就动态的获取了这个类的信息。这里用了反射。
- 用户不用创建对象,把自己的类放到配置文件里就可以运行了。
- 对指定名称的字节码文件加载并获取其内容进行调用
- 作用
- 大大提高了程序扩展性
- 小知识
- 学习框架需要知道框架是做什么的,需要知道配置文件怎么配置,需要知道常见对象怎么用即可。
- 也可以了解下框架的底层原理
- 反射所有的对象都在java.lang.reflect包下(反射包),用于解析类成员对象
- 学习框架需要知道框架是做什么的,需要知道配置文件怎么配置,需要知道常见对象怎么用即可。
- Class
- 含义
- 描述字节码文件的类,用来获取字节码文件的内容(名称,字段,构造函数,一般函数)
- 作用
- 用于获取字节码文件的所有内容。反射就是依靠该类完成的。
- 获取字节码的方式
- Object类的getClass方法。用的时候需要明确具体的类并创建对象
Worker worker = new Worker("name", 12); Class clazz = worker.getClass();
- 使用任意类的class静态属性
Class clazz = Worker.class;
-
通过给定类的字符串名称获取类,更具扩展性。使用Class类的方法。反射主要方式。知道类名字即可。
// 从当前根目录找,需要写类的全名 Class clazz = Class.forName("ml.lightly.bixiangdong.Worker"); Object obj = clazz.newInstance();
- Object类的getClass方法。用的时候需要明确具体的类并创建对象
-
早期和现在创建对象比较
-
早期:new的时候,根据类名称找到该类的字节码文件,加载到内存,创建该字节码文件对象,然后根据字节码文件对象创建该类对象
-
现在:forName()会找到类文件,并加载到内存,产生Class对象,然后newInstance(),创建该类对象
-
-
常用方法
Class forName(name) 获取某个类名的Class对象 Object newInstance() 创建获取的类的空参对象。没有空参构造函数抛出异常。 一般被反射的类都有空参构造函数。 Constructor<?>[] getDeclaredConstructors() 获取所有构造函数,包括私有 Constructor<?>[] getConstructors() 获取所有公共构造函数 Constructor<?> getConstructor(参数列表) 获取指定参数列表的公共构造函数。任何数据类型都可以被.class描述 getConstructor(String.class, int.class) Field[] getFields() 获取所有的公共字段。因为字段有字段修饰符,类型,字段的值,所以被封装为Field对象 Field getFields(String) 获取指定键的公共字段 Field[] getDeclaredFields() 获取所有字段,包括私有字段 Field getDeclaredField(String) 获取指定键的字段,包括私有字段 Method[] getMethods() 获取某类的所有公有方法,包括继承的 Method[] getDeclaredMethods() 只获取属于该类的所有方法,包括私有方法 Method getMethod(方法名, 参数列表) 获取某个方法,需要传入参数列表,没有参数列表传入null,有的话传入xx.class
-
小例子
Class clazz = Class.forName("ml.lightly.bixiangdong.Worker"); // 获取无参构造函数。如果没有无参构造函数,抛出异常 // Object obj = clazz.newInstance(); // 获取指定构造函数对象 Constructor constructor = clazz.getConstructor(String.class, int.class); // 根据指定构造函数对象创建实例 Worker worker = (Worker)constructor.newInstance("xia", 12); // 获取类的某个字段。该方法可以获取私有字段 Field ageField = clazz.getDeclaredField("age"); // 对于私有字段的访问需要使用这个方法取消权限限制 ageField.setAccessible(true); // 用于为某个对象设置该字段的值 ageField.set(worker, 25); // 获取某对象该字段的值 Object obj = ageField.get(worker); System.out.println(obj); // 获取某类的某个方法,因为有重载,所以需要传入参数列表。有参数传入xx.class,没有传入null Method setAgeMethod = clazz.getMethod("setAge", int.class); // 传入相应对象和参数列表,执行方法,并返回相应数据 setAgeMethod.invoke(worker, 23); // 获取某类的某个方法,因为有重载,所以需要传入参数列表。有参数传入xx.class,没有传入null Method method = clazz.getMethod("getAge", null); // 传入相应对象和参数列表,执行方法,并返回相应数据 Integer age = (Integer)method.invoke(worker, null); System.out.println(age);
-
电脑运行
-
对于接口的修改。适用于不修改代码,但是可以运行新的设备
// pci.properties pci0=SoundCard // 反射Demo import java.io.File; import java.io.FileInputStream; import java.util.Properties; public class ReflectDemo { public static void main(String[] args) throws Exception { MainBoard mb = new MainBoard(); // 主板运行 mb.run(); File config = new File("pci.properties"); Properties prop = new Properties(); // 读取配置文件 FileInputStream fis = new FileInputStream(config); prop.load(fis); for (int i = 0; i < prop.size(); i++) { // 获取配置文件中的设备 String pciName = "ml.lightly.bixiangdong." + prop.getProperty("pci" + i); // 获取该设备 Class clazz = Class.forName(pciName); // 因为设备有统一的接口,所以可以同该接口进行统一操作 PCI p = (PCI)clazz.newInstance(); // 运行该设备 mb.usePCI(p); } } } // 主板 class MainBoard { public void run() { System.out.println("main board run"); } // 用于接口操作 public void usePCI(PCI p) { if (p != null) { p.open(); p.close(); } } } // 统一规则 interface PCI{ public void open(); public void close(); } // 后期添加设备 class SoundCard implements PCI{ public void open() { System.out.println("SoundCard open"); } public void close() { System.out.println("SoundCard close"); } }
-
-
小知识
-
接口的使用
-
定义好规则,后期设备通过该规则扩充,前期设备只操作这个规则即可。但是如果代码写好,后期扩展也是需要再去修改新添加的设备。
public class Main { public static void main(String[] args) { MainBoard mb = new MainBoard(); // 后期需要修改新添加的设备 mb.usePCI(new SoundCard()); } } class MainBoard { public void run() { System.out.println("main board run"); } // 用于接口操作 public void usePCI(PCI p) { if (p != null) { p.open(); p.close(); } } } // 统一规则 interface PCI{ public void open(); public void close(); } // 后期添加设备 class SoundCard implements PCI{ public void open() { System.out.println("SoundCard open"); } public void close() { System.out.println("SoundCard close"); } }
-
-
- 含义
-
Constructor
- 特点
- 是获取的Class的构造器组成的对象
-
常用方法
Object newInstance(参数列表) 使用该构造器创建对象。传入相应的参数。
- 特点
-
Field
- 特点
- 是获取的Class的字段组成的对象
-
常用方法
setAccessible(boolean) 对私有字段的访问取消权限检查。暴力访问 Object get(Object) 获取传入对象的该字段的值。私有字段需要设置setAccessible(true) set(Object, Object) 给某个对象的该字段设置值。
- 特点
- Method
- 特点
- 是获取的Class的方法组成的对象
- 常用方法
Object invoke(Object, Object) 运行某方法,需要传入运行对象和参数列表。没有参数列表传null,有参数列表传相应的参数值。
- 特点
正则
- 作用
- 主要操作字符串
- 特点
- 通过特定符号体现
- 简化了,阅读性变差。
- 正则表达式常用符号
/** * 大括号控制次数,中括号控制数据范围,小括号用于符号封装(会对加了小括号的每个封装体从1进行编号,没有加括号就是第0组) * (.)\\1+ 在java里,括号封装了一个匹配模式,这是第一组模式,正则中用1来标识,但不是普通1,所以加转义符号来标识第一组,然后+就是用来对第一组进行操作的符号 * ^ 非,作用于后边的第一个控制范围 * - 用于数据范围 * && 与,用于交集 */ 字符 x 表示随意地一个字符 \\ 反斜杠 字符类 [abc] 某一位上只能是a或b或c [^abc] 某一位上是除了a或b或c的任意字符 [a-zA-Z] 所有字母 [a-z[A-Z]] 所有字母,可以多加一个中括号 [a-d&&c-e] 获取交集cd [a-z&&[^bc]] 获取a-d里边不包含bc的,等同[ad-z] 预定义字符类(已经定义好的字符含义) . 该位可以是任意字符 \d 表示数字,等同[0-9] \D 表示非数字,等同[^0-9] \s 表示空白字符,等同[ \t\n\x0b\f\r] \S 表示非空白字符,等同[^\s] \w 表示数字字母下划线,等同[0-9a-z_A-Z] \W 表示非数字字母下划线,等同[^\w] 边界匹配 ^ 放在最开始,表示行的开头 $ 放在最后,表示行的结尾 \b 表示单词边界,每个单词中间都有空格,空格就是边界 数量词 x? 随意字符跟一个问号,表示这个字符出现0或1次 x* 随意字符跟一个星号,表示这个字符出现0或多次 x+ 随意字符跟一个加号,表示这个字符出现1或多次 x{n} 随意字符跟一个大括号和次数,表示这个字符出现n次 x{n,} 随意字符跟一个大括号和次数和逗号,表示这个字符出现至少n次 x{n,m} 随意字符跟一个大括号和次数,逗号,次数,表示这个字符出现至少n次,至多出现m次 逻辑运算符 xy x字符后边跟一个y字符 x|y x或y (x) 作为捕获组,第一个左括号是第一组,第二个左括号是第二组,用数字标识(数字不是普通的数字字符,所以需要转义) 组0代表整个表达式 (x)\\1+ 第一组的匹配模式出现1次或多次 引用 \组号 用于匹配捕获组
- 正则表达式对字符串的常用操作
- 匹配
// 匹配手机号码 String tel = "18024726182"; // 第一位固定为1,第二位固定为[3586],剩下的都是[0-9]总长度为11, String regex = "1[3456789]\\d{9}";\\ [0-9]可以用\\d代表.java的\d是转移字符,这里需要普通符号,所以用\\d System.out.println(tel.matches(regex));
- 匹配手机号练习
-
切割
// 切割空格字符 String nameStr = "easul xiaoqiang zhaoliu"; String regex = "\\s+"; String[] results = nameStr.split(regex); for (String result :results) System.out.println(result); // 用.进行切割 nameStr = "easul.xiaoqiang.zhaoliu"; // 正则中.表示任意字符,这里如果要当作普通字符,需要进行转义 regex = "\\."; results = nameStr.split(regex); for (String result :results) System.out.println(result); // 切叠词 nameStr = "easultttttxiaoqiangnnnnnnnnzhaoliu"; regex = "(.)\\1+"; results = nameStr.split(regex); for (String result :results) System.out.println(result);
- 替换
// 将叠词替换为一个字符 String str = "asdffffffffasdfafdasdf"; String regex = "(.)\\1+"; str = str.replaceAll(regex, "#"); System.out.println(str); // 让第二个参数使用第一个参数的正则,使用$符号.$n就是匹配第几组 // 这里输出后第二个参数每次只获取第一个参数的一个字符 String str = "asdvvvgggggasdfafdasdf"; String regex = "(.)\\1+"; str = str.replaceAll(regex, "$1"); System.out.println(str); // 手机号隐藏中间四位.正则分组,用$n在替换的字符串中代表第几组 String tel = "15878783928"; String regex = "(\\d{3})\\d{4}(\\d{4})"; tel = tel.replaceAll(regex, "$1****$2"); System.out.println(tel);
- 获取
/** * 正则的获取常规操作.只能通过Pattern和Matcher获取 * * 将正则规则进行对象封装 * 通过正则对象的matcher方法与字符串关联,获取字符串操作匹配对象Matcher * 通过Matcher匹配器对象对字符串操作 */ // 获取三个字母组成的单词 String str = "da jia hao, ming tian bu fang jia"; String regex = "\\b[a-z]{3}\\b"; // 将正则规则进行对象封装 Pattern p = Pattern.compile(regex); // 通过正则对象的matcher方法与字符串关联,获取字符串操作匹配对象Matcher Matcher m = p.matcher(str); // 通过Matcher匹配器对象对字符串操作 while (m.find()) { System.out.println(m.group()); }
- 匹配
- 小例子
- 简单QQ号校验
/** * 需求:对QQ号校验 * 要求:长度为5-10位,0不能开头,只能是数字 * 思路: * 1 先判断长度,然后判断字符串是不是0开头,然后打散字符串,判断每一位是不是数字(这里也可以将字符串转为long型,用异常判断) * 2 正则 */ String qq = "123456789"; // 正则底层对应了代码 // 大括号控制次数,中括号放数据范围 String regex = "[1-9][0-9]{4,9}"; System.out.println(qq.matches(regex));
- 治疗口吃
// 治疗口吃 String str = "我我我...我我我我我我我要...要要要要要要要...要要要要要要要......学学学学学学学学..学学编编编编编....编编编编编编编编...编...程程程程程....程程程程程...程程程程程程.."; // 1 替换掉点 String regex = "\\.+"; str = str.replaceAll(regex, ""); // 2 替换掉叠词 regex = "(.)\\1+"; str = str.replaceAll(regex, "$1"); System.out.println(str);
- 对IP地址排序
// 因为是字符串排序,所以正常排序和字符串排序结果不同.所以可以想到每个都补成3位,然后按照字符串顺序排序. String ipStr = "192.168.10.34 127.0.0.1 3.3.3.3 105.70.11.55"; // 每一段都加两个0 String regex = "(\\d+)"; ipStr = ipStr.replaceAll(regex, "00$1"); // 每一段都保留三位数字 regex = "0*(\\d{3})"; ipStr = ipStr.replaceAll(regex, "$1"); regex = "\\s+"; String[] ipArr = ipStr.split(regex); TreeSet<String> ts = new TreeSet<String>(); for (String ip : ipArr) { ts.add(ip); } for (String ip : ts) { // 输出的时候,因为有0,至少要保留一位数字,所以用了\d+ System.out.println(ip.replaceAll("0*(\\d+)", "$1")); }
- 对邮件地址校验
// 可以用于关键字屏蔽. String mail = "asdf@qq.com.cn"; // 在这里如果只有一个组,可以不写组号 String regex = "[\\w]+@[\\w-&&[^_]]+(\\.[a-zA-Z]{2,3}){1,3}"; boolean b = mail.matches(regex); System.out.println(b);
- 爬虫:获取互联网中符合指定规则的数据.百度早期爬keywords,现在爬内容
// 爬取邮箱地址 // 读取源文件,本地文件 BufferedReader br = new BufferedReader(new FileReader("mail.html")); // 爬取网络内容 // URL url = new URL("网络地址"); BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream())); // 对读取的数据进行规则的匹配.从中获取符合规则的数据 String mailRegex = "\\w+@\\w+(\\.\\w+){1,2}"; List<String> list = new ArrayList<String>(); Pattern p = Pattern.compile(mailRegex); String line = null; while ((line = br.readLine()) != null) { Matcher m = p.matcher(line); while (m.find()) { // 将符合规则的数据存储到集合 list.add(m.group()); } } for (String mail : list) { System.out.println(mail); } }
- 简单QQ号校验
- String的正则方法
boolean matches(String regex) 检查字符串是否匹配该正则 String[] split(String regex) 将字符串按正则切割为字符串数组 String replaceAll(String regex, String replacement) 使用replacement替换符合正则的部分.第二个参数想要用第一个参数的正则内容,可以用$表示 replaceAll("(.)\\1+", "$1") $n就是匹配第n组 这里就可以在第二个参数获取第一个参数匹配的一个字符了.
- Pattern
- 含义
- 正则表达式的对象形式.
- 作用
- 用于正则表达式的封装,存在于java.util.regex包中.
- 常用方法
Pattern complie(regex) 将正则封装为正则对象 Matcher matcher(String) 将正则与要操作的字符串关联,返回匹配器对象
- 含义
- Matcher
- 含义
- 匹配器
- 作用
- 使用正则对象的匹配器对象操作字符串
- 匹配结果留在匹配器中
- 常用方法
Boolean find() 查找与该模式匹配的字符串的下一个子序列.查找一次,匹配一次. boolean matches() 判断正则与整个字符串是否存在匹配 String group() 返回匹配的子序列.需要先find才能获取 String group(int) 返回指定组匹配的子序列.需要先find才能获取 String replaceAll(String replacement) 对正则在字符串中匹配的地方修改为replacement,返回修改字符串 int start() 返回匹配的子序列的第一个字符下标 int end() 返回匹配的子序列的最后一个字符的后一个下标
- 含义