资源描述
2019/11/26,1,网络程序设计 Network Programming 第十章 网络通信,赵建立 山东科技大学信息科学与工程学院 College of Information Science and Engineering , Shandong University of Science and Technology zhaojianli,主要内容,10.1 网络通信简介 10.2 URL通信 10.3 Socket通信 10.4远程方法调用(RMI),10.1 网络通信简介,网络通信的核心是协议。协议是指进程之间交换信息已完成任务所使用的一系列规则和规范。它主要包含两个方面的定义: 1 定义了进程之间交换消息所必需遵循的顺序。 2 定义进程之间所交换的消息的格式。 通过定义协议,可以看出,两个进程只要遵循相同的协议,就可以相互交换信息,而这两个进程可以用不同的编程语言编写,可以位于两个完全不同的计算机上。国际标准化组织给出了一个通用的参考协议,称为开放式系统互连参考模型(ISO/OSI RM)。,图14-1 ISO/OSI RM分层图,主要内容,10.1 网络通信简介 10.2 URL通信 10.3 Socket通信 10.4远程方法调用(RMI),10.2 URL通信,10.2.1 URL简介 10.2.2 URL类 10.2.3 通过字节流访问WWW资源 10.2.4 通过URLConnection实现双向通信 10.2.5使用HttpURLConnection,10.2.1 URL简介,URL用来网络资源定位,它的值由5部分组成,格式如下所示 :/:/ 其中传输协议(protocol)指明获取资源所使用的传输协议,如http、ftp、mms等。主机名(hostname)指定资源所在的计算机,可以是IP地址,如127.0.0.1,也可以是主机名或域名,如。一个计算机中可能有多种服务(应用程序),端口号(port)用来区分不同的网络服务,如http服务的默认端口号是80,ftp服务的默认端口号是21等。文件名(filename)包括该文件的完整路径。在http协议中,缺省的文件名是index.html,因此,,就相等同于 ,10.2 URL通信,10.2.1 URL简介 10.2.2 URL类 10.2.3 通过字节流访问WWW资源 10.2.4通过URLConnection实现双向通信 10.2.5使用HttpURLConnection,10.2.2 URL类,1)URL(String spec),spec为一个完整的URL地址 2) URL(String protocol,String host,int port,String file) 将一个URL地址分解,按不同部分分别指定协议、主机、端口、文件。例如: URL u=new URL(“http”, ”, 80, “docs/books/tutorial.intro.html”); 3) URL(URL context, String spec) 这种方法基于一个已有的URL对象创建一个新的URL对象,多用于访问同一个主机上不同路径的文件,例如: URL u=new URL(“:80/docs/books/”); URL u1=new URL(u, ”tutorial.intro.html”); URL u2=new URL(u, ”tutorial.super.html”);,【例10-1】URL的使用,import java.io.*; import .*; public class URL1 public static void main(String args) throws IOException URL url = new URL(“ System.out.println(“Authority = “ + url.getAuthority(); System.out.println(“Default port = “ + url.getDefaultPort(); System.out.println(“File = “ + url.getFile(); System.out.println(“Host = “ + url.getHost(); System.out.println(“Path = “ + url.getPath(); System.out.println(“Port = “ + url.getPort(); System.out.println(“Protocol = “ + url.getProtocol(); System.out.println(“Query = “ + url.getQuery(); System.out.println(“Ref = “ + url.getRef(); System.out.println(“User Info = “ + url.getUserInfo(); ,101运行结果,10.2 URL通信,10.2.1 URL简介 10.2.2 URL类 10.2.3 通过字节流访问WWW资源 10.2.4通过URLConnection实现双向通信 10.2.5使用HttpURLConnection,10.2.3 通过字节流访问WWW资源,URL对象创建后,就可以通过它来访问指定的WWW资源。这时需要调用URL类的openStream()方法,该方法与指定的URL建立连接并返回一个InputStream类的对象,这样访问网络资源的操作就变成了我们熟悉的I/O操作,接下来就可以用字节流的方式读取资源数据。,【例10-2】通过URL对象访问资源,import java.io.*; import .*; public class URL2 public static void main (String args) throws IOException URL url = new URL (“ InputStreamReader isr = new InputStreamReader (url.openStream (); BufferedReader br=new BufferedReader(isr); String s; while (s = br.readLine () != null) System.out.print (s); br.close (); ,10.2 URL通信,10.2.1 URL简介 10.2.2 URL类 10.2.3 通过字节流访问WWW资源 10.2.4通过URLConnection实现双向通信 10.2.5 使用HttpURLConnection,实际应用中,只能读取数据是不够的,很多情况下,我们都需要将一些信息发送到服务器中去,这就要求我们能够实现同网络资源的双向通信,URLConnection类就是用来解决这一问题的。 类URLConnection也是定义在包里,它表示Java程序和URL在网络上的通信连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法openConnection()生成对应的URLConnection对象。URLConnection是以HTTP协议为中心的类,其中很多方法只有在处理HTTP的URL时才起作用。,1.建立连接 URL url=new URL(“ URLConnection con=url.openConnection(); 2. 向服务器端送数据 PrintStream ps=new PrintStream(con.getOutputStream(); ps.println(string_data); 3. 从服务器读数据 DataInputStream dis=new DataInputStream(con.getInputStream(); dis.readLine();,【例10-3】URLConnection的使用,import java.io.*; import .*; public class ComWithCgi public static void main(String args) throws Exception / 建立指向本地磁盘上cgi的URL对象 URL url = new URL(“http:/ URLConnection connection = url.openConnection(); connection.setDoOutput(true); PrintStream ps = new PrintStream(connection.getOutputStream(); ps.println(“0123456789“); ps.close(); / 向服务器输出数据 DataInputStream dis = new DataInputStream(connection. getInputStream(); String inputLine; while (inputLine = dis.readLine() != null) System.out.println(inputLine); dis.close();/ 从服务器读数据 ,10.2 URL通信,10.2.1 URL简介 10.2.2 URL类 10.2.3 通过字节流访问WWW资源 10.2.4通过URLConnection实现双向通信 10.2.5 使用HttpURLConnection,10.2.5 使用HttpURLConnection,HttpURLConnection是URLConnection的子类。HttpURLConnection提供了对Http协议的支持,如果所访问的URL地址是一个Http地址,那么就可以使用HttpURLConnection 。例如: URL url=new URL(“); HttpURLConnection connection=( HttpURLConnection)url.openConnection(); 但是要注意如果URL地址不是一个http地址,那么就无法用类型转换获取HttpURLConnection的实例。 由于HttpURLConnection是URLConnection的子类,因此HttpURLConnection具有URLConnection的全部public方法,HttpURLConnection的基本用法也与URLConnection相同。,HttpURLConnection独特的方法,public void disconnect(),断开与服务端的连接。 public InputStream getErrorStream(),返回错误流(Error Stream),所谓错误流是指连接失败时服务端返回的有用数据,这些有用数据通常通过错误流返回。例如服务器端返回404错误时(表示所访问的文件无法找到)。 public String getRequestMethod(),返回请求的类型,请求类型包括Get、POST、HEAD、OPTIONS、PUT、DELETE、TRACE。 public int getResponseCode(),返回服务器端响应的状态字,例如200表示OK,401表示Unauthorized。 public String getResponseMessage(),返回服务器端的响应消息,例如“HTTP/1.0 200 OK”或者“HTTP/1.0 404 Not Found”。 public void setRequestMethod(String method),设置请求的类型,请求类型包括Get、POST、HEAD、OPTIONS、PUT、DELETE、TRACE。 public boolean usingProxy(),返回当前HTTP连接是否使用了代理服务器。,主要内容,10.1 网络通信简介 10.2 URL通信 10.3 Socket通信 10.4远程方法调用(RMI),10.3 Socket通信,10.3.1服务器程序 10.3.2 客户机程序 10.3.3 服务多个客户 10.3.4 数据报通信,10.3.1客户-服务器模型,10.3.1服务器程序,服务器的任务就是等候建立一个连接,然后用那个连接产生的Socket 创建一个InputStream 以及一个OutputStream。之后,从InputStream 读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止,最后关闭连接。客户机连接与服务器的连接,然后创建一个OutputStream。文本行通过OutputStream 发送。客户机也会创建一个InputStream,用它收听服务器说些什么。服务器与客户机(程序)都使用同样的端口号,而且客户机利用本地主机地址连接位于同一台机器中的服务器(程序),所以不必在一个物理性的网络里完成测试。,注意,ServerSocket 只要一个端口编号,不需要IP 地址(因为它就在这台机器上运行)。调用accept()时,方法暂时陷入停顿状态,直到某个客户尝试同它建立连接。建好一个连接以后,accept()会返回一个Socket对象,它是那个连接的代表。假如ServerSocket 构建器失败,则程序简单地退出(注意必须保证ServerSocket 的构建器在失败之后不会留下任何打开的网络套接字)。针对这种情况,main()会“掷”出一个IOException 违例,所以不必使用一个try 块。若ServerSocket 构建器成功执行,则其他所有方法调用都必须到一个try-finally 代码块里寻求保护,以确保无论块以什么方式留下,ServerSocket 都能正确地关闭。,同样的道理也适用于由accept()返回的Socket。若accept() 失败,那么我们必须保证Socket 不再存在或者含有任何资源,以便不必清除它们。但假若执行成功,则后续的语句必须进入一个try-finally 块内,以保障在它们失败的情况下,Socket 仍能得到正确的清除。由于套接字使用了重要的非内存资源,所以在这里必须特别谨慎,必须自己动手将它们清除。 无论ServerSocket 还是由accept()产生的Socket 都打印到System.out 里。这意味着它们的toString方法会得到自动调用。这样便产生了: ServerSocketaddr=0.0.0.0,PORT=0,localport=8080 Socketaddr=127.0.0.1,PORT=1077,localport=8080 在后面的程序中大家会看到它们如何与客户程序做的事情配合。,程序的下一部分看来似乎仅仅是打开文件,以便读取和写入,只是InputStream 和OutputStream 是从Socket 对象创建的。利用两个“转换器”类InputStreamReader 和OutputStreamWriter ,InputStream 和OutputStream 对象已经分别转换成为Java 1.1 的Reader 和Writer 对象。也可以直接使用Java1.0 的InputStream 和OutputStream 类,但对输出来说,使用Writer 方式具有明显的优势。这一优势是通过PrintWriter 表现出来的,它有一个过载的构建器,能获取第二个参数一个布尔值标志,指向是否在每一次println()结束的时候自动刷新输出(但不适用于print()语句)。每次写入了输出内容后(写进out),它的缓冲区必须刷新,使信息能正式通过网络传递出去。对目前这个例子来说,刷新显得尤,为重要,因为客户和服务器在采取下一步操作之前都要等待一行文本内容的到达。若刷新没有发生,那么信息不会进入网络,除非缓冲区满(溢出),这会为本程序带来许多问题。 编写网络应用程序时,要特别注意自动刷新机制的使用。每次刷新缓冲区时,须创建和发出一个数据包(数据封)。就目前的情况来说,这正是我们所希望的,因为假如包内包含了还没有发出的文本行,服务器和客户机之间的相互联系就会停止。换句话说,一行的末尾就是一条消息的末尾。但在其他许多情况下,消息并不是用行分隔的,所以不如不用自动刷新机制,而用内建的缓冲区判决机制来决定何时发送一个数据包。这样一来,我们可以发出较大的数据包,而且处理进程也能加快。,注意和我们打开的几乎所有数据流一样,它们都要进行缓冲处理。无限while 循环从BufferedReader in 内读取文本行,并将信息写入System.out,然后写入PrintWriter.out。注意这可以是任何数据流,它们只是在表面上同网络连接。客户程序发出包含了“END“的行后,程序会中止循环,并关闭Socket。,【例10-4】Socket通信程序,/ Server_Socket.java import java.io.*; import .*; public class Server_Socket public static final int PORT = 8080; public static void main(String args) throws IOException ServerSocket s = new ServerSocket(PORT); System.out.println(“Started: “ + s); try Socket socket = s.accept(); try System.out.println(“Connection accepted: “ + socket);,BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(); PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), true); while (true) String str = in.readLine(); if (str.equals(“END“) break; System.out.println(“Echoing: “ + str); out.println(str + str + “ haha“); finally System.out.println(“closing.“); socket.close(); finally s.close(); ,10.3 Socket通信,10.3.1服务器程序 10.3.2 客户机程序 10.3.3 服务多个客户 10.3.4 数据报通信,例10-4客户程序的源码,import .*; import java.io.*; public class client_socket public static void main(String args)throws IOException InetAddress addr =InetAddress.getByName(null); System.out.println(“addr = “ + addr); Socket socket = new Socket(addr, server_socket.PORT); try ,10.3.2 客户机程序,System.out.println(“socket = “ + socket); BufferedReader in =new BufferedReader( new InputStreamReader(socket.getInputStream(); PrintWriter out =new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(),true); for(int i = 0; i 10; i +) out.println(“www “ + i); String str = in.readLine(); System.out.println(str);,10.3.2 客户机程序, out.println(“END“); finally System.out.println(“closing.“); socket.close(); ,输出结果,10.3 Socket通信,10.3.1服务器程序 10.3.2 客户机程序 10.3.3 服务多个客户 10.3.4 数据报通信,10.3.3 服务多个客户,server_socket可以正常工作,但每次只能为一个客户程序提供服务。在服务器中,我们希望同时能处理多个客户的请求。这个问题的关键就是多线程处理机制。对于那些本身不支持多线程的语言,达到这个要求是异常困难的。通过对多线程的学习,大家已经知道Java 已对多线程的处理进行了尽可能的简化。Java 的线程处理方式非常直接,让服务器控制多个客户并不是件难事。最基本的方法是在服务器程序里创建单个ServerSocket,并调用accept()来等候一个新连接。一旦accept()返回,我们就取得结果获得的Socket,并用它新建一个线程,令其只为那个特定的客户服务。然后再调用accept() ,等候下一次新的连接请求。对于下面这段服务器代码,大家可发现它与server_socket.java 例子非常相似,只是为一个特定的客户提供服务的所有操作都已移入一个独立的线程类中。,【例10-5】多客户Socket通信服务端程序,import java.io.*; import .*; class Mult extends Thread private Socket socket; private BufferedReader in; private PrintWriter out; public Mult(Socket s) throws IOException socket = s; in = new BufferedReader( new InputStreamReader(socket.getInputStream(); out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), true); start();, public void run() try while (true) String str = in.readLine(); if (str.equals(“END“) break; System.out.println(“Echoing: “ + str); out.println(str); System.out.println(“closing.“); catch (IOException e) finally try socket.close(); catch (IOException e) ,W,/ ServerSoketMult.java public class ServerSoketMult static final int PORT = 8080; public static void main(String args) throws IOException ServerSocket s = new ServerSocket(PORT); System.out.println(“Server Started“); try while (true) Socket socket = s.accept(); try new mult(socket); catch (IOException e) socket.close(); finally s.close(); ,【例10-6】多客户Socket通信客户端程序,import .*; import java.io.*; class ClientSocketMultThread extends Thread private Socket socket; private BufferedReader in; private PrintWriter out; private static int counter = 0; private int id = counter+; private static int threadcount = 0; public static int threadCount() return threadcount; ,public ClientSocketMultThread(InetAddress addr) System.out.println(“Making client “ + id); threadcount+; try socket = new Socket(addr, ServerSoketMult.PORT); catch (IOException e) try in = new BufferedReader( new InputStreamReader(socket.getInputStream(); out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), true); start(); catch (IOException e) try socket.close(); catch (IOException e2) ,public void run() try for (int i = 0; i 25; i+) out.println(“Client “ + id + “: “ + i); String str = in.readLine(); System.out.println(str); out.println(“END“); catch (IOException e) finally try socket.close(); catch (IOException e) threadcount-;,/ ClientSocketMult.java import java.io.IOException; import .InetAddress; public class ClientSocketMult static final int MAX_THREADS = 40; public static void main(String args) throws IOException, InterruptedException InetAddress addr = InetAddress.getByName(null); while (true) if (ClientSocketMultThread.threadCount() MAX_THREADS) new ClientSocketMultThread(addr); Thread.sleep(100); ,10.3 Socket通信,10.3.1服务器程序 10.3.2 客户机程序 10.3.3 服务多个客户 10.3.4 数据报通信,10.3.4 数据报通信,前面的例子使用的都是TCP协议。TCP协议具有高度的可靠性,能保证数据顺利抵达目的地。收到字节的顺序与它们发出来时是一样的。不过,TCP协议具有非常高的开销。此外,还有另一种UDP协议,它并不刻意追求数据包会完全发送出去,也不能担保它们抵达的顺序与它们发出时一样。我们认为这是一种“不可靠协议”(TCP 当然是“可靠协议”)。由于它的速度快得多,所以在很多场合是很适用的。 Java 对数据报的支持与它对TCP 套接字的支持大致相同,但也存在一个明显的区别。对数据报来说,我们在客户和服务器程序都可以放置一个DatagramSocket(数据报套接字),但与ServerSocket 不同,前者不会干巴巴地等待建立一个连接的请求。这是由于不再存在“连接”,取而代之的是一个数据报陈列出来。另一项本质的区别的是对TCP 套接字来说,一旦我们建好了连接,便不再需要关心谁向谁“说话”只需通过会话流来回传送数据即可。但对数据报来说,它的数据包必须知道自己来自何处,以及打算去哪里。这意味着我们必须知道每个数据报包的这些信息,否则信息就不能正常地传递。DatagramSocket 用于收发数据包,而DatagramPacket 包含了具体的信息。准备接收一个数据报时,只需提供一个缓冲区,以便安置接收到的数据。数据包抵达时,通过DatagramSocket,作为信息起源地的因特网地址以及端口编号会自动得到初化。,InetAddress类,在描述它们之前,必须了解位于同一个位置的InetAddress类。InetAddress实现了Java.io. Serializable接口,不允许继承。它用于描述和包装一个Internet IP地址,通过三个方法返回InetAddress实例: getLocalhost():返回封装本地地址的实例。getAllByName(String host):返回封装Host地址的InetAddress实例数组。 getByName(String host):返回一个封装Host地址的实例。其中,Host可以是域名或者是一个合法的IP地址。,DatagramSocket类,DatagramSocket类用于创建接收和发送UDP的Socket实例。和Socket类依赖SocketImpl类一样,DatagramSocket类的实现也依靠专门为它设计的DatagramScoketImplFactory类。DatagramSocket类有3个构造函数:DatagramSocket():创建实例。这是个比较特殊的用法,通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。 DatagramSocket(int port):创建实例,并固定监听Port端口的报文。DatagramSocket(int port, InetAddress localAddr):这是个非常有用的构造函数,当一台机器拥有多于一个IP地址的时候,由它创建的实例仅仅接收来自LocalAddr的报文。,值得注意的是,在创建DatagramSocket类实例时,如果端口已经被使用,会产生一个SocketException的异常抛出,并导致程序非法终止,这个异常应该注意捕获。DatagramSocket类最主要的方法有4个: Receive(DatagramPacket d):接收数据报文到d中。receive方法产生一个“阻塞”。 Send(DatagramPacket d):发送报文d到目的地。 SetSoTimeout(int timeout):设置超时时间,单位为毫秒。 Close():关闭DatagramSocket。在应用程序退出的时候,通常会主动释放资源,关闭Socket,但是由于异常地退出可能造成资源无法回收。所以,应该在程序完成时,主动使用此方法关闭Socket,或在捕获到异常抛出后关闭Socket。,DatagramPacket类,DatagramPacket类用于处理报文,它将Byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成Byte数组。应用程序在产生数据包是应该注意,TCP/IP规定数据报文大小最多包含65507个,通常主机接收548个字节,但大多数平台能够支持8192字节大小的报文。DatagramPacket类的构造函数共有4个: DatagramPacket(byte buf, int length, InetAddress addr, int port):从Buf数组中,取出length长的数据创建数据包对象,目标是Addr地址,Port端口。 DatagramPacket(byte buf, int offset, int length, InetAddress address, int port):从Buf数组中,取出Offset开始的、length长的数据创建数据包对象,目标是Addr地址,Port端口。 DatagramPacket(byte buf, int offset, int length):将数据包中从Offset开始、length长的数据装进Buf数组。 DatagramPacket(byte buf, int length):将数据包中length长的数据装进Buf数组。,主要内容,10.1 网络通信简介 10.2 URL通信 10.3 Socket通信 10.4远程方法调用(RMI),10.4远程方法调用(RMI),RMI使运行在同一台计算机上的Java对象可以通过远程方法调用来进行通信.这些方法调用和对同一程序中对象的操作是一样的.在面向过程的语言中实现类似功能的是远程过程调用RPC(Remote Procedure Call), RPC使得程序可以方便的调用另一个计算机上的函数,就像调用本机上的函数一样方便,这样就可以使程序员从复杂的网络通信中解脱出来从而集中精力于应用程序的其他工作.当然RPC有自己的缺点,首先RPC采用中性语言实现,并且返回的是用外部数据表示的值,对数据表示协议依赖很强,很难应用到面向对象分布计算系统中。而远程调用方法RMI(Remote Method Invocation)实质上模拟了应用在分布计算系统中的RPC,使用Java远程信息交换协议JRMP(Java Remote Messaging Protocol)进行通信,而JRMP是专为Java的远程对象通信制定的协议。因此,RMI就具有Java的可移植性,是分布应用的纯Java解决方案,更具有面向对象的特征。,RPC的另一个缺点是,它要求程序员掌握一种专门的接口定义语言(Interface Definition language, IDL)来描述可以被远程调用的函数.而RMI不要求程序员学习IDL语言,因为所有的网络连接代码都是从程序已有的类中直接生成的.由于RMI只支持Java一种语言,所以不需要中介语言的IDL,Java自己的接口就已经足够了. RMI具有以下优点,面向对象:RMI可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。也就是说,可以将类似Java哈西表这样的复杂类型作为一个参数进行传递。 可移动属性:RMI可将属性从客户机移动到服务器,或者从服务器移动到客户机。 设计方式:对象传递功能使您可以在分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统.如果用户能够传递属性,那么就可以在自己的解决方案中使用面向对象的设计方式。所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象就会失去设计方式上所提供的优点。 安全性:RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。RMI使用专门为保护系统免遭恶意小程序侵害而设计的安全管理程序。 便于编写和使用:RMI使得Java远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。,
展开阅读全文