网络编程
可以让设备中的程序与网络上其他设备中的程序进行数据交互的技术(实现网络通信)。
基本的通信架构
基本的通信架构有2种形式:
CS架构(Client客户端/Server服务端 )
BS架构(Browser浏览器/Server服务端)。
无论是CS架构,还是BS架构的软件都必须依赖网络编程!
java.net.*包下提供了网络编程的解决方案!
网络编程三要素
IP(设备在网络中的地址,是设备 在网络中的唯一标识 )
IP地址:
IP(Internet Protocol):全称”互联网协议地址 ”,是分配给上网设备的唯一标识 。
目前,被广泛采用的IP地址形式有两种:IPv4、IPv6 。
IPv4: 【只适合局域网使用】
IPv4是Internet Protocol version 4的缩写,它使用32位地址,通常以点分十进制表示 。
IPv6:
IPv6是Internet Protocol version 6的缩写,它使用128位地址,号称可以为地球上的每一粒沙子编号。
IPv6分成8段,每段每四位编码成一个十六进制位表示, 每段之间用冒号(:)分开,将这种方式称为冒分十六进制 。
IP域名(Domain Name)
例子:www.baidu.com或者www.jd.com。
DNS域名解析(Domain Name System)
是互联网中用于将域名转换为对应IP地址的分布式命名系统。它充当了互联网的“电话簿”,将易记的域名映射到数字化的IP地址,使得用户可以通过域名来访问网站和其他网络资源。
公网IP、内网IP:
内网IP:也叫局域网IP,是只能组织机构内部使用的IP地址;例如,192.168. 开头的就是常见的局域网地址,范围为192.168.0.0--192.168.255.255,专门为组织机构内部使用。
本机IP
127.0.0.1、localhost:代表本机IP,只会寻找当前程序所在的主机。
IP常用命令
InetAddress:代表IP地址。
InetAddress的常用方法
Java public static void main(String[] args) throws Exception { //目标:获取IP地址对象InetAddress //1.获取本机ip地址对象 InetAddress ipLocal = InetAddress.getLocalHost (); //获取本机的主机名 System.out .println("本机名:"+ipLocal.getHostName()); //获取本机ip地址 System.out .println("本机ip地址:"+ipLocal.getHostAddress()); //2.获取远程电脑百度服务器的IP地址对象 InetAddress ipBaidu = InetAddress.getByName ("www.baidu.com"); System.out .println("是否可以连通百度:" +ipBaidu.isReachable(5000)); System.out .println("百度服务器的主机名:"+ipBaidu.getHostName()); System.out .println("百度服务器的ip地址:"+ipBaidu.getHostAddress()); }
端口( 应用程序在设备中的唯一标识 )
用来标记标记正在计算机设备上运行的应用程序 ,被规定为一个 16 位的二进制,范围是 0~65535 。
端口分类
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
注册端口:1024~49151,分配给用户进程或某些应用程序。
动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则报错。
协议(连接和数据在网络中 传输的规则 )
通信协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
开放式网络互联标准:OSI网络参考模型
传输层的2个通信协议
UDP(User Datagram Protocol):用户数据报协议。(通信效率高)
不事先建立连接,数据按照包发,一包数据包含:自己的IP、端口、目的地IP、端口和数据(限制在64KB内 )等。
发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的 。
常见UDP协议的例子:视频直播、语音通话
TCP(Transmission Control Protocol) :传输控制协议。
TCP的最终目的:要保证在不可靠的信道上实现可靠的数据传输。
TCP主要有三个步骤实现可靠传输:三次握手建立连接 ,传输数据进行确认, 四次挥手断开连接 。
TCP三次握手四次挥手
可靠连接:确保通信的双方收发消息都是没问题的(全双工)
UDP通信
UDP通信实现
不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
Java提供了一个java.net.DatagramSocket 类来实现UDP通信。
DatagramSocket: 用于创建客户端、服务端
DatagramPacket:创建数据包
客户端实现步骤:
创建DatagramSocket对象(客户端对象)
创建DatagramPacket对象封装需要发送的数据(数据包对象)
使用DatagramSocket对象的send方法,传入DatagramPacket对象
释放资源
Java public class Client { public static void main(String[] args) throws Exception { //目标:使用udp协议发送数据,1发1收 //1.创建客户端发送对象DatagramSocket DatagramSocket socket = new DatagramSocket(); //2.创建数据包 byte[] bytes = "如有一味绝境,非历十方生死".getBytes(); DatagramPacket datagramPacket = new DatagramPacket( bytes,//发送的数据(字节数组) bytes.length,//发送的数据长度 InetAddress.getLocalHost (),//发送数据给目标服务器的iP地址对象(服务器的地址) 9999 //服务器接收数据程序的端口号 ); //3.发送数据 socket.send(datagramPacket); System.out .println("数据发送成功!!!!"); //4.释放资源 socket.close(); } }
服务端实现步骤:
创建DatagramSocket对象并指定端口(服务端对象)
创建DatagramPacket对象接收数据(数据包对象)
使用DatagramSocket对象的receive方法,传入DatagramPacket对象
释放资源
Java public class Server { public static void main(String[] args) throws Exception { //目标:创建服务器端接收数据 //1.创建服务器端接收数据对象 DatagramSocket socket = new DatagramSocket(9999); //2.创建数据包对象,用于封装接收到的数据 byte[] bytes = new byte[1024*64]; DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length); //3.阻塞等待接收数据 System.out .println("开始准备接收客户端的数据:"); socket.receive(datagramPacket);//阻塞等待接收数据,接收到的数据存储到bytes数组里面,有数据以后才往下走 //4.解析数据包,获取数据,并打印出来 int length = datagramPacket.getLength();//真正接收到数据的长度 String data = new String(bytes, 0, length); System.out .println("服务器接收到数据:" + data); //5.获取客户端ip地址和端口(想知道是谁发过来的) InetAddress ipClient = datagramPacket.getAddress();//得到客户端ip地址对象 String ip = ipClient.getHostAddress();//客户端ip地址 int port = datagramPacket.getPort();//客户端端口号 System.out .println("客户端:"+ip+":"+port); //6.释放资源 socket.close(); } }
多发多收
客户端可以反复发送数据
创建DatagramSocket对象(发送端对象
使用while死循环不断的接收用户的数据输入,如果用户输入的exit则退出程序
如果用户输入的不是exit, 把数据封装成DatagramPacket
使用DatagramSocket对象的send方法将数据包对象进行发送
释放资源
Java public class Client { public static void main(String[] args) throws Exception { //目标:使用udp协议发送数据,多发多收 //1.创建客户端发送对象DatagramSocket DatagramSocket socket = new DatagramSocket(); //创建输入输出对象Scanner Scanner sc = new Scanner(System.in ); while(true){ System.out .println("请输入要发送的消息:"); String msg = sc.nextLine(); if(msg.equals("exit")){ System.out .println("退出程序"); socket.close(); break; } //2.创建数据包 byte[] bytes = msg.getBytes(); DatagramPacket datagramPacket = new DatagramPacket( bytes,//发送的数据(字节数组) bytes.length,//发送的数据长度 InetAddress.getLocalHost (),//发送数据给目标服务器的iP地址对象(服务器的地址) 9999 //服务器接收数据程序的端口号 ); //3.发送数据 socket.send(datagramPacket); System.out .println("消息["+msg+"]数据发送成功!!!!"); } } }
接收端可以反复接收数据
创建DatagramSocket对象并指定端口(接收端对象)
创建DatagramPacket对象接收数据(数据包对象)
使用DatagramSocket对象的receive方法传入DatagramPacket对象
使用while死循环不断的进行第3步
Java public class Server { public static void main(String[] args) throws Exception { //目标:创建服务器端接收数据 //1.创建服务器端接收数据对象 DatagramSocket socket = new DatagramSocket(9999); //2.创建数据包对象,用于封装接收到的数据 byte[] bytes = new byte[1024*64]; DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length); //3.阻塞等待接收数据 System.out .println("开始准备接收客户端的数据:"); while(true){ socket.receive(datagramPacket);//阻塞等待接收数据,接收到的数据存储到bytes数组里面,有数据以后才往下走 //4.解析数据包,获取数据,并打印出来 int length = datagramPacket.getLength();//真正接收到数据的长度 String data = new String(bytes, 0, length); System.out .println("服务器接收到数据:" + data); //5.获取客户端ip地址和端口(想知道是谁发过来的) InetAddress ipClient = datagramPacket.getAddress();//得到客户端ip地址对象 String ip = ipClient.getHostAddress();//客户端ip地址 int port = datagramPacket.getPort();//客户端端口号 System.out .println("客户端:"+ip+":"+port); } //6.释放资源 //socket.close(); 不停接收不用关闭服务器端 } }
TCP通信
TCP通信的实现
通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。
Java提供了一个java.net.Socket 类来实现TCP通信。
客户端开发
客户端程序就是通过java.net包下的Socket类 来实现的。
客户端开发实现步骤:
创建客户端的Socket对象,请求与服务端的连接。
使用socket对象调用getOutputStream()方法得到字节输出流。
使用字节输出流完成数据的发送。
释放资源:关闭socket管道。
Java public class Client { public static void main(String[] args) throws Exception { // 目标:使用tcp协议Socket发送数据,多发多收 // 1.创建客户端发送对象 Socket socket = new Socket("localhost", 8181); // 2.获取发送数据的字节输出流 OutputStream outputStream = socket.getOutputStream(); // 3.将字节输出流包装成特殊数据流 DataOutputStream out = new DataOutputStream(outputStream); // 4.写出数据给服务器端 out.writeInt(100); out.writeUTF("如有一味绝境,非历十方生死"); System.out .println("数据发送成功"); // 5.释放资源 out.close();// 高级流调用关闭,里面会自动调用低级流的关闭 socket.close(); } }
服务端开发
服务端是通过java.net包下的ServerSocket 类来实现的。
服务器端实现步骤:
创建ServerSocket对象,注册服务端端口。
调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
释放资源:关闭socket管道
Java public class Server { public static void main(String[] args) throws Exception { // 目标:使用TCP协议创建服务器端ServerSocket对象接收数据 // 注意:在TCP协议通信中,一定要先运行服务器端,在运行客户端 // 1.创建网络通信服务器端SeverSocket对象,指定监听的端口 ServerSocket serverSocket = new ServerSocket(8181); // 2.接收客户端请求得到客户端Socket System.out .println("开始接收数据:"); Socket socket = serverSocket.accept();// 会阻塞等待客户端连接 // 打印客户端谁链接进来 System.out .println("客户端:" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + ",连接进来"); // 3.读取客户端原始字节输入流 InputStream inputStream = socket.getInputStream(); // 4.将字节输入流转换为特殊数据输入流 DataInputStream in = new DataInputStream(inputStream); // 5.读取数据 int number = in.readInt();// 这里会阻塞等待传递过来的数据 String content = in.readUTF();// 这里会阻塞等待传递过来的数据 System.out .println("接收到的数据:" + number + "," + content); // 6.释放资源 in.close(); serverSocket.close(); } }
拓展:查看端口号是否被占用
多发多收
服务端也使用死循环,控制服务端程序收完消息后,继续去接收下一个消息。
Java public class Client { public static void main(String[] args) throws Exception { // 目标:使用tcp协议Socket发送数据,多发多收 // 1.创建客户端发送对象 Socket socket = new Socket("localhost", 8181); // 2.获取发送数据的字节输出流 OutputStream outputStream = socket.getOutputStream(); // 3.将字节输出流包装成特殊数据流 DataOutputStream out = new DataOutputStream(outputStream); // 4.写出数据给服务器端 Scanner sc = new Scanner(System.in ); while (true) { System.out .println("请输入要发送的数据:"); String msg = sc.nextLine(); if (msg.equals("exit")){ System.out .println("退出循环"); out.close(); socket.close(); } out.writeUTF(msg); System.out .println("消息【"+msg+"】数据发送成功!!!!"); } } }
Java public class Server { public static void main(String[] args) throws Exception { // 目标:使用TCP协议创建服务器端ServerSocket对象接收数据 // 注意:在TCP协议通信中,一定要先运行服务器端,在运行客户端 // 1.创建网络通信服务器端SeverSocket对象,指定监听的端口 ServerSocket serverSocket = new ServerSocket(8181); // 2.接收客户端请求得到客户端Socket System.out .println("开始接收数据:"); Socket socket = serverSocket.accept();// 会阻塞等待客户端连接 // 打印客户端谁链接进来 System.out .println("客户端:" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort() + ",连接进来"); // 3.读取客户端原始字节输入流 InputStream inputStream = socket.getInputStream(); // 4.将字节输入流转换为特殊数据输入流 DataInputStream in = new DataInputStream(inputStream); // 5.读取数据 while (true) { String content = in.readUTF();// 这里会阻塞等待传递过来的数据 System.out .println("接收到的数据:"+ content); } } }
同时接收多个客户端的消息
TCP通信-支持与多个客户端同时通信
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
Java public class Client { public static void main(String[] args) throws Exception { //目标:使用tcp协议Socket发送数据,1个用户多发多收 //1.创建客户端发送对象 Socket socket = new Socket("localhost", 9090); //2.获取发送数据的字节输出流 OutputStream outputStream = socket.getOutputStream(); //3.将字节输出流包装成特殊数据流 DataOutputStream out = new DataOutputStream(outputStream); //4.写出数据给服务器端 Scanner sc = new Scanner(System.in ); while(true){ System.out .println("请输入您发送的消息:"); String msg = sc.nextLine(); if(msg.equals("exit")){ System.out .println("退出程序"); //5.释放资源 out.close();//高级流调用关闭,里面会自动调用低级流的关闭 socket.close(); } out.writeUTF(msg); System.out .println("数据["+msg+"]发送成功"); } } }
Java public class ServerReaderRunnable implements Runnable{ private Socket socket;//接收到的客户端通信对象 public ServerReaderRunnable(Socket socket) { this.socket = socket; } @Override public void run() { try { //3.读取客户端原始字节输入流 InputStream inputStream = socket.getInputStream(); //4.将字节输入流转换为特殊数据输入流 DataInputStream in = new DataInputStream(inputStream); //5.读取数据 while(true){ String content = in.readUTF();//这里会阻塞等待传递过来的数据 System.out .println("接收客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort()+"的数据::"+content); } } catch (IOException e) { System.out .println("客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort()+",下线了"); } } }
Java public class Server { public static void main(String[] args) throws Exception { //1.创建线程池 ExecutorService pool = new ThreadPoolExecutor( 10,// 参数1:核心线程数,相当于正式工,稳定常用的线程。核心线程创建后不会被销毁 20,// 参数2:最大线程数,最大线索数-核心线程数=临时线程数,临时线程看成临时工,当不忙的时候会销毁临时线程 //什么时候使用临时线程?答:核心线程没有空闲,工作队列也满了,才会使用临时线程 10,// 参数3:临时线程存活时间,临时线程空闲了多少秒/毫秒就销毁 TimeUnit.SECONDS ,// 参数4:临时线程存活时间的单位 new ArrayBlockingQueue<>(10),//参数5:设置工作队列,核心线程忙不过来就会存入工作对象 Executors.defaultThreadFactory (),//参数6:设置线程工厂,创建核心线程或临时线程 new ThreadPoolExecutor.AbortPolicy()//参数7:设置拒绝策略,当临时线程也忙不过来,会拒绝任务抛出异常 ); //目标:使用TCP协议创建服务器端ServerSocket对象接收数据 //1.创建网络通信服务器端SeverSocket对象,指定监听的端口 ServerSocket serverSocket = new ServerSocket(9090); //2.接收客户端请求得到客户端Socket System.out .println("开始接收数据;"); while(true){ Socket socket = serverSocket.accept();//会阻塞等待客户端连接 System.out .println("有人上线了~~~"); //交给线程池使用每个线程处理一个客户 pool.execute(new ServerReaderRunnable(socket)); } } }
其他应用:B/S架构的原理
注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据。
HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
拓展:模拟Tomcat服务器端响应数据给浏览器
Java public class Server { public static void main(String[] args) throws Exception { //1.创建线程池 ExecutorService pool = new ThreadPoolExecutor( 100,// 参数1:核心线程数,相当于正式工,稳定常用的线程。核心线程创建后不会被销毁 200,// 参数2:最大线程数,最大线索数-核心线程数=临时线程数,临时线程看成临时工,当不忙的时候会销毁临时线程 //什么时候使用临时线程?答:核心线程没有空闲,工作队列也满了,才会使用临时线程 10,// 参数3:临时线程存活时间,临时线程空闲了多少秒/毫秒就销毁 TimeUnit.SECONDS ,// 参数4:临时线程存活时间的单位 new ArrayBlockingQueue<>(1000),//参数5:设置工作队列,核心线程忙不过来就会存入工作对象 Executors.defaultThreadFactory (),//参数6:设置线程工厂,创建核心线程或临时线程 new ThreadPoolExecutor.AbortPolicy()//参数7:设置拒绝策略,当临时线程也忙不过来,会拒绝任务抛出异常 ); //目标:使用TCP协议创建服务器端ServerSocket对象接收数据 //1.创建网络通信服务器端SeverSocket对象,指定监听的端口 ServerSocket serverSocket = new ServerSocket(80 ); //2.接收客户端请求得到客户端Socket System.out .println("开始接收数据;"); while(true){ Socket socket = serverSocket.accept();//会阻塞等待客户端连接 System.out .println("有人上线了~~~"); //交给线程池使用每个线程处理一个客户 pool.execute(new ServerReaderRunnable(socket)); } } }
Java public class ServerReaderRunnable implements Runnable{ private Socket socket;//接收到的客户端通信对象 public ServerReaderRunnable(Socket socket) { this.socket = socket; } @Override public void run() { try { //获取数据数据给浏览器的输出字节流 OutputStream outputStream = socket.getOutputStream(); //将字节流包装成打印流 PrintWriter PrintWriter ps = new PrintWriter(outputStream); //打印输出(按照http协议响应数据格式打印) ps.println("HTTP/1.1 200 OK"); //响应行 //响应头 ps.println("Content-Type:text/html;charset=utf-8"); ps.println(); // 必须空一行。 //响应体:输出正文,给网页展现的数据 ps.println("<span style='color:red;font-size:120px;'>如有一味绝境,非历十方生死</span>"); ps.close(); socket.close(); } catch (Exception e) { System.out .println("客户端" + socket.getInetAddress().getHostAddress() + ":" + socket.getPort()+",下线了"); } } }