饿汉式:当加载类时,就创建实例,优点:没有线程安全的问题,缺点是增加项目的启动时间
懒汉式:当需要这个实例时(调用getInstance方法时),才会创建实例,缺点有线程安全问题,解决办法是加锁,延迟式
public class S2 { //构造器私有 private S2() {} //静态的实例声明 private static S2 s2; //公共的静态获取实例的方法 //double check 双重检测 方式一 //方式二 好处:写法简单 缺点:同步范围太大了,粒度太粗,效率就低了 public static synchronized S2 getInstance() { //判断 if(s2 == null) {//线程一和线程二都先后进入了这个判断 try { Thread.sleep(10);//线程一进来 休眠10毫秒,线程二休眠了10毫秒,线程一醒来,跑到22行new了一个实例出去,线程二醒来,跑到22行又new了一个实例出去 } catch (InterruptedException e) { e.printStackTrace(); } s2 = new S2(); } return s2; } } public class Demo { public static void main(String[] args) { new Thread(()->{ for(int i = 0;i <20;i++) { S2 s2 = S2.getInstance(); System.out.println(s2); } },"线程一").start(); new Thread(()->{ for(int i = 0;i <20;i++) { S2 s2 = S2.getInstance(); System.out.println(s2); } },"线程二").start(); } }网络
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
网络通信协议有很多种,常见的网络通信协议有:TCP/IP协议、IPX/SPX协议、NetBEUI协议等。目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protoal传输控制协议/英特网互联协议),具有很强的灵活性,支持任意规模的网络,几乎可连接所有服务器和工作站。在使用TCP/IP协议时需要进行复杂的设置,每个结点至少需要一个“IP地址”、一个“子网掩码”、一个“默认网关”、一个“主机名”。
在进行数据传输时,要求发送的数据与收到的数据完全一样,这时,就需要在原有的数据上添加很多信息,以保证数据在传输过程中数据格式完全一致。TCP/IP协议的层次结构比较简单,共分为四层,:网络接口层(又称链路层)、网络层(又称互联层)、传输层和应用层,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
计算机网络把全球各地的计算机通过外部设备连接起来,形成一个网络系统,这样就可以计算机之间互相传递信息,共享资源。
目的直接或间接地通过网络协议与其它计算机进行交互,通信。
有两个主要问题1 怎么找到计算机
2 找到了计算机怎么传输数据
通信两要素IP和端口号
IP InetAddress
方法:
getByName():获得确定主机名称的IP地址
getLocalHost():返回本地主机的地址
isReachable():测试该地址是否可达
toString():将此IP地址转换为 String
public class InetAddressDemo { @Test public void fun1() { System.out.println("hello"); } @Test public void fun2() throws IOException { //IP对象 InetAddress address = InetAddress.getByName("www.tmall.com"); //address是个字符串 www.tmall.com/121.9.203.233 System.out.println("address:" + address); //String.split("/") 分割返回一个数组 String[] str = address.toString().split("/"); System.out.println("域名:" + str[0]); System.out.println("ip:" + str[1]); //测试天猫的ip能否在20毫秒内到达 boolean reachable = address.isReachable(20); System.out.println("20毫秒内能否到达天猫:" + reachable); //查看本机的ip InetAddress ip2 = InetAddress.getLocalHost(); //打印地址 System.out.println("本机地址:" + ip2); //测试局域网 InetAddress ip3 = InetAddress.getByName("192.168.54.17"); System.out.println(ip3.isReachable(50)); } } 程序运行结果: hello address:www.tmall.com/121.9.203.233 域名:www.tmall.com ip:121.9.203.233 20毫秒内能否到达天猫:true 本机地址:DESKTOP-R0OTUI9/192.168.54.2 false端口号
0-65535 0-1024 :尽量不要使用 ,是一些特定的应用在使用
我们一般用1024后面的端口
常见的端口号:21-ftp,文件系统,比如飞秋
8080,默认端口,是web的默认的端口,可以设置省略
https使用的是443端口,加密端口
Junit单元测试 注意:语法 public void 方法名() {} //方法名不能有形参
public class InetAddressDemo { @Test public void fun1() { System.out.println("hello"); } @Test public void fun2() throws IOException { //IP对象 InetAddress address = InetAddress.getByName("www.tmall.com"); //address是个字符串 www.tmall.com/121.9.203.233 System.out.println("address:" + address); //String.split("/") 分割返回一个数组 String[] str = address.toString().split("/"); System.out.println("域名:" + str[0]); System.out.println("ip:" + str[1]); //测试天猫的ip能否在20毫秒内到达 boolean reachable = address.isReachable(20); System.out.println("20毫秒内能否到达天猫:" + reachable); //查看本机的ip InetAddress ip2 = InetAddress.getLocalHost(); //打印地址 System.out.println("本机地址:" + ip2); } }规则 OSI通信模型
过于理想化,没有进行推广
事实上的国际标准
网络通信协议 UDP编程UDP是无连接通信协议,UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。
即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
生活场景:微信语音,视频通话时使用UDP传输,通话过程中有可能是因为信号问题,有些话没有听清楚,有些画面没有看到。
udp:
不可靠传输
传输数据量小
把数据,ip,目标数据打包
发送也不管服务端是否收到
DataGramSocket构造器:DataGramSocket(int port) 服务端套接字
DataGramSocket(int port,InetAddress) 客户端套接字
方法:send(GramPacket ) 发送数据报包
receive(DataGramPaket p) :接收数据报包
服务端
public class Server { public static void main(String[] args) throws IOException { //创建一个绑定端口的构造器作为服务端 DatagramSocket ds = new DatagramSocket(8888); //创建一个缓冲数组 byte[] b = new byte[1024]; //通过报文对象接收数据,填充 DatagramPacket dp = new DatagramPacket(b, b.length); ds.receive(dp); //组装字符串,以便输出内容 String str = new String(b,0,dp.getLength()); System.out.println("接收的内容为:" + str); //关闭服务 ds.close(); } }
客户端
public class Client { public static void main(String[] args) throws IOException { //创建一个无参的DatagramSocket的构造器作为客户端 DatagramSocket ds = new DatagramSocket(); //准备发送到服务端的字符串 String str = "helo,hello,我是客户端"; //报文对象 DatagramPacket dp = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("192.168.54.2"), 8888); //使用套接字发送报文 ds.send(dp); //关闭客户端 ds.close(); } }
课堂练习:
1 服务端和客户端都可以连续不断接收和发送内容,提示,加个while循环
public class Server2 { public static void main(String[] args) throws IOException { //加上while循环 while(true) { //创建一个绑定端口的构造器作为服务端 DatagramSocket ds = new DatagramSocket(8888); //创建一个缓冲数组 byte[] b = new byte[1024]; //通过报文对象接收数据,填充 DatagramPacket dp = new DatagramPacket(b, b.length); ds.receive(dp); System.out.println("*******************************"); //客户端ip InetAddress ip = dp.getAddress(); System.out.println("客户端ip:" + ip.toString().split("/")[1]); //组装字符串,以便输出内容 String str = new String(b,0,dp.getLength()); System.out.println("接收的内容为:" + str); System.out.println("*******************************"); //关闭服务 ds.close(); } } }
2发送内容要从控制台输入,提示:利用Scanner对象
public class Client2 { public static void main(String[] args) throws IOException { //创建scanner对象 Scanner sc = new Scanner(System.in); //while循环 while(true) { //创建一个无参的DatagramSocket的构造器作为客户端 DatagramSocket ds = new DatagramSocket(); //准备发送到服务端的字符串 String str = sc.next();//包含空格 //报文对象 DatagramPacket dp = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("192.168.54.2"), 8888); //使用套接字发送报文 ds.send(dp); //关闭客户端 ds.close(); } } }
3当客户端发出字符串"bye" ,结束发送,不再循环
public class Client2 { public static void main(String[] args) throws IOException { //创建scanner对象 Scanner sc = new Scanner(System.in); //while循环 while(true) { //创建一个无参的DatagramSocket的构造器作为客户端 DatagramSocket ds = new DatagramSocket(); //准备发送到服务端的字符串 String str = sc.next();//包含空格 //报文对象 DatagramPacket dp = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("192.168.54.2"), 8888); //使用套接字发送报文 ds.send(dp); //关闭客户端 ds.close(); //判断字符串如果输入的是 "bye",结束客户端程序 if(str.equals("bye")) { break; } } } }TCP编程
TCP是可靠的传输协议,IP是网络互联协议,实际上是一组协议。
tcp协议:
先要经过三次握手,形成数据传输通道
进行通信有两个进程,一个是服务端,另一个是客户端
可传输大量数据
传输完毕后要释放资源
三次握手(笔试题)第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
四次挥手服务端:
方法:
accept:监听并接收客户端的套接字
构造器:
ServerSocket(int port):创建服务端套接字
步骤:
1 创建ServerSocket服务端套接字,监听端口
2 通过调用accept:获得客户端
3 获取客户端的输入流,以便获得客户端发送的信息,输出流对象,给客户端发送响应信息
4关闭socket
public class Server { public static void main(String[] args) throws IOException { //1 创建ServerSocket服务端套接字,监听端口 ServerSocket ss = new ServerSocket(8888); //2 通过调用accept:获得客户端 Socket s = ss.accept(); //3 获取客户端的输入流,以便获得客户端发送的信息 InputStream is = s.getInputStream(); //定义缓冲区 byte[] b = new byte[1024]; int hasRead; //循环输出 while((hasRead = is.read(b)) > 0) { //组成字符串 String str = new String(b,0,hasRead); System.out.println(str); } //4关闭资源 is.close(); s.close(); ss.close(); } }
客户端:
构造器:
Socket(String ip,int port):客户端套接字
方法:
getInputStream:获得套接字输入流
getOutputStream:获得套接字输出流
步骤:
1 创建客户端的套接字对象,传入ip和端口
2 获得输出流对象,向服务器写数据,获得输入流对象,以便获得服务端的响应
3 关闭socket
public class Client { public static void main(String[] args) throws IOException { //1 创建客户端的套接字对象,传入ip和端口 Socket s = new Socket(InetAddress.getByName("192.168.54.2"), 8888); //2 获得输出流对象,向服务器写数据 OutputStream os = s.getOutputStream(); //准备内容 String str = "hello,我是客户端"; //往服务端写 os.write(str.getBytes()); //3 关闭socket os.close(); s.close(); } }
练习:
加上循环,可以不断服务端接收,客户端发送内容
注意:同一个端口在同一个机器是不能启动两次或以上的,不然会报
解决方案: