网络通信全解析:一篇文章带你深入了解

发表时间: 2018-11-12 19:44

网络基本概念

• ISO/OSI 网络参考模型则包括七个层次:应用层、表示层、会话层、传输层、网络层、链路层、物理层。

• TCP/IP(传输控制协议/网际协议)是 Internet 的主要协议,定义了计算机和外设进行通信所使用的规则。

• TCP/IP网络参考模型包括五个层次:应用层、传输层、网络层、链路层、物理层。

○ 物理层:具体的传输时使用的设备及其拓扑结构 -- 光纤 / 电缆等

○ 链路层:数据传送中的一种传输控制 -- 主要设备是网桥 -- [载波监听多路访问] 先听后发 / 边听边发 / 随机重发

○ 网络层:点到点传送,对 TCP/IP 网络中的硬件资源进行标识 -- 主要设备是路由器

○ 传输层:端到端传送

§ 通过 TCP [Transport Control Protocol] 传送控制协议 和另一方进行交互 -- 电话

□ 服务端在某个端口对客户端的信息进行监听

□ 客户端在固定的端口向服务器发出连接请求

□ 若服务器允许,客户端和服务端之间建立一条信道

□ 开始交互 -- 若同时进行交互会造成死锁

□ 关闭信道

□ 特点:

® 精确性 / 有序性 / 可靠性 / 流协议 -- 信道里传送的是字节流

® 在端与端之间建立持续的连接

§ UDP [User Datagram Protocol] 用户数据报协议 -- 短信 / 邮递

□ 存储转发:

® 将要传输的数据定义成数据报-- 数据以字节数组的方式呈现,数据报类似于信封

® 在数据报中指明数据所要达到的Socket

® DatagramSocket 发送数据报

□ 特点:不精确 / 无序性 / 不可靠性 / 效率高

○ 应用层:基于Internet的应用程序 -- HTTP 协议 / 上传下载协议等

§ HTTP 协议:传输层用 TCP 协议

• Socket:标识网络上一台服务器的 IP 地址[点分十进制] + 端口号

• 端口号:一个标记机器的逻辑通信信道的正整数,不是物理实体

○ 用两个字节的整型数据表示 -- 即 16 位的正整数,范围为 0 - 65535

○ 0 - 1023 为系统所保留 -- http 80 / telnet 21 / ftp 23

○ MySQL 3306 / SQLServer 1433 / Oracle 1521

JAVA 中的网络支持

• 针对网络通信的不同层次,Java提供的网络功能有四大类:

○ InetAddress 类: 第三层,封装网络上网络设备的位置 -- 不提供构造方法,调用静态方

法创建对象

○ URLs 类:第五层,表明网络资源的位置 -- 统一资源定位器

○ Sockets 类:封装传输层的 TCP 协议

○ Datagram 类:封装传输层的 UDP 协议

• java.net 包中的主要的类和可能产生的异常:

○ 面向 IP 层的类:InetAddress

○ 面向应用层的类:URL -- 定位网络资源的位置 / URLConnection,和网络资源建立连接

○ 面向网络层的类:

§ TCP协议相关类:Socket、ServerSocket

§ UDP协议相关类:DatagramPacket、DatagramSocket、MulticastSocket

○ 可能产生的异常 [受检异常]:

§ BindException、ConnectException、MalformedURLException、

NoRouteToHostException、ProtocolException、SocketException、

UnknownHostException、UnknownS erviceException

InetAddress

• 创建 InetAddress 对象 -- 封装 IP地址 / 机器名 / 域名:

○ public static InetAddress getByName(String host) :host可以是一个机器名,也可以

是一个形如“%d.%d.%d.%d”的IP地址或一个DSN域名。

○ public static InetAddress getLocalHost() // 本机 InetAddress 对象

○ public static InetAddress[] getAllByName(String host) // 一台服务器可能有多个网

卡,此方法生成多个 InetAddress 对象

○ 复写 toString() -- 输出显示:主机名 + 反斜杠 + IP地址

○ 这三个方法通常会产生 UnknownHostException 异常,应在程序中捕获处理。

• 主要方法:

○ public byte[] getAddress():获得本对象的IP地址(存放在字节数组中)。

