网络编程概述
Java是网络编程是Java最擅长的方向之一,使用Java进行网络编程时,由虚拟机实现了底层复杂的网络协议,Java程序只需要调用Java标准库提供的接口,就可以简单高效地编写网络程序
网络基础
计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
网络通信要素概述
如何实现网络中的主机互相通信
通信双方地址
- IP
- 端口号
一定的规则(即:网络通信协议)
- OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型:事实上的国际标准
IP和端口号
IP地址:InetAddress
- 唯一的标识Internet上的计算机(通信实体)
- 本地回环地址(hostAddress):127.0.0.1主机名(hostName):localhost
- IP地址分类方式1:IPV4和IPV6
- IPV4:4个字节组成,4个0-255。大概42亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
- IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
端口号:标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个16位的整数0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
网络通信协议
OSI七层模型和TCP/IP四层模型
但这里存在一个问题,在将多个计算机网络进行连接时,不同的计算机网络,他们使用的协议可能各不相同,那这样就没法把不同的网络连接起来形成互联网。因此,为了把计算机网络接入互联网,国际标准化组织ISO提出了OSI(Open System Interconnection)开放系统互联的参考模型,为异构系统互联提供了概念性的框架。
并且由于计算机网络从底层的传输到高层的软件设计十分复杂,要合理地设计计算机网络模型,必须采用分层模型,每一层负责处理自己的操作。所以OSI网络模型是被ISO组织定义为一个七层计算机互联的标准模型,注意它只是一个定义,目的是为了简化网络各层的操作,提供标准接口便于实现和维护。这个模型从上到下依次是:
- 应用层,提供应用程序之间的通信;
- 表示层:处理数据格式,加解密等等;
- 会话层:负责建立和维护会话;
- 传输层:负责提供端到端的可靠传输;
- 网络层:负责根据目标地址选择路由来传输数据;
- 链路层和物理层负责把数据进行分片并且真正通过物理网络传输,如,无线网、光纤等。
- 互联网实际使用的TCP/IP模型并不是对应到OSI的7层模型,而是大致对应OSI的5层模型:
但是ISO制定的OSI参考模型的过于庞大、复杂招致了许多批评。技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。
TCP/IP模型的每一层的作用:
- 网络接口层负责接收IP数据报,并负责把这些数据报发送到指定网络上。
- 网络互联层功能为进行网络互连,根据网间报文IP地址,从一个网络通过路由器传到另一网络。
- 传输层的功能为通信双方的主机提供端到端的服务,传输层对信息流具有调节作用,提供可靠性传输,确保数据到达无误。
- 应用层的功能为对客户发出的一个请求,服务器作出响应并提供相应的服务。
有了TCP/IP四层模型后,只要计算机网络都遵循该模型,这些计算机网络就可以互联组成互联网。
而网络编程的实质就是编写程序直接或间接地通过网络与其它计算机上的某个程序进行通讯。
但现在还存在一个问题,如何找到网络上的主机上要进行通讯的程序呢?
我们的每一个主机都会有IP地址,而每一个程序都会有端口号,有了IP地址和端口号之后,我我们就可以找到网络上指定主机上要进行通讯的指定程序。其中IP地址用于唯一标识一个网络接口,一台联入互联网的计算机肯定有一个IP地址,但也可能有多个IP地址;端口,就是应用程序用于数据交互的通道,用于实现程序间的通信。每个应用程序都有固定的端口。
注意:两个应用程序如果使用同一个端口,那么会抛出端口冲突异常java.net.BindException。
就好像我要去某宝网购某个物品,我需要提供我的详细地址物品才可以正确邮寄到家。但是有正确的地址还不够,我们还需要找一家靠谱的快递公司来进行运输。
在网络编程中,数据的传输由TCP/IP分层模型中的传输层负责,该层包含TCP和UDP两种协议。
TCP和UDP
TCP协议:
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
套接字
Java语言引入了Socket套接字的编程模型,使得程序员很方便的访问TCP/IP协议,从而开发各种网络应用程序。
这里有个问题,为什么需要用到Socket呢?
因为仅仅通过IP地址进行通信是不够的,同一台计算机同一时间会运行多个网络应用程序,例如浏览器、QQ、邮件客户端等。当操作系统接收到一个数据包的时候,如果只有IP地址,它没法判断应该发给哪个应用程序,所以,操作系统抽象出Socket接口,每个应用程序需要各自对应到不同的Socket,数据包才能根据Socket正确地发到对应的应用程序。
一个Socket就是由IP地址和端口号(范围是0~65535)组成,可以把Socket简单理解为IP地址加端口号。端口号总是由操作系统分配,它是一个0~65535之间的数字,其中,小于1024的端口属于特权端口,需要管理员权限,大于1024的端口可以由任意用户的应用程序打开。
客户端和服务器通过套接字建立连接,进行数据的发送和接收。其中客户端是指向另一台计算机请求服务的计算机,服务器是指处理客户端请求的计算机。一般情况下服务器和客户端共同承担计算。
一个TCP/IP套接字,由一个IP地址、一个端对端协议(TCP或UDP协议)、以及一个端口号确定。
套接字模型
Host A上的程序A将一段信息写入Socket中,Socket的内容被Host A的网络管理软件访问,并将这段信息通过Host A的网络接口卡发送到Host B,Host B的网络接口卡接收到这段信息后,传送给Host B的网络管理软件,网络管理软件将这段信息保存在Host B的Socket中,然后程序B才能在Socket中阅读这段信息。
Java对网络编程的支持
JDK预定义的网络编程相关类均存放在java.net包中。其中有InetAddress、Socket、ServerSocket和SocketImpl、DatagramPacket和DatagramSocket在内的很多可能的类。
例如:InetAddress类用于封装计算机的IP地址和DNS。但该类没有公有构造方法,所以不能直接利用new 关键字创建对象。可以使用以下常用方法来获取该类对象。
- getLocalHost() – 返回表示本地主机InetAddress对象
- getByName(String hostName) – 根据主机名返回对象
- getAddress() – 返回IP地址
- getHostName() – 取得IP地址代表的主机名
两个java程序通过一个双向的网络通信连接实现数据交换,这个双向链路的一端称之为Socket套接字。Socket套接字通常用来实现客户服务器之间的连接。java.net包中的两个类Socket、ServerSocket分别用来实现客户端和服务端。
服务端实例(基础版)
public class Server {
public static void main(String[] args) {
try {
// 创建服务器端对象,监听2000端口
ServerSocket server = new ServerSocket(2000);
System.out.println("服务器启动成功,等待用户接入~~~");
// 等待用户接入,知道有用户接入为止
Socket client = server.accept();
// 得到客户端的ip地址
System.out.println("客户端接入,客户IP:" + client.getInetAddress());
// 从客户端生成网络输入流,用于接收来自网络的数据
InputStream in = client.getInputStream();
// 从客户端生成网络输出流,用户把数据发送到网络上
OutputStream out = client.getOutputStream();
// 定义一个字节数组,用于存储网络数据
byte[] bt = new byte[1024];
// 将数据写入字节数组,并获取数据长度
int len = in.read(bt);
// 将字节数组转化为字符串数据
String info = new String(bt,0,len);
System.out.println("来自客户端的消息:" + info);
out.write("我是服务器,欢迎光临:".getBytes());
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端实例(基础版)
public class Client {
public static void main(String[] args) {
// 创建客户端
try {
Socket socket = new Socket("127.0.0.1",2000);
System.out.println("连接服务器成功");
// 客户端生成输入输出流用以接收和输出数据
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write("我是客户端,欢迎光临".getBytes());
byte[] bt = new byte[1024];
int len = in.read(bt);
String info = new String(bt,0,len);
System.out.println("来自服务器的消息:" + info);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}