第 11章 Java网络通信程序的设计
第 11章 Java网络通信程序的设计
11.1 处理 URL内容
11.2 使用 Socket通信
11.3 使用 UDP通信
第 11章 Java网络通信程序的设计
11.1 处理 URL内容
URL(Uniform Resource Locator)是 Internet的关键部
分,它提供了人和机器的导航,其功能是指向计算机
里的资源,即定位。 URL可以分成三个部分:通信协
议、计算机地址和文件。 URL常见的通信协议有三种:
http,ftp和 file。所谓通信协议,就是客户端计算机与
服务器端计算机在网络上通信的方法。
第 11章 Java网络通信程序的设计
有时候在地址后面还要指定使用哪一个端口 (Port),
例如 http://www.sun.com:80/index.html。 如果 URL没有
指定使用哪一个端口, 则会根据通信协议使用默认的
端口 。 一般地, http协议默认端口为 80,ftp协议默认端
口为 21。
java.net包中包含两个专门用于 URL的关键类, 即
URL和 URLConnection。 URL和 URLConnection类封装
了检索远程站点信息的操作, 因而大大地降低了这些
操作的复杂性 。 下面几节将介绍这两个类 。
第 11章 Java网络通信程序的设计
11.1.1 URL类的基本方法
URL类提供的最基本的网络功能是以流的形式读
取 URL所指的的数据 。 URL类的实例可以用表示 URL
的文本串来建立, 以表示 URL所指的数据 。 构造一个
URL类实例的最简单方法是为 URL构造方法赋予一个
字符串:
URL url = new URL(http://java.sun.com/index.html);
这被称为, 绝对, URL,因为赋予的字串指定了
从协议到资源名的全部内容 。 另一种 URL类的构造方
法是构造一个, 相对, URL:
URL data = new URL(url,"data/data.html");
第 11章 Java网络通信程序的设计
这种构造方法指定了位于 url的 data子目录中的
data.html文件,它的绝对地址应该是
http://java.sun.com/data/data.html。
这两种构造方法都可以指定一个 URL,如果指定
的 URL是错误的, 构造方法会抛出一个运行时错误:
MalformedURLException,这个 Excetion通知用户构造
了一个形式错误的 URL。
第 11章 Java网络通信程序的设计
注,URL类既支持 http协议, 也支持 ftp和 file协议 。
如果 URL文本有错或者 Java平台不支持其协议部分,
则这个构造函数抛出一个 MalformedURLException,该
Exception是 java.io.IOException的子类, 指出给定的是
不合法的 URL。 通常应通过 try-catch块处理或声明让调
用方法传递这个异常 。
第 11章 Java网络通信程序的设计
URL类常用的构造函数有下列三种:
● URL(String spec) throws MalformedURLException
创建一个由 spec指定的 URL类的实例 。
● URL(String spec,String host,int port,String file)
throws MalformedURLException
创建一个 URL类的实例, 分别指定其通信协议
(protocal),计算机地址 (host),连接端口 (port)和文件
(file)。 如果 port值是 -1,则表示使用默认端口 。
第 11章 Java网络通信程序的设计
● URL(String protocal,String host,String file) throws
MalformedURLException功能同上, 但没有指定端口,
即使用默认的端口 。
URL类的一些主要方法如下:
● public String getFile():返回 URL中的文件部分 。
● public String getHost():返回 URL中的计算机地址部分 。
● public int getPort():返回 URL中所使用的端口 。
● public String getProtocal():返回 URL中通信协议的部分。
第 11章 Java网络通信程序的设计
下面是一个使用 URL类及其方法的例子 。
例 11.1 URLDemo.java
import genesis.*;
import java.net.*;
public class URLDemo
{
public static void main(String args[])
{
try
{
第 11章 Java网络通信程序的设计
// 创建一个指向 java.sun.com首页的 URL类的实例
URL url = new
URL("http://java.sun.com/index.html");
Transcript.println("Protocol," +
url.getProtocol());
Transcript.println("Host," + url.getHost());
Transcript.println("Port," + url.getPort());
Transcript.println("File," + url.getFile());
}
catch(MalformedURLException e)
{
Transcript.println("错误的 URL! ");
第 11章 Java网络通信程序的设计
}
}
}
这个例子很简单, 因为没有指定 URL的连接端口,
所以显示值为 -1。 实际连接时, 会根据通信协议而决
定使用哪一个端口 。 它的运行结果如图 11.1所示 。
第 11章 Java网络通信程序的设计
图 11.1
第 11章 Java网络通信程序的设计
11.1.2 用 URL类实现页面的访问
一旦构造了 URL,就可以用 URL类中的
openStream()方法读取 URL描述的数据。 openStream()打
开一个到 URL类指定资源的连接,并返回一个
InputStream对象。利用这个对象,可以方便的读取资源
的内容,也可以链接到其他类型的输入流和读取器上。
我们来看一个读取页面内容的例子 。
例 11.2 GetPage.java
import genesis.*;
import java.io.*;
import java.net.*;
第 11章 Java网络通信程序的设计
public class GetPage
{
public static void main(String args[])
{
try
{
URL url = new
URL("http://java.sun.com/index.html");
InputStream in = url.openStream();
BufferedReader reader =
new BufferedReader(new
InputStreamReader(in));
第 11章 Java网络通信程序的设计
// 打开 index.hmtl文件为写做准备
FileWriter fw = new FileWriter("index.html");
PrintWriter pw = new PrintWriter(fw);
String line;
// 逐行读入页面内容
while ((line = reader.readLine()) != null)
{
// 将读入的行保存到 index.html文件
pw.println(line);
// 将读入的行显示在窗口中
Transcript.println(line);
第 11章 Java网络通信程序的设计
}
reader.close();
pw.close();
fw.close();
}
catch(IOException e)
{
Transcript.println(e.getMessage());
}
}
}程序的运行结果如图 11.2所示 。
第 11章 Java网络通信程序的设计
图 11.2
第 11章 Java网络通信程序的设计
上面的例子通过读取 java.sun.com的首页 index.html,
展示了怎样通过 URL访问 Web页面的内容 。 图 11.2窗口
中显示的就是 index.html页面所包含的内容 (即它的源
码 )。 该例中将读入的内容保存在当前目录下, 名为
index.html的文件中, 用浏览器打开这个文件, 可以看
到如同访问 java.sun.com的页面 。 但是, 无法看到页面
中的图片, 因为打开的输入流只读入了页面的内容,
并没有将图片链接也读入进来 。 打开 index.html文件将
看到如图 11.3所示的页面 。
第 11章 Java网络通信程序的设计
图 11.3
第 11章 Java网络通信程序的设计
11.1.3 用 URLConnection类实现页面的访问
我们已经知道了如何通过 URL类访问 URL资源,
但如果想了解关于这个资源的更多信息,就需要使用
URLConnection类。 URLConnection类提供了访问网络
资源时更多、更好的控制方法。
使用 URLConnection类来访问 Web页面的步骤如下:
(1) 调用 URL类的 openConnection()方法得到一个
URLConnection类的实例:
URLConnection conn = url.openConnection();
(2) 调用以下方法, 设置所有相关属性:
第 11章 Java网络通信程序的设计
① setAllowUserInteraction()
② setDoInput()
③ setDoOutput()
④ setIfModifiedSince()
⑤ setUseCaches()
⑥ setRequestProperty()
(3) 调用 connect()方法连接远程资源,
conn.connect();
connect()方法除了创建一个连接指定服务器的套接字
连接外, 还可以查询服务器以获取相应头信息 (header
information)。
第 11章 Java网络通信程序的设计
(4) 连接服务器以后,使用 getHeaderFieldKey()和
getHeaderField()方法来枚举出头信息的所有域。此外,
也可以使用如下的方法来查询标准域的内容:
① getContentEncoding()
② getContentLength()
③ getContentType()
④ getData()
⑤ getExpiration()
⑥ getLastModified()
第 11章 Java网络通信程序的设计
(5) 使用 getInputStream()方法访问资源数据 。 用
getInputStream()方法将返回一个输入流, 此输入流和
URL类的 openStream()方法返回的输入流是相同的 。
下面我们将详细的介绍其中的一些方法 。 在用于
连接服务器前设置连接属性 (第 (2)步 )的几个方法中,
setDoInput()和 setDoOutput()这两个方法最为重要 。 缺
省时, 连接服务器后将产生一个用于读取服务器数据
的输入流, 但不会产生用于向服务器写出数据的输出
流 。 如果需要用到一个输出流 (例如, 用于向 CGI Form
发送数据 ),就必须调用
conn.setDoOutput(true);
第 11章 Java网络通信程序的设计
setIfModifiedSince()方法用于告诉连接:只需要在
给出的日期之后被修改过的数据 。
setUseCaches() 和 setAllowUserInteraction() 方法只
用于 Applet。 setUseCaches()方法指示浏览器先检查它
的高速缓存区, 这样可以优化访问, 如果设置其值为
false,将不使用浏览器的缓存, 其默认值为 true。
setAllowUserInteraction()允许 Applet弹出一个查询用户
名和密码的对话框 。 这些设置在 Applet外部不起作用 。
setRequestProperty()方法设置一个名字 /值对, 用
于说明某一特定的协议 。 例如, 想访问一个口令保护
的网页, 必须进行如下设置:
第 11章 Java网络通信程序的设计
String input = username +","+ password;
conn.setRequestProperty("Authorization","Basic " + input);
注:用 ftp协议访问一个有口令保护的文件时, 将
使用和上面完全不同的方法, 只需要创建一个如下形
式的 URL:
ftp:://username:password@ftp.ftpserver.com/pub/file.txt
当调用了 connect()方法后, 就可以查寻响应头信息 。
我们通过下面的例子来介绍如何获得头信息的所有域
和值以及页面内容 。
第 11章 Java网络通信程序的设计
例 11.3 GetHeaderField.java
import genesis.*;
import java.net.*;
import java.io.*;
public class GetHeaderField
{
public static void main(String args[])
{
try
第 11章 Java网络通信程序的设计
{
URL url = new
URL("http://www.sun.com/index.html");
// 得到一个 URLConnection对象
URLConnection conn = url.openConnection();
// 连接服务器
conn.connect();
int i = 1;
String key = "";
String value = "";
第 11章 Java网络通信程序的设计
// 逐一读出指定 URL中的所有域
while ((key = conn.getHeaderFieldKey(i))
!= null)
{
// 读出对应域的值
value = conn.getHeaderField(i);
Transcript.println(key + "," + value);
i++;
}
// 获取一个输入流
InputStream in = conn.getInputStream();
第 11章 Java网络通信程序的设计
BufferedReader reader =
new BufferedReader(new
InputStreamReader(in));
// 打开 index.hmtl文件为写做准备
FileWriter fw = new FileWriter("index.html");
PrintWriter pw = new PrintWriter(fw);
String line = "";
// 逐行读入页面内容
while ((line = reader.readLine()) != null)
第 11章 Java网络通信程序的设计
{
// 保存到 index.html文件中
pw.println(line);
}
reader.close();
pw.close();
fw.close();
}
catch(IOException e)
第 11章 Java网络通信程序的设计
{
Transcript.println(e.getMessage());
}
}
}
程序的运行结果如图 11.4所示 。
第 11章 Java网络通信程序的设计
图 11.4
第 11章 Java网络通信程序的设计
以上输出结果展示了一个典型的 HTTP请求的响应
头所包含的一组域。 getHeaderFieldKey()和
getHeaderField()方法分别返回响应头中指定位置的域
名和值。指定值基数为 1,当指定值为零或大于头中域
总数时,将返回 null串。由于 URLConnection类中没有
提供获得相应头中域总数的方法,所以需重复调用
getHeaderFieldKey()方法,直到返回 null串来确定头中
域的总数。
第 11章 Java网络通信程序的设计
程序中同时演示了如何通过 URLConnection类获取
页面内容。调用 URLConnection类的方法
getInputStream()将返回一个 InputStream类的实例,这个
对象和调用 URL类中 openStream()返回的 InputStream对
象是相同的。利用这个对象,我们可以建立一个获取
页面内容的输入流。运行例子后,当前目录将会生成
一个 index.html文件,通过浏览器打开这个文件,可以
看到,结果和前面通过 URL访问页面内容所得到的结
果相同。
此外,为了便于查询最常见的响应头域的值,Java
库提供了六个更为方便的方法直接访问这些域,如表
11.1所示。
第 11章 Java网络通信程序的设计
表 11.1 直接获取指定域值的方法
域 名 方 法 返回类型
Data getData long
Expires getExpiration long
Last-Modified getLastModified long
Content-Length getContentLength int
Content-Type getContenttype String
Content-Encoding getContentEncoding String
第 11章 Java网络通信程序的设计
11.1.4 与 CGI的沟通
在 Java技术出现之前,用 CGI(Common Gateway
Interface)来提供相互交换网络数据的方法就已广泛使
用。把信息从网络浏览器发送给相应的网络服务器,
例如使用电子邮件服务,通常需要填写用户名和提交
密码。传统的 HTML语言提供了 Form标记发送格式数
据。一个典型的 HTML Form描述如例 11.4。
第 11章 Java网络通信程序的设计
例 11.4 form.html
<html>
<head>
</head>
<body>
<form method="GET" action="/cgi-bin/login.exe">
<p>Name,<input type="text" name="name" value=""
size="40"></p>
第 11章 Java网络通信程序的设计
<p>Email Address,<input type="text" name="email"
value="" size="40"></p>
<p><input type="submit" name="submit"></p>
</form>
</body>
</html>
通过在浏览器上打开输入以上代码的 html文件, 我们
可以看到一个如图 11.5所示的页面 。
第 11章 Java网络通信程序的设计
图 11.5
第 11章 Java网络通信程序的设计
在 Internet上访问邮件服务时, 用户需要填写一个
类似上图的表单 。 当用户点击提交按键后, 浏览器将
会发送这些填写的数据到 action属性指定的服务器中的
CGI程序 。 CGI程序负责处理这些数据, 然后生成一个
HTML页面, 并送回浏览器 。 对于邮件服务, 这个新
的响应页通常就是我们登陆后的页面 。 (有关 CGI程序
和 HTML Form的详细内容, 本章将不再继续讨论, 如
需了解可参考有关书籍 )
第 11章 Java网络通信程序的设计
除了处理传统的非交互式类型外, URLConnection
类也提供了用于 CGI或 Servlet连接的功能 。 和 HTML表
单页一样, Java程序可以向服务器发出一个 CGI请求,
这个请求既可以设为 GET,也可以设为 POST。 此外,
也可在程序中拦截 CGI程序的输出, 所以通过 Java程序
可以实现和 CGI程序交互信息的全部过程 。
第 11章 Java网络通信程序的设计
通过 GET方法向 CGI程序发送信息时, 只需将参数
置于 URL的末尾, 并用,?”分隔 。 URL的基本格式为
http://host/script?parameters
当包含两个以上的参数时, 用一个, &”分开每个
参数, 并对参数做以下的编码处理:用, +”代替所有
的空格, 用 %加两位十六进制数代替所有非字母的字
符 ( 包括汉字 ) 。 例如, 假 设 一 个 参 数 为, C++
Language”,它将被编码为, C%2b%2b+Language”,
其中 2b(十进制数 43)就是 ASCII码的, +”字符 。 这种编
码方法避免了空格干扰, 并可以解释其他字符, 这种
编码方式称为 URL编码 。
第 11章 Java网络通信程序的设计
GET方法非常方便, 但是有一个大的限制, 就是
浏览器一般会限制 GET请求的 URL串的字符个数 。 而
POST的方法则不同, 使用 POST请求的时候, 不把参
数放在 URL的末端, 而是采用建立一个到服务器的输
出流的方式 。 通过得到一个来自 URLConnection的输出
流, 并把参数名 /值对逐个写入到输出流, 就可以实现
POST方法的请求 。 使用 POST方法时, 仍需用 URL编
码方式处理这些输入的参数, 并用, &”将参数分开 。
第 11章 Java网络通信程序的设计
下面我们仍然通过一个例子来详细的了解 GET和
POST方法与 CGI通信的过程以及它们的不同之处 。
例 11.5 CGITest.java
import genesis.*;
import java.io.*;
import java.net.*;
import java.util.*;
public class CGITest
{
第 11章 Java网络通信程序的设计
URL url = null;
URLConnection conn = null;
FileWriter fw = null;
PrintWriter pw = null;
public static void main(String[] args)
{
CGITest test = new CGITest();
ArrayList param = new ArrayList();
if (args[0].equals("GET") ||
args[0].equals("get"))
第 11章 Java网络通信程序的设计
{
// GET方法所需要的参数
param.add("p");
param.add(args[1]);
param.add("u");
param.add("B");
// GET方法通过 Yahoo提供的搜索引擎搜索给定的关键字
test.doGet("http://search.yahoo.com/search",
param);
}
第 11章 Java网络通信程序的设计
if (args[0].equals("POST") || args[0].equals("post"))
{
// POST方法所需要的参数
param.clear();
param.add("user");
param.add(args[1]);
param.add("pass");
param.add(args[2]);
param.add("type");
第 11章 Java网络通信程序的设计
param.add("0");
param.add("verifycookie");
param.add("y");
// POST方法登陆 163邮箱
test.doPost("http://bjweb.163.net/cgi/163/login_pro.cgi",
param);
}
}
第 11章 Java网络通信程序的设计
public String doGet(String location,java.util.List param)
{
if (param != null)
{
StringBuffer bufParam = new StringBuffer();
// 通过一个枚举器列出所有参数
Iterator iterator = param.iterator();
String key = null;
String value = null;
第 11章 Java网络通信程序的设计
// 枚举出所有的参数
while (iterator.hasNext())
{
key = (String)iterator.next();
value = (String)iterator.next();
// 对参数进行编码, 然后转化成 key=value的格式
// 并将它们串在一起, 通过 &分隔开
bufParam.append(URLEncoder.encode(key));
bufParam.append("=");
bufParam.append(URLEncoder.encode(value));
第 11章 Java网络通信程序的设计
if (iterator.hasNext())
bufParam.append("&");
}
// 把参数连接在 URL后面, 用?号分隔开
location += "?" + bufParam.toString();
}
StringBuffer page = new StringBuffer();
第 11章 Java网络通信程序的设计
try
{
url = new URL(location);
conn = url.openConnection();
// 连接服务器
conn.connect();
// 得到一个输入流
InputStream in = conn.getInputStream();
BufferedReader reader =
new BufferedReader(new
InputStreamReader(in));
第 11章 Java网络通信程序的设计
// 打开 Get.html文件准备写
fw = new FileWriter("Get.html");
pw = new PrintWriter(fw);
String line = "";
// 逐行读入
while ((line = reader.readLine()) != null)
{
page.append(line + "\n");
pw.println(line);
第 11章 Java网络通信程序的设计
}
pw.close();
fw.close();
reader.close();
in.close();
}
catch(IOException e)
{
Transcript.println(e.getMessage());
}
第 11章 Java网络通信程序的设计
return page.toString();
}
public String doPost(String location,java.util.List param)
{
StringBuffer page = new StringBuffer();
try
{
url = new URL(location);
conn = url.openConnection();
第 11章 Java网络通信程序的设计
// 同时打开到服务器的输入和输出流
conn.setDoOutput(true);
conn.setDoInput(true);
if (param != null)
{
// 获取一个到服务器的输入通道
PrintWriter out = new
PrintWriter(conn.getOutputStream());
Iterator iterator = param.iterator();
第 11章 Java网络通信程序的设计
String key = null;
String value = null;
// 枚举列出所有参数
while (iterator.hasNext())
{
// 对参数进行编码, 并逐个将参数转化成
// key=value的格式, 直接输出到服务器
key=
URLEncoder.encode((String)iterator.next());
value=
URLEncoder.encode((String)iterator.next());
第 11章 Java网络通信程序的设计
out.print(key + "=" + value);
if (iterator.hasNext())
out.print("&");
}
out.close();
}
// 连接服务器
conn.connect();
第 11章 Java网络通信程序的设计
// 获取一个输入流
InputStream in = conn.getInputStream();
BufferedReader reader =
newBufferedReader(new
InputStreamReader(in));
// 打开 Post.html准备写
fw = new FileWriter("Post.html");
pw = new PrintWriter(fw);
String line = "";
// 逐行读入
第 11章 Java网络通信程序的设计
while ((line = reader.readLine()) != null)
{
page.append(line + "\n");
pw.println(line);
}
pw.close();
fw.close();
reader.close();
in.close();
第 11章 Java网络通信程序的设计
}
catch(IOException e)
{
Transcript.println(e.getMessage());
}
return page.toString();
}
}
第 11章 Java网络通信程序的设计
这个例子比较长, 但它并不复杂 。 程序中, 通过
两个方法,doGet()和 doPost()分别实现了 GET和 POST
两种方式与 CGI的通信 。 在 doGet()方法中, 测试的是
使用 Yahoo提供的搜索引擎;在 doPost()方法中, 测试
的是登陆 163.net的免费邮件系统 。
编译这个程序后, 测试 doGet()方法, 可以用下面
的命令行运行它:
java CGITest get Java
第 11章 Java网络通信程序的设计
产生的结果将保存在一个叫 Get.html的文件中 。 用
浏览器打开它, 可以看到同在 www.yahoo.com上的搜
索引擎中输入, Java”搜索产生的页面相同的效果 。
doGet()方法需要提供两个参数, 第一个参数是创建交
互的 CGI程序的 URL,第二个参数是一个 List模板类,
里面存放了提供的参数 。 要注意的是, 把这些参数串
在一起并接在 URL的末尾时, 必须用 URLEncoder类的
encode()方法对参数进行 URL编码 。
第 11章 Java网络通信程序的设计
如果要测试 doPost()方法, 须通过下面的命令行运行
这个程序:
java CGITest post Username Password
要测试登陆 163.net的免费邮箱,就必须在 163.net上
注册一个邮箱,并在命令行的第 2,3个参数提供登陆邮
箱的用户名和密码。产生的结果保存在一个叫 Post.html
的文件中。同样,浏览这个文件看到的同在 www.163.net
网站上登录免费邮件系统所看到的页面一样。
第 11章 Java网络通信程序的设计
doPost()方法同样需要提供交互用的 URL和保存参数的
List,但是这里我们没有把参数串在一起, 而是通过
URLConnection类的 getOutputStream()方法打开输入流,
逐个将参数写入到输出流中 。 这样无论有多少参数也
没关系了, 但要注意的是, 仍然要对参数进行 URL编
码并用, &”将它们分开 。
第 11章 Java网络通信程序的设计
注:由于各网站的 CGI程序 URL和参数可能会随
着网站的 程序修改而改变, 所以这个程序并不能保
证一直得到正确的结果, 但是只需查看网站的源代码,
并找到 HTML Form的 action以及 input的所有参数, 对
上面例子中的程序略作修改, 就可以顺利的得到正确
结果了 。
第 11章 Java网络通信程序的设计
11.2 使用 Socket通信
URL和 URLConnection类提供给我们一种简便的方
法编写网络程序, 实现一些较高级的协议访问 Internet。
但是通常, 这些协议是不够的, 我们常需要用一种更
通用, 更底层的方法编写网络应用程序, 这就是我们
最常用的套接字 (Socket)通信 。
第 11章 Java网络通信程序的设计
11.2.1 InetAddress类
Internet上的每一台计算机都拥有一个惟一的地址,
这样才能和其他网络上的计算机互通信息 。 我们常说
的 TCP/IP中的 IP(Internet Protocal)。 就是定义 Internet上
计算机的地址的 。 我们知道, 一个 IP地址必须由四个
0~ 255之间的数字组成, 数字之间用点隔开 。 由于很
难记忆这个由四个数字组成的地址, 所以有了主机名
(host name)的出现, 例如, www.sun.com就是一个主机
名 。 InetAddress就是用来实现 IP的类 。
第 11章 Java网络通信程序的设计
InetAddress 类的声名如下:
public final class InetAddress extends Object
implements SerializableInetAddress类没有提供任何构造
函数, 我们只能通过它本身提供的一些静态方法来建
立一个它的实例 。 通常, 用于建立一个 InetAddress类
的方法有如下几种:
● public static InetAddress getByName(String host)
throws UnknowHostException指定主机名建立一个
InetAddress的实例。
第 11章 Java网络通信程序的设计
● public static InetAddress getByAddress(byte[] addr)
throws UnknowHostException 指定 IP 地 址 建 立 一 个
InetAddress的实例 。
● public static InetAddress getLocalHost()throws
UnknowHostException建立本机的 InetAddress的实例 。
下面我们通过一个实例来看看如何使用 InetAddress类 。
第 11章 Java网络通信程序的设计
例 11.6 InetAddressDemo.java
import genesis.*;
import java.net.*;
public class InetAddressDemo
{
public static void main(String[] args)
{
try
{
第 11章 Java网络通信程序的设计
InetAddress addr1 =
InetAddress.getByName("www.szptt.net.cn");
Transcript.println(addr1.getHostAddress());
InetAddress addr2 =
InetAddress.getByAddress(addr1.getAddress());
Transcript.println(addr2.getHostName());
Transcript.println();
第 11章 Java网络通信程序的设计
InetAddress addr3 = InetAddress.getLocalHost();
Transcript.println(addr3.getHostName());
Transcript.println(addr3.getHostAddress());
}
catch(UnknownHostException e)
{
Transcript.println(e.getMessage());
}
}
}
程序的运行结果如图 11.6所示 。
第 11章 Java网络通信程序的设计
图 11.6
第 11章 Java网络通信程序的设计
从运行结果我们可以看到,调用 getByName()方法
通过主机名建立一个 InetAddress类的实例后,我们就
可以得到对应于这个主机名的地址。需要注意的是,
在调用建立 InetAddress实例的几个静态方法时,这些
方法会连接指定的地址或域名,如果无法连通,会抛
出一个 UnknowHostException,所以必须用 try-catch块
包含这个方法,并处理无法连通的情况。
第 11章 Java网络通信程序的设计
11.2.2 客户端 Socket类
TCP/IP最主要的功能是提供点对点通信机制 。 一
个机器要在网络上与另一台机器通信, 就需要建立一
个互联的通道 。 这个通道传输两个数据流, 一个为发
出的数据流, 一个为接收的数据流 。 由于有了 IP地址,
我们可以方便的在 Internet上找到另一台机器 。 假如自
己的机器要和另一台机器通信, 就必须向那台主机发
起一个连接请求 。 如果这个请求被接受, 这次通信过
程就开始了 。 通常情况下, 我们把发起请求的机器叫
客户端, 被请求的机器则称为服务器 。
第 11章 Java网络通信程序的设计
Socket类是用来编写客户端程序的类 。 Socket类的
声明如下:
public class Socket extends Objects
Socket类常用的方法有下列几种:
● Socket(InetAddress address,int port) throws
IOException建立一个 Socket的实例,用来连接指定的
地址和端口。
● Socket(String host,int port) throws
UnknowHostException,IOException和上面类似,但是
通过主机名代替 InetAddress。
第 11章 Java网络通信程序的设计
● public void close() throws IOException
中断该 Socket连接 。
● public InetAddress getInetAddress()
返回该 Socket实例所连接的地址 。
● public InputStream getInputStream() throws IOException
返回一个输入流, 用来接收服务器端发送的信息 。
● public OutputStream getOutputStream() throws
IOException返回一个输出流, 用来向服务器端发送信息 。
第 11章 Java网络通信程序的设计
● public int getPort()
返回该 Socket实例连接远程的端口 。
● public int getSoTimeout() throws SocketException
返回接收信息的时间限制 。 如果返回值为 0,则表示没有
限制 。
● public void setSoTimeout(int timeout) throws
SocketException
第 11章 Java网络通信程序的设计
设置接收信息的时间限制, 以千分之一秒为单位 。
利用 InputStream接收信息时, 程序会一直挂起, 直到
接收到信息, 或者到达时间限制为止 。 如果出现超时,
则会抛出一个 java.io.InterruptedIOException 。 如果
timeout设为 0,则没有时间限制, 程序会一直挂起等待 。
下面来看一个简单的 Socket编程的示例 。
例 11.7 SocketDemo.java
import genesis.*;
import java.io.*;
import java.net.*;
第 11章 Java网络通信程序的设计
public class SocketDemo
{
public static void main(String[] args)
{
try
{
// 构造一个连接到时间服务器 time-
A.timefreq.bldrdoc.gov
// 端口 13的 Socket类实例 。
Socket sock = new Socket("time-
A.timefreq.bldrdoc.gov",13);
第 11章 Java网络通信程序的设计
// 从 Socket获得一个接收服务器信息的输入流
BufferedReader in
= new BufferedReader(
new
InputStreamReader(sock.getInputStream()));
while(true)
{
String line = in.readLine();
if (null == line)
break;
else
第 11章 Java网络通信程序的设计
Transcript.println(line);
}
}
catch(IOException e)
{
Transcript.println(e.getMessage());
}
}
}
程序的运行结果如图 11.7所示 。
第 11章 Java网络通信程序的设计
图 11.7
第 11章 Java网络通信程序的设计
这个例子非常简单,它打开了一个 Socket类,这
个 Socket类连接到时间服务器 time-
A.timefreq.bldrdoc.gov的 13端口上。当连接成功后,
Socket类的 getInputStream()方法返回一个 InputStream对
象,然后程序把流对象链接到一个 BufferedReader上。
接着就可以用 readLine()方法读取服务器发送的所有字
符,并把它们输出到屏幕上。直到 readLine()方法返回
null,即读完所有服务器发送的信息,程序才会结束。
在 Windows命令行下输入 telnet time-
A.timefreq.bldrdoc.gov 13,可以看到和上面类似的输出
结果,如图 11.8所示。
第 11章 Java网络通信程序的设计
图 11.8
第 11章 Java网络通信程序的设计
11.2.3 服务器端 ServerSocket类
前面实现了一个简单的网络客户程序, 它可以从
服务器上接收信息, 下面我们学习实现服务器端的编
程方法 。 一个服务器程序启动后, 通常会监听某一个
端口, 等待某一台客户端计算机发出连接的请求, 然
后作出反应 。 ServerSocket类的声明如下:
public class ServerSocket extends Objects
ServerSocket类的大部分方法都和 Socket类的类似,
它最常用的构造方法如下:
ServerSocket(int port) throws IOException
第 11章 Java网络通信程序的设计
创建一个 ServerSocket示例是不需要指定 IP地址的,
SeverSocket总是处于监听本机端口的状态 。
下面我们看一个服务器程序的示例 。
例 11.8 ReversalServer.java
import java.io.*;
import java.net.*;
public class ReversalServer
{
public static void main(String[] agrs)
第 11章 Java网络通信程序的设计
{
try
{
// 创建一个监听 8189端口的 ServerSocket
ServerSocket s = new ServerSocket(8189);
// 启动 ServerSocket的监听
Socket insock = s.accept();
// 建立输入流通道
BufferedReader in = new BufferedReader(
new
InputStreamReader(insock.getInputStream()));
第 11章 Java网络通信程序的设计
// 建立输出流通道
PrintWriter out =
new
PrintWriter(insock.getOutputStream(),true/*autoFlush*/);
out.println("Enter quit to exit.");
while(true)
{
String line = in.readLine();
if (null == line)
continue;
第 11章 Java网络通信程序的设计
if (line.trim().equals("quit"))
break;
else
{
// 翻转输入的字符串
StringBuffer rline = new StringBuffer();
for (int i = line.length(); i > 0; i--)
rline.append(line.charAt(i - 1));
out.println("Reversed," + rline.toString());
}
}
第 11章 Java网络通信程序的设计
out.close();
in.close();
insock.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
第 11章 Java网络通信程序的设计
上面的例子是一个简单的服务器程序, 它启动了
一个 ServerSocket监听本机的 8189端口, 这个端口一般
不会被用到 。 当有其他客户端请求与它联接时,
accept()方法将接受这个请求并创建一个独立的 Socket
实例 。 通过这个实例, 我们可以取得输入和输出的两
个流 。 程序首先通过输出流向客户端发送了一条问候
信息:
out.println("Hello! Please input,Enter quit to exit.");
第 11章 Java网络通信程序的设计
然后, 服务器程序接收来自客户端的输入 。 每次
从客户端读取一行信息, 就将这些输入的字符串翻转
以后再送回给客户端, 所以这个服务器程序叫做
ReversalSever。
编译并运行这个程序,然后在 Windows命令行下输
入,telnet 127.0.0.1 8189”。 IP地址 127.0.0.1是一个特殊
的地址,称为本机回送地址 (Local Loopback Address),
它代表着本机。
第 11章 Java网络通信程序的设计
由于运行服务器程序的机器和启动 telnet测试的是
同一主机, 所以这个地址也正是我们要连接的 。 当然,
也可以从其他机器上启动 telnet来测试这个服务器程序,
这时候就必须 telnet到运行服务器程序的 IP地址上 。 启
动 telnet以后, 输入任何信息, 看看结果是不是你想象
的, 最后输入 quit结束连接 。 此处如输入, 123456”,
则结果如图 11.9所示 。
第 11章 Java网络通信程序的设计
图 11.9
第 11章 Java网络通信程序的设计
11.2.4 多客户通信机制
我们注意到, 前面例子中的服务器程序只能服务
于一个客户端, 也就是说, 一个客户连接到这个服务
程序后, 将一直独占它 。 而通常情况下, 我们看到的
服务器总是要服务于许许多多的客户端的, 比如一个
网页服务器, 可以接受许多客户的浏览 。 利用线程的
特性, 我们就可以很好地解决服务于多个客户的问题 。
第 11章 Java网络通信程序的设计
我们只需要对上面的程序做少许修改, 就可以使
它服务于多个客户端 。 首先, 应该把主程序部分放入
一个循环, 每接收到一个来自客户端的请求, 就启动
一个线程来处理他 。 而主程序则可以继续等待来自其
他客户端的请求 。
下面看修改以后的程序, 可试着启动多个 telnet连
接它进行测试 。
第 11章 Java网络通信程序的设计
例 11.9 ThreadedReversalServer.java
import java.io.*;
import java.net.*;
public class ThreadedReversalServer
{
public static void main(String[] agrs)
{
int i = 0;
第 11章 Java网络通信程序的设计
try
{
// 创建一个监听 8189端口的 ServerSocket
ServerSocket s = new ServerSocket(8189);
for (;;)
{
// 启动 ServerSocket的监听
Socket insock = s.accept();
System.out.println("Thread " + i + " run.");
// 启动处理客户端信息的线程
第 11章 Java网络通信程序的设计
new ThreadReversal(insock,i).start();
i++;
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
第 11章 Java网络通信程序的设计
class ThreadReversal extends Thread
{
private Socket sock;
private int counter;
public ThreadReversal(Socket s,int i)
{
sock = s;
counter = i;
}
第 11章 Java网络通信程序的设计
public void run()
{
try
{
// 建立输入流通道
BufferedReader in =
new BufferedReader(
new
InputStreamReader(sock.getInputStream()));
// 建立输出流通道
PrintWriter out =
第 11章 Java网络通信程序的设计
new PrintWriter(sock.getOutputStream(),true/*autoFlush*/);
out.println("Hello! Please input,Enter quit to exit.");
while(true)
{
String line = in.readLine();
if (null == line)
continue;
if (line.trim().equals("quit"))
break;
第 11章 Java网络通信程序的设计
else
{
// 翻转输入的字符串
StringBuffer rline = new StringBuffer();
for (int i = line.length(); i > 0; i--)
rline.append(line.charAt(i - 1));
out.println("Reversed," + rline.toString());
}
}
第 11章 Java网络通信程序的设计
out.close();
in.close();
sock.close();
System.out.println("Thread " + counter + " closed.");
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
第 11章 Java网络通信程序的设计
11.3 使用 UDP通信
前面一节我们学习了如何编写 Socket的网络程序,
即基于 TCP(Transmission Control Protocol,传输控制协
议 )的网络编程。 TCP只能创建两个计算机之间的可靠
连接。一旦建立起一个 TCP连接,我们就不需要处理
传输可靠性的问题,可以认为通过 Socket发送和接收数
据的操作总是成功的。
第 11章 Java网络通信程序的设计
Java平台也支持所谓的 UDP(User Datagram Protocol,
用户数据报协议 )。 UDP可以发送数据报, 但它的开销
比 TCP少得多 。 UDP的缺陷是它不保证数据发送的可
靠性, 数据接收到的顺序可能和发送的顺序不同, 甚
至还可能完全丢失数据报 。 程序设计者必须负责整理
和验证这些数据和请求重发 。 UDP特别适合容忍数据
报部分丢失, 而对实时性要求更高的应用程序, 例如
语音传输等 。
第 11章 Java网络通信程序的设计
java.net包提供了 DatagrameSocket和
DatagramPacket这两个类帮助我们实现基于 UDP的网络
程序设计。 DatagramSocket用于收发数据报,而
DatagramPacket包含发送的具体信息。数据报到达时,
通过 DatagramSocket,信息源的地址和端口将自动初始
化,所以用于接收数据报的 DatagramPacket构造方法为
DatagramPacket(byte[] buf,int length)
第 11章 Java网络通信程序的设计
而发出一个数据报时,DatagramPacket不仅需要包
含数据内容,还要包含发送的地址以及端口,所以用
于发送数据极的 DatagramPacket构造方法为
DatagramPacket(byte[] buf,int length,InetAddress
address,int port)
下面我们看一个 UDP编程的例子。
第 11章 Java网络通信程序的设计
例 11.10
服务器程序 DayBcase.java
import java.net.*;
import java.util.*;
public class DayBcast
{
private DatagramSocket ds;
private DatagramPacket dp;
private InetAddress addr;
第 11章 Java网络通信程序的设计
public static void main(String[] args) throws Exception
{
DayBcast db = new DayBcast("localhost");
db.go();
}
public DayBcast(String target) throws Exception
{
addr = InetAddress.getByName(target);
ds = new DatagramSocket();
第 11章 Java网络通信程序的设计
}
public void go() throws Exception
{
byte[] buff;
for (;;)
{
Thread.sleep(1000);
System.out.println("Sending...");
String s = (new Date()).toString();
第 11章 Java网络通信程序的设计
buff = s.getBytes();
dp = new DatagramPacket(buff,buff.length,
addr,1313);
ds.send(dp);
}
}
}
客户端程序 DayWatch.java
第 11章 Java网络通信程序的设计
import java.net.*;
public class DayWatch
{
private DatagramSocket ds;
private DatagramPacket dp;
public static void main(String args[]) throws Exception
{
DayWatch d = new DayWatch();
d.go();
}
第 11章 Java网络通信程序的设计
public void go() throws Exception
{
byte[] buff = new byte[64];
String s;
ds = new DatagramSocket(1313);
dp = new DatagramPacket(buff,buff.length);
for (;;)
{
第 11章 Java网络通信程序的设计
ds.receive(dp);
s = new String(dp.getData());
System.out.println("Time signal received form
" +
dp.getAddress() + "\n Time is," + s);
}
}
}
先运行服务器程序, 再运行客户端程序, 客户端将
以 1 s的间隔不断显示当前的时间 。 服务器的运行状态
如图 11.10所示 。 客户端的运行状态如图 11.11所示 。
第 11章 Java网络通信程序的设计
图 11.10
第 11章 Java网络通信程序的设计
图 11.11