2020重新出发,JAVA基础,网络编程
@
Java网络编程
随着互联网的发展趋势,大量的两络应用程序涌现出来,使得网络编程技术得到了很好的发展。网络编程就是在两个或者两个以上的设备(例如计算机)之间传输数据,编程人员所做的事情就是把数据发送到指定位置或者接收到指定的数据,这就是狭义的网络编程。
Java 语言中设计了一些 API 来专门实现数据发送和接收等功能,只需要编程人员调用即可。要进行网络编程就必须对网络协议、端口和套接字等知识有所了解。
Java网络编程基础知识
网络编程的目的就是直接或间接地通过网络协议与其他计算机进行通信。在 Java 语言中包含网络编程所需要的各种类,编程人员只需要创建这些类的对象,调用相应的方法,就可以进行网络应用程序的编写。
要进行网络程序的编写,编程人员需要对网络传输协议、端口和套接字等方面的知识有一定的了解。
网络分类
了解网络编程之前首先带领读者对计算机网络进行一些简单的了解。计算机网络是指将有独立功能的多台计算机,通过通信设备线路连接起来,在网络软件的支持下,实现彼此之间资源共享和数据通信的整个系统。
按照地理范围主要将网络分为局域网、城域网、广域网和因特网。
- 局域网(LocalArea Network)简称 LAN,是一种在小范围内实现的计算机网络,一般在一个建筑物内或者一个工厂、一个事业单位内部独有,范围较小。
- 城域网(Metropolitan Area Network)简称为 MAN,一般是一个城市内部组建的计算机信息网络,提供全市的信息服务。
- 广域网(Wide Area Network)简称为 WAN,它的范围很广,可以分布在一个省、一个国家或者几个国家。
- 因特网(Internet)则是由无数的 LAN 和 WAN 组成的。
网络编程模型
在网络通信中主要有两种模式的通信方式:一种是客户机/服务器(Client/Server)模式,简称为 C/S 模式;另一种是浏览器/服务器(Browser/Server)模式,简称 B/S 模式。下面主要针对这两种模式进行介绍。
Client/Server 模式
图是客户机、服务器以及网络三者之间的关系图,使用这种模式的程序很多,例如网络游戏,需要在本机上安装一个客户端,服务器运行在游戏开发公司的机房。
使用 C/S 模式的程序,在开发时需要分别针对客户端和服务器端进行专门开发。这种开发模式的优势在于由于客户端是专门开发的,表现力会更强。缺点就是通用性差,也就是说一种程序的客户端只能和对应的服务器端进行通信,不能和其他的服务器端进行通信,在实际维护中,也需要维护专门的客户端和服务器端,维护的压力较大。
Browser/Server 模式
对于很多程序,运行时不需要专门的客户端,而是使用通用的客户端,例如使用浏览器。用户使用浏览器作为客户端的这种模式叫作浏览器/服务器模式。使用这种模式开发程序时只需开发服务器端即可,开发的压力较小,不需要维护客户端。但是对浏览器的限制比较大,表现力不强。
网络协议
网络协议是网络上所有设备(网络服务器、计算机及交换机、路由器、防火墙等)之间通信规则的集合,它规定了通信时信息必须采用的格式和这些格式的意义。目前的网络协议有很多种,在这里简单介绍几种常用的网络协议。
IP 协议
IP 是英文 Internet Protocol(网络之间互联的协议)的缩写,中文简称为网协,也就是为计算机网络相互连接进行通信而设计的协议。在 Internet 中它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在 Internet 上进行通信时应当遵守的规则。任何厂家生产的计算机系统只有遵守 IP 协议才可以与 Internet 互联。
Internet 网络中采用的协议是 TCP/IP 协议,其全称是 Transmission Control Protocol/Internet Protocol。Internet 依靠 TCP/IP 协议在全球范围内实现不同硬件结构、不同操作系统、不同网络的互联。
对网络编程来说,主要是计算机和计算机之间的通信,首要的问题就是如何找到网络上数以亿计的计算机。为了解决这个问题,网络中的每个设备都会有唯一的数字标识,也就是 IP 地址。
在计算机网络中,现在命名 IP 地址的规定是 IPv4 协议,该协议规定每个 IP 地址由 4 个 0~255 的数字组成。每台接入网络的计算机都拥有一个唯一的 IP 地址,这个地址可能是固定的,也可能是动态的。
目前 IETF(Internet Engineering Task Force,互联网工程任务组)设计的用于替代现行版本 IP 协议(IPv4)的下一代协议 IPv6,釆用 6 字节来表示 IP 地址,但目前还没有开始使用。
TCP/IP 定义了电子设备如何连入 Internet 以及数据如何在它们之间传输的标准。协议采用 4 层的层级结构,分别是应用层、传输层、网络层和网络接口层。每一层都呼叫它的下一层所提供的网络来完成自己的需求。
TCP 负责发现传输的问题,一有问题就发出信号要求重新传输,直到所有数据安全正确地传输到目的地,而 IP 是给 Internet 的每一台电脑规定一个地址。图 2 是 TCP/IP 层次结构图。
TCP 与 UDP 协议
尽管 TCP/IP 协议从名称看只包括 TCP 这个协议名,但是在 TCP/IP 协议的传输层同时存在 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)两个协议。
在网络通信中 TCP 协议类似于使用手机打电话,可以保证把信息传递给别人;而 UDP 协议类似于发短信,接收人有可能接收不到传递的信息。
在网络通信中使用 TCP 的方式需要建立专门的虚拟连接,然后进行可靠的数据连接,如果数据发送失败,客户端会自动重发该数据。而使用 UDP 方式不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。
TCP 协议是一种以固定连线为基础的协议,它提供两台计算机之间可靠的数据传送。而 UDP 无连接通信协议,它不保证可靠数据的传输,但能够向若干目标发送数据以及接收来自若干源的数据。
对于一些重要的数据,一般使用 TCP 方式来进行数据传输,而大量的非核心数据则通过 UDP 方式进行传递。使用 TCP 方式传递的速度稍微慢一点,而且传输时产生的数据量会比 UDP 大一点。
套接字和端口
在网络上很多应用程序都是采用客户端/服务器(C/S)的模式,实现网络通信必须将两台计算机连接起来建立一个双向的通信链路,这个双向通信链路的每一端称之为一个套接字(Socket)。
一台服务器上可能提供多种服务,使用 IP 地址只能唯一定位到某一台计算机,却不能准确地连接到想要连接的服务器。通常使用一个 0~65535 的整数来标识该机器上的某个服务,这个整数就是端口号(Port)。端口号并不是指计算机上实际存在的物理位置,而是一种软件上的抽象。
端口号主要分为以下两类:
- 由 Internet 名字和号码指派公司 ICANN 分配绐一些常用的应用层程序固定使用的熟知端口,其值是 0~1023。例如 HTTP 服务一般使用 80 端口,FTP 服务使用 21 端口。
- 一般端口用来随时分配绐请求通信的客户进程。
运行在一台特定机器上的某个服务器(如 FTP 服务器)都有一个套接字绑定在该服务器上,服务器只是等待和监听客户的连接请求。客户端客户需要知道服务器的主机名和端口号。
为了建立连接请求,客户机试图与服务器上指定端口号上的服务进行连接,这个请求过程如图 所示。
如果服务器接收到客户端的请求,就会创建一个套接字,客户端使用该套接字与服务器通信,但此时客户端的套接字并没有绑定到与服务器连接的端口号上。
Java InetAddress类及其常用方法
Internet 上的主机有两种方式表示地址,分别为域名和 IP 地址。java.net 包中的 InetAddress 类对象包含一个 Internet 主机地址的域名和 IP 地址。
InetAddress 类提供了操作 IP 地址的各种方法。该类本身没有构造方法,而是通过调用相关静态方法获取实例。InetAddress 类中的常用方法如下表 所示。
方法名称 | 说明 |
---|---|
boolean equals(Object obj) | 将此对象与指定对象比较 |
byte[] getAddress() | 返回此 InetAddress 对象的原始 IP 地址 |
static InetAddress[] getAHByName(String host) | 在给定主机名的情况下,根据系统上配置的名称,服务器返 回其 IP 地址所组成的数组 |
static InetAddress getByAddress(byte[] addr) | 在给定原始 IP 地址的情况下,返回 InetAddress 对象 |
static InetAddress getByAddress(String host) | 在给定主机名的情况下确定主机的 IP 地址 |
String getCanonicalHostName() | 获取此 IP 地址的完全限定域名 |
String getHostAddress() | 返回 IP 地址字符串(以文本表现形式) |
String getHostName() | 返回此 IP 地址的主机名 |
static InetAdderss getLocalHost() | 返回本地主机 |
例 编写程序练习 InetAddress 类的基本使用方法,主要步骤如下所示。
(1) 创建类。在 main() 方法中创建一个 InetAddress 对象,调用getByName() 方法并传递参数“www.qq.com”输出此对象的 IP 地址字符串和主机名代码如下。
public static void main(String[] args)
{
try
{
InetAddress ia1=InetAddress.getByName("www.qq.com");
System.out.println(ia1.getHostName());
System.out.println(ia1.getHostAddress());
}
catch(UnknownHostException e)
{
e.printStackTrace();
}
}
(2) main() 方法添加代码,创建一个 InetAddress 对象,调用 getByName() 方法并传递参数“61.135.169.105”输出此对象的 IP 地址字符串和主机名,如下所示。
try
{
InetAddress ia2=InetAddress.getByName("61.135.169.105");
System.out.println(ia2.getHostName());
System.out.println(ia2.getHostAddress());
}
catch(UnknownHostException e)
{
e.printStackTrace();
}
(3) 创建一个 InetAddress 对象用于获取本地主机的信息,输出此对象的 IP 地址字符串和主机名,如下所示。
try
{
InetAddress ia3=InetAddress.getLocalHost();
System.out.println("主机名:"+ia3.getHostName());
System.out.println("本地ip地址:"+ia3.getHostAddress());
}
catch(UnknownHostException e)
{
e.printStackTrace();
}
(4) 执行程序,运行结果如下所示。
www.qq.com
123.151.137.18
61.135.169.105
61.135.169.105
主机名:WQ-20161107KCPN
本地ip地址:192.168.0.102
注意:在上述代码中包含互联网的地址,所以运行时需要连网,否则会出现异常。
Java TCP通信:Java ServerSocket类和Socket类
TCP 网络程序是指利用 Socket 编写的通信程序。利用 TCP 协议进行通信的两个应用程序是有主次之分的,一个是服务器程序,一个是客户端程序,两者的功能和编写方法不太一样。其中 ServerSocket 类表示 Socket 服务器端,Socket 类表示 Socket 客户端,两者之间的交互过程如下:
- 服务器端创建一个 ServerSocket(服务器端套接字),调用 accept() 方法等待客户端来连接。
- 客户端程序创建一个 Socket,请求与服务器建立连接。
- 服务器接收客户的连接请求,同时创建一个新的 Socket 与客户建立连接,服务器继续等待新的请求。
ServerSocket 类
ServerSocket 类是与 Socket 类相对应的用于表示通信双方中的服务器端,用于在服务器上开一个端口,被动地等待数据(使用 accept() 方法)并建立连接进行数据交互。
服务器套接字一次可以与一个套接字连接,如果多台客户端同时提出连接请求,服务器套接字会将请求连接的客户端存入队列中,然后从中取出一个套接字与服务器新建的套接字连接起来。若请求连接大于最大容纳数,则多出的连接请求被拒绝;默认的队列大小是 50。
ServerSocket 的构造方法
ServerSocket 的构造方法如下所示。
- ServerSocket():无参构造方法。
- ServerSocket(int port):创建绑定到特定端口的服务器套接字。
- ServerSocket(int port,int backlog):使用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口。
- ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口、监听 backlog 和要绑定到本地的 IP 地址创建服务器。
在上述方法的参数中 port 指的是本地 TCP 端口,backlog 指的是监听 backlog,bindAddr 指的是要将服务器绑定到的 InetAddress。
创建 ServerSocket 时可能会拋出 IOException 异常,所以要进行异常捕捉。如下所示为使用 8111 端口的 ServerSocket 实例代码。
try{
ServerSocket serverSocket=new ServerSocket(8111);
}catch(IOException e){
e.printStackTrace();
}
ServerSocket 的常用方法
ServerSocket 的常用方法如下所示。
- Server accept():监听并接收到此套接字的连接。
- void bind(SocketAddress endpoint):将 ServerSocket 绑定到指定地址(IP 地址和端口号)。
- void close():关闭此套接字。
- InetAddress getInetAddress():返回此服务器套接字的本地地址。
- int getLocalPort():返回此套接字监听的端口。
- SocketAddress getLocalSoclcetAddress():返回此套接字绑定的端口的地址,如果尚未绑定则返回 null。
- int getReceiveBufferSize():获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是从 ServerSocket 接收的套接字的建议缓冲区大小。
调用 accept() 方法会返回一个和客户端 Socket 对象相连接的 Socket 对象,服务器端的 Socket 对象使用 getOutputStream() 方法获得的输出流将指定客户端 Socket 对象使用 getInputStream() 方法获得那个输入流。同样,服务器端的 Socket 对象使用的 getInputStream() 方法获得的输入流将指向客户端 Socket 对象使用的 getOutputStream() 方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之同样如此,整个过程如图所示。
了解上面的基础知识后,下面使用 ServerSocket 类在本机上创建一个使用端口 8888 的服务器端套接字,实例代码如下所示。
public static void main(String[] args)
{
try
{
//在8888端口创建一个服务器端套接字
ServerSocket serverSocket=new ServerSocket(8888);
System.out.println("服务器端Socket创建成功");
while(true)
{
System.out.println("等待客户端的连接请求");
//等待客户端的连接请求
Socket socket=serverSocket.accept();
System.out.println("成功建立与客户端的连接");
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
如上述代码所示,在成功建立 8888 端口的服务器端套接字之后,如果没有客户端的连接请求,则 accept() 方法为空,所以不会输出“成功建立与客户端的连接”,运行结果如下所示。
服务器端S.ocket创違成功
等待客户端的连接请求
Socket 类
Socket 类表示通信双方中的客户端,用于呼叫远端机器上的一个端口,主动向服务器端发送数据(当连接建立后也能接收数据)。
Socket 的构造方法
Socket 的构造方法如下所示。
- Socket():无参构造方法。
- Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口。
- Soclcet(InetAddress address,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。
- Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口。
- Socket(String host,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。Socket 会通过调用 bind() 函数来绑定提供的本地地址及端口。
在上述方法的参数中,address 指的是远程地址,port 指的是远程端口,localAddr 指的是要将套接字绑定到的本地地址,localPort 指的是要将套接字绑定到的本地端口。
Socket 的常用方法
Socket 的常用方法如下所示。
- void bind(SocketAddress bindpoint):将套接字绑定到本地地址。
- void close():关闭此套接字。
- void connect(SocketAddress endpoint):将此套接字连接到服务器。
- InetAddress getInetAddress():返回套接字的连接地址。
- InetAddress getLocalAddress():获取套接字绑定的本地地址。
- InputStream getInputStream():返回此套接字的输入流。
- OutputStream getOutputStream():返回此套接字的输出流。
- SocketAddress getLocalSocketAddress():返回此套接字绑定的端点地址,如果尚未绑定则返回 null。
- SocketAddress getRemoteSocketAddress():返回此套接字的连接的端点地址,如果尚未连接则返回 null。
- int getLoacalPort():返回此套接字绑定的本地端口。
- intgetPort():返回此套接字连接的远程端口。
编写 TCP 程序,包括一个客户端和一个服务器端。要求服务器端等待接收客户端发送的内容,然后将接收到的内容输出到控制台并做出反馈。
(1) 创建一个类作为客户端,首先在 main() 方法中定义一个 Socket 对象、一个 OutputStream 对象和一个 InputStream 对象并完成初始化。接着定义服务器端的 IP 地址和端口号,代码如下所示。
public static void main(String[] args){
Socket socket=null;
OutputStream out=null;
InputStream in=null;
String serverIP="127.0.0.1"; //服务器端 IP 地址
int port=5000; //服务器端端口号
}
(2) 建立与服务器端的连接并将数据发送到服务器端,代码如下所示。
socket=new Socket(serverIP,port); //建立连接
out=socket.getOutputStream(); //发送数据
out.write("我是客户端数据 ".getBytes());
(3) 从输入流中读出服务器的反馈信息并输出到控制台,代码如下所示。
byte[] b=new byte[1024];
in=socket.getInputStream();
int len=in.read(b);
System.out.println(" 服务器端的反馈为:"+new String(b,0,len));
(4) 关闭输入/输出流以及 Socket 对象,代码如下所示。
in.close();
out.close();
socket.close();
(5) 创建一个类作为服务器端,编写 main() 方法,创建 ServerSocket、Socket、InputStream、OutputStream 以及端口号并初始化,代码如下所示。
ServerSocket ServerSocket=null;
Socket socket=null;
InputStream in=null;
OutputStream out=null;
int port=5000;
(6) 开启服务器并接收客户端发送的数据,代码如下所示。
ServerSocket=new ServerSocket(port); //创建服务器套接字
System.out.println("服务器开启,等待连接。。。");
socket=ServerSocket.accept(); //获得连接
//接收客户端发送的内容
in=socket.getInputStream();
byte[] b=new byte[1024];
int len=in.read(b);
System.out.println("客户端发送的内容为:"+new String(b,0,len));
(7) 使用输出流对象将信息反馈给客户端,代码如下所示。
out=socket.getOutputStream();
out.write("我是服务器端".getBytes());
(8) 关闭输入/输出流、Socket 对象以及 ServerSocket 对象,代码如下所示。
in.close();
out.close();
ServerSocket.close();
socket.close();
(9) 运行服务器端程序代码,运行结果如下所示。
服务器开启,等待连接。。。
(10) 为了使程序的结果更加清晰,在步骤 (2) 的代码最后加入一句代码“Thread.sleep(1000);”。接着运行客户端程序代码,刚开始会出现如下所示的运行结果。
服务器开启,等待连接。。。
客户端发送的内容为:我是客户端数据
紧接着又会出现如下所示的运行结果。
客户端的反馈为:我是服务器端
客户端与服务器端的简单通信
在了解 TCP 通信中 ServerSocket 类和 Socket 类的简单应用之后,本节将编写一个案例实现客户端向服务器发送信息,服务器读取客户端发送的信息,并将读取的数据写入到数据流中。
首先来看一下客户端的代码,如下所示:
public class SocketDemo
{
public static void main(String[] args)
{
Socket socket=null;
PrintWriter out=null;
BufferedReader in=null;
String serverIP="127.0.0.1"; //服务器端ip地址
int port=5000; //服务器端端口号
try
{
socket=new Socket(serverIP,port);
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
out=new PrintWriter(socket.getOutputStream(),true);
while(true)
{
int number=(int)(Math.random()*10)+1;
System.out.println("客户端正在发送的内容为:"+number);
out.println(number);
Thread.sleep(2000);
}
}
catch(IOException | InterruptedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
如上述代码所示,客户端代码主要是使用 Socket 连接 IP 为 127.0.0.1(本机)的 5000 端口。在建立连接之后将随机生成的数字使用 PrintWriter 类输出到套接字。休眠 2 秒后,再次发送随机数,如此循环。
再来看一个服务器端的代码,如下所示:
package ch16;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketDemoServer1
{
public static void main(String[] args)
{
ServerSocket serverSocket=null;
Socket clientSocket=null;
BufferedReader in=null;
int port=5000;
String str=null;
try
{
serverSocket=new ServerSocket(port); //创建服务器套接字
System.out.println("服务器开启,等待连接。。。");
clientSocket=serverSocket.accept();// 获得链接
//接收客户端发送的内容
in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
while(true)
{
str=in.readLine();
System.out.println("客户端发送的内容为:"+str);
Thread.sleep(2000);
}
}
catch(IOException | InterruptedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
如上述代码所示,服务器端与客户端代码类似,首先使用 ServerSocket 在 IP为127.0.0.1(本机)的 5000 端口建立套接字监听。在 accept() 方法接收到客户端的 Socket 实例之后调用 BufferedReader 类的 readLine() 方法,从套接字中读取一行作为数据,再将它输出到控制后休眠 2 秒。
要运行本案例,必须先执行服务器端程序,然后执行客户端程序。客户端每隔 2 秒向服务器发送一个数字,如下所示。
客户端正在发送的内容为:10
客户端正在发送的内容为:5
客户端正在发送的内容为:10
客户端正在发送的内容为:4
客户端正在发送的内容为:3
服务器端会将客户端发送的数据输出到控制台,如下所示。
服务器幵启,等待连接。。。客户端发送的内容为:7客户端发送的内容为:2客户端发送的内容为:10客户端发送的内容为:5客户端发送的内容为:10
......
传输对象数据
经过前面的学习,掌握了如何在服务器开始一个端口监听套接字,以及如何在客户端连接服务器,发送简单的数字。
本次案例将实现如何在客户端发送一个对象到服务器端,服务器如何解析对象中的数据。
第一步是创建用于保存数据的类。这里使用的 User 类是一个普通的类,包含 name 和 password 两个成员。由于需要序列化这个对象以便在网络上传输,所以需要实现 java. io.Serializable 接 P。
User 类的代码如下:
package ch16;
public class User implements java.io.Serializable
{
private String name;
private String password;
public User(String name,String password)
{
this.name=name;
this.password=password;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name=name;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password=password;
}
}
接下来编写服务器端的代码。服务器的作用是接收客户端发送过来的数据,将数据转换成 User 对象并输出成员信息,然后对 User 对象进行修改输出给客户端。
服务器端 MyServer 类的实现代码如下:
package ch16;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer
{
public static void main(String[] args) throws IOException
{
// 监听10000端口
ServerSocket server=new ServerSocket(10000);
while(true)
{
//接收客户端的连接
Socket socket=server.accept();
//调用客户端的数据处理方法
invoke(socket);
}
}
private static void invoke(final Socket socket) throws IOException
{
//开启一个新线程
new Thread(new Runnable()
{
public void run()
{
//创建输入流对象
ObjectInputStream is=null;
//创建输出流对象
ObjectOutputStream os=null;
try
{
is=new ObjectInputStream(socket.getInputStream());
os=new ObjectOutputStream(socket.getOutputStream());
//读取一个对象
Object obj = is.readObject();
//将对象转换为User类型
User user=(User) obj;
//在服务器端输出name成员和password成员信息
System.out.println("user: "+user.getName()+"/"+user.getPassword());
//修改当前对象的name成员数据
user.setName(user.getName()+"_new");
//修改当前对象的password对象数据
user.setPassword(user.getPassword()+"_new");
//将修改后的对象输出给客户端
os.writeObject(user);
os.flush();
}
catch(IOException|ClassNotFoundException ex)
{
ex.printStackTrace();
}
finally
{
try
{
//关闭输入流
is.close();
//关闭输出流
os.close();
//关闭客户端
socket.close();
}
catch(Exception ex){}
}
}
}).start();
}
}
如上述代码所示,在服务器端分别使用 ObjectInputStream 和 ObjectOutputStream 来接收和发送 socket 中的 InputStream 和OutputStream,然后转换成 User 对象。
客户端需要连接服务器,接收服务器输出的数据并解析,同时需要创建 User 对象并发给服务器。客户端 MyClient 类的实现代码如下:
package ch16;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class MyClient
{
public static void main(String[] args) throws Exception
{
//循环100次
for(int i=0;i<100;i++)
{
//创建客户端Socket
Socket socket=null;
//创建输入流
ObjectOutputStream os=null;
//创建输出流
ObjectInputStream is=null
try
{
//连接服务器
socket=new Socket("localhost",10000);
//接收输出流中的数据
os=new ObjectOutputStream(socket.getOutputStream());
//创建一个User对象
User user=new User("user_"+i,"password_"+i);
//将User对象写入输出流
os.writeObject(user);
os.flush();
//接收输入流中的数据
is=new ObjectInputStream(socket.getInputStream());
//读取输入流中的数据
Object obj=is.readObject();
//如果数据不空则转换成User对象,然后输出成员信息
if(obj!=null)
{
user=(User) obj;
System.out.println("user: "+user.getName()+"/"+user.getPassword());
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
finally
{
try
{
//关闭输入流
is.close();
//关闭输出流
os.close();
//关闭客户端
socket.close();
}
catch(Exception ex) {}
}
}
}
}
仔细观察上述代码可以发现,客户端与服务器端的代码类似,同样使用 ObjectOutputStream 和 ObjectInputStream 来处理数据。
先运行服务器端程序 MyServer,再运行客户端程序 MyClient。此时将在客户端看到下所示的输出。
user:user_86_nevj/password_86_new
user:user_87_new/password_87_new
user:user_88_new/password_88_new
user:user_89_new/password_89_new
user:user_90_new/password_90_new
user:user_91_new/password_91_new
user:user_92_new/password_92_new
user:user_93_new/password_93_new
user:user_94_new/password_94_new
user:user_95_new/password_95_new
user:user_96_new/password_96_new
user:user_97_new/password_97_new
user:user_98_new/password_98_new
user:user_99_new/password_99_new
服务器端的输出如下所示。
user:user_86/password_86
user:user_87/password_87
user:user_88/password_88
user:user_89/password_89
user:user_90/password_90
user:user_91/password_91
user:user_92/password_92
user:user_93/password_93
user:user_94/password_94
user:user_95/password_95
user:user_96/password_96
user:user_97/password_97
user:user_98/password_98
user:user_99/password_99
Java UDP通信:Java DatagramSocket类和DatagramPacket类
在 TCP/IP 协议的传输层除了一个 TCP 协议之外,还有一个 UDP 协议。UDP 协议是用户数据报协议的简称,也用于网络数据的传输。虽然 UDP 协议是一种不太可靠的协议,但有时在需要较快地接收数据并且可以忍受较小错误的情况下,UDP 就会表现出更大的优势。
下面是在 Java 中使用 UDP 协议发送数据的步骤。
- 使用 DatagramSocket() 创建一个数据包套接字。
- 使用 DatagramPacket() 创建要发送的数据包。
- 使用 DatagramSocket 类的 send() 方法发送数据包。
接收 UDP 数据包的步骤如下:
- 使用 DatagramSocket 创建数据包套接字,并将其绑定到指定的端口。
- 使用 DatagramPacket 创建字节数组来接收数据包。
- 使用 DatagramPacket 类的 receive() 方法接收 UDP 包。
DatagramPacket 类
java.net 包中的 DatagramPacket 类用来表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。下表简单介绍了 DatagramPacket 的构造方法和常用方法。
构造方法 | 说明 |
---|---|
DatagramPacket(byte[] buf,int length) | 构造 DatagramPacket,用来接收长度为 length 的数据包。 |
DatagramPacket(byte[] buf,int offset, int length) | 构造 DatagramPacket,用来接收长度为 length 的包,在缓 冲区中指定了偏移量。 |
DatagramPacket(byte[] buf,int length, InetAddress address,int port) | 构造 DatagramPacket,用来将长度为 length 的包发送到指 定主机上的指定端口。 |
DatagramPacket(byte[] buf,int length, SocketAddress address) | 构造数据报包,用来将长度为 length 的包发送到指定主机上 的指定端口。 |
DatagramPacket(byte[] buf,int offset, int length,InetAddress address,int port) | 构造 DatagramPacket,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口。 |
DatagramPacket(byte[] buf,int offset, int length,SocketAddress address) | 构造数据报包,用来将长度为 length、偏移量为 offset 的包发 送到指定主机上的指定端口。 |
方法 | 说明 |
---|---|
InetAddress getAddress() | 返回某台机器的 IP 地址,此数据报将要发往该机器或者 从该机器接收。 |
byte[] getData() | 返回数据缓冲区。 |
int getLength() | 返回将要发送或者接收的数据的长度。 |
int getOffset() | 返回将要发送或者接收的数据的偏移量。 |
int getPort() | 返回某台远程主机的端口号,此数据报将要发往该主机或 者从该主机接收。 |
getSocketAddress() | 获取要将此包发送或者发出此数据报的远程主机的 SocketAddress(通常为 IP地址+端口号)。 |
void setAddress(InetAddress addr) | 设置要将此数据报发往的目的机器的IP地址。 |
void setData(byte[] buf) | 为此包设置数据缓冲区。 |
void setData(byte[] buf,int offset, int length) | 为此包设置数据缓冲区。 |
void setLength(int length) | 为此包设置长度。 |
void setPort(int port) | 设置要将此数据报发往的远程主机的端口号。 |
void setSocketAddress(SocketAddress address) | 设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP地址+端口号)。 |
DatagramSocket 类
DatagramSocket 类用于表示发送和接收数据报包的套接字。数据报包套接字是包投递服务的发送或接收点。每个在数据报包套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
DatagramSocket 类的常用构造方法如表所示。
构造方法 | 说明 |
---|---|
DatagramSocket() | 构造数据报包套接字并将其绑定到本地主机上任何可用的端口。 |
DatagramSocket(int port) | 创建数据报包套接字并将其绑定到本地主机上的指定端口。 |
DatagramSocket(int portJnetAddress addr) | 创建数据报包套接字,将其绑定到指定的本地地址。 |
DatagramSocket(SocketAddress bindaddr) | 创建数据报包套接字,将其绑定到指定的本地套接字地址。 |
DatagramSocket 类的常用方法如表 所示。
方法 | 说明 |
---|---|
void bind(SocketAddress addr) | 将此 DatagramSocket 绑定到特定的地址和端口。 |
void close() | 关闭此数据报包套接字。 |
void connect(InetAddress address,int port) | 将套接字连接到此套接字的远程地址。 |
void connect(SocketAddress addr) | 将此套接子连接到远程套接子地址(IP地址+端口号)。 |
void disconnect() | 断开套接字的连接。 |
InetAddress getInetAddress() | 返回此套接字连接的地址。 |
InetAddress getLocalAddress() | 获取套接字绑定的本地地址。 |
int getLocalPort() | 返回此套接字绑定的本地主机上的端口号。 |
int getPort() | 返回此套接字的端口。 |
编写 UDP 程序,要求客户端程序可以向服务器端发送多条数据,服务器端程序可以接收客户端发送的多条数据并将其信息输出在控制台,主要步骤如下所示。
(1) 创建一个类作为客户端,在 main() 方法定义一个 DatagramSocket 对象和一个 DatagramPacket 对象并初始化为 null。然后再定义一个 InetAddress 对象和一个端口号并分别进行初始化,代码如下所示。
public static void main(String[] args)
{
DatagramSocket ds=null;
DatagramPacket dpSend=null;
InetAddress ia=InetAddress.getByName("127.0.0.1");
int port=3021;
}
(2) 使用 DatagramSocket 的 send(DatagramPacket p) 方法向服务器端发送数据报包,使用循环的方式完成 5 次数据的发送,每发送 1 次数据线程休眠 1000 毫秒,数据发送完毕调用 close() 方法,关闭 DatagramSocket 对象,代码如下。
try
{
ds=new DatagramSocket();
for(int i=0;i<5;i++)
{
byte[] data=("我是 UDP 客户端"+i).getBytes();
dpSend=new DatagramPacket(data,data.length,ia,port);
ds.send(dpSend);
Thread.sleep(1000);
}
ds.close();
}
catch(IOException | InterruptedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
(3) 创建一个类作为服务器端,在 main() 方法中定义一个 DatagramSocket 对象和一个 DatagramPacket 对象并初始化为 null,再定义一个端口号,如下所示。
public static void main(String[] args)
{
DatagramSocket ds=null;
DatagramPacket dpReceive=null;
int port=3021;
}
(4) 如果成功连接到 UDP 服务器则输出“UDP 服务器已启动。”。循环接收客户端发送的数据,并将其发送的内容以及IP地址等信息输出到控制台,代码如下所示。
try
{
ds=new DatagramSocket(port);
System.out.println("UDP服务器已启动。。。");
byte[] b=new byte[1024];
while(ds.isClosed()==false)
{
dpReceive=new DatagramPacket(b, b.length);
try
{
ds.receive(dpReceive);
byte[] Data=dpReceive.getData();
int len=Data.length;
System.out.println("UDP客户端发送的内容是:" + new String(Data, 0, len).trim());
System.out.println("UDP客户端IP:" + dpReceive.getAddress());
System.out.println("UDP客户端端口:" + dpReceive.getPort());
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
catch(SocketException e1)
{
// TODO 自动生成的 catch 块
e1.printStackTrace();
}
}
(5) 最后关闭 DatagramSocket 对象。执行服务器端程序,运行结果如下所示。
UDP服务器已启动。。。
(6) 再运行客户端程序,控制台的输出结果如下所示。
UDP客户端发送的内容是:我是UDP客户端0
UDP客户端IP:/127.0.0.1
UDP客户端端口:53472
UDP客户端发送的内容是:我是UDP客户端1
UDP客户端IP:/127.0.0.1
UDP客户端端口:53472
UDP客户端发送的内容是:我是UDP客户端2
UDP客户端IP:/127.0.0.1
UDP客户端端口:53472
UDP客户端发送的内容是:我是UDP客户端3
UDP客户端IP:/127.0.0.1
UDP客户端端口:53472
UDP客户端发送的内容是:我是UDP客户端4
UDP客户端IP:/127.0.0.1
UDP客户端端口:53472
什么是URL?及URL类和URLConnection类
在 Java 的 API 中的 java.net 包中包含一个 URL 类和一个 URLConnection 类。
URL 概念
URL 是统一资源定位符(Uniform Resource Locator)的简称,它表示 Internet 上某一资源的地址。通过 URL 用户可以访问各种网络资源,比如常见的 WWW 以及 FTP 站点。浏览器可以通过解析给定的 URL 在网络上查找相应的文件或其他资源。
URL 的语法格式如下所示。
protocol://resourceName
协议名(protocol)指明获取资源所使用的传输协议,如 HTTP、FTP 和 file 等,资源名(resourceName)则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。下面是一些简单的 URL 示例。
http://www.sun.com/ 协议名://主机名
http://localhost:8080/Test/admin/login.jsp 协议名://机器名:端口号/文件名
URL 类
在 java.net 包中包含专门用来处理 URL 的类 URL,可以获得 URL 的相关信息,例如 URL 的协议名和主机名等。
构造方法 | 说明 |
---|---|
public URL (String spec) | 通过一个表示 URL 地址的字符串可以构造一个 URL 对象。 |
public URL(URL context,String spec) | 使用基本地址和相对 URL 构造一个 URL 对象。 |
public URL(String protocol,String host,String file) | 使用指定的协议、主机名和文件名创建一个 URL 对象。 |
public URL(String protocol,String host,int port,String file) | 使用指定的协议、主机名、端口号和文件名创建一个 URL 对象。 |
URL 的常用方法如表所示。
方法 | 说明 |
---|---|
public String getProtocol() | 获取该 URL 的协议名。 |
public String getHost() | 获取该 URL 的主机名。 |
public int getPort() | 获取该 URL 的端口号,如果没有设置端口,返回 -1。 |
public String getFile() | 获取该 URL 的文件名。 |
public String getRef() | 获取该 URL 在文件中的相对位置。 |
public String getQuery() | 获取该 URL 的查询信息。 |
public String getPath() | 获取该 URL 的路径。 |
public String getAuthority() | 获取该 URL 的权限信息。 |
public String getUserInfo() | 获得使用者的信息。 |
public String getRef() | 获得该 URL 的锚点。 |
URLConnection 类
完成了 URL 的定义,接下来就可以获得 URL 的通信连接。在 java.net 包中,定义了专门的 URLConnection 类来表示与 URL 建立的通信连接,URLConnection 类的对象使用 URL 类的 openConnection() 方法获得。
URLConnection 类的主要方法如表所示。
方法 | 说明 |
---|---|
void addRequestProperty(String key,String value) | 添加由键值对指定的一般请求属性。key 指的是用于识别请求的关键字 (例如 accept),value 指的是与该键关联的值。 |
void connect() | 打开到此 URL 所引用的资源的通信链接(如果尚未建立这样的链接)。 |
Object getConnection() | 检索此 URL 链接的内容。 |
InputStream getInputStream() | 返回从此打开的链接读取的输入流。 |
OutputStream getOutputStream() | 返回写入到此链接的输出流。 |
URL getURL() | 返回此 URLConnection 的 URL 字段的值。 |
例 使用 URL 和 URLConnection 类获取与百度首页的链接并将其页面信息输出到控制台,主要步骤如下所示。
(1) 创建一个类,编写 main() 方法,在该方法中创建一个 URL 对象,然后传入参数“http://www.baidu.com/”,输出 URL 的相关信息,代码如下所示。
package ch16;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class URLDemo
{
public static void main(String[] args)
{
try
{
URL url=new URL("http://www.baidu.com/");
System.out.println("协议:" + url.getProtocol());
System.out.println("主机:" + url.getHost());
System.out.println("端口:" + url.getPort());
InputStream in;
}
catch(IOException e)
{
//TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
(2) 在 main() 方法的 try 模块中继续添加代码,获得 URLConnection 对象,通过输入流读取页面源代码并将信息输出到控制台,代码如下所示。
URLConnection uc=url.openConnection();
in=uc.getInputStream();
byte[] b=new byte[1024];
int len;
while((len=in.read(b))!=-1)
{
System.out.println(new String(b,0,len));
}
in.close();
(3) 运行程序,执行结果如下所示。
协议:http
主机:www.baidu.com
端口:-1
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/......<img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
提取 URL 协议名称
我们知道 URL 中必须有一个协议名称,常用的协议有 HTTP、HTTPS 和 FTP 等。本实例将允许用户输入一个 URL,然后从中提取出协议名称。
实例代码如下:
package ch16;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Scanner;
public class URLDemo1
{
public static void main(String[] args)
{
try
{
Scanner scan=new Scanner(System.in); //创建输入扫描器
System.out.println("请输入一个完整的网址:");
String line=scan.nextLine(); //获取用户输入文本
URL url=new URL(line); //创建URL对象
System.out.println("这个网址的主机名称是:"+url.getHost()); //获取主机名称
System.out.println("这个网址的URL协议名称是:"+url.getProtocol()); //获取协议名称
}
catch(MalformedURLException e)
{
System.out.println("输入的是非法网址"); //提示错误信息
}
}
}
如上述代码所示,在创建一个 URL 类对象之后调用 getHost() 方法获取主机名称,调用 getPmtocol() 方法获取协议名称。实例运行结果如下所示。
请输入一个完整的网址:
http://www.baidu.com
这个网址的主机名称是:www.baidu.com
这个网址的URL协议名称是:http
请输入一个完整的网址:
ftp://www.baidu.com/seo
这个网址的主机名称是:www.baidu.com
这个网址的URL协议名称是:ftp