Java 面向对象程序设计|二人间对话示例
简介: Java 面向对象程序设计|二人间对话示例
01、点对点通信模型
基于 Socket 的点对点通信与生活中两部手机通信连接过程(见图 1)相似。手机开机后,自动向通信服务器注册手机位置(即基站位置编号),并置本机在线标记。拨号时,先向服务器查询对方号码的位置信息,若找到且对方在线,则通过服务器向该号码发送连接请求;接通后,两部手机间建立完整的通信链路。
■ 图 1 两部手机的通信连接
图 2 是手机通信模型与基于 Socket 的点对点通信模型的比较。对比发现,二者极为相似。ServerSocket 对象类似手机通信中的服务器,Socket 类似手机,基于 Socket 对象创建的输入流/输出流对象,对应手机中的听筒/扬声器。通信端点是 Socket 对象,代表对方,即从输入流(即手机中的听筒)读取的数据是由对方发送,向输出流写入数据必会传给对方。
■ 图 2 手机通信模型与基于 Socket 的点对点通信模型的比较
假设 A 向 B 发消息,B 进行反馈。A 是通信的发起方,常称作主叫;B 等待对方连接(类似待机),常称作被叫。通信模型执行的具体步骤见图 3:
■ 图 3 基于 Socket 的点对点通信实施步骤
(1)被叫方开启待机状态。即执行图中的第【1】【2】。被叫方创建 ServerSocket 对象,启动侦听服务 accept()。若无连接请求则处于等待状态;若连接请求到达,则结束等待,并根据连接者自身携带的信息创建一个代表对方(即主叫方)的 Socket 对象(即图中的 sk)。
(2)主叫方发起呼叫,即执行模型第【3】步。主叫根据被叫方的 socket 信息(ip+port)创建 Socket 对象,并自动向该 socket 发出连接请求。若被叫方已经启动 accept()方法,则连接成功。至此,主叫、被叫均拥有了代表对方的 Socket 对象,奠定了通信的基础。
(3)双方基于 Socket 对象获取输入/输出流,即执行模型第【4】步。通过 Socket 对象的 getXxxStream()可获得输入/输出流。注意,借助底层类库支持,获得的流对象实际上已经完成了流的配置(即 A 的输入/出流与 B 的输出/入流完成了对接)。
(4)实施数据传输,即发送信息是向输出流写入数据,接收消息是从输入流读取数据,若对方并未发送数据,则读取动作将执行等待,直至对方写入数据。鉴于通信双方基于 Socket 建立了持续可靠字节流通道,故这种传输方式属于有连接的流通信方式。
(5)通信结束,通信完成后,关闭己方的流和 Socket 对象。
下面通过示例具体阐述通信程序的设计。
02、基于 Socket 的点对点通信方式-示例
【例 1】使用基于 Socket 的点对点通信方式,实现如下通信内容:张三说:“李四,你吃了吗?”,李四回答:“还没呢。”上述两条信息在两台主机上均完整显示。
目的:通过简单的通信内容,凸显通信程序的设计框架和实施步骤,即如何使用 Socket 和 ServerSocket,如何创建输入输出流、如何收发消息,以及实施过程中有何注意事项。
设计:本例主要设计了 3 个类:SocketStr、Caller(主叫方)、Callee(被叫方)。
类 SocketStr 描述用于收/发 String 数据的 Socket,有属性 socket、in、out,对应通信端点、输入/输出流。主叫方的 SocketStr 对应构造函数 SocketStr(ip,port),基于 ip+port 创建 socket 引用的对象,继而基于 socket 获取 in、out;被叫方的 SocketStr 对应构造函数 SocketStr(socket),即基于 accept()返回的 Socket 对象为 socket 赋值,并获取 in、out。SocketStr 类主要提供发送、接收、关闭三个方法。
Callee 面向被叫,main 中描述了启动待机、发送/接收数据、关闭通信的执行过程;
Caller 面向主叫,main 中描述了主叫发起呼叫、发送/接收数据、关闭通信的执行过程。
注意:为凸显注意事项,这里给出的是有问题的设计:输出结果存在问题。
03、输出结果
■ 图 4 一句话通信过程展示
04、示例剖析
程序在单击上的执行方式:在单击上运行本程序需要开启两个 JVM(或者说两个控制台)。先执行 Callee,启动被叫的 accept()服务,等待接受连接。若此时无连接,则等待,见图 4.(a);之后,再开启一个控制台,执行 Caller(见图 4.(b)),执行后 Callee 收到了呼叫,解除等待状态(见图 4.(c)),两个窗口继续执行直至结束。
通信的基础是两端的 Socket 对象,ServerSocket 对象旨在让被叫处于监听服务(即执行 accept())状态。主叫方的 Socket 对象是主动创建,被叫方的 Socket 对象则由 accept()方法返回。输入/输出流从 Socket 对象获得。Socket 对象代表对方,即向输出流中写入数据,定会发送给对方;从输入流获取的信息,定由对方传来。
Callee 的执行结果存在问题:问话与回答的次序颠倒了。这是因为 Callee 是被叫,应先接收消息(即对方问题)再回答。而 main 中先输出“自己的回答”并发送,继而接收“对方的问题”并输出,次序颠倒了。理论上,二人通信时,收发次序有四种可能:
(1)主叫(发-收)+被叫(发-收);(2)主叫(发-收)+被叫(收-发)
(3)主叫(收-发)+被叫(发-收);(4)主叫(收-发)+被叫(收-发)
实际上,只有第 2 种才能产生合法的结果,第 1、3 种产生错误的输出结果,第 4 种将产生死锁。因为通过输入流接收消息时,若对方未发送任何消息,则接收消息的方法将处于等待状态。这样,第 4 种方式主叫、被叫均将进入等待状态。
若主叫/被叫采用正确的发送和接收消息次序,能否放入循环,持续进行二人通话呢?可以,但存在缺陷:只能严格遵循一人一句的次序。如某人未说,则另一人会陷入等待。请读者自行尝试上述四种通信顺序,以及持续通信,进行验证。
在 Callee 中,必须以 ServerSocket 返回的 Socket 对象为基础构造输入输出流,不能另行创建 Socket 对象继而构造输入输出流。因为 accept()方法返回的 Socket 对象中携带了对方(即 Caller)的 Socket 信息(IP 和 port)。
127.0.0.1 是特殊的 IP 地址,也称回绕地址,指本机,一般用来测试使用。例如:ping 127.0.0.1 来测试本机 TCP/IP 是否正常。读者也可通过 ipconfig 指令先获得被叫的 IP 地址,继而将 Callee 中 127.0.0.1 替换为被叫的 IP 地址,在两台主机上进行通信实验。
版权声明: 本文为 InfoQ 作者【TiAmo】的原创文章。
原文链接:【http://xie.infoq.cn/article/c3ab1c8cd5a909c9f2f067146】。文章转载请联系作者。
评论