目录
设计模式
设计模式分为三类:
创建型:创建对象;结构型:对象的组成;行为型:对象的功能
主要说创建型:
1、单例模式:
指的是在程序的运行过程中,内存中只允许一个对象存在
如何保证内存中只允许一个对象存在:
1、将构造方法私有化;2、在类的成员变量位置上创建一个对象;3、提供一个静态方法让外界能够获取到这个对象
饿汉式:随着类的加载,对象就创建好了
懒汉式(延迟加载):用的时候再去创建对象,但内存中始终只有一个对象
懒汉式容易出现线程安全问题
饿汉式举例:
public class HungryMan {
private static HungryMan hm=new HungryMan();
private HungryMan(){}
public static HungryMan getHungry(){
return hm;
}
}
测试类:
public class HungryManDemo {
public static void main(String[] args) {
HungryMan man = HungryMan.getHungry();
HungryMan man1 = HungryMan.getHungry();
System.out.println(man);
System.out.println(man1);
System.out.println(man==man1);
}
}
输出结果:
通过结果发现测试类中两次创建的对象都是同一个对象
懒汉式举例:
public class LazyMan {
private static LazyMan lazyMan = null;
private LazyMan(){
}
public synchronized static LazyMan getLazy(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
测试类:
public class LazyManDemo {
public static void main(String[] args) {
LazyMan lazy1 = LazyMan.getLazy();
LazyMan lazy2 = LazyMan.getLazy();
System.out.println(lazy1);
System.out.println(lazy2);
System.out.println(lazy1==lazy2);
}
}
输出结果:
2、简单工厂模式
简单工厂模式就是把对象的创建放在一个工厂类中,再通过类名的方式去调用工厂类从而创建对象
代码举例:
动物抽象类:
public abstract class Animal {
//吃
public abstract void eat();
}
两种动物类:猫类
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("🐱吃🐟");
}
}
狗类:
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("🐕吃🥩");
}
}
动物工厂类:
public class AnimalFactory{
// 构造方法私有化,让外界无法创建该类的实例
private AnimalFactory(){}
// 一般做法就是定义两个静态方法返回对象
// 使用多态的形式去创建所要调用的方法:
public static Animal createAnimal(String type){
if("Cat".equals(type)){
return new Cat;
}else if("Dog".equals(type)){
return new Dog;
}else{
System.out.println("该工厂不支持创建"+type+"类型的动物");
return null;
}
}
}
测试类:
public class AnimalDemo {
public static void main(String[] args) {
Animal dog = AnimalFactory.createAnimal("Dog");
Animal cat = AnimalFactory.createAnimal("Cat");
if(dog!=null){
dog.eat();
}
if(cat!=null){
cat.eat();
}
// 创建🐖对象
Animal pig = AnimalFactory.createAnimal("Pig");
if(pig!=null){
pig.eat();
}
}
}
输出结果:
简单工厂模式的优缺点:
优点:客户端不需要负责创建对象,从而明确了各个类的职责
缺点:定义的静态工厂类负责对象的创建,如果有新的对象增加,获取某些对象的方式不同,就需要不断的去修改工厂类,不利于后期维护。
3、工厂方法模式
工厂方法模式就是在简单工厂模式的基础上将工厂类定义为一个接口,当需要获取新的对象时,只需要加入一个新的对象类实现工厂类接口重写其中的方法即可,不需要修改原有的代码。
代码举例:
动物抽象类:
public abstract class Animal {
public abstract void eat();
}
工厂接口:
public interface Factory {
// 定义创建动物的抽象方法,未来只需要重写该方法即可
public abstract Animal createAnimal();
}
三种动物类:猫类:
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("🐱吃🐟");
}
}
猫工厂类:
public class CatFactory implements Factory{
@Override
public Animal createAnimal() {
return new Cat();
}
}
狗类:
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("🐕吃🥩");
}
}
狗工厂类:
public class DogFactory implements Factory{
@Override
public Animal createAnimal() {
return new Dog();
}
}
后来需要加入的猪类:
public class Pig extends Animal{
@Override
public void eat() {
System.out.println("🐖吃饲料");
}
}
加入一个猪工厂类:
public class PigFactory implements Factory{
@Override
public Animal createAnimal() {
return new Pig();
}
}
测试类:
public class AnimalDemo {
public static void main(String[] args) {
//我想要只狗
DogFactory dogFactory = new DogFactory();
Animal dog = dogFactory.createAnimal();
dog.eat();
//我想要只猫
CatFactory catFactory = new CatFactory();
Animal cat = catFactory.createAnimal();
cat.eat();
//我想要只猪
PigFactory pigFactory = new PigFactory();
Animal pig = pigFactory.createAnimal();
pig.eat();
}
}
输出结果:
工厂方法模式的优缺点:
优点:客户端不需要负责对象的创建,从而明确各个类的职责;如果有新的对象增加,只需要加入一个具体的类和具体的工厂类即可;不影响其他代码,后期容易维护,增强了系统的扩展性。
缺点:需要额外的代码,增加了工作量
网络编程概述:
计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
网络编程
就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。
网络模型一般是指:
OSI(Open System Interconnection开放系统互连)参考模型
TCP/IP参考模型(HTTP/HTTPS)
网络参考模型图
网络编程三要素
IP地址
网络中计算机的唯一标识
IP地址的组成:网络号段 + 主机号段
IP地址的分类:
A类:第一号段定义为网络号段 + 后三个号段定义为主机号段,可以连接256*256*255台主机
B类:前两个号段定义为网络号段 + 后两个号段定义为主机号段,可以连接256*255台主机
C类:前三个号段定义为网络号段 + 后一个号段定义为主机号段,可以连接255台主机,局域网
D类:224.0.0.1---239.255.255.254
E类 : 240.0.0.1---247.255.255.254
特殊的IP地址:
1、localhost = 本机 = 127.0.0.1
127.0.0.1:回环地址,可以用于测试本机的网络是否有问题,DOS命令为:ping 127.0.0.1
2、广播地址:x.x.x.255
3、网络地址:x.x.x.0
三个DOS命令:
ipconfig Windows: 查看ip地址
Linux:
centOS6:ifconfig
centOS7:ip addr
ping:测试网络是否联通 :ping + IP地址/host名
为了方便对IP地址的获取和操作,Java提供了一个类InetAddress 供使用
InetAddress中的方法:
public static InetAddress getByName(String host) throws UnknownHostException
确定主机名称的IP地址。
public String getHostName():获取此IP地址的主机名
public String getHostAddress():返回文本显示中的IP地址字符串
代码举例:
import java.net.InetAddress;
public class IAdemo {
public static void main(String[] args) throws Exception{
// 获取主机名称的IP地址
InetAddress name = InetAddress.getByName("192.168.7.37");
System.out.println(name);
// 获取此IP地址的主机名
String hostName = name.getHostName();
System.out.println(hostName);
// 返回文本显示的IP地址字符串
String hostAddress = name.getHostAddress();
System.out.println(hostAddress);
}
}
输出结果:
端口号
物理端口:网卡口
逻辑端口:我们指的就是逻辑端口
每个网络程序都会至少有一个逻辑端口
用于标识进程的逻辑地址,不同进程的标识
有效端口:0~65535,其中0~1024系统使用或保留端口。
通过netstat -ano可以查看端口号
传输协议
一般传输协议有两种:UDP协议和TCP协议
UDP协议:
将数据源和目的封装成数据包中,不需要建立连接;每个数据报包的大小在限制在64k;因无连接,是不可靠协议;不需要建立连接,速度快
UDP协议发送数据:
1、创建发送端的Socket对象
DatagramSocket与DatagramPacket
2、创建数据,并把数据打包
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
3、调用Socket对象的发送方法将数据发送出去
void send(DatagramPacket p):从此套接字发送数据报包。
4、释放资源,关闭Socket
代码举例:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class SendDemo {
public static void main(String[] args) throws Exception {
//1、创建发送端Socket对象
DatagramSocket socket = new DatagramSocket();
//创建数据并打包
//从键盘录入
// Scanner sc = new Scanner(System.in);
// 改进从键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
// 判断,如果输入886就退出输入
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
// 将数据打包
DatagramPacket packet = new DatagramPacket(line.getBytes(), line.getBytes().length, InetAddress.getByName("192.168.7.37"), 1234);
// 发送数据
socket.send(packet);
}
br.close();
}
}
输出结果:
UDP协议接收数据:
1、创建接收端Socket对象
2、创建一个数据包(接收容器)
3、调用Socket对象的接收方法接收数据:receive方法
4、解析数据包,得到数据并显示在控制台上
5、释放资源,关闭Socket
代码举例:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveDemo {
public static void main(String[] args) throws Exception{
DatagramSocket socket = new DatagramSocket(1234);
while(true){
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
// 接收数据包
socket.receive(datagramPacket);
//解析数据包
String ip = datagramPacket.getAddress().getHostAddress();
String hostName = datagramPacket.getAddress().getHostName();
byte[] data = datagramPacket.getData();
int length = datagramPacket.getLength();
System.out.println();
String string = new String(data, 0, length);
System.out.println(ip+":"+hostName+"发送的数据为:"+string);
}
}
}
输出结果:
注意:接收端程序在绑定同一个端口的时候不能同时运行多个接收端
TCP协议
建立连接,形成传输数据的通道;在连接中进行大数据量传输;通过三次握手完成连接,是可靠协议;必须建立连接,效率会稍低
Socket套接字:
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
Socket原理机制: 通信的两端都有Socket。 网络通信其实就是Socket间的通信。 数据在两个Socket间通过IO传输。
TCP协议客户端代码实现:
1、创建客户端的Socket对象 这一步如果成功创建对象,就说明连接已经建立成功
Socket(InetAddress address, int port) :创建流套接字并将其连接到指定IP地址的指定端口号。
Socket(String host, int port) :创建流套接字并将其连接到指定主机上的指定端口号。
2、获取输出流对象,向服务器写数据
3、释放资源,关闭Socket服务
代码举例:
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class CLDemo {
public static void main(String[] args) throws Exception{
//客户端创建指定服务器对象
Socket socket = new Socket("192.168.7.37", 1234);
Scanner sc = new Scanner(System.in);
// 获取输出流对象
OutputStream os = socket.getOutputStream();
while(true){
String s = sc.next();
if("886".equals(s)){
break;
}
// 写入数据
os.write(s.getBytes());
}
socket.close();
}
}
TCP协议服务器端代码实现:
1、创建服务器端Socket对象(ServerSocket)
ServerSocket(int port) :创建绑定到指定端口的服务器套接字。
2、调用accept()方法,监听客户端的连接,返回一个对应客户端连接的Socket对象
3、获取通道中的输入流对象,读取客户端发送的数据,并显示在控制台上
4、释放资源,关闭Socket服务,但是一般服务器端需要一直提供服务,所以一般不关闭服务端
注意:要先启动服务器端,再启动客户端,否则会报连接被拒绝异常
代码举例:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SEDemo {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(1234);
// 创建监听对象监听通道
Socket a = ss.accept();
// 获取通道中的数据
while(true){
// 获取通道中的输入流对象
InputStream is = a.getInputStream();
byte[] bytes = new byte[1024];
int length = is.read(bytes);
String string = new String(bytes, 0,length);
String ip = a.getInetAddress().getHostAddress();
String hostName = a.getInetAddress().getHostName();
System.out.println(ip+":"+hostName+"发送的数据为:"+string);
}
}
}
输出结果:客户端:
服务器端:
改进操作:加入服务器端收到数据的反馈:
客户端:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class CLDemo {
public static void main(String[] args) throws Exception{
//客户端创建指定服务器对象
Socket socket = new Socket("192.168.7.37", 1234);
Scanner sc = new Scanner(System.in);
// 获取输出流对象
OutputStream os = socket.getOutputStream();
// 获取通道输入流对象,接收服务器的反馈
InputStream is = socket.getInputStream();
while(true){
String s = sc.next();
if("886".equals(s)){
break;
}
// 写入数据
os.write(s.getBytes());
// 读取服务器端的反馈
byte[] bytes = new byte[1024];
int length = is.read(bytes);
String s1 = new String(bytes, 0, length);
System.out.println("服务器端的反馈:"+s1);
}
socket.close();
}
}
服务端:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SEDemo {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(1234);
// 创建监听对象监听通道
Socket a = ss.accept();
// 获取通道中的输入流对象
// 获取通道中的输出流对象给出反馈
OutputStream os = a.getOutputStream();
// 获取通道中的数据
while(true){
InputStream is = a.getInputStream();
byte[] bytes = new byte[1024];
int length = is.read(bytes);
String string = new String(bytes, 0,length);
String ip = a.getInetAddress().getHostAddress();
String hostName = a.getInetAddress().getHostName();
System.out.println(ip+":"+hostName+"发送的数据为:"+string);
// 写入接收数据反馈
os.write("服务器已经收到数据".getBytes());
}
}
}
输出结果:
客户端:
服务器端:
但是一旦关闭客户端之后,服务端会报错: SocketException: Connection reset
这是由于在这里对异常的处理是抛出异常,只要把异常处理成一行输出语句即可。
再次改进:使用多线程实现多个客户端与服务端之间的通信
客户端:
import java.io.BufferedWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
/*
客户端
*/
public class CLDemo2 {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("192.168.7.37",1234);
OutputStream os = socket.getOutputStream();
// 改进为字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
Scanner sc = new Scanner(System.in);
while(true){
String s = sc.next();
if("886".equals(s)){
break;
}
bw.write(s);
bw.newLine();
bw.flush();
}
os.close();
}
}
服务端:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SEDemo2 {
public static void main(String[] args) throws Exception{
System.out.println("=======服务器启动========");
ServerSocket ss = new ServerSocket(1234);
// 监听的时候,对应一个客户端,返回一个Socket对象
// 定义循环不断接收客户的连接请求
while(true){
Socket socket = ss.accept();
new ServerThread(socket).start();
}
}
}
class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
// 改进为字符流
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
// 获取ip和主机名称
String ip = socket.getInetAddress().getHostAddress();
String hostName = socket.getInetAddress().getHostName();
String line;
while((line=br.readLine())!=null){
// 加入当前接收消息的时间
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(date);
System.out.println(s+":"+ip+"("+hostName+")发送的数据为:"+line);
}
} catch (IOException e) {
// 处理异常为输出语句
System.out.println(socket.getInetAddress().getHostName()+"已下线。。。");
}
}
}
输出结果:
客户端:
服务端: