JavaSE 基础笔记之网络编程

     第十四章 网络编程                                        

 

教学目标:

²        掌握网络编程基本概念

²        掌握 TCP/IP   编程

²        掌握 UDP  编程

一:网络编程基本概念                                                                                                                                      

1:网络通信协议及接口                                                                  

    计算机网络中实现通信必须有一些约定即通信协议,对速率、传输代码、代码结构、传 输控制步骤、出错控制等制定标准。而为了使两个结点之间能进行对话,必须在它们之间

立通信工具,即网络通信接口,使彼此之间能进行信息交换。接口包括两部分:

  1. 硬件装置:实现结点之间的信息传送 
  2. 软件装置:规定双方进行通信的约定协议

    由于结点之间的联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。 最常用的复合方式是层次方式, 即同层间可以通信、 上一层可以调用下

一层,而与再下一层不发生关系。通信协议的分层原则为,把用户应用程序作为最高层,物理通信线路作为最低层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每

层的接口标准,如图所示:

 

    非计算机专业的读者常感困惑的是, 在上述各层间进行通信时到底做了什么事情?简单的说,就是进行了数据的封装和拆封:发送方数据在网络模型的各层传送过程中加入头尾说明性信息,而接受方收到数据后去除相应的头尾。就好比张三与李四两人的通信过程:

张三将写好的信纸装入信封,在这一封装过程中添加了收信人的邮政编码、地址、姓名等信息;而李四收到信后,要先撕开信封才能读

取其中有用的信息。而在此期间,邮局以及运输公司等为保证信件传递的顺利进行,可能还要进行中间层次的封装和拆封工作。

1.1TCP/IP协议

      TCPTransmission  Control  Protocol/IPInternet  Protocol)协议是当前网络数据传输的基础协议。他们可保证不同厂家生产的计算机能在共同网络环境下运行,解

决异构网通信问题,TCP/IP 与低层的数据链路层和物理层无关,能广泛地支持由低两层协议构的物理网络结构。TCP -- 面向连接的可靠数据传输协议;TCP 重发一切没有收到

的数据,进行数据内容准确性检查并保证数据分组的正确顺序。

      IP 协议是网际层的主要协议,支持网间互连的数据报通信。它提供主要功能有:无连接数据报传送、数据报路由选择和差错控制。

      IP 协议主要特性为,IP 协议将报文传送到目的主机后,无论传送正确与否都不进行检验、不回送确认、不保证分组的正确顺序。 为实

现网络中不同计算机之间的通信,每台机器都必须有一个与众不同的标识,这就是IP地址,TCP/IP 使用IP地址来标识源地址和目的地址。IP 地址格式:数字型,32 位,由4 8

的二进制数组成,每8位之间用圆点隔开,如:166.111.78.98

2URL                                                                                          

     URL(统一资源定位器,Uniform  Resource  Locator)用于表示 Internet 上资源的地址。

   这里所说的资源,可以是文件、目录或更为复杂的对象的引用。

     URL一般由协议名、资源所在的主机名和资源名等部分组成,例如下面的URL

     http://home.netscape.com/home/welcome.html 

    所用的协议是httpHypertext Transfer Protocol,超文本传输协议)协议,资源所在的主机名为home.netscape.com,资源名 home/welcome.html。有时资源名也

可省略,

   这样将指向默认的主页面,如http://www.sun.comURL还可以包含端口号来指定与远端主机相连接的端口。如果不指定端口号,则使用默认值。例如,http协议的默认端口

号是 80

   显式指定端口号的URL 形式如下:

    http://java.cs.tsinghua.edu.cn:8888/

    java.net 包定义了对应的URL类。其常用构造方法及用法举例如下:

 

 1 public URL(String spec);
 2 
 3       URL u1 = new URL("http://home.netscape.com/home/");
 4 
 5 public URL(URL context, String spec);
 6 
 7       URL u2 = new URL(u1, "welcome.html");
 8 
 9 public URL(String protocol,String host,String file);
10 
11        URL u3 = new URL("http","www.sun.com","index.html");
12 
13 public URL (String protocol,String host,int port,String file);
14 
15       URL u4 = new URL("http", "www.sun.com", 80, "index.html");

 

 

 

  

 

使用 URL 类的 openStream()方法可以建立到当前 URL 的连接并返回一个可用于从该连接读取数据的输入流对象,方法格式为:

public final InputStream openStream() throws IOException

示例 使用URL 读取网络资源程序:URLReader.java

 

import java.io.*;

import java.net.*;

public class URLReader{

  public static void main(String args[]){

    try{

      URL tirc = new URL("http://www.tsinghua.edu.cn/");

      BufferedReader in = new BufferedReader(new 

        InputStreamReader(tirc.openStream()));

      String s;

      while((s = in.readLine())!=null)

        System.out.println(s);

      in.close(); 

    }catch(MalformedURLException e) {

      System.out.println(e); 

    }catch(IOException e){

      System.out.println(e);

    } 

    }

}

 

程序运行结果为:

 1  
 2 
 3 <html>
 4 
 5 <head>
 6 
 7 <title>清华大学网站首页</title>
 8 
 9 <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
10 
11 <meta http-equiv="refresh" content="0;URL=./eng/index.htm">
12 
13 </head>
14 
15 <body  bgcolor="#FEFAF2"  text="#000000"  topmargin=0  leftmargin=0  marginwidth=0
16 
17 marginheight=0>
18 
19 </body>
20 
21 </html>

 

    如使用了代理服务器,则应通过添加系统属性的方式指名代理服务器的机器名(或 IP地址)及端口号。此时运行命令格式如下:

java -Dhttp.proxyHost=<hostname|hostIP> -Dhttp.proxyPort=<port> <class_name>

例如java -Dhttp.proxyHost=196.168.100.2 -Dhttp.proxyPort=8129 URLReader

3:连接的地址                                                                           

    你发起电话呼叫时,你必须知道所拨的电话号码。如果要发起网络连接,你需要知道远程机器的地址或名字。此外,每个网络连接需要一个端口号,你可以把它想象成电话的分

号码。一旦你和一台计算机建立连接,你需要指明连接的目的。所以,就如同你可以使用一个特定的分机号码来和财务部门对话那样, 你可以使用一个特定的端口号来和会计程序

进行通信。

4:端口号                                                                               

     TCP/IP 系统中的端口号是一个16位的数字,它的范围是 0~65535。实际上,小于 1024的端口号保留给预定义的服务,而且除非要和那些服务之一进行通信(例如

telnetSMTP邮件和ftp),否则你不应该使用它们。 客户和服务器必须事先约定所使用的端口。如果系统两部分所使用的端口不一致,那就不能进行通信。

5Java网络模型                                                                        

    在Java编程语言中,TCP/IP  socket连接是用 java.net包中的类实现的。下图说明了服务器和客户端所发生的动作。

 

 

  1. 服务器分配一个端口号。如果客户请求一个连接,服务器使用 accept()方法打开socket 连接。
  2. 客户在hostport端口建立连接。
  3. 服务器和客户使用InputStream OutputStream进行通信。

 

二:TCP/IP   编程                                                                                

1Socket基础                                                                          

    计算机以一种非常简单的方式进行相互间的操作和通信。 计算机芯片是以 1 0 的形式存储并传输数据的开—闭转换器的集合。当计算机想共享数据时,它们所需做的全部就

以一致的速度、顺序、定时等等来回传输几百万比特和字节的数据流。每次想在两个应用程序之间进行信息通信时,您怎么会愿意担心那些细节呢?

    为免除这些担心,我们需要每次都以相同方式完成该项工作的一组包协议。这将允许我们处理应用程序级的工作,而不必担心低级网络细节。这些成包协议称为协议栈

stack

    TCP/IP 是当今最常见的协议栈。多数协议栈(包括 TCP/IP)都大致对应于国际标准化组织International  Standards OrganizationISO)的开放系统互连参考模型

Open  Systems Interconnect  Reference  ModelOSIRM)。OSIRM 认为在一个可靠的计算机组网中有七个逻辑层(见图)。

     各个地方的公司都对这个模型某些层的实现做了一些贡献,从生成电子信号(光脉冲、射频等等)到提供数据给应用程序。

       socket 是指在一个特定编程模型下,进程间通信链路的端点。因为这个特定编程模型的流行,socket这个名字在其他领域得到了复用,包括 Java 技术。

     当进程通过网络进行通信时,Java 技术使用它的流模型。一个 socket包括两个流:一个输入流和一个输出流。如果一个进程要通过网络向另一个进程发送数据,只需简单地

写入socket相关联的输出流。 一个进程通过从与 socket相关联的输入流读来读取另一个进程所写的数据。

    建立网络连接之后,使用与socket 相关联的流和使用其他流是非常相似的。 我们不想涉及层的太多细节,但您应该知道套接字位于什么地方

     使用套接字的代码工作于表示层。表示层提供应用层能够使用的信息的公共表示。假设您打算把应用程序连接到只能识别 EBCDIC的旧的银行系统。应用程序的域对象以 ASCII

格式存储信息。在这种情况下, 您得负责在表示层上编写把数据从 EBCDIC 转换成ASCII 代码,然后(比方说)给应用层提供域对象。应用层然后就可以用域对象来做它想做的

任何事情。

   您编写的套接字处理代码只存在于表示层中。 您的应用层无须知道套接字如何工作的任何事情。

   简言之,一台机器上的套接字与另一台机器上的套接字交谈就创建一条通信通道。程序员可以用该通道来在两台机器之间发送数据。当您发送数据时,TCP/IP 协议栈的每一层都

 

会添加适当的报头信息来包装数据。这些报头帮助协议栈把您的数据送到目的地。好消息 Java 语言通过""为您的代码提供数据,从而隐藏了所有这些细节,这也是为什么它们

时候被叫做流套接字(streaming

socket)的原因。

   把套接字想成两端电话上的听筒 我和您通过专用通道在我们的电话听筒上讲话和聆听。直到我们决定挂断电话,对话才会结束(除非我们在使用蜂窝电话)。而且我们各自

电话线路都占线,直到我们挂断电话。

  如果想在没有更高级机制如 ORB(以及 CORBARMIIIOP 等等)开销的情况下进行两台计算机之间的通信,那么套接字就适合您。套接字的低级细节相当棘手。幸运的

是,Java 平台给了您一些虽然简单但却强大的更高级抽象,使您可以容易地创建和使用套接字。

 

2:套接字的类型                                                                          

   一般而言,Java 语言中的套接字有以下两种形式:

  1. l  TCP 套接字(由  Socket  类实现,稍后我们将讨论这个类) 
  2. l  UDP 套接字(由  DatagramSocket  类实现) 

    TCP UDP 扮演相同角色,但做法不同。两者都接收传输协议数据包并将其内容向前传送到表示层。TCP 把消息分解成数据包(数据报,datagrams)并在接收端以正确的顺

把它们重新装配起来。TCP 还处理对遗失数据包的重传请求。有了 TCP,位于上层的层要心的事情就少多了。UDP 不提供装配和重传请求这些功能。它只是向前传送信息包。

位于上层的层必须确保消息是完整的并且是以正确的顺序装配的。

   一般而言,UDP 强加给您的应用程序的性能开销更小,但只在应用程序不会突然交换大量数据并且不必装配大量数据报以完成一条消息的时候。否则,TCP 才是最简单或许也是

高效的选择.因为多数读者都喜欢 TCP 胜过 UDP 所以我们将把讨论限制在 Java 语言中面 TCP 的类。

 

3:编写简单的Socket程序                                                                

     java.net 包中定义了两个类Socket ServerSocket,分别用来表示双向连接的客户端和服务器端。其常用的构造方法有:

Socket(InetAddress  address, int  port);

Socket(InetAddress  address, int  port, boolean  stream);

Socket(String host, int port);

Socket(String host, int port, boolean stream);

ServerSocket(int port);

ServerSocket(int port, int count);

   网络编程的四个基本步骤为:

   创建socket

   打开连接到socket的输入/输出流

   按照一定的协议对socket进行读/写操作

   关闭socket

示例 简单的client/server 程序:TestServer.java

 1 import java.net.*;
 2 
 3 import java.io.*;
 4 
 5  
 6 
 7 public class TestServer {
 8 
 9   public static void main(String args[]) {
10 
11     try {   
12 
13       ServerSocket s = new ServerSocket(8888);
14 
15       while (true) {
16 
17         Socket s1 = s.accept();
18 
19         OutputStream os = s1.getOutputStream();
20 
21         DataOutputStream dos =
22 
23 new DataOutputStream(os);
24 
25         dos.writeUTF("Hello," +s1.getInetAddress() +
26 
27 "port#" + s1.getPort() + "\nbye!");
28 
29         dos.close();
30 
31         s1.close();
32 
33       }
34 
35     }catch (IOException e) {
36 
37       System.out.println("程序运行出错:" + e);   
38 
39     }
40 
41   }
42 
43 }

 

 

程序:TestClient.java

 1 import java.net.*;
 2 
 3 import java.io.*;
 4 
 5  
 6 
 7 public class TestClient {
 8 
 9   public static void main(String args[]) {
10 
11  
12 
13     try {
14 
15       Socket s1 = new Socket("127.0.0.1", 8888);
16 
17       InputStream is = s1.getInputStream();
18 
19       DataInputStream dis = new DataInputStream(is);
20 
21       System.out.println(dis.readUTF());
22 
23       dis.close();
24 
25       s1.close();
26 
27     } catch (ConnectException connExc) {
28 
29       System.err.println("服务器连接失败!");
30 
31     } catch (IOException e) {
32 
33     }
34 
35   }
36 
37 }

 

 

      TestServer.java TestClient.java 程序可运行在两台不同的机器上,首先运行TestServer程序,创建ServerSocket对象、监听所机器(服务器)的指定 8888号端

口,等待客户端连接请求;然后运行 TestClient 程序,创建 Socket 对象,连接服务器的 8888号端口。建立连接后,服务器端程序打开其 Socket 对象关联的输出流,并向其中

写出有关连接者的信息,然后关闭输出流及Socket对象、程序退出;客户端程序打开 Socket 对象关联的输入流,从中读取服务器端发送来的信息并显示到屏幕上。程序运行结果为:

Hello,zhangliguo/127.0.0.1port#1032  bye-bye!

    其中,"zhangliguo/127.0.0.1"为客户端机器名和 IP地址,1032是客户端由机器指定的发出连接请求的端口号。上述程序也可以运行在同一台计算机上, 此时该机器既是服

务器,又是客户机,但运行原理是相同的。

 

4JAVA中的socket实现                                                                   

      Java 平台在  java.net 包中提供套接字的实现。在本教程中,我们将与  java.net 中的以

   下三个类一起工作:

URLConnection  Socket ServerSocket 

     java.net 中还有更多的类,但这些是您将最经常碰到的。让我们从 URLConnection 始。 这个类为您不必了解任何底层套接字细节就能在 Java 代码中使用套接字提供一种

途径URLConnection.

     URLConnection 类是所有在应用程序和 URL 之间创建通信链路的类的抽象超类。

     URLConnection 在获取 Web 服务器上的文档方面特别有用, 但也可用于连接由 URL 标识的任何资源。该类的实例既可用于从资源中读,也可用于往资源中写。例如,您可

以连接到一 servlet 并发送一个格式良好的 XML String 到服务器上进行处理。

     URLConnection 具体子类(例如 HttpURLConnection)提供特定于它们实现的额外功能。对于我们的示例,我们不想做任何特别的事情,所以我们将使用

URLConnection 本身提供的缺省行为。

连接到 URL 包括几个步骤:

1.  创建 URLConnection 

2.  用各种 setter 方法配置它 

3.  连接到 URL 

4.  用各种 getter 方法与它交互 

5.  接着,我们将看一些演示如何用  URLConnection 来从服务器请求文档的样本代码。

 

4.1URLClient

   我们将从  URLClient 类的结构讲起。

 1 import java.io.*;
 2 
 3 import java.net.*;
 4 
 5 public class URLClient {
 6 
 7     protected URLConnection connection;
 8 
 9     public static void main(String[] args) {
10 
11     }
12 
13     public String getDocumentAt(String urlString) {
14 
15     }
16 
17 }

 

    要做的第一件事是导入 java.net java.io 我们给我们的类一个实例变量以保存一 URLConnection。我们的类有一个 main()方法,它处理浏览文档的逻辑流。我们的类

有一个 getDocumentAt() 方法,该方法连接到服务器并向它请求给定文档。下面我们将别探究这些方法的细节。

4.2:浏览文档

      main() 方法处理浏览文档的逻辑流:

1 public static void main(String[] args) {
2 
3     URLClient client = new URLClient();
4 
5     String yahoo = client.getDocumentAt("http://www.yahoo.com");
6 
7     System.out.println(yahoo);
8 
9 }

 

    我们的 main() 方法只是创建一个新的 URLClient 并用一个有效的 URL String 调用 getDocumentAt() 当调用返回该文档时, 我们把它存储在 String 然后将它打印到控制台。

    然而,实际的工作是在 getDocumentAt() 方法中完成的。

4.3:从服务器请求一个文档

     getDocumentAt() 方法处理获取  Web 上的文档的实际工作:

 1 public String getDocumentAt(String urlString) {
 2 
 3     StringBuffer document = new StringBuffer();
 4 
 5     try {
 6 
 7         URL url = new URL(urlString);
 8 
 9         URLConnection conn = url.openConnection();
10 
11         BufferedReader  reader  =  new  BufferedReader(new
12 
13  
14 
15 InputStreamReader(conn.getInputStream()));
16 
17  
18 
19         String line = null;
20 
21         while ((line = reader.readLine()) != null)
22 
23         document.append(line + "\n");
24 
25         reader.close();
26 
27     } catch (MalformedURLException e) {
28 
29         System.out.println("Unable  to connect  to  URL:  "  +  urlString);
30 
31     } catch (IOException e) {
32 
33         System.out.println("IOException when connecting to URL: " +
34 
35 urlString);
36 
37     }
38 
39     return document.toString();
40 
41 }

 

      getDocumentAt() 方法有一个  String 参数,该参数包含我们想获取的文档的  URL。我们在开始时创建一个  StringBuffer 来保存文档的行。然后我们用我们传进去的 

