Java面向对象程序设计|二人间对话示例

简介: Java面向对象程序设计|二人间对话示例

01、点对点通信模型


基于Socket的点对点通信与生活中两部手机通信连接过程(见图1)相似。手机开机后,自动向通信服务器注册手机位置(即基站位置编号),并置本机在线标记。拨号时,先向服务器查询对方号码的位置信息,若找到且对方在线,则通过服务器向该号码发送连接请求;接通后,两部手机间建立完整的通信链路。

image.png

■ 图1  两部手机的通信连接


图2是手机通信模型与基于Socket的点对点通信模型的比较。对比发现,二者极为相似。ServerSocket对象类似手机通信中的服务器,Socket类似手机,基于Socket对象创建的输入流/输出流对象,对应手机中的听筒/扬声器。通信端点是Socket对象,代表对方,即从输入流(即手机中的听筒)读取的数据是由对方发送,向输出流写入数据必会传给对方。

image.png

■ 图2 手机通信模型与基于Socket的点对点通信模型的比较


假设A向B发消息,B进行反馈。A是通信的发起方,常称作主叫;B等待对方连接(类似待机),常称作被叫。通信模型执行的具体步骤见图3:

image.png

■ 图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中描述了主叫发起呼叫、发送/接收数据、关闭通信的执行过程。


注意:为凸显注意事项,这里给出的是有问题的设计:输出结果存在问题。

import java.net.Socket; import java.net.ServerSocket;
import java.io.BufferedReader; import java.io.PrintWriter;
import java.io.InputStreamReader; import java.io.IOException;
class SocketStr{//用于传输String的Socket,既可用于主叫,也可用于被叫
  private Socket socket; //相当于手机,代表对方
  private BufferedReader in; //基于socket产生的输入流,相当于听筒
  private PrintWriter out; //基于socket产生的输出流,相当于麦克风
  public SocketStr(Socket sk){ //由被叫方使用,其中sk是accept()的返回值
    socket=sk; creatInOut();
  }
  public SocketStr(String ip, int port){//由主叫方使用
    try{socket=new Socket(ip,port); creatInOut();}
    catch(ConnectException ee){
      System.out.println("被叫的accept服务未启动,不接受呼叫!");
      System.exit(0); }
    catch(Exception e){ System.out.println("有异常:"); e.printStackTrace();}
  }
  private void creatInOut(){//构造输入流和输出流
    try{ in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             out = new PrintWriter(socket.getOutputStream(), true); //true表示自动刷新
    } catch(IOException e){ System.out.println("有异常:"); e.printStackTrace();}
  }
  public void send(String info){ out.println(info); } //发送消息info
  public String receive(){String s=null;
    try{ s=in.readLine(); }
       catch(IOException e){ System.out.println("有异常:"); e.printStackTrace();}
    return s;
  }
  public void close(){
    try{ in.close(); out.close(); socket.close(); }
       catch(IOException e){ System.out.println("有异常:"); e.printStackTrace(); }
  }
}
class Callee{//被叫方,注:被叫需要先待机(对应待机端口号),以便接收呼叫
  public static void main(String[] args){
    String name="李四"; ServerSocket srv=null; Socket skt=null;
    try{ srv= new ServerSocket(6666);//本机使用6666端口提供通信服务
       System.out.println( "服务端启动,等待连接。。。");
       skt=srv.accept(); //无连接时等待,连接成功后返回Socket对象,代表主叫方
       System.out.println( "连接成功!开始通话。。。");
    } catch(IOException e){ System.out.println("有异常:"); e.printStackTrace(); }
    SocketStr sk=new SocketStr(skt); //必须使用ServerSocket返回的Socket
    String msg="还没呢!"; //自己要说的话
    System.out.println( name+": "+msg);//在本地端显示发送的内容
    sk.send( name+": "+msg ); //发送消息
    msg=sk.receive(); //接收消息
    System.out.println( msg ); //在本地端显示收到的信息
    sk.close(); //关闭Socket和输入流、输出流
  }
}
class Caller{//主叫方
  public static void main(String[] args){
    String name="张三"; String msg="李四,你吃了吗?"; //待发送内容
    SocketStr sk=new SocketStr("127.0.0.1", 6666); //127.0.0.1代表本机
    System.out.println( name+": "+ msg);//在本地端显示发送的消息
    sk.send(name+": "+msg ); //发送消息
    msg=sk.receive(); //接收消息
    System.out.println( msg ); //在本地端显示收到的信息
    sk.close(); //关闭Socket和输入流、输出流
  }
}


03、输出结果


image.png

■ 图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地址,在两台主机上进行通信实验。


目录
相关文章
|
10天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
30天前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
43 1
|
29天前
|
Java 测试技术 开发者
💡Java 零基础:彻底掌握 for 循环,打造高效程序设计
【10月更文挑战第15天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
110 63
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
67 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
24天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
14 2
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
1月前
|
存储 Java
什么是带有示例的 Java 中的交错数组?
什么是带有示例的 Java 中的交错数组?
46 9
|
1月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
27 1
|
1月前
|
Java API 网络安全
Java 发送邮件示例
本示例展示了如何使用Java编程语言发送电子邮件。通过利用JavaMail API,这段代码实现了从配置SMTP服务器,设置邮件属性,到发送邮件的全过程,为开发者提供了实用的参考。
|
2月前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
35 4
下一篇
无影云桌面