○ public String getHostAddress():获得本对象的IP地址“%d.%d.%d.%d”。

○ public String getHostName():获得本对象的机器名。

URL -- 统一资源定位器 / 定位符

• 作用:标识网络上的某个资源

• 一个URL包括两部分内容:协议名称和资源名称,中间用冒号隔开:

○ 协议名称:获取资源时所使用的应用层协议,如 http,ftp,file [本机找资源的协议] 等

○ 资源名称:资源的完整地址,包括主机名、端口号、文件名或文件内部的一个应用

○ Protocol:resourceName 如:http://www.ztenc.com.cn

import java.net.*;

import java.io.*;

public class URLReader {

public static void main (String args[])

• URL 和 InetAddress 的联合使用,URL url = getCodeBase(); //客户端的浏览器执行 --

获得提供代码的主机的URL实例String host = url.getHost(); // 得到提供代码的服务器

的主机名

Try{// 通过主机名定位到主机的IP地址

InetAddress address = InetAddress.getByName(host);

}catch(Exception e){} Try{

DatagramSocket socket = new DatagramSocket();

DatagramPacket packet = new DatagramPacket(buf, length, address, port);

socket.send(packet);

}catch(Exception e){}

• URL 获取网络资源 [下载]

• 建立 URL 连接 [下载和上传]

{

try{

// 在客户端通过域名创建一个定位服务器资源的URL对象

URL gis = new URL("http://www.ztenc.com.cn/test.htm");

// 通过URL和网络资源之间连接创建网络字节输入流,转换为字符流后套接到

Buffered 流

BufferedReader in = new BufferedReader(

new InputStreamReader( gis.openStream() ) );

String line;

○ 下载:

URL gis = new URL("http://gis.pku.edu.cn/test.htm");

// 通过URL和网络资源创建连接

URLConnection uc = gis.openConnection();

// 通过连接创建 Buffered 输入流

BufferedReader in = new BufferedReader(

new InputStreamReader( uc.getInputStream() ) );

○ 上传:

URL url = new URL("http://www.ztenc.com.cn/~lyw/cgi-bin/test.cgi");

URLConnection uc = url.openConnection();

uc.setDoOutput(true);

PrintStream out = new PrintStream(uc.getOutputStream()); BufferedReader in =

new BufferedReader(

new InputStreamReader( uc.getInputStream() ) );

null )

}

}

while( (line = in.readLine()) ! {

System.out.println(line);

}

in.close();

}catch(Exception e){ System.out.println(e);

}

Socket 通信 -- TCP 协议

• 服务端要在某个端口上创建 ServerSocket 对象,并调用 accept() 方法 -- 在此端口做监听

○ server = new ServerSocket(4331);

○ you = server.accept();

• 客户端通过new Socket(IP地址,端口号) 向某个IP地址的特定端口发出连接请求

○ mysocket = new Socket("localhost",4331);

• 若不产生异常,服务端与客户端之间建立起一条信道,此信道对象为服务端和客户端公有的

Socket 对象 -- 在客户端和服务端有不同的变量名:客户端为mysocket,服务端为 you

• 客户端和服务端分别通过 Socket 对象的地址建立起各自的输入流和输出流对象

○ 客户端通过 Socket 对象创建出网络字节输入流 / 输出流对象,并套接到数据流上 -- 从

信道中读取数据到客户端内存 / 从客户端内存中放置数据到信道中

§ in = new DataInputStream(mysocket.getInputStream());

§ out = new DataOutputStream(mysocket.getOutputStream());

○ 服务端也创建出相应的数据输入流 [从信道中读数据到服务端内存] / 输出流对象 [从服

务端内存放数据到信道中]

半双工:

§ in = new DataInputStream(you.getInputStream());

§ out = new DataOutputStream(you.getOutputStream());

○ 全双工理论: 没有明确地把输入流和输出流分开,客户端通过网络字节输入流从信道里读取的一定是服务端通过字节输出流放置到信道中的信息服务端从信道中读取的一定是客户端放置在信道中的信息-- 相当于在客户端和服务端之间建立了两条虚管道,通过全双工理论创建了两端之间的连接