urlString 建一个新  URL。接着创建一个  URLConnection 并打开它:

URLConnection conn = url.openConnection();

    一 一个   URLConnection ,我 们就    InputStream  InputStreamReader 然后我们又把 InputStreamReader  包装进 

BufferedReader  以使我们能够读取想从服务器上获取的文档的行。在  Java  代码中处理套接字时,我们将经常使用这种包装技术,但我们不会总是详细讨论它。在我们继续往前

讲之前,您应该熟悉它:

BufferedReader reader= new BufferedReader(new InputStreamReader(conn.getInputStream()));

    有了  BufferedReader,就使得我们能够容易地读取文档内容。我们在  while  循环中调  reader  上的  readLine()

String line = null;

while ((line = reader.readLine()) != null)

    document.append(line + "\n");

    对 readLine() 的调用将直至碰到一个从 InputStream 传入的行终止符 (例如换行符)时才阻塞。如果没碰到,它将继续等待。只有当连接被关闭时,它才会返回 null。在这

案例中,一旦我们获取一个行(line ,我们就把它连同一个换行符一起附加append)到名为 document StringBuffer 上。这保留了服务器端上读取的文档的格式。

    我们在读完行之后关闭 BufferedReader

reader.close();

    如 供给   URL     urlString  ,那 MalformedURLException。如果发生了别的错误,例如当从连接上获取 InputStream 时,那

么将抛出 IOException

 

4.4:总结

    实际上,URLConnection 使用套接字从我们指定的 URL 中读取信息(它只是解析成 IP 地址),但我们无须了解它,我们也不关心。但有很多事;我们马上就去看看。

    在继续往前讲之前,让我们回顾一下创建和使用 URLConnection 的步骤:

用您想连接的资源的有效  URL  String 实例化一个  URL(如有问题则抛出

MalformedURLException)。

打开该 URL 上的一个连接。

把该连接的 InputStream 包装进 BufferedReader 以使您能够读取行。

BufferedReader 读文档。

关闭 BufferedReader 

URLClient 的代码清单

 1 import java.io.*;
 2 
 3 import java.net.*;
 4 
 5 public class URLClient {
 6 
 7     protected HttpURLConnection connection;
 8 
 9     public String getDocumentAt(String urlString) {
10 
11         StringBuffer document = new StringBuffer();
12 
13         try {
14 
15             URL url = new URL(urlString);
16 
17             URLConnection conn = url.openConnection();
18 
19             BufferedReader  reader  =  new  BufferedReader(new
20 
21 InputStreamReader(conn.getInputStream()));
22 
23             String line = null;
24 
25             while ((line = reader.readLine()) != null)
26 
27                 document.append(line + "\n");
28 
29             reader.close();
30 
31         } catch (MalformedURLException e) {
32 
33             System.out.println("Unable  to  connect  to  URL:  "  +
34 
35 urlString);
36 
37         } catch (IOException e) {
38 
39             System.out.println("IOException when connecting to URL: "
40 
41 + urlString);
42 
43         }
44 
45         return document.toString();
46 
47     }
48 
49     public static void main(String[] args) {
50 
51         URLClient client = new URLClient();
52 
53         String  yahoo  =  client.getDocumentAt("http://www.yahoo.com");
54 
55         System.out.println(yahoo);
56 
57     }
58 
59 }

 

 

三:UDP 编程                                                                                        

      TCP/IP是面向连接的协议。而用户数据报协议(UDP)是一种无连接的协议。要区分这两种协议,一种很简单而又很贴切的方法是把它们比作电话呼叫和邮递信件。

    电话呼叫保证有一个同步通信;消息按给定次序发送和接收。而对于邮递信件,即使能收到所有的消息,它们的顺序也可能不同。

    用户数据报协议(UDP)Java软件的 DatagramSocket DatagramPacket类支持。 包是自包含的消息,它包括有关发送方、消息长度和消息自身。

 

1DatagramPacket                                                                     

DatagramPacket 有两个构造函数:一个用来接收数据,另一个用来发送数据:

DatagramPacket(byte [] recvBuf, int readLength)

     用来建立一个字节数组以接收 UDP 包。byte 数组在传递给构造函数时是空的,而int值用来设定要读取的字节数(不能比数组的大小还大)

        DatagramPacket(byte [] sendBuf, int sendLength, InetAddress iaddr, int iport) 用来建立将要传输的UDP包。 sendLength应该比sendBuf 字节数组的大小要大。 

 

