一、网络编程概述
1、什么是网络编程
Java是Internet上的语言,从语言级别上提供了网络应用程序的支持,通过java.net网络功能包,能够很容易的开发网络应用程序。
联网的底层细节被隐藏在Java的本机安装系统里,由JVM进行控制。并且Java实现了一个跨平台的网络库,相当于程序员面对的是一个统一的网络编程环境。
说是说给我们提供了网络功能包,但是对于网络一些基础知识应该是在要知晓并且了解的,负责碰到实际一些麻烦时若是没有一些基础会找不到问题源头所在。
计算机网络:将分布在不同地理区域的计算机与专门的外部设备用通信线路互连城一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
网络编程目的:直接或间接地通过网络协议与其他计算机实现数据交换,进行通讯。
2、网络通信要素
认识网络通信协议
问题描述
问题1:如何准确定位网络上一台或多太主机;定位主机上的特定应用?
通过IP地址与端口号
问题2:找到主机后如何进行可靠高效的数据传输?
提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理层以及数据链路层)
网络通信也可以称为socket通信或者socket编程。
认识一下网络通信协议
对于网络通信协议有两套参考模型:
OSI参考模型:模型过于理想化,未在因特网上广泛推行。
TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
网络通信协议对速率、传输代码、代码结构、传输控制步骤以及出错控制等制定标准。
网络通信涉及内容很多,例如指定源地址和目标地址、加密节目,压缩加压,差错控制,流量控制,路由控制,那么这么多内容如何更好的处理呢?
将通信协议分层,即同层间可以通信、上一层可以调用下一层,并且与下一层不发生关系,各层互不影响便于系统开发与扩展。
分层如下图:
对于传输的数据在不同层也进行了封装:
要素1:IP地址和端口号
知识补充
①IP地址(InetAddress):唯一标识Internet的计算机。
分类一:IPV4和IPV6
IPV4:4个字节组成,4个0-255,大概42亿个,北美有30亿,亚洲4亿,在2011年初已经用尽,例如192.168.0.1
IPV6:16个字节,写成8个无符号整数,每个整数用四个十六进制来表示,例如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984。(冒号隔开)
分类2:公网地址(万维网使用)和私有地址(局域网使用)。例如192.168.开头就是私有地址,范围为192.168.0.0-192.168.255.255。
本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
②TCP端口:只是一个16位宽、用来识别服务器上特定程序的数字。
网页服务器(8080),Telnet(23),POP3邮件服务器(110),SMTP邮局交换服务器(25),Tomcat(8080),Mysql(3306),Oracle(1521)。
每个服务器上都有65536个端口(0-65535)
0-1023的TCP端口号是保留给已知的特定服务器使用,不应该使用这个范围的,若是我们自己使用的话一般在1024-65535这个范围。
不同程序无法使用同一个端口,若是想要使用(称绑定)会受到BindException。。
IP地址与端口号组合就能够得出一个网络套接字:Socket。
通过Socket进行连接,Socket是个代表两台机器之间网络连接的对象(java.net.Socket),我们不需要在乎低层的细节,因为这是在底层的网络设备中处理掉的。这种 网络设备是一种让运行在Java虚拟机上的程序能够找到方法去通过实际的硬件(如网卡)在机器之间传送数据的机制。
这部分由操作系统的特定部分以及Java的网络API所组成,我们只需要考虑高层的部分。
认识InetAddress类
在Java中使用InetAddress类来代表IP地址,查看一下实例化方法:
比较常用的是getByName(String host)、getLocalhost()
并且这里列举两个常用方法:
getHostAddress():获取主机地址 getHostName():获取主机域名 @Test public void test01() throws UnknownHostException { //获取实例方法1:返回回送地址 InetAddress ip = InetAddress.getLoopbackAddress(); System.out.println(ip);//localhost/127.0.0.1 //获取实例方法2:通过输入主机名获取id地址 涉及到DNS解析 InetAddress ip1 = InetAddress.getByName("www.baidu.com"); System.out.println(ip1);//www.baidu.com/36.152.44.95 //使用两个常用方法分别获取主机名以及ip地址 System.out.println(ip1.getHostName());//www.baidu.com //获取原始ip地址 System.out.println(ip1.getAddress());//[B@621be5d1 System.out.println(ip1.getHostAddress());//36.152.44.95 }
要素2:网络协议
网络协议有很多很多,对于不同层也有不同的协议。
传输层两个重要的协议:
传输控制协议TCP(Transmission Control Protocal)
使用该协议前需要建立TCP连接,采用三次握手方式(可靠的),形成传输数据通道之后进行大数据量传输,传输完毕之后还有四次挥手来中断连接。安全,但是效率低。
TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络 拥挤等问题,它保证数据可靠的传送。
用户数据报协议UDP(User Datagram Protocal)
直接将数据、源、目的封装成数据包发送过去,不需要进行连接,直接发送。每个数据包大小限制在64K内,发送数据结束后也无需释放资源,开销小速度快,但是不安全,容易丢失。
TCP场景:打电话
UDP场景:发送短信,发电报
二、TCP网络编程
例题一:创建客户端与服务端实现发送与接收
客户端:
//客户端 @Test public void test01() { //首先创建ip地址实例 InetAddress ip = InetAddress.getLocalHost(); //创建socket实例,添加一个目标端口 try(Socket socket = new Socket(ip, 8899); OutputStream os = socket.getOutputStream();){ //发送数据,通过写入字节传输 os.write("服务端你好,我是客户端".getBytes()); } catch (IOException e) { e.printStackTrace(); } }
创建Socket实例(包含ip地址以及端口号),接着使用getOutputStream()获取到其中的输出流,直接使用write()写入想要传的字符串即可发送过去。
服务端:
//服务端 @Test public void test02(){ try( //创建ServerSocket用来接收Socket ServerSocket ss = new ServerSocket(8899); //accept()方法会在等待用户的Socket连接时闲置下来,一旦用户连接,会返回一个Socket来方便通信 Socket socket = ss.accept(); InputStream is = socket.getInputStream(); //通过这个字节数组输出流来获取传入过来的字节 ByteArrayOutputStream baos = new ByteArrayOutputStream();){ //进行读取数据 byte[] data = new byte[20]; int len; while((len = is.read(data))!=-1){ baos.write(data,0,len); } //打印出输入到该流中的内容 System.out.println("接收到主机名:"+socket.getInetAddress().getHostName()+ ",ip地址为"+socket.getInetAddress().getHostAddress()+" 接收内容如下:"); System.out.println(baos.toString()); } catch (IOException e) { e.printStackTrace(); } }
创建ServerSocket实例,指定开放的端口,通过accept()方法等待接收Socket数据包。
通过ByteArrayOutputStream来存储从Socket中读出的数据,创建ByteArrayOutputStream不需要指定节点流,获取读出的字节内容直接使用toString()方法即可。
先启动服务端,之后启动客户端发送数据包:
例题二:客户端发送一张图片到服务器端
思路:客户端使用FileInputStream来读取本地的图片字节,之后通过指定的IP地址与端口号的Socket将数据传输出去,服务端使用FileOutputStream从ServiceSocket中的Socket读取传来的数据直接保存到本地。
客户端:
//客户端 @Test public void test01() { InetAddress ip = InetAddress.getLocalHost(); try( Socket socket = new Socket(ip, 8899); //获取输入流 OutputStream os = socket.getOutputStream(); //读取本地文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1234.jpg"));){ byte[] data = new byte[1024]; int len; while((len = bis.read(data))!=-1){ //发送字节 os.write(data,0,len); } } catch (IOException e) { e.printStackTrace(); } }
服务端:
//服务端 @Test public void test02(){ try( //创建ServerSocket用来接收Socket ServerSocket ss = new ServerSocket(8899); //accept()方法会在等待用户的Socket连接时闲置下来,一旦用户连接,会返回一个Socket来方便通信 Socket socket = ss.accept(); InputStream is = socket.getInputStream(); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("changlu.jpg"));){ //进行读取数据 byte[] data = new byte[20]; int len; while((len = is.read(data))!=-1){ bos.write(data,0,len); } //打印出输入到该流中的内容 System.out.println("接收到主机名:"+socket.getInetAddress().getHostName()+ ",ip地址为"+socket.getInetAddress().getHostAddress()+"发送的图片"); } catch (IOException e) { e.printStackTrace(); } }
这里使用了缓冲流来处理文件流。
例题三:客户端发送数据到服务端,服务端再返回数据到客户端
与上面例2有些相同,只不过多了一些做的内容,本题在接收到数据之后还要重新发送数据到客户端,客户端也要进行接收,接下来看下面的演示,主要是-------------下内容。
客户端:
//客户端 @Test public void test01() { InetAddress ip = InetAddress.getLocalHost(); try( Socket socket = new Socket(ip, 8899); //获取输入流 OutputStream os = socket.getOutputStream(); //读取本地文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1234.jpg"));){ byte[] data = new byte[1024]; int len; while((len = bis.read(data))!=-1){ //发送字节 os.write(data,0,len); } System.out.println("客户端已成功发送图片"); //----------------------------------- //主要来接收服务端收到我们照片后发来信息 //首先禁用此套接字的输出流,之后进行读取操作 socket.shutdownOutput(); InputStream is = socket.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); data = new byte[1024]; len = 0; while((len = is.read(data)) != -1){ baos.write(data,0,len); } System.out.println("接收到服务端发送的数据为:"+baos.toString()); } catch (IOException e) { e.printStackTrace(); } }
关键部分是21行的socket.shutdownOutput();是关闭输出流,如果不手动关闭的话会有阻塞问题,如下
服务端:
//服务端 @Test public void test02(){ try( //创建ServerSocket用来接收Socket ServerSocket ss = new ServerSocket(8899); //accept()方法会在等待用户的Socket连接时闲置下来,一旦用户连接,会返回一个Socket来方便通信 Socket socket = ss.accept(); InputStream is = socket.getInputStream(); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("changlu.jpg"));){ //进行读取数据 byte[] data = new byte[20]; int len; while((len = is.read(data))!=-1){ bos.write(data,0,len); } //打印出输入到该流中的内容 System.out.println("接收到主机名:"+socket.getInetAddress().getHostName()+ ",ip地址为"+socket.getInetAddress().getHostAddress()+"发送的图片"); //------------------------------------ //发送数据到客户端 //获取到数据流 OutputStream os = socket.getOutputStream(); os.write("服务端已接收到图片,感谢你的分享!".getBytes()); } catch (IOException e) { e.printStackTrace(); } }
这里当图片数据读取之后可以直接获取Socket的输出流,再通过输出流write()即可发送数据了。
服务端—Tomcat
服务器处理流程:针对于jsp
客户端请求 —> tomcat服务器 -->java程序 —>返回相对应的html(前后端没有分离返回的是html,分离了就返回json数据结构)
**介绍:**服务器实际上就是已经使用代码编写号的一个可以根据浏览器发送的请求到本地服务器并调用执行对应逻辑代码的容器。我们只需要安装服务器到本地并将事先编写好的处理业务逻辑请求的代码放置到服务器指定位置。服务器一旦启动就能够根据接收到的请求来执行指定的逻辑代码。
之后我们会使用servlet来处理业务逻辑。
三、UDP网络编程
认识UDP的相关类
UDP是直接发送数据,不需要与服务端进行连接的,使用UDP编程的话,java也有相应的API可供调用使用,一般使用DatagramSocket与DatagramPacket搭配并使用send()与receive()方法进行发送与接收。
相对于TCP编程不同点介绍:
作为发送端DatagramSocket是无参构造器,而DatagramPacket的构造器中则需要填入指定内容的字节数组、长度、ip地址以及端口号。
作为接收端DatagramSocket需要确定端口号,DatagramPacket仅仅填入字节数组与长度
两个类介绍:
DatagramSocket:数据报包,用于实现无连接分组传送服务,下面是常用方法
public InetAddress getLocalAddress():获取套接字绑定的本地地址。
public int getLocalPort():返回此套接字绑定的本地主机上的端口号。
public InetAddress getInetAddress():返回此套接字连接的地址。如果套接字未连接,则返回 null。
public int getPort():返回此套接字的端口。如果套接字未连接,则返回 -1。
DatagramSocket:发送与接收数据报数据包的套接字,下面是常用方法
public InetAddress getAddress():返回某台机器的 IP 地址,此数据报将要发往该 机器或者是从该机器接收到的。
public int getPort():返回某台远程主机的端口号,此数据报将要发往该主机或 者是从该主机接收到的。
public byte[] getData():返回数据缓冲区。接收到的或将要发送的数据从缓冲区 中的偏移量 offset 处开始,持续 length 长度。
public int getLength():返回将要发送或接收到的数据的长度。
小案例
描述:使用UDP来发送指定内容的数据包到接收端,打印出来。
发送端:
//发送端 @Test public void test01() { //创建一个空参的DatagramSocket实例 try(DatagramSocket ds = new DatagramSocket();){ byte[] data = "我是UDP传来的数据包".getBytes(); //创建数据包 待发出的字节数组、发送数据包长度,InetAddress实例,端口号 DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 8899); //发送数据包 ds.send(packet); }catch (IOException e) { e.printStackTrace(); } }
直接将数据放置到DatagramPacket实例中,并用DatagramSocket的send()发送数据包
接收端:
//接收端 @Test public void test02(){ //创建实例时需要指定端口 try(DatagramSocket ds = new DatagramSocket(8899);){ byte[] data = new byte[1024]; //这里需要填入一个字节数组用于等会进行接收,还有作为接收的长度 DatagramPacket packet = new DatagramPacket(data, data.length); //进行接收,需要数据包作为参数之后将数据放置到其中 ds.receive(packet); //使用ds.getData()方法获取字节数组,之后转为字符串打印输出 System.out.println(new String(packet.getData(),0,packet.getData().length)); } catch (IOException e) { e.printStackTrace(); } }
我们需要拿到客户端发送的数据包内容那么就需要使用DatagramSocket实例的receive()方法,注意其中需要有一个DatagramPacket实例来进行接收数据。
如何拿到包中的数据呢?调用DatagramPacket的getData()即可拿到传输过来的字节。
**注意:**UDP编程与TCP编程最大的不同就是TCP需要开启服务端之后再进行发送端发送,才能够顺利进行;UDP是直接发送数据过去,速度快但是并不稳定安全,系统不保证 UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
四、URL编程、
1、认识URL及URL类
了解一下URL
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
URL不仅用来标识一个资源,还指名了如何locate(定位)这个资源。
通过URL我们可以访问Internet上的各种网络资源,例如最常见的www,ftp站点。
浏览器通过解析指定的URL可以在网络上查找相应的文件及资源。
URL基本组成(5部分):<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
#表示指定锚点,用于当前页面的指定部分。
参数列表:表示传递的参数
认识URL类
通过URL对象可以创建当前应用程序和 URL 表示的网络资源之 间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去
在java中使用URL类来表示URL,我们通过构造器来创建实例,其实例会抛出异常:
public URL (String spec):通过一个表示URL地址的字符串可以构造一个URL对象
例如:URL url = new URL (“http://www. atguigu.com/”);
public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象
例如:URL downloadUrl = new URL(url, “download.html")
public URL(String protocol, String host, String file);:通过协议,主机名及指定页面
例如:URL down = new URL(“http”, “www.atguigu.com”, “download. html");
public URL(String protocol, String host, int port, String file); :通过协议,主机名、端口号、指定页面
例如:URL gamelan = new URL(“http”, “www.atguigu.com”, 80, “download.html");
URL实例方法:
public String getProtocol( ):获取该URL的协议名
public String getHost( ):获取该URL的主机名
public String getPort( ):获取该URL的端口号
public String getPath( ):获取该URL的文件路径
public String getFile( ):获取该URL的文件名
public String getQuery( ):获取该URL的查询名
实操一下:
//客户端 @Test public void test01() throws MalformedURLException { URL url = new URL("https://www.baidu.com/"); System.out.println("Protocol():"+url.getProtocol()); System.out.println("getHost():"+url.getHost()); System.out.println("getPort():"+url.getPort()); System.out.println("getPath():"+url.getPath()); System.out.println("getFile():"+url.getFile()); System.out.println("getQuery():"+url.getQuery()); }
2、针对Http协议的URLConnection类
若我们想向服务器发送一些数据,例如向服务器端的CGI(公共网关接口-Common GatewayInterface,是用户浏览器与服务器端的应用程序连接的接口)数据,我们就需要与URL建立连接,之后才能进行读写操作,那么就需要使用URLConnection。
URLConnection类:表示到URL所引用的远程对象连接。当我们实例化URL建立连接时,首先要在URL对象上通过方法openConnection()生成对应的URLConnection对象。
通过该对象来获取输入流与输出流,与服务器端的CGI程序进行交互。
相关方法:
public void connect():若是调用时尚未与url连接,那么打开与此URL引用的资源通信链接。
public Object getContent( ) throws IOException:检索此url连接的内容。
public int getContentLength( ):返回content-encoding标题字段的值。
public String getContentType( ):返回content-type标题字段的值。
public long getDate( ):取到date字段的值。
public long getLastModified( ):取得last-modified的值。
public InputStream getInputStream( )throws IOException:获得打开连接的读取输入流。
public OutputSteram getOutputStream( )throws IOException:获取写入此连接的输出流。
小案例
下载指定url的图片:
@Test public void test01() throws IOException { URL url = new URL("https://pics7.baidu.com/feed/fc1f4134970a304ec448a9204a685981c8175c2e.jpeg?token=b8477ffbb8442bf478c2a6cfeeefc62a&s=FCCB905416190DD0962277900300D09C"); //获取URLConnection实例 URLConnection conn = url.openConnection(); //进行连接 conn.connect(); //获取连接的输入流 InputStream is = conn.getInputStream(); //创建字节缓冲流包裹文件输出流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("upload.jpg")); //读取操作 byte[] data = new byte[1024]; int len; while((len = is.read(data))!=-1){ bos.write(data,0,len); } }
将指定url地址的图片下载到本地。
参考资料
[1]. 尚硅谷-Java30天基础-网络编程(宋红康)