网络编程(二十) 网络编程 :计算机与计算机之间通过网络进行数据传输。
Java中可以使用java.net包下的技术开发出常见的网络应用程序。
常见的软件架构 CS CS,Client/Server,客户端/服务器,在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。
通过客户端访问服务器。
适合定制专业化的办公类软件,例如:IDEA,网游
优点 :
快速响应:由于客户端和服务端的分工协作,能够迅速响应用户请求,提供更快的响应速度。
功能复杂:客户端可以在响应服务端的同时执行一些处理逻辑,提供更加完整的功能。
易于维护:由于各模块分工明确,每个模块可以独立开发,便于管理和维护。
缺点 :
成本高:客户端和服务端需要独立开发和管理,所需人力物力较多。
安全性差:由于客户端和服务端之间的通信频繁,增加了被攻击的风险。
BS BS,Brower/Server,浏览器/服务器,只需要一个浏览器,用户通过不同的网址,客户访问不同的服务器。
通过浏览器访问服务器。
适合移动互联网应用,可以在任何地方任何时间访问的系统。
优点 :
低成本:相比于CS架构,BS架构只需服务端部分进行专门的开发,降低了成本。
易于维护:应用逻辑全部集中在服务端,便于后台管理。
安全性高:由于所有操作都在服务器端进行,可以减少安全隐患,实现较高的防御能力。
缺点 :
响应速度慢:由于服务器承担了所有的计算和存储任务,文件的传输和页面的刷新等耗时的操作都需要等待服务器完成,这可能导致用户体验不佳。
功能不足:由于大多数处理逻辑都在服务器端完成,用户可能无法获得完整的用户体验。
网络编程三要素
IP :设备在网络中的地址,是唯一的标识
端口号 :应用程序在设备中唯一的标识
协议 :数据在网络中传输的规则,常见的协议有UDP,TCP,http,https,ftp
IP 全称:Internet Protocol,是互联网协议地址,也称IP地址,是分配给上网设备的数字标签,即上网设备在网络中的地址,是唯一的。
常见IP分类 :IPv4,IPv6
IPv4 全称:Internet Protocol Version 4,互联网通信协议第四版,采用32位地址长度,分为4组,每组8位二进制数。
可利用点分十进制表示法 来表示。
目前的主流方案,最多只有2^32次方个IP,目前已经用完,现在利用局域网IP解决IP不够的问题。
IPv4 的地址分类形式:
公网地址(万维网使用)和私有地址(局域网使用)
192.168.开头代表的就是私有地址,范围为192.168.0.0-192.168.255.255,专门为组织机构内部使用,以此节省IP。
特殊IP地址 :127.0.0.1,也可以是localhost,是回送地址也称本地回环地址,也称本机IP,永远只会寻找当前所在本机。
IPv6 全称:Internet Protocol Version 6,互联网通信协议第六版,采用128位地址长度,分为8组,每组16位二进制数。
可利用冒分十六进制表示法 。
最多有2^128次方个IP。
端口号 由两个字节表示的整数,取值范围:0-65535
其中0-1023之间的端口用于一些知名的网络服务或应用,我们自己使用1024以上的端口号就可以了。
注 :一个端口号只能被一个应用程序使用
协议 计算机网络中,连接和通信的规则被称为网络通信协议。
OSI参考模型 :世界互联协议标准,全球通信规范,单模型过于理想化,未在因特网上进行广泛传播
应用层
表示层
会话层
传输层
网路层
数据链路层
物理层
TCP/IP参考模型 :或TCP/IP协议,事实上的国际标准
应用层:HTTP,FTP,Telnet,DNS…
传输层:TCP,UDP…
网络互联层:IP,ICMP,ARP…
网络接入层:硬件设备0101010….
UDP协议 用户数据报协议,User Datagram Protocol
UDP是面向无连接 (不管是否连接成功)的通信协议。
速度快,有大小限制,一次最多发64K,数据不安全,易丢失数据。
TCP协议 传输控制协议,Transmission Control Protocol
TCP协议是面向连接 (确保连接成功)的通信协议。
速度慢,无大小限制,数据安全。
InetAddress类 此类表示互联网协议(IP)的地址,该类并未对外提供构造方法,需要通过静态方法getByName获取对象。
InetAddress 有两个子类:Inet4Address,Inet6Address
getByName方法 会判断是使用的IPv4还是IPv6。
若是IPv4则会创建Inet4Address对象进行返回。
若是IPv6则会创建Inet6Address对象进行返回。
常用方法 getByName 格式 :
static InetAddress getByName(String host)
说明 :确定主机名称的IP地址,主机名称可以是机器名称,也可以是IP地址。
getHostName 格式 :
String getHostName()
说明 :获取此IP地址的主机名
getHostAddress 格式 :
String getHostAddress()
说明 :返回文本显示中的IP地址字符串
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.net.InetAddress; import java.net.UnknownHostException; public class Test { public static void main(String[] args) throws UnknownHostException { InetAddress address1=InetAddress.getByName("192.168.19.1"); System.out.println(address1);//打印出 /IP地址 System.out.println(address1.getHostAddress());//打印出IP地址 System.out.println(address1.getHostName());//打印出设备名称 System.out.println(); InetAddress address2=InetAddress.getByName("DESKTOP-FAEF9UU"); System.out.println(address2);//打印出 设备名/IP地址 System.out.println(address2.getHostAddress());//打印出IP地址 System.out.println(address2.getHostName());//打印出设备名称 } }
UDP通信程序 发送数据 步骤 :
创建发送端的DatagramSocket对象
绑定端口,以后我们就是通过这个端口往外发送
空参:所有可用的端口中随机一个进行使用
有参:指定端口号进行使用
数据打包DatagramPacket
发送数据
释放资源
接收数据 步骤 :
创建接收端的DatagramSocket对象
接收打包好的数据
解析数据包
释放资源
范例1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 //必须先执行接收文件,再执行发送文件 //ReceiveMessageDemo.java import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class ReceiveMessageDemo { public static void main(String[] args) throws IOException { DatagramSocket ds=new DatagramSocket(10086); byte[] bytes=new byte[1024]; DatagramPacket dp=new DatagramPacket(bytes,bytes.length); ds.receive(dp); byte[] data=dp.getData(); int length=dp.getLength(); InetAddress address =dp.getAddress(); int port=dp.getPort(); System.out.println("接收到数据"+new String(data,0,length)); System.out.println("该数据是从"+address+"电脑中端口为"+port+"的位置发出"); ds.close(); } } /* 接收到数据你好 该数据是从/127.0.0.1电脑中端口为52727的位置发出 */ //SendMessageDemo.java import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class SendMessageDemo { public static void main(String[] args) throws IOException { //1.创建DatagramSocket对象 DatagramSocket ds=new DatagramSocket(); //2.打包数据 InetAddress address=InetAddress.getByName("127.0.0.1"); String s="你好"; byte[] bytes=s.getBytes(); int port=10086; DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port); //3.发送数据 ds.send(dp); //4.释放数据 ds.close(); } }
范例2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 //按照下面的要求实现程序 //UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束 //UDP接收数据:因为接收端不知道发送端什么时候停止,故采用死循环 //SendMessageDemo.java import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner; public class SendMessageDemo { public static void main(String[] args) throws IOException { DatagramSocket ds=new DatagramSocket(); Scanner sc=new Scanner(System.in); while (true) { System.out.print("请输入你要传输的数据:"); String s=sc.nextLine(); byte[] bytes=s.getBytes(); InetAddress address=InetAddress.getByName("127.0.0.1"); int port=10086; DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port); ds.send(dp); if(s.equals("886")){ System.out.println("通讯结束"); break; } } ds.close(); } } //ReceiveMessageDemo.java import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class ReceiveMessageDemo { public static void main(String[] args) throws IOException { DatagramSocket ds=new DatagramSocket(10086); byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); while(true){ ds.receive(dp); int port = dp.getPort(); String address = dp.getAddress().getHostAddress(); String name = dp.getAddress().getHostName(); byte[] data = dp.getData(); int len = dp.getLength(); System.out.println("接收到数据:"); String s = new String(data, 0, len); System.out.println(s); System.out.println("该数据来自" + address + "主机名"+name+"端口为" + port + "的位置"); } } }
通信方式
单播:只给一个网络设备发送数据,之前的两个范例便时单播
组播:给一组网络设备发送数据
组播地址:224.0.0.0-239.255.255.255,其中224.0.0.0-224.0.0.255为预留的组播地址
组播接收端:不创建DatagramSocket对象,而需要创建MulticastSocket对象,且需要将当前本机添加到指定组中
组播发送端:不创建DatagramSocket对象,而需要创建MulticastSocket对象,需将目的地址改成组播地址
广播:给局域网内所有网络设备发送数据
广播地址:255.255.255.255
和单播大致没区别,只需要把地址目的地址改为255.255.255.0
组播范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 //SendMessageDemo.java import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.util.Scanner; public class SendMessageDemo { public static void main(String[] args) throws IOException { MulticastSocket ms=new MulticastSocket(); System.out.print("请输入数据:"); Scanner sc=new Scanner(System.in); String s=sc.nextLine(); byte[] bytes=s.getBytes(); InetAddress address=InetAddress.getByName("224.0.0.1"); int port=10086; DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port); ms.send(dp); ms.close(); } } //ReceiveMessageDemo1.java import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; public class ReceiveMessageDemo1 { public static void main(String[] args) throws IOException { MulticastSocket ms=new MulticastSocket(10086); InetAddress address=InetAddress.getByName("224.0.0.1"); ms.joinGroup(address); byte[] bytes=new byte[1024]; DatagramPacket dp=new DatagramPacket(bytes,bytes.length); ms.receive(dp); int port=dp.getPort(); String sendAddress=dp.getAddress().getHostAddress(); String sendName=dp.getAddress().getHostName(); int len=dp.getLength(); byte[] data = dp.getData(); String s = new String(data, 0, len); System.out.println(s); System.out.println("该数据来自" + sendAddress + "主机名"+sendName+"端口为" + port + "的位置"); ms.close(); } } //ReceiveMessageDemo2.java import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; public class ReceiveMessageDemo2 { public static void main(String[] args) throws IOException { MulticastSocket ms=new MulticastSocket(10086); InetAddress address=InetAddress.getByName("224.0.0.1"); ms.joinGroup(address); byte[] bytes=new byte[1024]; DatagramPacket dp=new DatagramPacket(bytes,bytes.length); ms.receive(dp); int port=dp.getPort(); String sendAddress=dp.getAddress().getHostAddress(); String sendName=dp.getAddress().getHostName(); int len=dp.getLength(); byte[] data = dp.getData(); String s = new String(data, 0, len); System.out.println(s); System.out.println("该数据来自" + sendAddress + "主机名"+sendName+"端口为" + port + "的位置"); ms.close(); } }
广播范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 //SendMessageDemo.java import java.io.IOException; import java.net.*; import java.util.Scanner; public class SendMessageDemo { public static void main(String[] args) throws IOException { DatagramSocket ds=new DatagramSocket(); Scanner sc=new Scanner(System.in); System.out.print("请输入数据:"); String s=sc.nextLine(); byte[] bytes=s.getBytes(); InetAddress address=InetAddress.getByName("255.255.255.255"); int port=10086; DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port); ds.send(dp); ds.close(); } } //ReceiveMessageDemo1.java import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class ReceiveMessageDemo1 { public static void main(String[] args) throws IOException { DatagramSocket ds=new DatagramSocket(10086); byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length); ds.receive(dp); int port = dp.getPort(); String address = dp.getAddress().getHostAddress(); String name = dp.getAddress().getHostName(); byte[] data = dp.getData(); int len = dp.getLength(); System.out.println("接收到数据:"); String s = new String(data, 0, len); System.out.println(s); System.out.println("该数据来自" + address + "主机名"+name+"端口为" + port + "的位置"); } }
TCP通信程序 TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象。
通信之前要保证连接已经建立。
通过Socket产生IO流来进行网络通信。
三次握手
客户端向服务器发出连接请求,等待服务器确认
服务器向客户端返回一个响应,告诉客户端收到了请求
客户端向服务器再次发出确认信息,连接建立
四次挥手
客户端向服务器发出取消连接请求
服务器向客户端返回一个响应,表示收到客户端取消请求,随后服务器将最后的数据处理完毕。
服务器向客户端发送确认取消信息
客户端再次发送确认信息,连接取消
代码思路 客户端 :
创建客户的Socket对象(Socket)与指定服务端连接,三次握手协议保证连接建立,若连接不上,代码会报错
Socket(String host,int port)
获取输出流,写数据
OutputStream getOutputStream()
释放资源,利用四次挥手协议断开连接,且确保连接通道里的数据已经处理完毕
服务器 :
创建服务端的Socket对象(ServerSocket)
监听客户单连接,返回一个Socket对象
获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream
释放资源
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 //需要先运行服务端 //client.java import java.io.IOException; import java.io.OutputStream; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { Socket socket=new Socket("127.0.0.1",10086); String s="hello!你好!"; OutputStream os=socket.getOutputStream(); os.write(s.getBytes()); os.close(); socket.close(); } } //Server.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket ss=new ServerSocket(10086); Socket accept = ss.accept(); BufferedReader br=new BufferedReader(new InputStreamReader(accept.getInputStream())); int ch; while((ch=br.read())!=-1){ System.out.print((char)ch); } br.close(); accept.close(); ss.close(); } }
范例 例1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 //多发多收 //客户端:多次发送数据 //服务器多次接收数据,并打印 //Client.java import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; public class Client { public static void main(String[] args) throws IOException { Socket socket=new Socket("127.0.0.1",10086); Scanner sc=new Scanner(System.in); OutputStream os=socket.getOutputStream(); while (true) { System.out.print("请输入数据:"); String s=sc.nextLine(); if(s.equals("886")){ break; } os.write(s.getBytes()); } socket.close(); } } //Server.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket ss=new ServerSocket(10086); Socket socket=ss.accept(); BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream())); int ch; while((ch=br.read())!=-1){ System.out.print((char)ch); } socket.close(); ss.close(); } } /*client的运行 请输入数据:saf 请输入数据:453 请输入数据:996 请输入数据:886 */ /*Server的运行 saf453996 */
例2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 //接收与反馈 //客户端:发送一条消息,接收服务端反馈的消息并打印 //服务器:接收数据并打印,在给客户端反馈消息 //Server.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { //1.创建对象并绑定10086接口 ServerSocket ss=new ServerSocket(10086); //2.等待客户端连接 Socket s=ss.accept(); //3.socket中获取输入流读取数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); int ch; //细节:read方法会从接连通道中读取数据 //需要有一个结束标记,此处循环才会停止 //程序就会一直挺爱read方法这里,等待读取下面的数据 while ((ch=br.read())!=-1){ System.out.print((char)ch); } //4.回写数据 OutputStream os = s.getOutputStream(); os.write("服务端已收到消息".getBytes()); //5.释放资源 s.close(); ss.close(); } } //Client.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { //1.创建socket对象连接客户端 Socket s=new Socket("127.0.0.1",10086); //2.写出数据 OutputStream os = s.getOutputStream(); os.write("你好".getBytes()); //3.写出一个结束标记 s.shutdownOutput(); //4.接收服务端回写的数据 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); int ch; while ((ch=br.read())!=-1){ System.out.print((char)ch); } //5.释放资源 os.close(); } } /*Server收到的消息 你好 */ /*Client发送的消息 服务端已收到消息 */
例3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 //上传文件 //客户端:将本地文件上传到服务器,接收服务器的反馈 //服务器:接收客户端上传的文件,上传完毕后给出反馈 //服务器接收后文件是唯一的,每次接收的内容一样,文件名不一样 //利用UUID类的randomUUID方法实现,UUID表示通用唯一标识符UUID类,UUID表示一个128位的值) //Server.java import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.UUID; public class Server { public static void main(String[] args) throws IOException { ServerSocket ss=new ServerSocket(10086); Socket accept = ss.accept(); BufferedInputStream bis = new BufferedInputStream(accept.getInputStream()); String name= UUID.randomUUID().toString().replace("-",""); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("serverphoto\\"+name+".jpg")); byte[] bytes=new byte[1024]; int len; while((len=bis.read(bytes))!=-1){ bos.write(bytes,0,len); } BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(accept.getOutputStream())); bw.write("上传成功"); bw.newLine(); bw.close(); bos.close(); accept.close(); ss.close(); } } //Client.java import java.io.*; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { Socket s=new Socket("127.0.0.1",10086); BufferedInputStream bis=new BufferedInputStream(new FileInputStream("clientphoto\\photo.jpg")); BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream()); byte[] bytes=new byte[1024]; int len; while((len=bis.read(bytes))!=-1){ bos.write(bytes,0,len); } bos.flush(); s.shutdownOutput(); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str=br.readLine(); System.out.println(str); br.close(); bis.close(); s.close(); } }
例4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 //上传文件(多线程版) //若服务器不停止,能接收很多用户上传的图片 //提示:可用循环或多线程,但循环不合理,最优解法(循环+多线程+线程池)改写 //Server.java import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Server { public static void main(String[] args) throws IOException { ThreadPoolExecutor pool=new ThreadPoolExecutor( 3, 16, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); ServerSocket ss=new ServerSocket(10086); while (true) { Socket accept=ss.accept(); pool.execute(new MyRunnable(accept)); } } } //Client.java import java.io.*; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { Socket s=new Socket("127.0.0.1",10086); BufferedInputStream bis=new BufferedInputStream(new FileInputStream("clientphoto\\photo.jpg")); BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream()); byte[] bytes=new byte[1024]; int len; while((len=bis.read(bytes))!=-1){ bos.write(bytes,0,len); } bos.flush(); s.shutdownOutput(); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String str=br.readLine(); System.out.println(str); br.close(); bis.close(); s.close(); } }
例5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 //BS,接收浏览器的消息并打印 //无需写客户端 //服务器:接收数据并打印 //在浏览器搜索 127.0.0.1:端口号 import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket ss=new ServerSocket(10086); Socket accept = ss.accept(); InputStreamReader isr = new InputStreamReader(accept.getInputStream()); int ch; while ((ch=isr.read())!=-1){ System.out.print((char)ch); } accept.close(); ss.close(); } }