2DatagramSocket                                                                     

        DatagramSocket 用来读写 UDP 包。这个类有三个构造函数,允许你指定要绑定的端口号和internet地址:

        DatagramSocket()-绑定本地主机的所有可用端口

        DatagramSocket(int port)-绑定本地主机的指定端口

        DatagramSocket(int port, InetAddress iaddr)-绑定指定地址的指定端口

 

3:最小UDP服务器                                                                      

    最小UDP服务器在8000 端口监听客户的请求。当它从客户接收到一个DatagramPacket时,它发送服务器上的当前时间。

      UDP socket

  1. 它们是无连接的协议。
  2. 不保证消息的可靠传输。
  3. 它们由Java 技术中的 DatagramSocket DatagramPacket支持。

      DatagramPacket

      DatagramPacket 有两个构造函数:一个用来接收数据,另一个用来发送数据。

      DatagramPacket(byte [] recvBuf, int readLength)

      DatagramPacket(byte [] sendBuf, int sendLength, InetAddress iaddr, int iport)

 

       

  1  import java.io.*;
  2 
  3         import java.net.*; 
  4 
  5         import java.util.*; 
  6 
  7         public class UdpServer{ 
  8 
  9         //This method retrieves the current time on the server 
 10 
 11         public byte[] getTime(){ 
 12 
 13         Date d= new Date(); 
 14 
 15        return d.toString().getBytes(); 
 16 
 17        } 
 18 
 19        // Main server loop. 
 20 
 21       public void go() throws IOException { 
 22 
 23        DatagramSocket datagramSocket; 
 24 
 25        DatagramPacket inDataPacket; // Datagram packet from the client 
 26 
 27        DatagramPacket outDataPacket; // Datagram packet to the client 
 28 
 29        InetAddress clientAddress; // Client return address 
 30 
 31        int clientPort; // Client return port 
 32 
 33        byte[] msg= new byte[10]; // Incoming data buffer. Ignored. 
 34 
 35        byte[] time; // Stores retrieved time 
 36 
 37        // Allocate a socket to man port 8000 for requests. 
 38 
 39        datagramSocket = new DatagramSocket(8000); 
 40 
 41        System.out.println("UDP server active on port 8000"); 
 42 
 43        // Loop forever 
 44 
 45        while(true) { 
 46 
 47        // Set up receiver packet. Data will be ignored. 
 48 
 49        inDataPacket = new DatagramPacket(msg, msg.length); 
 50 
 51        // Get the message. 
 52 
 53       datagramSocket.receive(inDataPacket); 
 54 
 55        // Retrieve return address information, including InetAddress 
 56 
 57        // and port from the datagram packet just recieved. 
 58 
 59        clientAddress = inDataPacket.getAddress(); 
 60 
 61        clientPort = inDataPacket.getPort(); 
 62 
 63        // Get the current time. 
 64 
 65        time = getTime(); 
 66 
 67       //set up a datagram to be sent to the client using the 
 68 
 69  
 70 
 71        //current time, the client address and port 
 72 
 73        outDataPacket = new DatagramPacket 
 74 
 75        (time, time.length, clientAddress, clientPort); 
 76 
 77        //finally send the packet 
 78 
 79        datagramSocket.send(outDataPacket); 
 80 
 81        } 
 82 
 83        } 
 84 
 85        public static void main(String args[]) { 
 86 
 87        UdpServer udpServer = new UdpServer(); 
 88 
 89        try { 
 90 
 91       udpServer.go(); 
 92 
 93        } catch (IOException e) { 
 94 
 95        System.out.println ("IOException occured with socket."); 
 96 
 97        System.out.println (e); 
 98 
 99        System.exit(1); 
100 
101        } 
102 
103        } 
104 
105        } 

 

 

 

 

4:最小UDP客户                                                                        

   最小UDP客户向前面创建的客户发送一个空包并接收一个包含服务器实际时间的包。

     

 1   import java.io.*;
 2 
 3         import java.net.*;   
 4 
 5         public class UdpClient { 
 6 
 7        public void go() throws IOException, UnknownHostException { 
 8 
 9        DatagramSocket datagramSocket; 
10 
11         DatagramPacket outDataPacket; // Datagram packet to the server 
12 
13         DatagramPacket inDataPacket; // Datagram packet from the server 
14 
15        InetAddress
16 
17 serverAddress;  //  Server  host
18 
19 address 
20 
21        byte[] msg = new byte[100]; // Buffer space. 
22 
23        String receivedMsg; // Received message in String form. 
24 
25        // Allocate a socket by which messages are sent and received. 
26 
27        datagramSocket = new DatagramSocket(); 
28 
29        // Server is running on this same machine for this example. 
30 
31        // This method can throw an UnknownHostException. 
32 
33        serverAddress = InetAddress.getLocalHost(); 
34 
35  
36 
37   
38 
39       // Set up a datagram request to be sent to the server. 
40 
41       // Send to port 8000. 
42 
43        outDataPacket = new DatagramPacket(msg, 1, serverAddress, 8000); 
44 
45        // Make the request to the server. 
46 
47        datagramSocket.send(outDataPacket);   
48 
49        // Set up a datagram packet to receive server's response. 
50 
51        inDataPacket = new DatagramPacket(msg, msg.length);   
52 
53        // Receive the time data from the server 
54 
55        datagramSocket.receive(inDataPacket); 
56 
57        // Print the data received from the server 
58 
59        receivedMsg = new String 
60 
61       (inDataPacket.getData(), 0, inDataPacket.getLength()); 
62 
63        System.out.println(receivedMsg);   
64 
65        //close the socket 
66 
67        datagramSocket.close(); 
68 
69        } 
70 
71        public static void main(String args[]) { 
72 
73        UdpClient udpClient = new UdpClient(); 
74 
75        try { 
76 
77        udpClient.go(); 
78 
79        } catch (Exception e) { 
80 
81        System.out.println ("Exception occured with socket."); 
82 
83        System.out.println (e); 
84 
85        System.exit(1); 
86 
87        } 
88 
89        } 
90 
91        } 

 

 

 

练习实践                                            

本章的内容为Socket,实践重点:

  1. l  Socket基础

程序 1                                                                                 

Socket通讯

需求:用Socket创建一个服务器及客户端,并使其通讯。

目标:

1 Socket基础;

2 服务器的建立,客户端的建立;

3 通讯方式。

程序1(服务器端程序):

 

//: JabberServer.java

 1 import java.io.*;
 2 
 3 import java.net.*;
 4 
 5  
 6 
 7 public class JabberServer {
 8 
 9          // Choose a port outside of the range 1-1024:
10 
11          public static final int PORT = 8080;
12 
13          public static void main(String[] args)
14 
15                              throws IOException {
16 
17                    ServerSocket s = new ServerSocket(PORT);
18 
19                    System.out.println("Started: " + s);
20 
21                    try {
22 
23                              // Blocks until a connection occurs:
24 
25                              Socket socket = s.accept();
26 
27                              try {
28 
29                                        System.out.println(
30 
31                                                              "Connection  accepted:  "+
32 
33 socket);
34 
35                                        BufferedReader in =
36 
37                                                              new BufferedReader(
38 
39                                                              new InputStreamReader(
40 
41                                                              socket.getInputStream()));
42 
43                                        // Output is automatically flushed
44 
45                                        // by PrintWriter:
46 
47                                        PrintWriter out =
48 
49                                                              new PrintWriter(
50 
51                                                              new BufferedWriter(
52 
53                                                              new OutputStreamWriter(
54 
55                                                              socket.getOutputStream())),true
56 
57                                        while (true) {
58 
59                                                  String str = in.readLine();
60 
61                                                  if (str.equals("END")) break;
62 
63                                                  System.out.println("Echoing: " + str);
64 
65                                                  out.println(str);
66 
67                                        }
68 
69                                        // Always close the two sockets...
70 
71                              } finally {
72 
73                                        System.out.println("closing...");
74 
75                                        socket.close();
76 
77                              }
78 
79                    } finally {
80 
81                              s.close();
82 
83                    }
84 
85          }
86 
87 }
88 
89  

说明:

1 ServerSocket需要的只是一个端口编号,不需要IP地址(因为它就在这台机器上运行)。调用 accept()时,方法会暂时陷入停顿状态(堵塞) ,直到某个客户

尝试同它建立连接。换言之,尽管它在那里等候连接,但其他进程仍能正常运行。建好一个连接以后,accept()就会返回一个 Socket 对象,它是那个连接的代表。

 

2 假如ServerSocket构建器失败, 则程序简单地退出 (注意必须保证 ServerSocket的构建器在失败之后不会留下任何打开的网络套接字)。针对这种情

况,main()会抛出一个IOException违例,所以不必使用一个try块。若 ServerSocket构建成功执行,则其他所有方法调用都必须到一个try-finally代码块里寻求

保护,以确保无论块以什么方式留下,ServerSocket都能正确地关闭。

 

3 同样的道理也适用于由accept()返回的 Socket。若 accept()失败,那么我们必须保证Socket不再存在或者含有任何资源, 以便不必清除它们。 但假若执行成

功,则后续的语句必须进入一个 try-finally 块内,以保障在它们失败的情况下,Socket仍能得到正确的清除。由于套接字使用了重要的非内存资源,所以在这里必须

特别谨慎,必须自己动手将它们清除Java 中没有提供“破坏器”来帮助我们做这件事情)。

 

4 无论ServerSocket还是由 accept()产生的 Socket都打印到 System.out里。这意味着它们的toString方法会得到自动调用。这样便产生了:

ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]

Socket[addr=127.0.0.1,PORT=1077,localport=8080]

 

5 一般服务器应先启动,启动后,运行如下:

 

 

 

    程序2(客户端程序):

 

//: JabberClient.java

 1 import java.net.*;
 2 
 3 import java.io.*;
 4 
 5  
 6 
 7 public class JabberClient {
 8 
 9          public static void main(String[] args)
10 
11                              throws IOException {
12 
13                    // Passing null to getByName() produces the
14 
15                    // special "Local Loopback" IP address, for
16 
17                    // testing on one machine w/o a network:
18 
19                    InetAddress addr =
20 
21                                        InetAddress.getByName(null);
22 
23                    // Alternatively, you can use
24 
25                    // the address or name:
26 
27                    // InetAddress addr =
28 
29                    //     InetAddress.getByName("127.0.0.1");
30 
31                    // InetAddress addr =
32 
33                    //     InetAddress.getByName("localhost");
34 
35                    System.out.println("addr = " + addr);
36 
37                    Socket socket =
38 
39                                        new Socket(addr, JabberServer.PORT);
40 
41                    // Guard everything in a try-finally to make
42 
43                    // sure that the socket is closed:
44 
45                    try {
46 
47                              System.out.println("socket = " + socket);
48 
49                              BufferedReader in =
50 
51                                                  new BufferedReader(
52 
53                                                  new InputStreamReader(
54 
55                                                  socket.getInputStream()));
56 
57  
58 
59                              // Output is automatically flushed
60 
61                              // by PrintWriter:
62 
63                              PrintWriter out =
64 
65                                                  new PrintWriter(
66 
67                                                  new BufferedWriter(
68 
69                                                  new OutputStreamWriter(
70 
71                                                  socket.getOutputStream())),true);
72 
73                              for(int i = 0; i < 10; i ++) {
74 
75                                        out.println("howdy " + i);
76 
77                                        String str = in.readLine();
78 
79                                        System.out.println(str);
80 
81                              }
82 
83                              out.println("END");
84 
85                    } finally {
86 
87                              System.out.println("closing...");
88 
89                              socket.close();
90 
91                    }
92 
93          }
94 
95 }

 

 

    说明:

1.  运行客户端时,不能关服务器端程序运行窗口,而应新开一个窗口;

 

2.  main()中,大家可看到获得本地主机 IP 地址的 InetAddress 的三种途径:使用 null使用localhost,或者直接使用保留地址 127.0.0.1。当然,如果想通过网络同一台远程主机连接,也可以换用那台机器的 IP地址。打印出 InetAddress addr 后(通过对 toString()

方法的自动调用),结果如下:

localhost/127.0.0.1

通过向getByName()传递一个 null,它会默认寻找 localhost,并生成特殊的保留地址127.0.0.1

 

3.  一次独一无二的因特网连接是用下述四种数据标识的:clientHost(客户主机) clientPortNumber(客户端口号) serverHost(服务主机)以及

serverPortNumber(服务端口号)。服务程序启动后,会在本地主机(127.0.0.1)上建立为它分配的端口(8080)。一旦客户程序发出请求, 机器上下一个可用

的端口就会分配给它 (这种情况下是1077),这一行动也在与服务程序相同的机器(127.0.0.1)上进行。

 

4.  现在,为了使数据能在客户及服务程序之间来回传送,每一端都需要知道把数据发到哪里。所以在同一个“已知”服务程序连接的时候,客户会发出一个“返回地址”,使服务器程序知道将自己的数据发到哪儿。我们在服务器端的示范输出中可以体会到这一情况: 

Socket[addr=127.0.0.1,port=1077,localport=8080]

 

这意味着服务器刚才已接受了来自 127.0.0.1这台机器的端口 1077的连接,同时监听自己的本地端口(8080)。而在客户端:

Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]

这意味着客户已用自己的本地端口 1077 127.0.0.1机器上的端口 8080 建立了连接。

 

5.  客户端程序运行如下:

 

 

 

与此同时,服务器端程序改变如下:

 

 

 

 

 

作业                                                                              

1:修改程序 1,删去为输入和输出设计的所有缓冲机制,然后再次编译和运行,观察一下结果。

2:修改程序2JabberClient,禁止输出刷新,并观察结果。

3:可以尝试做一个类似QQ 的聊天通讯工具

 

 

posted @ 2014-04-24 20:11  Jemutse  阅读(163)  评论(0)    收藏  举报