• 服务端和客户端通过 in.readUTF() 和out.writeUTF() 进行信息交互in.readUTF() -- 只能读取一句话,若服务端和客户端都不放置数据,却都想从信道中读数据 -- 产生死锁[某一方占用对方的资源不释放, 又想获取对方的资源]

• 一旦用户下线,抛出IOException

正常下线:关闭输入流/ 输出流/ Socket将输入流和输出流分开

Socket 通信 -- 多客户端

• 将服务器写成多线程的,不同的处理线程为不同的客户服务。

• 主线程只负责循环等待,处理线程负责网络连接,接收客户输入的信息。

○ 多个客户端对服务器发出请求,创建出多个信道,将信道作为线程处理,服务器标识并返

回不同的信道号 1 / 2 / 3

§ 私聊:客户端 1 发送信息*3* I love 给服务器,服务器通过* 标识截取到*3*,将客

户端 1的信息发送给 3 号信道的客户端

§ 公聊:客户端 1 发送不带* 标识的信息给服务器,服务器将此信息发送给其他所有

客户端

• 这些客户端线程用线程数组/ 集合[线程池] 进行管理

Socket 通信 -- UDP 协议

• UDP 协议不分服务端和客户端

• 进行 awt / swing 布局 -- 输出文本框 / 输入文本域 / 发送按钮 [对此按钮注册监听器]

• UDP 协议不分服务端和客户端

• 进行 awt / swing 布局 -- 输出文本框 / 输入文本域 / 发送按钮 [对此按钮注册监听器]

• 新建一个线程用来接收信息 [主线程发送信息]

• 发送方的按钮监听器 -- 绑定 UDP 协议:

○ 获取输出文本框中的信息 - 修剪前后空格 - 转换为字节类型放入字节数组中

§ byte buffer[] = out_message.getText().trim().getBytes();

○ 创建 InetAddress 对象封装接收数据方的 IP 地址

§ InetAddress address = InetAddress.getByName("localhost");

○ 将数据信息封装入数据报 DatagramPacket

§ DatagramPacket data_pack = new DatagramPacket(buffer,buffer.length,

address,888);

○ 创建 DatagramSocket 对象发送数据报

§ DatagramSocket mail_data = new DatagramSocket();

§ mail_data.send(data_pack);

• 接收方的新线程 run() 方法代码块接收信息:

○ 准备一个空数据报接收信息

§ byte data[] = new byte[8192]; // 定义一个空字节数组对象

§ DatagramPacket pack = new DatagramPacket(data,data.length); // 创建一个数据

报封装空的数组对象

○ 创建 DatagramSocket 对象在指定的端口接收数据报

§ mail_data = new DatagramSocket(888);

§ mail_data.receive(pack); // 将网络上接收来的信息放入准备好的空数据报中

○ 拆数据报 -- pack.getData();

Socket 通信 -- 多播监听

• 线程完成,封装UDP 协议

• 发送端不需要知道接收端的IP 和端口号,只在特定的端口和组上发送信息

• 接收端进行awt / swing 布局,并在特定的端口和组上接收信息

• 设置多播组端口和组地址

○ int port=5858;

○ InetAddress group=null;

• 在多播端口上创建多播套接字,将其加入到多播组地址上,在多播端口的组地址上进行播送

• 创建数据报,由多播套接字发送数据包[UDP 协议]

• 接收端用线程完成接收 -- 主线程完成对信息的处理发送,新建线程完成信息的接收

○ 在构造方法中创建线程

○ 在多播端口上创建多播套接字,且加入到多播组

○ 接收传播过来的数据放入空数据报中 -- socket.receive(packet)

• 接收端 -- 开始接收监听器

○ 判断新建线程是否为活线程,若不是则创建一个新的接收线程,激活该新建线程,将全局

停止变量设为 false,开始执行线程的 run() 方法

○ UDP 协议接收数据并显示在布局上

• 接收端 -- 停止接收监听器

○ 向接收数据的新建线程发送一个中断信息 thread.interrupt()

§ 发送方 2 秒发一次信息,接收方会间断地处于阻塞态;当接收方接收线程处于阻塞态

时,收到主线程发来的中断信息,并将全局停止变量设为true

§ 接收方收到InterruptException,从 catch 代码块开始执行,终止线程