一、网络编程简介
计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统。网络编程就就是编写程序使联网的两个(或多个)设备(例如计算机)之间进行数据传输。Java 语言对网络编程提供了良好的支持,通过其提供的接口我们可以很方便地进行网络编程。
Java 的网络应用程序编程接口主要包括:
(1)
通信套接字接口
(Socket Interface):比较原始的通信方式,设备之间通信时,必须先对这些通信数据进行处理。
(2)远程方法调用
(RMI):比较高层级的通信方式,只要通信双方约定好通信的接口,其他的通信细节就可以借助中间件来完成。
java.net 包中的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节
。
(1)处理 IP地址与域名、网络主机
● InetAddress:处理主机名和 IP 地址
● Inet4Address
● Inet6Adress(2)关于 URL 通信协议
● URL:处理 URL 并下载 URL 的相关数据
● URLConnection(3)关于 TCP 通信协议
● Socket:处理 TCP 通信协议
(4)关于 UDP 通信协议
● DatagramSocket:处理 UDP 通信协议
(5)服务器的使用问题
● SercerSocket:提供服务端使用
java.net 包中提供了两种常见的网络协议的支持:
● TCP : TCP是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称TCP/IP。 面向连接的,安全,效率低,开销大。
● UDP :UDP是用户数据报协议的缩写,一个无连接的协议,提供了应用程序之间要发送的数据的数据包。不安全,效率高,协议简单,开销小。
域名系统(DNS)
域名系统(DNS)是因特网的电话簿。人类通过域名在线访问信息,如 nytimes.com 或 espn.com。Web 浏览器通过 Internet 协议(IP)地址进行交互。
DNS 将域名转换为 IP 地址,以便浏览器可以加载 Internet 资源
。在解析域名时,可以首先采用静态域名解析的方法,如果静态域名解析不成功,再采用动态域名解析的方法,域名是互联网上的身份标识,是不可重复的唯一标识资源。
(1)加载网页涉及4个DNS服务器:● DNS recursor - recursor 可以被认为是一个图书管理员,被要求在图书馆的某个地方找到一本特定的书。DNS recursor是一个服务器,旨在通过Web浏览器等应用程序从客户端计算机接收查询。通常,recursor负责发出其他请求以满足客户端的DNS查询。
● 根名称服务器 - 根服务器是将人类可读主机名转换(解析)为IP地址的第一步。它可以被认为是图书馆中指向不同书籍书籍的索引 - 通常它可以作为对其他更具体位置的参考。
● TLD名称服务器 - 顶级域名服务器(TLD)可以被视为库中特定的书架。此名称服务器是搜索特定IP地址的下一步,它托管主机名的最后一部分(在example.com中,TLD服务器是“com”)。
● 权限名称服务器 - 这个最终名称服务器可以被认为是书架上的字典,其中可以将特定名称翻译成其定义。权限名称服务器是名称服务器查询中的最后一站。如果权限名称服务器可以访问所请求的记录,它将把请求的主机名的IP地址返回给发出初始请求的 DNS Recursor(图书管理员)。
(2)DNS 工作流程
● 第一步:客户机提出域名解析请求,并将该请求发送给本地域名服务器。
● 第二步:当本地域名服务器收到请求后,就先查询本地缓存,如果有该纪录项,则本地域名服务器就直接把查询结果返回。
● 第三步:如果本地缓存中没有该纪录,则本地域名服务器就向根域名服务器发起迭代查询:如果根域名服务器缓存有该纪录项,则根域名服务器就直接把查询结果返回;否则根域名服务器解析出顶级域(.com)服务器的地址,再返回给本地域名服务器。
● 第四步:本地域名服务器去请求顶级域名服务器:如果顶级域名服务器缓存有该纪录项,则顶级域名服务器就直接把查询结果返回;否则顶级域解析出权限域(baidu.com)的地址,返回给本地域名服务器。本地服务器再向一步返回 域名服务器发送请求,然后接受请求 服务器查询自己 缓存,如果没 该纪录,则返回相关 下级 域名服务器 地址。
● 第五步:本地域名服务器向权限域名服务器www.baidu.com发起请求进行查询,权限域名服务器 www.baidu.com 告诉本地域名服务器所查询的主机的 IP 地址。
● 第六步:本地域名服务器把返回结果保存到缓存,以备下一次使用,同时还将结果返回给客户机。
为了提高DNS查询效率,并减轻服务器的负荷和减少因特网上的DNS查询报文数量,在域名服务器中广泛使用了高速缓存,用来存放最近查询过的域名以及从何处获得域名映射信息的记录。
1、网络编程的目的
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
2、网络编程中有两个主要的问题
① 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用。
② 找到主机后如何可靠高效地进行数据传输。
3、如何实现网络中的主机互相通信
① 通信双方地址:
IP 和 端口号
(通过 IP 地址准确定位主机,通过端口号准确定位主机上的应用)
② 一定的规则(即:网络通信协议
,有两套参考模型)
● OSI 参考模型:模型过于理想化,未能在因特网上进行广泛推广。
● TCP/IP 参考模型(或TCP/IP协议):事实上的国际标准。
二、TCP 网络编程
1、Socket 介绍
Sokcet,即套接字,是一个对 TCP / IP 协议进行封装 的编程调用接口(API)。通过 Socket,我们可以基于 TCP/IP 协议进行开发。Socket 不是一种协议,而是一个编程调用接口(API),属于传输层(主要解决数据如何在网络中传输)。
套接字(Socket)是通信的基石,是支持 TCP/IP 协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:
连接使用的协议
,本地主机的IP地址
,本地进程的协议端口
,远地主机的IP地址
,远地进程的协议端口
。
应用层通过传输层进行数据通信时,TCP 会遇到同时为多个应用程序进程提供并发服务的问题。多个 TCP 连接或多个应用程序进程可能需要通过同一个 TCP 协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与 TCP/IP 协议交互提供了套接字(Socket)接口。应用层可以和传输层通过 Socket 接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
(1)IP 地址:InetAddress(在 Java 中使用 InetAddress 类代表 IP)
● 唯一的标识 Internet 上的计算机(通信实体)。
● 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost。
● P地址分类方式1:IPV4 和 IPV6。
IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。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,专门为组织机 构内部使用。
(2)InetAddress 类
● Internet上的主机有两种方式表示地址:
①域名(hostName):www.baidu.com
②IP 地址(hostAddress):14.215.177.38
● InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address
● InetAddress 类对象含有一个 Internet 主机地址的域名和IP地址:www.baidu.com 和
14.215.177.38
(3)端口号
端口号就是标识正在计算机上运行的进程(程序),不同的进程有不同的端口号,被规定为一个 16 位的整数 0~65535。
端口分类:① 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口 80,FTP占用端口21,Telnet占用端口23)
② 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占
用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。③ 动态/私有端口:49152~65535
(4)Socket分类:
1、 流套接字(stream socket):使用TCP提供可依赖的字节流服务
2、 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
2、基于 Socket 的 TCP 编程
套接字使用 TCP 提供了两台计算机之间的通信机制。
客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信
。java.net.Socket 类代表一个套接字,并且java.net.ServerSocket 类为服务器程序提供了一种来监听客户端
,并与他们建立连接的机制。
1、Socket 通信的过程
Java 语言的基于套接字编程分为服务端编程和客户端编程,其通信模型如图所示:
(1)客户端Socket的工作过程包含以下四个基本的步骤:
①
创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象
。若服务器端响应,则建立客户端到服务端的通信路线。若连接失败,则会出现异常。②
打开连接到 Socket 的输入/出流
: 使用 getInputStream() 方法获得输入流,使用 getOutputStream() 方法获得输出流,进行数据传输。③
按照一定的协议对 Socket 进行读/写操作
:通过输入流读取服务器放入线路的信息(但不能读取自己放入路线的信息),通过输出流将信息写入线程。④ 关闭 Socket:断开客户端到服务器的连接,释放线路。
(2)服务器(服务端)程序的工作过程:
①
调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上
。用于监听客户端的请求。②
调用 ServerSocket 类的 accept():监听连接请求
,如果客户端请求连接,则接受连接,返回通信套接字对象。③
调用该 Socket 类对象的 getOutputStream() 和 getInputStream ()
:获取输出
流和输入流,开始网络数据的发送和接收。④ 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。
综上,在两台计算机之间使用套接字建立TCP连接时的具体步骤为:
① 服务器实例化一个 ServerSocket 对象,绑定监听端口。
② 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,监听客户端请求,直到客户端连接到服务器上给定的端口。
③ 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
④ Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
⑤ 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 与客户端的 socket 连接建立后,通过使用 I/O 流读写进行通信,每一个 socket 都有一个输出流和输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
2、Socket 编程的常用 API
(1)Socket 类
java.net.Socket类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象实例化。而服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
Socket类有五个构造方法:
当Socket构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。客户端和服务器都有一个 Socket 对象,所以无论客户端还是服务器都能够调用这些方法。
(2)ServerSocket 类
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类共有四个构造方法:
如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的接口,并且侦听客户端请求。这里有一些 ServerSocket 类的常用方法:
(3)InetAddress 类
这个类表示互联网协议(IP)地址。下面列出了Socket编程时比较有用的方法:
3、网络编程 demo
服务端 server
public class Server {
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedReader br = null;
PrintWriter pw = null;
System.out.println("服务端开启...");
try{
// 创建 ServerSocket 对象,绑定监听端口 9000
ServerSocket server = new ServerSocket(9000);
// 监听客户端连接请求`,如果客户端请求连接,则接受连接,并返回通信套接字对象
Socket socket = server.accept();
// 获取 socket 的输入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取 socket 的输出流
pw = new PrintWriter(socket.getOutputStream(),true);
// 读取socket 的输入流数据,从而获得客户端发送的数据
String s = br.readLine();
System.out.print("接收到的是" + s);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
br.close();
pw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
客户端 client
public class Client {
public static void main(String[] args) {
BufferedReader br = null;
OutputStream os = null;
try{
Socket socket = new Socket("localhost",9000);
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取 socket 的字节输出流
os = socket.getOutputStream();
// 将字符串转换为字节数组
byte[] buffer = "hello".getBytes();
// 将字节数组写入到客户端 socket 的输出流,从而发送到服务端
os.write(buffer);
System.out.println("发送的数据: " + new String(buffer));
}catch(Exception e){
e.printStackTrace();
}finally{
try {
br.close();
pw.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
结果