概述
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
Java支持下列常用网络操作:
- InetAddress:用于表示网络上的硬件资源,即 IP 地址;
- URL:统一资源定位符;
- Socket:使用 TCP 协议实现网络通信。TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
- Datagram:使用 UDP 协议实现网络通信。UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。
下面我们分别来学习:
InetAddress
java.net.InetAddress类是Java对IP地址(包括IPv4和IPv6)的高层表示。大多数其他网络类都要用到这个类,包括Socket,ServerSocket,URL,DatagramSocket,DatagramPacket等。一般地讲,它包括一个主机名和一个IP地址。
主机名到 IP 地址的解析 通过使用本地机器配置信息和网络命名服务(如域名系统(Domain Name System,DNS)和网络信息服务(Network Information Service,NIS))来实现。
反向名称解析 意味着对于任何 IP 地址,都返回与 IP 地址关联的主机。
InetAddress 类提供将主机名解析为其 IP 地址(或反之)的方法。
- static InetAddress[] getAllByName(String host)
在给定主机名的情况下,根据系统上配置的名称服务返回所有的 IP 地址。 - static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。 - static InetAddress getByAddress(String host, byte[] addr)
根据提供的主机名和 IP 地址创建 InetAddress。 - static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。 - String getCanonicalHostName()
获取此 IP 地址的完全限定域名。 - String getHostAddress()
返回 IP 地址字符串,比如192.168.1.1 - String getHostName()
获取此 IP 地址的主机名。比如 www.baidu.com - static InetAddress getLocalHost()
返回本地主机。 - boolean isReachable(int timeout)
测试是否可以达到该地址。
URL
URL(Uniform Resource Locator)中文名为统一资源定位符,有时也被俗称为网页地址。表示为互联网上的资源,如网页或者FTP地址。
URL可以分为如下几个部分:
protocol://host:port/path?query#fragment
实例:http://www.runoob.com/index.html?language=cn#j2se
URL 各部分解析:
protocol(协议):可以是 http、https、ftp 、file等,上面例子是http
host(主机):www.runoob.com
port(端口号):80 ,以上URL实例并未指定端口,因为 HTTP 协议默认的端口号为 80。
path(文件路径):/index.html
query(请求参数):language=cn
fragment(定位位置):j2se,定位到网页中 id 属性为 j2se 的 HTML 元素位置 。
URL 类方法
在java.net包中定义了URL类,该类用来处理有关URL的内容。对于URL类的创建和使用,下面分别进行介绍。
java.net.URL提供了丰富的URL构建方式,并可以通过java.net.URL来获取资源。
URL类中包含了很多方法用于访问URL的各个部分,具体方法及描述如下:
以上实例演示了使用java.net的URL类获取URL的各个部分参数:
URLDemo.java
import java.net.*;
import java.io.*;
public class URLDemo
{
public static void main(String [] args)
{
try
{
URL url = new URL("http://www.runoob.com/index.html?language=cn#j2se");
System.out.println("URL 为:" + url.toString());
System.out.println("协议为:" + url.getProtocol());
System.out.println("验证信息:" + url.getAuthority());
System.out.println("文件名及请求参数:" + url.getFile());
System.out.println("主机名:" + url.getHost());
System.out.println("路径:" + url.getPath());
System.out.println("端口:" + url.getPort());
System.out.println("默认端口:" + url.getDefaultPort());
System.out.println("请求参数:" + url.getQuery());
System.out.println("定位位置:" + url.getRef());
}catch(IOException e)
{
e.printStackTrace();
}
}
}
结果如下:
URL 为:http://www.runoob.com/index.html?language=cn#j2se
协议为:http
验证信息:www.runoob.com
文件名及请求参数:/index.html?language=cn
主机名:www.runoob.com
路径:/index.html
端口:-1
默认端口:80
请求参数:language=cn
定位位置:j2se
URLConnections 类方法
openConnection() 返回一个 java.net.URLConnection。
例如:
- 如果你连接HTTP协议的URL, openConnection() 方法返回 HttpURLConnection 对象。
- 如果你连接的URL为一个 JAR 文件, openConnection() 方法将返回 JarURLConnection 对象。
URLConnection 方法列表如下:
以下实例中URL采用了HTTP 协议。 openConnection 返回HttpURLConnection对象,并通过该对象得到URL的输入流,读取字节流数据:
public static void main(String[] args) throws IOException {
URL url = new URL("http://www.baidu.com");
/* 字节流 */
InputStream is = url.openStream();
/* 字符流 */
InputStreamReader isr = new InputStreamReader(is, "utf-8");
/* 提供缓存功能 */
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
Socket 编程
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个全双工的通信协议,因此数据可以通过两个数据流在同一时间发送。以下是一些类提供的一套完整的有用的方法来实现 socket。
ServerSocket 类的方法
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有四个构造方法:
如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket 类的常用方法:
Socket 类的方法
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
Socket 类有五个构造方法:
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法:
Socket 客户端实例
如下的 GreetingClient 是一个客户端程序,该程序通过 socket 连接到服务器并发送一个请求,然后等待一个响应。
GreetingClient.java
import java.net.*;
import java.io.*;
public class GreetingClient
{
public static void main(String [] args)
{
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try
{
System.out.println("连接到主机:" + serverName + " ,端口号:" + port);
Socket client = new Socket(serverName, port);
System.out.println("远程主机地址:" + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("Hello from " + client.getLocalSocketAddress());
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("服务器响应: " + in.readUTF());
client.close();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
Socket 服务端实例
如下的GreetingServer 程序是一个服务器端应用程序,使用 Socket 来监听一个指定的端口。
GreetingServer.java
import java.net.*;
import java.io.*;
public class GreetingServer extends Thread
{
private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException
{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
}
public void run()
{
while(true)
{
try
{
System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("远程主机地址:" + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!");
server.close();
}catch(SocketTimeoutException s)
{
System.out.println("Socket timed out!");
break;
}catch(IOException e)
{
e.printStackTrace();
break;
}
}
}
public static void main(String [] args)
{
int port = Integer.parseInt(args[0]);
try
{
Thread t = new GreetingServer(port);
t.run();
}catch(IOException e)
{
e.printStackTrace();
}
}
}
编译以上两个 java 文件代码,并执行以下命令来启动服务,使用端口号为 6066:
$ javac GreetingServer.java
$ java GreetingServer 6066
等待远程连接,端口号为:6066...
新开一个命令窗口,执行以上命令来开启客户端:
$ javac GreetingClient.java
$ java GreetingClient localhost 6066
连接到主机:localhost ,端口号:6066
远程主机地址:localhost/127.0.0.1:6066
服务器响应: 谢谢连接我:/127.0.0.1:6066
Goodbye!
Datagram
UDP和TCP的特点
用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信,效率高但不可靠。事实上,可以用UDP实现一个可靠的文件传输协议,而且很多人确实是这样做的:网络文件系统,简单FTP都使用了UDP协议。在这些协议中由应用程序来负责可靠性。
传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一),效率低但安全可靠。
java中对UDP编程的支持
java中的UDP实现分为两个类:DatagramPacket和 DatagramSocket。DatagramPacket类将数据字节填充到UDP包中,这称为数据报。 DatagramSocket来发送这个包。要接受数据,可以从DatagramSocket中接受一个 DatagramPack对象,然后从该包中读取数据的内容。
这种职责的划分与TCP使用的Socket和ServerSocket有所不同。首先,UDP没有两台主机间唯一连接的概念,它不需要知道对方是哪个远程主机。它可以从一个端口往多个主机发送信息,但是TCP是无法做到的。其次,TCP socket把网络连接看作是流:通过从Socket得到的输入和输出流来收发数据。UDP不支持这一点,你处理总是单个数据包。填充在一个数据报中的所有数据会以包的形式进行发送,这些数据要么作为一个组要么全部接收,要么全部丢弃。一个包不一定与下一个包相关。给定两个包,没有办法知道哪个先发哪个后发。对于流来说,必须提供数据的有序队列,与之不同,数据报会尽可能快的蜂拥到接收方。
DatagramSocket类
java.net.DatagramSocket 此类表示用来发送和接收数据报包的套接字
该类主要有下列构造方法:
- DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。
- DatagramSocket(int port, InetAddress laddr) 创建数据报套接字,将其绑定到指定的地址。
常用的方法:
- send(DatagramPacket p) 从此套接字发送数据报包。
- receive(DatagramPacket p)从此套接字接收数据报包。
- close() 关闭此数据报套接字。
DatagramPacket类
此类表示数据报包。
构造方法(因为UDP是无连接的,所以在用于发送的数据包中附加目的地址,而用于接收的数据包不用加地址信息):
- DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。 - DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。 - DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramSocket 客户端实例
UDPClient.java
public class UDPClient {
public static void main(String[] args) {
try {
//1.创建数据报套接字
DatagramSocket datagramSocket = new DatagramSocket(6666);
//2.创建数据报包用于封装数据和目标地址
String str="hello world!";
byte[] content = str.getBytes();//将字符串转换为字节的数组
DatagramPacket datagramPacket = new DatagramPacket(content, content.length,InetAddress.getLocalHost(), 9999);
//3.调用send方法进行发送数据
datagramSocket.send(datagramPacket);
//4.释放资源
datagramSocket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
DatagramSocket 服务端实例
UDPServer.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* DatagramSocket(int port)
* DatagramPacket(byte[] buf, int length) 构造 DatagramPacket,用来接收长度为 length 的数据包。
* DataPacket类中方法:
* getData() 返回数据缓冲区。
* getLength()返回将要发送或接收到的数据的长度。
* getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的
* getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
*
*/
public class UDPServer {
public static void main(String[] args) {
try {
//1.创建数据报套接字
DatagramSocket socket = new DatagramSocket(9999);
//2.创建一个数据报包
byte[] content = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(content,content.length);
//3.调用receive方法接收数据包
socket.receive(datagramPacket);
//4.从数据报包中获取数据
byte[] data= datagramPacket.getData();//获取数据报包中的数据
int length = datagramPacket.getLength();//
InetAddress ip = datagramPacket.getAddress();
int port = datagramPacket.getPort();
System.out.println("内容:"+new String(data,0,length));
System.out.println("数据长度:"+length);
System.out.println("发送方的IP地址:"+ip);
System.out.println("发送方的端口号:"+port);
//5.释放资源
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
利用UDP实现聊天功能
UdpChatClient.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 利用UDP实现聊天功能
* @author Administrator
*
*/
public class UdpChatClient {
public static void main(String[] args) {
System.out.println("---------顾客---------");
try {
//1.创建数据报套接字
DatagramSocket socket = new DatagramSocket(6666);
Scanner input = new Scanner(System.in);
while(true){
//2.获取用户输入
String message = input.next();
byte[] bs = message.getBytes();
//3.创建数据报包
DatagramPacket packet = new DatagramPacket(bs, bs.length, InetAddress.getByName("127.0.0.1"),8888);
//4.发送数据
socket.send(packet);
//接收数据
byte[] bs2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(bs2, bs2.length);
socket.receive(packet2);//接收数据
byte[] serverMesage = packet2.getData();
String str=new String(serverMesage,0,serverMesage.length);
System.out.println("客服说:"+str);
if(message.equals("bye")){
break;
}
}
//释放资源
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UdpChatServer.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 利用UDP实现聊天功能
* @author Administrator
*
*/
public class UdpChatServer {
public static void main(String[] args) {
System.out.println("---------客服---------");
try {
//1.创建数据报套接字
DatagramSocket socket = new DatagramSocket(8888);
Scanner input = new Scanner(System.in);
while(true){
//接收数据
byte[] bs2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(bs2, bs2.length);
socket.receive(packet2);//接收数据
byte[] serverMesage = packet2.getData();
String str=new String(serverMesage,0,serverMesage.length);
System.out.println("顾客说:"+str);
//2.获取用户输入
String message = input.next();
byte[] bs = message.getBytes();
//3.创建数据报包
DatagramPacket packet = new DatagramPacket(bs, bs.length, InetAddress.getByName("127.0.0.1"),6666);
//4.发送数据
socket.send(packet);
if(message.equals("bye")){
break;
}
}
//释放资源
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}