`
holdbelief
  • 浏览: 699473 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

第十七课时: 网络编程

阅读更多

一. 网络编程的基础知识

1、网络基础知识

计算机网络通常是按照规模大小和延伸范围来分类的, 常见的划分为: 局域网(LAN), 城域网(MAN), 广域网(WAN). INTERNET 可以视为世界上最大的广域网.

如果按照网络的拓扑结构来划分, 可以分为星型网络, 总线网络, 环形网络, 树形网络, 星型环线网络等; 如果按照网络的传输介质来划分, 可以分为双绞线网, 同轴电缆网, 光纤网和卫星网等.

计算机网络中实现通信必须有一些约定, 即通信协议. 对速率, 传输代码, 代码结构, 传输控制步骤, 出错控制等制定标准. 国际标准化组织 ISO 于 1978 年提出"开放系统互连参考模型", 即著名的 OSI (Open System Interconnection), 它把计算机网络分成物理层, 数据链路层, 网络层, 传输层, 会话层, 表示层, 应用层等七层.

网络通信之间必须有硬件和软件方面的支持, 通信协议是网络通信的基础, 而 IP 协议是一种非常重要的协议, IP (InternetProtocol) 协议又称互联网协议, 是支持网间互联的数据报协议, 它提供网间连接的完善功能, 包括 IP 数据报规定互联网络内的地址格式.

经常与 IP 协议放在一起的还有 TCP(Transmission Control Protocol) 协议, 即传输控制协议, 它规定一种可靠的数据信息传递服务. 虽然 IP 和 TCP 这两个协议功能不尽相同, 也可以分开单独使用, 但它们是在同一个时期作为一个协议设计的, 并且在功能上也是互补的, 因此实际使用中常把这两个协议称为 TCP/IP 协议.

按照 TCP/IP 协议模型, 网络通常被分为一个四层模型, 这个四层模型和前面的 OSI 七层模型有大致的对应关系


OSI 分层模型和 TCPIP 分层模型的对应关系

 

2、IP 地址和端口号

IP 地址用于标识网络中的一个通信实体, 这个通信实体可以是一台主机, 也可以是一台打印机, 或者是路由器的某一个端口. 而在基于 IP 协议的网络中传输数据包, 都必须使用 IP 地址来进行标识.

每个被传输的数据包要包含一个源 IP 地址和一个目的 IP 地址.

IP 地址是数字型的, IP 地址是一个 32 位 (32 bit) 整数, 但通常为了便于记忆, 将它分成 4 个 8 位的二进制数组成, 因此 IP 地址通常显示为如下形式: 202.9.128.88.

NIC (Internet Network Information Center) 统一负责全球 Internet IP 地址的规划和管理, 而 Inter NIC, APNIC 和 RIPE 三大网络信息中心具体负责美国及其他地区的 IP 地址分配, APNIC 负责亚太地区的 IP 管理, APNIC 的总部设在日本的东京大学.

IP 地址可以唯一地确定网络上的一个通信实体, 但一个通信实体可以有多个通信程序同时提供网络服务, 此事还需要使用端口

端口是一个 16 位数的整数, 用于表示数据交给哪个通信程序处理, 因此端口就是应用程序与外界交流的出入口, 它是一种抽象的软件结构, 包括一些数据结构和 I/O (基本输入/输出) 缓冲区.

不同的应用程序处理不同端口上的数据,同一台机器上不能有两个程序使用同一个端口, 端口号可以从 0 到 65535, 通常将端口分为 3 类:

 

  • 公认端口 (Well Known Ports): 从 0 到 1023,它们紧密绑定 (Bingding) 一些服务
  • 注册端口 (Registered Ports): 从 1024 到 49151, 它们松散地绑定一些服务
  • 动态和/或私有端口 (Dynamic and/or Private Ports): 从 49152 到 65535, 这些端口是应用程序使用的动态端口, 应用程序一般不会主动使用这些端口
二. Java 的基本网络支持
Java 为网络支持提供了 java.net 包, 该包下的 URL 和 URLConnection 类等提供了以编程方式访问 Web 服务的功能, 而 URLDecoder 和 URLEncoder 则提供普通字符串和 application/x-www-form-urlencoded MIME 字符串相互转换的静态方法.
1、使用 InetAddress
Java 使用 InetAddress 类来代表 IP 地址, InetAddress 下还有两个子类: Inet4Address 和 Inet6Address, 它们分表代表 Internet Protocol version 4 (IPv4) 地址和 Internet Protocol version 6 (IPv6) 地址.
InetAddress 类没有构造器, 而是提供了两个静态方法来获取 InetAddress 实例:
  • getByName(String host): 根据主机获取对应的 InetAddress 对象
  • getByAddress(byte[] addr): 根据原始 IP 地址来获取对应的 InetAddress 对象
InetAddress 还提供了如下三个方法来获取 InetAddress 实例对应的 IP 地址和主机名
  • String getCanonicalHostName(): 获取此 IP 地址的全限定域名
  • String getHostAddress(): 返回该 InetAddress 实例对应的 IP 地址字符串
  • String getHostName(): 获取此 IP 地址的主机名
除此之外, InetAddress 类还提供了一个 getLocalHost() 方法来获取本机 IP 地址对应的 InetAddress 实例.
InetAddress 类还提供了一个 isReachable() 方法, 用于测试是否可以到达该地址
示例:
import java.net.InetAddress;

public class InetAddressTest {

/**
* @param args
*/
public static void main(String[] args) throws Exception{
// 根据主机名来获取对应的 InetAddress 实例
InetAddress ip = InetAddress.getByName("www.sina.com");
// 判断是否可到达
System.out.println("sina 是否可到达: " + ip.isReachable(2000));
// 获取该 InetAddress 实例的 IP 字符串
System.out.println(ip.getHostAddress());
// 根据原始 IP 地址获取对应的 InetAddress 实例
InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
System.out.println("本机是否可以到达:" + local.isReachable(5000));
// 获取该 InetAddress 实例对应的全限定域名
System.out.println(local.getCanonicalHostName());
}

}

2、使用 URLDecoder 和 URLEncoder
URLDecoder 和 URLEncoder 用于完成普通字符串和 application/x-www-form-urlencoded MIME 字符串之间的转换.
我们首先使用 google 搜索关键字 "Java 程序员", 将看到地址栏中出现 "http://www.google.com/search?hl=zh-CN&q=Java+%E7%A8%8B%E5%BA%8F%E5%91%98&lr=", 其中"+%E7%A8%8B%E5%BA%8F%E5%91%98", 当我们搜索的关键字中出现中文时, 这些关键字就会变成 application/x-www-form-urlencoded MIME 字符串.
当 URL 地址里包含非西欧字符的字符串时, 系统会将这些非西欧字符串转换成特殊字符串. 那么编程过程中可能设计奖普通字符串和这种特殊字符串的相关转换, 这就需要使用 URLDecoder 和 UREncoder 类, 其中
  • URLDecoder 类包含一个 decode(String s, String enc) 静态方法, 他可以让看上去是乱码的特殊字符串转换成普通字符串
  • URLEncoder 类包含一个 encode(String s, String enc) 静态方法, 他可以将普通字符串转换成 application/x-www-form-urlencoded MIME 字符串
示例:
import java.net.URLDecoder;
import java.net.URLEncoder;

public class URLDecoderTest {
public static void main(String[] args) throws Exception{
String keyWord = URLDecoder.decode("%E9%9D%9E%E8%A5%BF%E6%AC%A7%E5%AD%97%E7%AC%A6", "utf-8");
System.out.println(keyWord);
String urlStr = URLEncoder.encode("西欧字符", "GBK");
System.out.println(urlStr);
}
}

3、使用 URL 和 URLConnection
URL (Uniform Resource Locator) 对象代表统一资源定位器, 它是指向互联网 "资源" 的指针. URL 由协议名, 主机, 端口和资源组成, 即满足如下格式:
protocol://host:post/resourceName
例如: http:www.sina.com/index.html
JDK 中还提供了一个 URI (Uniform Resource Indentifiers) 类, 其实例代表一个统一资源标识符, Java 的 URI 不能用于定为任何资源, 它的唯一作用就是解析, 与此对应的是, URL 则包含一个可打开到达该资源的输入流, 因此我们可以将 URL 理解成 URI 的特例.
URL 类提供了多个构造器用于创建 URL 实例, 一旦获得了 URL 对象之后, 可以访问如下方法来访问该 URL 对应的
资源.
  • String getFile(): 获取此 URL 的资源名
  • String getHost(): 获取此 URL 的主机名
  • String getPath(): 获取此 URL 的路径部分
  • int getPort(): 获取此 URL 的端口号
  • String getProtocol(): 获取此 URL 的协议名称
  • String getQuery(): 获取此 URL 的查询字符串部分
  • URLConnection openConnection(): 返回一个 URLConnection() 对象, 它表示到 URL 所引用的远程对象的连接
  • InputStream openStream(): 打开与此 URL 的链接, 并返回一个用于读取该 URL 资源的 InputStream
示例;

URL 的 openConnecetion() 方法将返回一个 URLConnection 对象, 该对象表示应用程序和 URL 之间的通信连接. 程序可以通过 URLConnection 实例向该 URL 发送请求, 读取 URL 引用的资源.
通常创建一个和 URL 的链接, 并发送请求, 读取此 URL 引用的资源需要如下几个步骤:
  • 通过调用 URL 对象 openConnection() 方法来创建 URLConnection 对象
  • 设置 URLConnection 的参数和普通请求属性
  • 如果只是发送 GET 方法请求, 使用 connect 方法建立和远程资源之间的实例连接即可, 如果需要发送 POST 方式的请求, 需要获取 URLConnection 实例对应的输出流来发送请求参数
  • 远程资源变为可用, 程序可以访问远程资源的头字段或通过输入流读取远程资源的数据
在建立和远程资源的实际连接之前, 程序可以通过如下方法来设置请求头字段:
  • setAllowUserInteraction: 设置该 URLConnection 的 allowUserInteraction 请求头字段的值
  • setDoInput: 设置该 URLConnection 的 doInput 请求字段的值
  • setDoOutput: 设置该 URLConnection 的 doOutput 请求字段的值
  • setIfModifiedSince: 设置该 URLConnection 的 ifModifiedSince 请求头字段的值
  • setUseCaches: 设置该 URLConnection 的 useCaches 请求头字段的值
除此之外, 还可以使用如下方法来设置或增加通用头字段:
  • setRequestProperty(String key, String value): 为该 URLConnection 的 key 请求头字段的值为 value:
  • addRequestProperty(String key, String value): 为该 URLConnection 的 key 请求头字段的增加 value 值, 该方法不会覆盖原请求头字段的值, 而是将新值追加到原请求头字段中
当远程资源可用之后, 程序可以使用以下方法用于访问头字段和内容:
  • Object getContent(): 获取该 URLConnection 的内容
  • String getHeaderField(String name): 获取指定响应头字段的值
  • getInputStream(): 返回该 URLConnection 对应的输入流, 用于获取 URLConnection 响应的内容
  • getOutputStream(): 返回该 URLConnection 对应的输出流, 用于向 URLConnection 发送请求参数
  • getHeaderField: 用于根据响应头字段来返回对应的值, 而某些头字段由于需要经常访问, 所以 Java 提供以下方法来访问特定响应头字段的值:
  • getContentEncoding: 获取 content-encoding 响应头字段的值
  • getContentLength: 获取 content-length 响应头字段的值
  • getContentType: 获取 content-type 响应头字段的值
  • getDate(): 获取 date 响应头字段的值
  • getExpiration(): 获取 expires 响应头字段的值
  • getLastModified(): 获取 last-modified 响应头字段的值
示例:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;


public class TestGetPost {

/**
* 向指定的 URL 发送 GET 方法的请求
* @param url 发送请求的 URL
* @param param 
* @return
*/
public static String sendGet(String url, String param)
{
String result = "";
BufferedReader in = null;
try
{
String urlName = url + "?" + param;
URL realUrl = new URL(urlName);
// 打开和 URL 之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible MSIE 6.0; Windows NT 5.1; SV1)");
// 建立实际连接
conn.connect();
// 获取所有的响应头字段
Map<String, List<String>> map = conn.getHeaderFields();
for(String key: map.keySet())
{
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader 输入流来读取 URL 的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
result += "\n" + line;
}
}
catch (Exception e) 
{
System.out.println("发送 GET 请求出现异常!" + e);
e.printStackTrace();
}
finally
{
try
{
if(in != null)
{
in.close();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
return result;
}
public static String sendPost(String url, String param)
{
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try
{
URL realUrl = new URL(url);
// 打开和 URL 之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible MSIE 6.0; Windows NT 5.1; SV1)");
// 发送 POST 请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取 URLConnection 对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush 输出流的缓冲
out.flush();
// 定义 BufferedReader 输入流来读取 URL 的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null)
{
result += "\n" + line;
}
}
catch (Exception e) 
{
System.out.println("发送 POST 请求出现异常" + e);
e.printStackTrace();
}
finally
{
try
{
if(out != null)
{
out.close();
}
if(in != null)
{
in.close();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
/**
* @param args
*/
public static void main(String[] args) {
// 发送 Get 请求
String s = TestGetPost.sendGet("http://localhost:8888/abc/login.jsp", null);
System.out.println(s);
// 发送 POST 请求
String s1 = TestGetPost.sendPost("http://localhost:8888/abc/a.jsp", "user=李自成&pass=abc");
System.out.println(s1);
}

}
三. 基于 TCP 协议的网络编程
TCP/IP 通信协议是一种可靠的网络协议, 它在通信的两端各建立一个 Socket, 从而在通信的两端之间形成网络虚拟链路.
1. TCP 协议基础
IP 协议是 Internet 上使用的一个关键协议, 它的全称是 Internet Protocol, 即 Internet 协议, 通常简称 IP 协议. 通过使用 IP 协议, 从而使 Internet 成为一个允许连接不同类型计算机和操作系统的网络.
要是两台计算机彼此之间进行通信, 必须要使两台计算机使用同一种 "语言", IP 协议只保证计算机能发出和接受分组数据, IP 协议负责将消息从一个主机传送到另一个主机, 消息在传送的过程中被分割成一个个的小包.
尽管计算机通过安装 IP 软件, 保证了计算机之间可以发送和接收数据, 但 IP 协议不能解决数据分组在传输过程中可能出现的问题, 因此, 要解决可能出现的问题, 连上 Internet 的计算机还需要安装 TCP 协议来提供可靠并且无差错的通信服务.
TCP 协议被称作一种端对端协议. 这是因为他为两台计算机之间的链接起了重要作用: 当一台计算机需要与另一台远程计算机连接时, TCP 协议会让他们建立一个连接:  用于发送和接收数据的虚拟链路.
TCP 协议负责收集这些信息包, 并将其按适当的次序放好传送, 在接收端收到后再将其正确地还原, TCP 协议保证了数据包在传送中准确无误. TCP 协议使用重发机制: 当一个通信实体发送一个消息给另一个通信实体后, 需要收到另一个通信实体的确认消息, 如果没有收到另一个通信实体的确认信息, 则会再次重发刚才发送的信息.
通过这种重发机制, TCP 协议向应用程序提供可靠的通信连接, 使它能够自动适应网上的各种变化, 即使在 Internet 暂时出现阻塞的情况下, TCP 也能够保证通信的可靠.
2、使用 ServerSocket 创建 TCP 服务器端
Java 中能接受其他通信实体连接请求的类是 ServerSocket, ServerSocket 对象用于监听来自客户端的 Socket 连接, 如果没有请求, 它将一直处于等待状态. ServerSocket 包含一个监听自客户端连接请求的方法:
Socket accept(): 如果接收到一个客户端 Socket 的连接请求, 该方法返回一个与客户端对应的 Socket; 否则该方法将一直处于等待状态, 线程也被阻塞.
为了创建 ServerSocket 对象, ServerSocket 类提供了如下几个构造器:
  • ServerSocket(int port): 用指定的端口 port 来创建一个 ServerSocket. 该端口应该是一个有效的端口整数值: 0-65535
  • ServerSocket(int port, int backlog): 增加一个用来改变连接队列长度的参数 backlog.
  • ServerSocket(int port, int backlog, InetAddress localAddr): 在机器存在多个 IP 地址的情况下, 允许通过 localAddr 这个参数来指定将 ServerSocket 绑定到指定的 IP 地址.
当 ServerSocket 使用完毕, 应使用 ServerSocket 的 close() 方法来关闭该 ServerSocket. 在通常情况下, 服务器不应该只接受一个客户端请求, 而应该不断地接受来自客户端的所有请求, 所以 Java 程序通常会通过循环, 不断调用 ServerSocket 的 accept() 方法. 示例:
// 创建一个 ServerSocket, 用于监听客户端 Socket 的连接请求
ServerSocket ss = new ServerSocket(30000);
// 采用循环不断接受来自客户端的请求
while (true)
{
// 每当接收到客户端 Socket 的请求, 服务器端也对应产生一个 Socket
Socket s = ss.accept();
// 下面就可以使用 Socket 进行通信了
...
}
3、使用 Socket 进行通信
客户端通常可以使用 Socket 的构造器来连接到指定服务器, Socket 通常可以使用如下两个构造器:
  • Socket(InetAddress/String remoteAddress, int port): 创建连接到指定远程主机, 远程端口的 Socket, 该构造器没有指定本地地址,本地端口,默认使用本地主机的默认 IP 地址, 默认使用系统动态指定的 IP 地址.
  • Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort): 创建连接到指定远程主机, 远程端口的 Socket, 并指定本地 IP 地址和本地端口号, 适用于本地主机有多个 IP 地址的情形
示例:
// 链接到本机, 30000 端口的 Socket
Socket s = new Socket("127.0.0.1", 30000);
// 下面就可以使用 Socket 进行通信了
...
执行上面的程序之后, 该代码将会链接到指定的服务器, 让服务器的 ServerSocket 的 accept() 方法向下执行, 于是服务器和客户端就产生一对互相连接的 Socket.
当客户端,服务器端产生了对应的 Socket 之后, 程序无须在区分服务器, 客户端, 而是通过各自的 Socket 进行通信, Socket 提供如下两个方法来获得输入流和输出流:
  • InputStream getInputStream(): 返回该 Socket 对象对应的输入流, 让程序通过该输入流从 Socket 中取出数据
  • OutputStream getOutputSteam(): 返回该 Socket 对象对应的输出流, 让程序通过该输出流向 Socket 中输出数据
示例:

  • 大小: 12.8 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics