部分内容来自以下博客:
https://www.cnblogs.com/renyuan/p/2698779.html
https://blog.csdn.net/qq_35860138/article/details/82054793
https://segmentfault.com/a/1190000018582150
https://www.cnblogs.com/kuangzhisen/p/7053689.html
1 网络编程概述
网络编程就是在两个或两个以上的设备之间传输数据。程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据,这个就是狭义的网络编程范畴。在发送和接收数据时,大部分的程序设计语言都设计了专门的API实现这些功能,程序员只需要调用即可。
网络编程的基本模型就是客户机到服务器模型,简单的说就是两个进程之间相互通讯,然后其中一个必须提供一个固定的位置,而另一个则只需要知道这个固定的位置。并去建立两者之间的联系,然后完成数据的通讯就可以了,这里提供固定位置的通常称为服务器,而建立联系的通常叫做客户端。
Java是Internet上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在Java的本机安装系统里,由JVM进行控制。并且Java实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
1.1 计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统。
从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
1.2 网络编程的目的
直接或间接地通过网络协议与其它计算机进行通讯。
1.3 网络编程的两个问题
如何准确地定位网络上一台或多台主机,如何定位主机上的特定的应用。
找到主机后如何可靠高效地进行数据传输。
1.4 网络编程中的两个要素
1)通信双方的地址:IP地址和端口号。
2)一定的规则,即网络通讯协议,有两套参考模型:
OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广。
TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
2 网络通信要素
2.1 IP地址和端口号
为了能够方便的识别网络上的每个设备,网络中的每个设备都会有一个唯一的数字标识,这个就是IP地址。
由于IP地址不容易记忆,所以为了方便记忆,有创造了另外一个概念:域名(Domain Name),例如sohu.com等。一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。
在网络中传输的数据,全部是以IP地址作为地址标识,所以在实际传输数据以前需要将域名转换为IP地址,实现这种功能的服务器称之为DNS服务器,也就是通俗的说法叫做域名解析。例如当用户在浏览器输入域名时,浏览器首先请求DNS服务器,将域名转换为IP地址,然后将转换后的IP地址反馈给浏览器,然后再进行实际的数据传输。
IP地址和域名很好的解决了在网络中找到一个计算机的问题,但是为了解决在网络中找到一个计算机上的特定应用的问题,就引入了另外一个概念:端口(Port)。
在同一个计算机中每个程序对应唯一的端口,这样一个计算机上就可以通过端口区分发送给每个端口的数据了,换句话说,也就是一个计算机上可以并发运行多个网络程序,而不会在互相之间产生干扰。
有了IP地址和端口的概念以后,在进行网络通讯交换时,就可以通过IP地址查找到该台计算机,然后通过端口标识这台计算机上的一个唯一的程序,这样就可以进行网络数据的交换了。
2.2 网络通讯协议
最后再介绍一个网络编程中最重要,也是最复杂的概念:协议(Protocol)。
按照前面的介绍,网络编程就是运行在不同计算机中两个程序之间的数据交换。在实际进行数据交换时,为了让接收端理解该数据,计算机比较笨,什么都不懂的,那么就需要规定该数据的格式,这个数据的格式就是协议。
2.3 两种协议的比较
?
2.4 TCP/IP参考模型说明
TCP/IP以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即:网络接口层、网络层、传输层和应用层。
2.4.1 应用层
TCP/IP模型将OSI参考模型中的会话层和表示层的功能合并到应用层实现。
应用层面向不同的网络应用引入了不同的应用层协议。其中,有基于TCP协议的,如文件传输协议(File Transfer Protocol,FTP)、虚拟终端协议(TELNET)、超文本链接协议(Hyper Text Transfer Protocol,HTTP)等。也有基于UDP协议的,如网络文件系统(Network File System,NFS)、简单网络管理协议(Simple Network Management Protocol,SNMP)、主域名称系统(Domain Name System,DNS)、通用文件传输协议(Trivial File Transfer Protocol,TFTP)等。
2.4.2 传输层
在TCP/IP模型中,传输层的功能是使源端主机和目标端主机上的对等实体可以进行会话。在传输层定义了两种服务质量不同的协议,即:传输控制协议TCP(transmission control protocol)和用户数据报协议UDP(user datagram protocol)。
TCP协议是一个面向连接的、可靠的协议。它将一台主机发出的字节流无差错地发往互联网上的其他主机。在发送端,它负责把上层传送下来的字节流分成报文段并传递给下层。在接收端,它负责把收到的报文进行重组后递交给上层。TCP协议还要处理端到端的流量控制,以避免缓慢接收的接收方没有足够的缓冲区接收发送方发送的大量数据。
UDP协议是一个不可靠的、无连接协议,主要适用于不需要对报文进行排序和流量控制的场合。
2.4.3 互连网络层
网络互连层是整个TCP/IP协议栈的核心。它的功能是把分组发往目标网络或主机。同时,为了尽快地发送分组,可能需要沿不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能不同,这就需要上层必须对分组进行排序。
网络互连层定义了分组格式和协议,即IP协议(Internet Protocol)。
网络互连层除了需要完成路由的功能外,也可以完成将不同类型的网络(异构网)互连的任务。除此之外,网络互连层还需要完成拥塞控制的功能。
2.4.4 网络接口层(主机到网络层)
实际上TCP/IP参考模型没有真正描述这一层的实现,只是要求能够提供给其上层一个访问接口,以便在其上传递IP分组。由于这一层次未被定义,所以其具体的实现方法将随着网络类型的不同而不同。
2.5 TCP和UDP比较
2.6 TCP的建立连接和释放连接
2.6.1 TCP的三次握手建立连接
客户端首先向服务端发送请求报文。
服务端收到请求报文后,发送响应报文和服务端的请求报文。
客户端收到服务端的响应报文和请求报文后,回传给服务端响应报文,进入连接建立状态。
服务端在收到客户端的响应报文后,也进入了连接状态。
2.6.2 TCP的四次挥手释放连接
客户端首先向服务端发送请求关闭报文。
服务端收到请求关闭报文后,发送响应关闭报文。
客户端收到服务端的响应报文后,进入等待关闭状态,停止数据发送。服务端在此期间继续处理残余报文,等到处理完毕之后,向客户端发送请求关闭报文。
客户端收到服务端的请求关闭报文后,回传给服务端响应关闭报文。
服务端在收到客户端的响应关闭报文后,进入了连接关闭状态。
3 常用类
3.1 InetAddress类
此类表示互联网协议(IP)地址。
3.1.1 常用方法
// 根据IP地址或者域名获取InetAddress实例。
public static InetAddress getByName(String host);
// 获取IP地址为本地的InetAddress实例。
public static InetAddress getLocalHost();
// 获取InetAddress实例的主机名。
public String getHostName();
// 获取InetAddress实例的IP地址。
public String getHostAddress();
3.1.2 测试代码
测试代码如下:
public static void main(String[] args) {
?? ?try {
?? ??? ?InetAddress baidu = InetAddress.getByName("www.baidu.com");
?? ??? ?InetAddress local = InetAddress.getLocalHost();
?? ??? ?System.out.println(baidu.getHostName());
?? ??? ?System.out.println(local.getHostAddress());
?? ?} catch (UnknownHostException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
运行结果:
www.baidu.com
192.168.1.109
3.2 InetSocketAddress类
此类实现IP套接字地址(IP地址和端口号)。
3.2.1 构造方法
// 根据端口创建本地套接字地址。
public InetSocketAddress(int port);
// 根据IP地址和端口号创建套接字地址。
public InetSocketAddress(InetAddress addr, int port);
// 根据主机名和端口号创建套接字地址。
public InetSocketAddress(String hostname, int port);
3.2.2 常用方法
// 获取InetAddress实例。
public final InetAddress getAddress();
// 获取主机名。
public final String getHostName();
// 获取端口号。
public final int getPort();
// 构造此InetSocketAddress的字符串表示形式(主机名/IP:端口号)。
public String toString();
3.2.3 测试代码
测试代码如下:
public static void main(String[] args) {
?? ?try {
?? ??? ?InetSocketAddress local = new InetSocketAddress(InetAddress.getLocalHost(), 80);
?? ??? ?InetSocketAddress baidu = new InetSocketAddress("www.baidu.com", 80);
?? ??? ?System.out.println(baidu);
?? ??? ?System.out.println(baidu.getHostName());
?? ??? ?System.out.println(local.getAddress().getHostAddress());
?? ??? ?System.out.println(local.getPort());
?? ?} catch (UnknownHostException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
运行结果:
www.baidu.com/182.61.200.7:80
www.baidu.com
192.168.1.109
80
3.3 Socket类
此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
3.3.1 构造方法
// 创建一个流套接字并将其连接到指定IP地址的指定端口号。
public Socket(InetAddress address, int port);
// 创建一个流套接字并将其连接到指定主机上的指定端口号。
public Socket(String host, int port);
3.3.2 常用方法
// 返回此套接字的输出流。
public OutputStream getOutputStream();
// 返回此套接字的输入流。
public InputStream getInputStream();
// 关闭此套接字。
public void close();
3.4 ServerSocket类
此类实现服务器套接字。服务器套接字等待请求通过网络传入。
3.4.1 构造方法
// 创建绑定到特定端口的服务器套接字。
public ServerSocket(int port);
3.4.2 常用方法
// 侦听并接受到此套接字的连接。
public Socket accept();
// 关闭此套接字。
public void close();
3.5 DatagramSocket类
此类表示用来发送和接收数据报包的套接字。
3.5.1 构造方法
// 创建数据报套接字,将其绑定到本地地址上的随机端口。
public DatagramSocket();
// 创建数据报套接字,将其绑定到本地地址上的指定端口。
public DatagramSocket(int port);
// 创建数据报套接字,将其绑定到指定地址上的指定端口。
public DatagramSocket(int port, InetAddress laddr);
3.5.2 常用方法
// 从此套接字发送数据报包。
public void send(DatagramPacket p);
// 从此套接字接收数据报包。
public synchronized void receive(DatagramPacket p);
// 关闭此数据报套接字。
public void close();
3.6 DatagramPacket类
此类表示数据报包。
3.6.1 构造方法
// 用来接收数据包。
public DatagramPacket(byte buf[], int length);
// 用来接收数据包。
public DatagramPacket(byte buf[], int offset, int length);
// 用来将数据包发送到指定主机上的指定端口。
public DatagramPacket(byte buf[], int length, InetAddress address, int port);
// 用来将数据包发送到指定主机上的指定端口。
public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port);
4 TCP网络编程
4.1 客户端编程说明
4.1.1 建立连接
在客户端网络编程中,首先需要建立连接,也就是创建Socket类型的对象,实例代码如下:
Socket s = new Socket("192.168.1.103", 10000);
上面的代码中,实现的是连接到IP地址是192.168.1.103的计算机的10000号端口,至于底层网络如何实现建立连接,对于程序员来说是完全透明的。如果建立连接时,本机网络不通,或服务器端程序未开启,则会抛出异常。
4.1.2 数据交换
连接一旦建立,则完成了客户端编程的第一步,紧接着的步骤就是进行网络数据交换,也就是说只需要从连接中获得输入流和输出流即可,然后将需要发送的数据写入连接对象的输出流中,在发送完成以后从输入流中读取数据即可。实例代码如下:
OutputStream os = s.getOutputStream();// 获得输出流。
InputStream is = s.getInputStream();// 获得输入流。
上面的代码中,从连接对象获得了输出流和输入流对象,在整个网络编程中,后续的数据交换就变成了IO操作,也就是遵循“请求响应”模型的规定,先向输出流中写入数据,这些数据会被系统发送出去,然后在从输入流中读取服务器端的反馈信息,这样就完成了一次数据交换过程,当然这个数据交换过程可以多次进行。
4.1.3 关闭连接
最后当数据交换完成以后,关闭网络连接,释放网络连接占用的系统端口和内存等资源,完成网络操作,实例代码如下:
s.close();
4.2 服务端编程说明
4.2.1 监听端口
在服务器端程序编程中,由于服务器端实现的是被动等待连接,所以服务器端编程的第一个步骤是监听端口,也就是监听是否有客户端连接到达。实现服务器端监听的代码为:
ServerSocket ss = new ServerSocket(10000);
该代码实现的功能是监听当前计算机的10000号端口,如果在执行该代码时,10000号端口已经被别的程序占用,那么将抛出异常。
4.2.2 获得连接
服务器端编程的第二个步骤是获得连接。该步骤的作用是当有客户端连接到达时,建立一个和客户端连接对应的Socket连接对象,从而释放客户端连接对于服务器端端口的占用。实现获得连接的代码是:
Socket s = ss.accept();
该代码实现的功能是获得当前连接到服务器端的客户端连接。当无连接时,将阻塞程序的执行,直到连接到达,才执行该行代码。另外获得的连接会在服务器端的该端口注册,这样以后就可以通过在服务器端的注册信息直接通信,而注册以后服务器端的端口就被释放出来,又可以继续接受其它的连接了。
4.2.3 数据交换
连接获得以后,后续的编程就和客户端的网络编程类似了,这里获得的Socket类型的连接就和客户端的网络连接一样了,只是服务器端需要首先读取发送过来的数据,然后进行逻辑处理以后再发送给客户端,也就是交换数据的顺序和客户端交换数据的步骤刚好相反。
4.2.4 关闭连接
最后,在服务器端通信完成以后,关闭服务器端连接。实现的代码为:
s.close();
4.3 客户端向服务端发送消息
客户端代码:
public static void main(String[] args) {
?? ?Socket s = null;
?? ?OutputStream os = null;
?? ?try {
?? ??? ?System.out.println("客户端已启动");
?? ??? ?s = new Socket(InetAddress.getLocalHost(), 8800);
?? ??? ?os = s.getOutputStream();
?? ??? ?os.write("你好,我是客户端".getBytes());
?? ??? ?System.out.println("客户端成功发送数据");
?? ?} catch (IOException e) {
?? ??? ?e.printStackTrace();
?? ?} finally {
?? ??? ?if (os != null) {
?? ??? ??? ?try {
?? ??? ??? ??? ?os.close();
?? ??? ??? ?} catch (IOException e) {
?? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ?}
?? ??? ?}
?? ??? ?if (s != null) {
?? ??? ??? ?try {
?? ??? ??? ??? ?s.close();
?? ??? ??? ?} catch (IOException e) {
?? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ?}
?? ??? ?}
?? ?}
}
服务端代码:
public static void main(String[] args) {
?? ?ServerSocket ss = null;
?? ?Socket s = null;
?? ?InputStream is = null;
?? ?ByteArrayOutputStream baos = null;
?? ?try {
?? ??? ?System.out.println("服务端已启动");
?? ??? ?ss = new ServerSocket(8800);
?? ??? ?s = ss.accept();
?? ??? ?is = s.getInputStream();
?? ??? ?byte[] temp = new byte[1024];
?? ??? ?int len;
?? ??? ?baos = new ByteArrayOutputStream();
?? ??? ?while ((len = is.read(temp)) != -1) {
?? ??? ??? ?baos.write(temp, 0, len);
?? ??? ?}
?? ??? ?System.out.println("服务端成功接收数据:" + baos.toString());
?? ?} catch (IOException e) {
?? ??? ?e.printStackTrace();
?? ?} finally {
?? ??? ?if (baos != null) {
?? ??? ??? ?try {
?? ??? ??? ??? ?baos.close();
?? ??? ??? ?} catch (IOException e) {
?? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ?}
?? ??? ?}
?? ??? ?if (is != null) {
?? ??? ??? ?try {
?? ??? ??? ??? ?is.close();
?? ??? ??? ?} catch (IOException e) {
?? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ?}
?? ??? ?}
?? ??? ?if (s != null) {
?? ??? ??? ?try {
?? ??? ??? ??? ?s.close();
?? ??? ??? ?} catch (IOException e) {
?? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ?}
?? ??? ?}
?? ??? ?if (ss != null) {
?? ??? ??? ?try {
?? ??? ??? ??? ?ss.close();
?? ??? ??? ?} catch (IOException e) {
?? ??? ??? ??? ?e.printStackTrace();
?? ??? ??? ?}
?? ??? ?}
?? ?}
}
客户端结果:
客户端已启动
客户端成功发送数据
服务端结果:
服务端已启动
服务端成功接收数据:你好,我是客户端
4.4 客户端向服务端发送消息,服务端返回消息
客户端代码:
public static void main(String[] args) {
????Socket s = null;
????OutputStream os = null;
????InputStream is = null;
????ByteArrayOutputStream baos = null;
????try {
????????System.out.println("客户端已启动");
????????s = new Socket(InetAddress.getLocalHost(), 8800);
????????os = s.getOutputStream();
????????os.write("你好,我是客户端".getBytes());
????????System.out.println("客户端成功发送数据");
????????s.shutdownOutput();
????????is = s.getInputStream();
????????byte[] temp = new byte[1024];
????????int len;
????????baos = new ByteArrayOutputStream();
????????while ((len = is.read(temp)) != -1) {
????????????baos.write(temp, 0, len);
????????}
????????System.out.println("客户端成功接收服务端回传数据:" + baos.toString());
????} catch (IOException e) {
????????e.printStackTrace();
????} finally {
????????if (os != null) {
????????????try {
????????????????os.close();
????????????} catch (IOException e) {
????????????????e.printStackTrace();
????????????}
????????}
????????if (s != null) {
????????????try {
????????????????s.close();
????????????} catch (IOException e) {
????????????????e.printStackTrace();
????????????}
????????}
????????if (baos != null) {
????????????try {
????????????????baos.close();
????????????} catch (IOException e) {
????????????????e.printStackTrace();
????????????}
????????}
????????if (is != null) {
????????????try {
????????????????is.close();
????????????} catch (IOException e) {
????????????????e.printStackTrace();
????????????}
????????}
????}
}
服务端代码:
public static void main(String[] args) { ?? ?ServerSocket ss = null; ?? ?Socket s = null; ?? ?InputStream is = null; ?? ?ByteArrayOutputStream baos = null; ?? ?OutputStream os = null; ?? ?try { ?? ??? ?System.out.println("服务端已启动"); ?? ??? ?ss = new ServerSocket(8800); ?? ??? ?s = ss.accept(); ?? ??? ?is = s.getInputStream(); ?? ??? ?byte[] temp = new byte[1024]; ?? ??? ?int len; ?? ??? ?baos = new ByteArrayOutputStream(); ?? ??? ?while ((len = is.read(temp)) != -1) { ?? ??? ??? ?baos.write(temp, 0, len); ?? ??? ?} ?? ??? ?System.out.println("服务端成功接收数据:" + baos.toString()); ?? ??? ? ?? ??? ?os = s.getOutputStream(); ?? ??? ?os.write("你好,我是服务端".getBytes()); ?? ??? ?System.out.println("服务端成功发送回传数据"); ?? ?} catch (IOException e) { ?? ??? ?e.printStackTrace(); ?? ?} finally { ?? ??? ?if (baos != null) { ?? ??? ??? ?try { ?? ??? ??? ??? ?baos.close(); ?? ??? ??? ?} catch (IOException e) { ?? ??? ??? ??? ?e.printStackTrace(); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?if (is != null) { ?? ??? ??? ?try { ?? ??? ??? ??? ?is.close(); ?? ??? ??? ?} catch (IOException e) { ?? ??? ??? ??? ?e.printStackTrace(); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?if (s != null) { ?? ??? ??? ?try { ?? ??? ??? ??? ?s.close(); ?? ??? ??? ?} catch (IOException e) { ?? ??? ??? ??? ?e.printStackTrace(); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?if (ss != null) { ?? ??? ??? ?try { ?? ??? ??? ??? ?ss.close(); ?? ??? ??? ?} catch (IOException e) { ?? ??? ??? ??? ?e.printStackTrace(); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?if (os != null) { ?? ??? ??? ?try { ?? ??? ??? ??? ?os.close(); ?? ??? ??? ?} catch (IOException e) { ?? ??? ??? ??? ?e.printStackTrace(); ?? ??? ??? ?} ?? ??? ?} ?? ?} }
客户端结果:
客户端已启动
客户端成功发送数据
客户端成功接收服务端回传数据:你好,我是服务端
服务端结果:
服务端已启动
服务端成功接收数据:你好,我是客户端
服务端成功发送回传数据
5 UDP网络编程
5.1 客户端编程说明
UDP客户端编程涉及的步骤也是4个部分:建立连接、发送数据、接收数据和关闭连接。
5.1.1 建立连接
首先介绍UDP方式的网络编程中建立连接的实现。其中UDP方式的建立连接和TCP方式不同,只需要建立一个连接对象即可,不需要指定服务器的IP和端口号码。实现的代码为:
DatagramSocket ds = new DatagramSocket();
这样就建立了一个客户端连接,该客户端连接使用系统随机分配的一个本地计算机的未用端口号。在该连接中,不指定服务器端的IP和端口,所以UDP方式的网络连接更像一个发射器,而不是一个具体的连接。
当然,可以通过制定连接使用的端口号来创建客户端连接。
DatagramSocket ds = new DatagramSocket(5000);
这样就是使用本地计算机的5000号端口建立了一个连接。一般在建立客户端连接时没有必要指定端口号码。
5.1.2 发送数据
在UDP方式的网络编程中,IO技术不是必须的,将数据内容、服务器IP和服务器端口号一起构造成一个DatagramPacket类型的对象,就可以完成数据的准备,发送该对象即可。
按照UDP协议的约定,在进行数据传输时,系统只是尽全力传输数据,但是并不保证数据一定被正确传输,如果数据在传输过程中丢失,那就丢失了。
5.1.3 接收数据
UDP方式在进行网络通讯时,也遵循“请求响应”模型,在发送数据完成以后,就可以接收服务器端的反馈数据了。
首先构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据,该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度。然后以该缓冲数组为基础构造一个DatagramPacket数据包对象,用于接收数据。
5.1.4 关闭连接
UDP方式客户端网络编程的最后一个步骤就是关闭连接。实现的代码如下:
ds.close();
需要说明的是,和TCP建立连接的方式不同,UDP方式的同一个网络连接对象,可以发送到达不同服务器端IP或端口的数据包,这点是TCP方式无法做到的。
5.2 服务端编程说明
UDP方式网络编程的服务器端实现和TCP方式的服务器端实现类似,也是服务器端监听某个端口,然后获得数据包,进行逻辑处理以后将处理以后的结果反馈给客户端,最后关闭网络连接,下面依次进行介绍。
5.2.1 监听端口
首先UDP方式服务器端网络编程需要建立一个连接,该连接监听某个端口,实现的代码为:
DatagramSocket ds = new DatagramSocket(10010);
由于服务器端的端口需要固定,所以一般在建立服务器端连接时,都指定端口号。例如该实例代码中指定10010端口为服务器端使用的端口号,客户端在连接服务器端时连接该端口号即可。
5.2.2 获得连接
接着服务器端就开始接收客户端发送过来的数据,其接收的方法和客户端接收的方法一样,当无连接时,将阻塞程序的执行,直到连接到达,才执行该行代码。接收到客户端发送过来的数据以后,服务器端对该数据进行逻辑处理,然后将处理以后的结果再发送给客户端,在这里发送时就比客户端要麻烦一些,因为服务器端需要获得客户端的IP和客户端使用的端口号,这个都可以从接收到的数据包中获得。实例代码如下:
InetAddress clientIP = receiveDp.getAddress();// 获得客户端的IP
Int clientPort = receiveDp.getPort();// 获得客户端的端口号
5.2.3 数据交换
使用以上代码,就可以从接收到的数据包对象中获得客户端的IP地址和客户端的端口号,这样就可以在服务器端中将处理以后的数据构造成数据包对象,然后将处理以后的数据内容反馈给客户端了。
5.2.4 关闭连接
最后,当服务器端实现完成以后,关闭服务器端连接。实例代码如下:
ds.close();
5.3 客户端向服务端发送消息
客户端代码:
public static void main(String[] args) {
?? ?try {
?? ??? ?System.out.println("客户端已启动");
?? ??? ?DatagramSocket ds = new DatagramSocket();
?? ??? ?byte[] buffer = "我是客户端".getBytes();
?? ??? ?DatagramPacket dp = new DatagramPacket(buffer, 0, buffer.length, InetAddress.getLocalHost(), 8800);
?? ??? ?ds.send(dp);
?? ??? ?System.out.println("客户端成功发送数据");
?? ??? ?ds.close();
?? ?} catch (IOException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
服务端代码:
public static void main(String[] args) {
?? ?try {
?? ??? ?System.out.println("服务端已启动");
?? ??? ?DatagramSocket ds = new DatagramSocket(8800);
?? ??? ?byte[] buffer = new byte[1024];
?? ??? ?DatagramPacket dp = new DatagramPacket(buffer , 0, buffer.length);
?? ??? ?ds.receive(dp);
?? ??? ?System.out.println("服务端成功接收数据:" + new String(dp.getData(), 0, dp.getLength()));
?? ??? ?ds.close();
?? ?} catch (IOException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
客户端结果:
客户端已启动
客户端成功发送数据
服务端结果:
服务端已启动
服务端成功接收数据:我是客户端
5.4 客户端和服务端实现聊天的功能
客户端代码:
public static void main(String[] args) {
?? ?try {
?? ??? ?System.out.println("顾客已上线");
?? ??? ?Scanner scan = new Scanner(System.in);
?? ??? ?DatagramSocket ds = new DatagramSocket(8811);
?? ??? ?while (true) {
?? ??? ??? ?String message = scan.next();
?? ??? ??? ?byte[] bufMes = message.getBytes();
?? ??? ??? ?DatagramPacket dpMes = new DatagramPacket(bufMes, 0, bufMes.length, InetAddress.getLocalHost(), 8800);
?? ??? ??? ?ds.send(dpMes);
?? ??? ??? ?
?? ??? ??? ?byte[] bufRec = new byte[1024];
?? ??? ??? ?DatagramPacket dpRec = new DatagramPacket(bufRec , 0, bufRec.length);
?? ??? ??? ?ds.receive(dpRec);
?? ??? ??? ?String receive = new String(dpRec.getData(), 0, dpRec.getLength());
?? ??? ??? ?System.out.println("客服说:" + receive);
?? ??? ??? ?
?? ??? ??? ?if (message.equals("bye")) {
?? ??? ??? ??? ?break;
?? ??? ??? ?}
?? ??? ?}
?? ??? ?scan.close();
?? ??? ?ds.close();
?? ?} catch (IOException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
服务端代码:
public static void main(String[] args) {
?? ?try {
?? ??? ?System.out.println("客服已上线");
?? ??? ?Scanner scan = new Scanner(System.in);
?? ??? ?DatagramSocket ds = new DatagramSocket(8800);
?? ??? ?while (true) {
?? ??? ??? ?byte[] bufRec = new byte[1024];
?? ??? ??? ?DatagramPacket dpRec = new DatagramPacket(bufRec , 0, bufRec.length);
?? ??? ??? ?ds.receive(dpRec);
?? ??? ??? ?String receive = new String(dpRec.getData(), 0, dpRec.getLength());
?? ??? ??? ?System.out.println("顾客说:" + receive);
?? ??? ??? ?String message = scan.next();
?? ??? ??? ?byte[] bufMes = message.getBytes();
?? ??? ??? ?DatagramPacket dpMes = new DatagramPacket(bufMes, 0, bufMes.length, InetAddress.getLocalHost(), 8811);
?? ??? ??? ?ds.send(dpMes);
?? ??? ??? ?
?? ??? ??? ?if (message.equals("bye")) {
?? ??? ??? ??? ?break;
?? ??? ??? ?}
?? ??? ?}
?? ??? ?scan.close();
?? ??? ?ds.close();
?? ?} catch (IOException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
客户端结果:
顾客已上线
客服您好!
客服说:您好,请问有什么事情吗?
我要退货
客服说:好的,请填写退货单号
退货单号已经填好了
客服说:退货成功!请问还有其他事情吗?
没有了,再见!
客服说:好的,再见!
bye
客服说:bye
服务端结果:
客服已上线
顾客说:客服您好!
您好,请问有什么事情吗?
顾客说:我要退货
好的,请填写退货单号
顾客说:退货单号已经填好了
退货成功!请问还有其他事情吗?
顾客说:没有了,再见!
好的,再见!
顾客说:bye
Bye
6 URL编程
6.1 什么是URL
URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某一资源的地址。
通过URL我们可以访问Internet上的各种网络资源,比如最常见的www、ftp站点。
浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
6.2 组成部分
URL主要由协议名和资源名组成,其中,资源名又包括主机名、端口号、文件名、文件内部的引用。
URL各部分的写法规则:传输协议://主机名:端口号/文件名#内部引用?参数列表。
6.3 URL的使用
6.3.1 构造方法
// 根据String表示形式创建URL对象。
public URL(String spec);
// 根据指定protocol、host、port和file创建URL对象。
public URL(String protocol, String host, int port, String file);
6.3.2 常用方法
// 获取此URL的协议名称。
public String getProtocol();
// 获取此URL的主机名(如果适用)。
public String getHost();
// 获取此URL的端口号。
public int getPort();
// 获取此URL的文件名。
public String getFile();
// 获取此URL的内部引用。
public String getRef();
// 获取此URL的路径部分。
public String getPath();
// 读取网络资源。
public final InputStream openStream();
// 创建URLConnection实例对象。
public URLConnection openConnection();
6.3.3 测试代码
public static void main(String[] args) {
?? ?try {
?? ??? ?URL url = new URL("http://www.gamelan.com:80/Gamelan/network.html#BOTTOM");
?? ??? ?System.out.println(url.getProtocol());
?? ??? ?System.out.println(url.getHost());
?? ??? ?System.out.println(url.getPort());
?? ??? ?System.out.println(url.getFile());
?? ??? ?System.out.println(url.getRef());
?? ??? ?System.out.println(url.getPath());
?? ?} catch (MalformedURLException e) {
?? ??? ?e.printStackTrace();
?? ?}
}
运行结果:
http
www.gamelan.com
80
/Gamelan/network.html
BOTTOM
/Gamelan/network.html
6.4 操作网络资源
URL的方法openStream()能从网络上读取数据。
若希望输出数据,例如向服务器端的CGI(Common Gateway Interface,公共网关接口,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用URLConnection。
6.5 针对HTTP协议的URLConnection类
URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法openConnection()生成对应的URLConnection对象。如果连接过程失败,将产生IOException。
6.5.1 获取方法
// 通过URL类的方法获取URLConnection实例。
public URLConnection openConnection();
6.5.2 常用方法
// 获取输入流。
public InputStream getInputStream();
// 获取输出流。
public OutputStream getOutputStream();
|