接下来说Java中非常重要的一个技术部分,就是网络编程。Java语言涵盖的领域非常广,对于网络这一块它也不示弱,Java也可以完成网络通讯的部分。
我们以前写代码是单机版的,比如把代码从C盘拷贝到D盘。那么能不能从我的机器上发条信息,到张三的机器上,张三再给我回复条信息,让我们两个机器之间利用Java语言实现数据的通讯,而数据通讯的原理就是数据传输。那么它和我们之前本机上数据传输的唯一不同就是涉及到了网络。
画图来表示一下通讯的过程:
02-网络编程(概述2)
IP地址分四段组成,每一段其实就是一个字节,一个字节最大值就是255(8个1)。
有一个IP地址非常特殊,就是127.0.0.1,这个是一个本地回环地址。也就是说,本地没有配任何IP地址的情况下,本机默认的IP地址就是127.0.0.1。
可以PING它来测试网卡:
后面4段的IP地址已经不够用了(IPV4),所以就出现了6段的IP地址(IPV6),IPV6不光包含数字,还包含字母,它排列组合之后地址非常多,足够使用了。这是我们新段地址(IPV6)和老段地址(IPV4)的区别。
下面说端口,端口数字的选择范围为0-65535。通常0-1024这个范围内的端口被系统所保留了。
03-网络编程(网络模型)
说完了网络传输的基本三要素:IP地址、端口、协议以外,我们就要说一说具体的传输方式。
首先说一下网络模型。
网络在传输的过程中,因为每层次所对应的功能不一样,而有了层次的细致划分。
网络参考模型:
刚开始7层参考模型数据封包的过程:
数据拆包的过程:
后来有了简化的参考模型,将应用层、表示层、会话层合成了应用层,将数据链路层和物理层合成了主机至网络层,而传输层和网络层(网际层)因为比较重要依然单独存在。简化后的参考模型只有四层了。
那我们现在主要学习哪一层呢?
因为我们是学软件的,所以硬件部分主机至网络层不用管,主要学应用层、传输层、网际层,其中传输层和网际层是我们学习的重点,之前我们写的程序都在这一层。
而如果学Java Web开发的话,就主要在应用层混。
所以说,我们现在要玩的是软件中比较底层的(传输层和网际层),而应用层是基于我们学的这些,就它封装了。所以应用层更简单。
而每个层都有自己的协议(通讯规则),传输层最常见的协议就是TCP和UDP,网际层最常见的协议就是IP,应用层最常见的协议就是HTTP和FTP。
我们接下来的计划是,先学网际层和传输层,掌握它们的特点后,再学应用层。
04-网络编程(IP地址)
我们知道网络通讯有三个要素:IP地址、端口号、传输协议。那么如何用Java来操作它呢?我们想到了封装成对象。
先来看一下这几个要素的特点。
IP地址:
1,是网络中设备的标识。
2,不易记忆,可用主机名。
3,本地回环地址:127.0.0.1 主机名:localhost
主机名和IP地址是相对应的,每台计算机都有自己的主机名:
那么IP到底用什么来表示呢?
我们来java.net包中看一看。
net包中用于描述IP的对象是InetAddress,它有两个子类,“4”和“6”:
打印本地的主机名和地址:
还可以用非静态的方法单独获取主机名和地址:
有没有办法获取任意一台主机的主机名和地址呢?
我们想一想,如果存在这个方法,那么这个方法的返回值类型是IP地址对象(即InetAddress),另外任意一台主机的话,为了清除到底是哪一台,必须将这台主机指定进去传进去,所以这个方法还需要有参数。分析得到这两个要素,我们在方法中找一找有没有符合则两个要素的方法。
我们看到一个,传入主机名返回IP地址对象的:
代码演示:
可是这里我们还是用自己的本机试的,我们试试其他机子:
运行的很慢:
如果这个地址和它对应的主机名没有在网络上,那么拿这个地址出去找就没有解析成功。那为什么我们本机的可以解析这个不能解析呢?因为本机的信息本机上都有,而另外一台机子的信息没有找到,所以返回的主机名还是IP地址。
其实只要连上网,我们可以直接获取百度主机名和地址(不用写它的IP地址,直接写它的域名就好了,它就和IP地址相对应),可是百度面向的人群那么大,它肯定有多台主机,而且一个地址有可能对应着多个服务器,所以有可能返回来的IP对象不唯一,所以这里还有一个传入主机名返回数组的方法:
05-网络编程(TCP和UDP)
端口号:
1,是用于标识进程的逻辑地址,不同进程的标识。
2,有效端口:0~65535,其中0~1024为系统使用或保留端口。
端口就是一个数字标识,所以它没有必要封装成对象。
下面重点说一下传输协议。
传输协议:
1,它是通讯的规则。
2,常见协议:TCP,UDP。
TCP和UDP到底有什么区别呢?
UDP是面向无连接的,将数据封装包中,然后贴上小纸条,标明它的地址和端口。一般传输都有两端:发送端和接收端,如果用UDP传输,那么对方在不在都无所谓,我就是发我的,把数据打成一个包发出去,你在就收到了,不在这个包就丢掉。另外,在传输的时候也可能存在地址不存在的情况。
UDP的特点:
1,将数据及源和目的封装在数据包中,不需要建立连接。
2,每个数据包的大小限制在64k内。
3,因无连接,是不可靠协议,容易丢包。
4,不需要建立连接,速度快。
我们聊天、视频共享就是UDP,在不在不重要,把数据发出去就行了。
下面说TCP,TCP必须是面向连接的。也就是说,如果用TCP通讯的话,对方必须在,对方不在就不行。它比较可靠,如果单方面断开的话,数据就不传了。必须形成通路,数据才能传输。
那怎么确认对方在不在呢?
我们通过三次握手来完成。
TCP的特点:
1,建立连接,形成传输数据的通道。
2,在连接中进行大数据量传输。
3,通过三次握手完成连接,是可靠协议。
4,必须建立连接,效率会稍低。
TCP相当于打电话,只有对方接电话才能通话。
06-网络编程(Socket)
下面说Socket,我们说的网络编程,其实就是Socket编程。
Socket就是为网络服务提供的一种机制。
通信的两端都有Socket,有了Socket才能建立连接。它就相当于码头,有了码头,船只才能在两个码头间通行、搬运货物。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
07-网络编程(Udp-发送端)
Socket有了,但是传输协议不同,每个传输协议都有自己建立端点的方式。
我们先来说UDP的Socket服务的建立。
我们看到java.net包中有两个socket对象,一个叫DatagramSocket:
这个对象既能发送又能接收,如果有两个端点发送和接收数据,那这两个端点每个端点都要有这个对象。
那么发送和接收什么数据是不是就要指定一下呢:
UDP要把数据封装成包发过去,其中包括扩对方的地址和端口、自己的地址和端口,都在这个数据包中,这个数据包很复杂,所以把这个数据包封装成了对象,这个对象就是DatagramPacket:
接下来要写代码,我们的需求是:
代码:
不要忘记导入包:
因为面向无连接,所以这个数据就丢失了,那怎么能够让这个数据被收到呢?
08-网络编程(Udp-接收端)
之所以数据丢失是因为接收端没开,我们这节课来写接收端的代码,为了看起来更直观就将接收端代码和发送端代码写在同一个java文件中了,但是它们是两段独立运行的程序,所以里面都有自己的主函数。
接收端的需求和思路:
定义udpsocket服务的时候,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识,方便于明确哪些数据过来该应用程序可以处理。
代码:
接下来要运行了,开启两个dos命令行:
这里的1093是系统随机为发送端产生的数字标识(在我们没有为发送端指定数字标识的前提下):
多运行几次我们发现每一次都不一样:
若我们为发送端指定了数字标识8888:
则这里就会显示我们指定的数字标识,不用系统随机分配了:
09-网络编程(UDP-键盘录入方式数据)
在这里加上while(true)循环:
我们在这里加上while(true)循环的好处是,接收端运行的时候运行一次就OK了,因为receive方法是阻塞式方法,它会一直等着,我们运行一次之后它就一直等着,每次一有数据传输过来它都可以接收到,实现了重复接收:
但是我们发现老师每次发送的都是同一句话,能不能发送点别的呢?
那我们把这里改一下重新编译运行发送?
可是每次只能发一句,而且每发一句就要重新编译运行,好麻烦呢。
那我们能不能通过键盘录入呢?
代码:
运行效果:
我们刚刚用的地址是192.168.1.254,最后一段的取值中0和255比较特别,192.168.1.0代表这一个网络段,而192.168.1.255是这一个网络段的广播地址,就是给这个网络段的所有地址读发送。
改成这个地址就可以实现群聊了:
10-网络编程(UDP-聊天)
我们刚刚的效果是仿造的聊天,但是是在两个窗口实现,能不能一个窗口实现呢?
这个时候就要用到多线程的技术,一个线程控制收,一个线程控制发。
因为收和发动作是不一致的,所以要定义两个run方法,而且这两个方法要封装到不同的类中。
记得导入这两个包:
运行效果:
但是有一个问题,我们正在写数据还没有回车发送的时候,如果地方此时发送了一条消息就会出现下面的问题,和我们正在编辑的数据混在一起:
这都是因为,发送端和接收端混在同一个面积区域中,如果我们做一个图形化界面,将发送端和接收端分成两个区域,就解决了这个问题。
11-网络编程(TCP传输)
说完了UDP传输,我们说TCP传输。
实在好着急,下面是纯截屏,好着急好着急。
演示TCP传输:
12-网络编程(TCP传输2)
刚才只是客户端给服务端发了信息,但是服务端并没有给客户端反馈,下面我们要实现反馈动作。
需求描述和实现步骤:
代码:
运行结果:
13-网络编程(TCP练习)
接下来通过一个练习来用到开发中常用到的对象。
需求描述:
代码:
运行之后发现客户端发信息,服务端并不能收到。是哪里出问题了呢?
首先,考虑一下客户端到底发出去了没有呢?
客户端的数据写入了缓冲区,在这里恍然大悟,刚刚忘记写flush了,我们把flush加上。
另一个问题就是服务端读数据判断结束的标记是换行标记:
而客户端发送数据后面并没有换行标记,于是我们再加上换行符,修改后的代码如下:
运行,服务端收到了客户端的数据,但是客户端并没有收到服务端返回的大写数据。
我们又忘记给服务端flush了,因为它的数据也是写在缓冲区中呢,另外,和服务端一样,客户端判断数据结束的标记也是换行符,所以我们也加上换行符,代码修改如下(和刚刚客户端代码添加的内容是一样的):
运行:
该例子中出现的问题:
另外,这个例子中的还有可以简化的代码,客户端和服务端简化的部分都一样,这里以服务端部分示例:
14-网络编程(TCP复制文件)
刚才的例子是通过键盘录入,从客户端向服务端发数据。下面再说一个例子,将客户端的数据发送到服务端,再由服务端将数据存到一个文件中去,这就相当于数据拷贝。
以前是将文件从C盘拷到D盘,现在是将文件从我的机子中拷到你的机子中,其中加入了网络的技术,但是还是IO。
代码:
记得导包哦:
但是程序一直停不下来,没有像我们预料中那样出现上传成功的文字:
但是我们去文件夹重看了一下,发现文件已经成功存入了呢。
原来,是因为代码中没有结束标记,于是我们在客户端的代码中,在文件中数据全部读取完毕之后,加上结束标记“over”:
在服务端中判断结束标记“over”,若碰到则结束:
但是如果这个文件中本身就正好包含“over”,和我们的结束标记“over”重名了怎么办呢?
不如我们用下面这串字符:
但即使是这串字符,这种定义方式也是容易重复的,我们用一个唯一的标记:时间戳,因为每个时刻的时间是唯一的。
注意,在客户端代码中,发送数据之前需要先将时间戳发给服务端,让它知道这是结束标记,然后客户端中读完了源文件的数据,再在后面补上时间戳作为结束标记,下面是客户端和服务端代码的修改,客户端:
服务端:
其实,这还是一个比较麻烦的方法,我们只是做一下了解(好气喔!)
其实有更简单的方法,而且只需要修改客户端中的代码,且只用加上这一句代码:
OK:
day23的课程实在太仓促了,好着急,所以好多都用截屏了,也没有听的很细致,好惭愧。
这两天好难受,宿舍和实验室都空荡荡的,一个人待在实验室学习,其实注意力好难集中,因为真的很难受,但是,一切都会过去的,小楠楠加油(;′⌒`)
现在是21:27,宿管阿姨让我早点回宿舍,因为外面很黑,也没有人,她说很危险,我要快速去外面吃个饭然后回宿舍,我心里,好怕喔(;′⌒`)
小楠楠要加油!!!