一、网络的相关概念
1.1 网络通信
1、概念:两台设备之间通过网络实现数据传输
2、网络通信:将数据通过网络从一台设备传输到另一台设备
3、java.net 包下提供了一系列的类或接口,供程序员使用,完成网络通信
1.2 网络
1、概念:两台或多台设备通过一定物理设备连接起来构成了网络
2、根据网络的覆盖范围不同,对网络可以分为局域网、地域网、广域网等
1.3 ip 地址
1、概念:用于唯一标识网络中的每台计算机
2、查看 ip 地址:ipConfig
3、ip 地址的表示形式:点分十进制 xx.xx.xx.xx
4、每一个十进制数的范围:0~255
5、ip 地址的组成 = 网络地址 + 主机地址,比如:192.168.16.69
6、iPv6 是互联网工程任务组设计的用于替代 iPv4 的下一代 IP 协议,其地址数量可以为全世界的每一粒沙子编上一个地址。
7、由于 iPv4 最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。iPv6 的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种设备接入互联网的障碍。
1.4 ipv4 地址分类
1.5 域名
1、www.baidu.com
2、好处:为了方便记忆,解决记 ip 的困难
3、概念:将 ip 地址映射成域名
1.6 端口
1、概念:用于标记计算机上某个特定的网络程序
2、表示形式:以整数的形式,范围 0 ~ 65535
3、0 ~ 1024 已经被占用,比如 ssh 22,ftp 21,smtp 25,http 80
4、常见的网络程序端口号:tomcat(8080)、mysql(3306)、oracle(1521)、sqlserver(1433)
1.7 网络通信协议
协议(tcp/ip),TCP/IP(Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通信协议,这个协议是 Internet 最基本的协议,Internet 国际互联网络的基础,简单地说,就是由网络层的 IP 地址和传输层的 TCP 协议组成的,如下图
OSI 模型为理论模型,在现实里面是没有这个模型的,因为分的太细了,没有人用。最终给它做了一个简化 ,简化成了 TCP/IP 模型,其中传输层就是 TCP 层,网络层就是 IP 层。
1.8 TCP 和 UDP
1.8.1 TCP 传输控制协议
1、使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道。
2、传输前,采用 “三次握手” 方式,是可靠的。
3、TCP 协议进行通信的两个应用进程:客户端、服务端
4、在连接中可进行大数据量的传输
5、传输完毕,须释放已经建立的连接,效率低
1.8.2 UDP 用户数据协议
1、将数据、源、目的封装成数据包,不需要建立连接
2、每个数据包的大小限制在 64K 内,不适合传输大量数据
3、因无需连接,故是不可靠的
4、发送数据结束时无需释放资源(因为不是面向连接的),速度快
二、InetAddress 类
2.1 相关方法
// 获取本机 InetAddress 对象
getLocalHost()
// 根据指定主机名/域名获取 ip 地址对象
getByName()
// 获取 InetAddress 对象的主机名
getHostName()
// 获取 InetAddress 对象的地址
getHostAdderss()
2.2 示例代码
public static void main(String[] args) throws Exception{
// 1、获取本机 InetAddress 对象
InetAddress localHost = Inet4Address.getLocalHost();
System.out.println(localHost);// DESKTOP-B0B82CP/192.168.229.1
// 2、根据指定主机名/域名获取 ip 地址对象
InetAddress host1 = Inet4Address.getByName("DESKTOP-B0B82CP");
System.out.println(host1);// DESKTOP-B0B82CP/192.168.229.1
// 3、根据域名返回 InetAddress 对象,比如 www.baidu.com
InetAddress host2 = Inet4Address.getByName("www.baidu.com");
System.out.println(host2);// www.baidu.com/110.242.68.4
// 4、通过 InetAddress 对象获取对应的地址
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress);// 110.242.68.4
// 5、通过 InetAddress 对象获取对应的主机名或域名
String hostName = host2.getHostName();
System.out.println(hostName);// www.baidu.com
}
2.3 作用
1、这个类的主要的作用是获取本机的主机名/IP 信息。
2、通过域名获取远程服务器的 IP 等信息。
三、Socket
3.1 基本介绍
1、套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准。
2、通信的两端都要有 Socket,是两台机器间通信的端点。
3、网络通信其实就是 Socket 间的通信。
4、Socket 允许程序将网络连接当成一个流,数据在两个 Socket 间通过 IO 传输。
5、一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
3.2 示意图
四、TCP 网络通信编程
4.1 基本介绍
1、基于客户端--服务器的网络通信
2、底层使用的是 TCP/IP 协议
3、应用场景为:客户端发送数据,服务端接收并显示
4、是基于 Socket 的 TCP 编程
4.2 案例一
4.2.1 需求分析
1、使用字节流
2、编写一个服务器端和一个客户端。
3、服务端在 9999 端口监听。
4、客户端连接到服务器端,发送 "hello server" ,然后退出。
5、服务器端接收到客户端发送的信息,输出并退出。
4.2.2 代码编写
服务端代码如下
public class SocketTcp01Server {
public static void main(String[] args) throws IOException {
// 1、监听本机的 9999 端口,等待连接
// 细节:需要注意的是本机没有其他服务在监听 9999
// 细节:ServerSocket 可以通过 accept() 返回多个 socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在 9999 端口等待连接");
// 2、当没有客户端连接 9999 端口时,会阻塞等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket 返回="+socket.getClass());
// 3、通过 ocket.getInputStream() 读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
// 4、IO 读取
byte [] buff = new byte[1024];
int readline =0;
while((readline = inputStream.read(buff)) != -1){
// 根据实际读取到的字符串显示内容
System.out.println(new String(buff,0,readline));
}
// 5、关闭相关的流和 socket
inputStream.close();
socket.close();
serverSocket.close();
}
}
客户端代码如下
public class SocketTcp01Client {
public static void main(String[] args) throws IOException {
// 1、连接服务端(ip,端口)
// 连接本机的 9999 端口,如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端 socket 返回="+socket.getClass());
// 2、连接上后,生成 socket 对象
// 再获取到和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
// 3、通过输出流写入数据到数据通道
outputStream.write("hello server".getBytes());
// 4、关闭流对象和 socket
outputStream.close();
socket.close();
System.out.println("客户端退出了");
}
}
4.3 案例二
4.3.1 需求分析
1、使用字节流
2、编写一个服务器端和一个客户端。
3、服务端在 9999 端口监听。
4、客户端连接到服务器端,发送 "hello server" ,并接收服务端回发的 "hello client" 后退出。
5、服务器端接收到客户端发送的信息,输出并发送 "hello client" 再退出。
4.3.2 代码编写
服务端代码如下
public class SocketTcp02Server {
public static void main(String[] args) throws IOException {
// 1、监听本机的 9999 端口,等待连接
// 细节:需要注意的是本机没有其他服务在监听 9999
// 细节:ServerSocket 可以通过 accept() 返回多个 socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在 9999 端口等待连接");
// 2、当没有客户端连接 9999 端口时,会阻塞等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket 返回="+socket.getClass());
// 3、通过 ocket.getInputStream() 读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
// 4、IO 读取
byte [] buff = new byte[1024];
int readline =0;
while((readline = inputStream.read(buff)) != -1){
// 根据实际读取到的字符串显示内容
System.out.println(new String(buff,0,readline));
}
// 5、获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello client".getBytes());
// 设置结束标记
socket.shutdownOutput();
// 5、关闭相关的流和 socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
客户端代码如下
public class SocketTcp02Client {
public static void main(String[] args) throws IOException {
// 1、连接服务端(ip,端口)
// 连接本机的 9999 端口,如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端 socket 返回="+socket.getClass());
// 2、连接上后,生成 socket 对象
// 再获取到和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
// 3、通过输出流写入数据到数据通道
outputStream.write("hello server".getBytes());
// 设置结束标记
socket.shutdownOutput();
// 获取和 socket 关联的输入流,读取数据(字节)并显示
InputStream inputStream = socket.getInputStream();
byte [] buff = new byte[1024];
int readLine = 0;
while((readLine = inputStream.read(buff)) != -1){
System.out.println(new String(buff,0,readLine));
}
// 4、关闭流对象和 socket
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出了");
}
}
4.4 案例三
4.4.1 需求分析
1、使用字符流
2、编写一个服务器端和一个客户端。
3、服务端在 9999 端口监听。
4、客户端连接到服务器端,发送 "hello server" ,并接收服务端回发的 "hello client" 后退出。
5、服务器端接收到客户端发送的信息,输出并发送 "hello client" 再退出。
4.4.2 代码编写
服务端代码如下
public class SocketTcp03Server {
public static void main(String[] args) throws IOException {
// 1、监听本机的 9999 端口,等待连接
// 细节:需要注意的是本机没有其他服务在监听 9999
// 细节:ServerSocket 可以通过 accept() 返回多个 socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在 9999 端口等待连接");
// 2、当没有客户端连接 9999 端口时,会阻塞等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端 socket 返回="+socket.getClass());
// 3、通过 socket.getInputStream() 读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
// 4、IO 读取,使用字符流
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
System.out.println(s);
// 5、获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
bw.write("hello client 字符流");
bw.newLine();// 插入一个换行符,表示写入的内容结束
bw.flush();// 注意需要手动的 flush
// 5、关闭相关的流和 socket
bw.close();// 只关闭外层流即可
br.close();// 只关闭外层流即可
socket.close();
serverSocket.close();
}
}
客户端代码如下
public class SocketTcp03Client {
public static void main(String[] args) throws IOException {
// 1、连接服务端(ip,端口)
// 连接本机的 9999 端口,如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端 socket 返回="+socket.getClass());
// 2、连接上后,生成 socket 对象
// 再获取到和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
// 3、通过输出流,写入数据到数据通道,使用字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
bw.write("hello server 字符流");
bw.newLine();// 插入一个换行符,表示写入的内容结束
bw.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道
// 4、获取和 socket 关联的输入流,读取数据(字符)并显示
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
System.out.println(s);
// 5、关闭流对象和 socket
br.close();// 只关闭外层流即可
bw.close();// 只关闭外层流即可
socket.close();
System.out.println("客户端退出了");
}
}
4.5 案例四
4.5.1 需求分析
1、编写一个服务器端和一个客户端。
2、服务端在 8888 端口监听。
3、客户端连接到服务器端,发送 一张图片 f:\\pic.jpg
4、服务器端接收到客户端发送的图片,保存到 src 下,发送 "收到图片" 再退出。
5、客户端接收到服务端发送的 "收到图片",再退出。
6、使用 BufferedInputStream 和 BufferedOutputStream 字节流
4.5.2 代码编写
服务端代码如下
public class TcpFileUploadServer {
public static void main(String[] args) throws Exception {
// 1、服务端监听本机的 8888 端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在 8888 端口等待");
// 2、等待连接
Socket socket = serverSocket.accept();
System.out.println("服务端 socket 返回=" + socket.getClass());
// 3、读取客户端发送过来的数据
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
// 4、将 bytes 数组,写入到指定的路径下
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\pic.jpg"));
bos.write(bytes);
bos.close();
// 5、获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
bw.write("收到图片");
bw.flush();// 注意需要手动的 flush
socket.shutdownOutput();
// 6、关闭其他资源
bw.close();
bis.close();
socket.close();
serverSocket.close();
}
}
客户端代码如下
public class TcpFileUploadClient {
public static void main(String[] args) throws Exception {
// 1、客户端连接的 8888 端口,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 2、创建读取磁盘文件的输入流
String path = "f:\\pic.jpg";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
// 3、将文件读取到字节数组中
byte[] bytes = StreamUtils.streamToByteArray(bis);
// 4、通过 socket 获取到输出流,将 bytes 数据发送给服务器
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);// 将文件对应的字节数组内容,写入到数据通道
bis.close();
socket.shutdownOutput();// 设置写入数据的结束标记
// 5、接收从服务端回复的消息
InputStream inputStream = socket.getInputStream();
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);
// 6、关闭流对象和 socket
inputStream.close();
bos.close();
socket.close();
}
}
工具类代码如下
public class StreamUtils {
/**
* 功能:将输入流转换成 byte 数组,即把文件的内容读入到 byte 数组
*/
public static byte [] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();// 创建输出流对象
byte [] b = new byte[1024];
int len;
while((len=is.read(b)) != -1){
bos.write(b,0,len);
}
byte[] array = bos.toByteArray();
bos.close();
return array;
}
/**
* 功能:将输入流的数据直接转换为字符串
*/
public static String streamToString(InputStream is) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while((line = br.readLine()) != null){
sb.append(line+"\r\n");
}
return sb.toString();
}
}
4.6 netstat 指令
4.6.1 简介
1、netstat -an 可以查看当前主机网络情况,包括端口监听和网络连接情况
2、netstat -an | more 可以分页显示,空格显示下一页,回车显示下一条记录
3、需要在 dos 控制台下执行
4.6.2 演示
1、LISTENING 表示某个端口在监听,ESTABLISHED 表示已经建立连接
2、如果有一个外部程序(客户端)连接到监听端口,就会显示一条 ESTABLISHED 连接信息
3、可以使用 ctrl+c 退出指令
4.7 客户端随机端口
当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是 TCP/IP 来分配的,是不确定的,是随机的。
五、UDP 网络通信编程
5.1 基本介绍
1、DatagramSocket 类和 DatagramPacket (数据包/数据报)实现了基于 UDP 协议网络程序。
2、UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
3、DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号。
4、UDP 协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接。
5.2 UDP 原理示意图
5.3 基本流程
1、核心的两个类/对象 DatagramSocket 与 DatagramPacket。
2、需要建立发送端和接收端(没有服务端和客户端概念)
3、发送数据前,建立数据包/报 DatagramPacket 对象
4、调用 DatagramSocket 的发送、接收方法
5、关闭 DatagramSocket
5.4 实战演练
5.4.1 需求分析
1、编写一个接收端 A 和发送端 B
2、接收端 A 在 9999 端口等待接收数据
3、发送端 B 向接收端 A 发送数据 "hello,明天吃火锅~"
4、接收端 A 接收到发送端 B 发送的数据,回复 ”好的,明天见“,再退出
5、发送端接收回复的数据后,再推出。
5.4.2 代码编写
UDP 接收端代码如下
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
// 1、创建一个 DatagramSocket 对象,准备在 9999 接收数据
DatagramSocket socket = new DatagramSocket(9999);
// 2、创建一个 DatagramPacket 对象,准备接收数据
// 一个数据包最大为 64k
byte [] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 3、调用接收方法,将通过网络传输的 DatagramPacket 对象 填充到 packet 对象
// 当有数据报发送到 9999 端口时,就会接收到数据
// 当没有数据发送到 9999 端口时,就会阻塞等待
System.out.println("接收端A等待接收数据......");
socket.receive(packet);
// 4、对 packet 进行拆包,取出数据并显示
int length = packet.getLength();// 获取实际接收到的数据字节长度
byte[] data = packet.getData();// 获取实际接收到的数据
String s = new String(data, 0, length);
System.out.println(s);
/** 回复消息给 B 端 **/
// 将需要发送的数据,封装到 DatagramPacket 对象
data = "好的明天见".getBytes();
packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"),9998);
// 发送数据
socket.send(packet);
// 关闭资源
socket.close();
System.out.println("A端退出");
}
}
UDP 发送端代码如下
public class UDPReceiverB {
public static void main(String[] args) throws IOException {
// 1、创建一个 DatagramSocket 对象,准备在 9998 接收数据
DatagramSocket socket = new DatagramSocket(9998);
// 2、将需要发送的数据,封装到 DatagramPacket 对象
byte[] data = "hello 明天吃火锅".getBytes();
// new DatagramPacket(data内容字节数组,data.length,主机(IP),端口号)
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"),9999);
// 3、发送数据
socket.send(packet);
/** 接收 A 端回复的消息 **/
// 创建一个 DatagramPacket 对象,准备接收数据
byte [] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
// 对 packet 进行拆包,取出数据并显示
int length = packet.getLength();// 获取实际接收到的数据字节长度
data = packet.getData();// 获取实际接收到的数据
String s = new String(data, 0, length);
System.out.println(s);
// 6、关闭资源
socket.close();
System.out.println("B端退出");
}
}
六、习题练习
6.1 练习一
6.1.1 需求分析
1、使用字符流的方式,编写一个客户端程序和服务器端程序
2、客户端发送 ”name“ ,服务端接收到后,返回 ”我是 nova“
3、客户端发送 ”hobby“ ,服务端接收到后,返回 ”编写 java 程序“
4、若问的不是这两个问题,返回 ”你说啥呢“
6.1.2 代码实现
客户端代码如下
public class SocketTcp03Client {
public static void main(String[] args) throws IOException {
// 1、连接服务端(ip,端口)
// 连接本机的 9999 端口,如果连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
// 2、连接上后,生成 socket 对象
// 再获取到和 socket 对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
// 3、通过输出流,写入数据到数据通道,使用字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
// 从键盘获取输入的值
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的问题");
String next = scanner.next();
bw.write(next);
bw.newLine();// 插入一个换行符,表示写入的内容结束
bw.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道
// 4、获取和 socket 关联的输入流,读取数据(字符)并显示
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
System.out.println(s);
// 5、关闭流对象和 socket
br.close();// 只关闭外层流即可
bw.close();// 只关闭外层流即可
socket.close();
System.out.println("客户端退出了");
}
}
服务端代码如下
public class SocketTcp03Server {
public static void main(String[] args) throws IOException {
// 1、监听本机的 9999 端口,等待连接
// 细节:需要注意的是本机没有其他服务在监听 9999
// 细节:ServerSocket 可以通过 accept() 返回多个 socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在 9999 端口等待连接");
// 2、当没有客户端连接 9999 端口时,会阻塞等待连接
// 如果有客户端连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
// 3、通过 socket.getInputStream() 读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
// 4、IO 读取,使用字符流
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
System.out.println("服务端接收到的消息为:"+s);
String answer = "";
if("name".equals(s)){
answer = "我是nova";
}else if("hobby".equals(s)){
answer = "编写java程序";
}else{
answer = "你说啥呢";
}
// 5、获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
bw.write(answer);
bw.newLine();// 插入一个换行符,表示写入的内容结束
bw.flush();// 注意需要手动的 flush
// 5、关闭相关的流和 socket
bw.close();// 只关闭外层流即可
br.close();// 只关闭外层流即可
socket.close();
serverSocket.close();
}
}
6.2 练习二
6.2.1 需求分析
1、编写一个接收端 A 和一个发送端 B,使用 UDP 协议完成
2、接收端在 8888 端口等待接收数据
3、发送端向接收端发送数据 ”四大名著有哪些“
4、接收端接收到发送端发送的问题后,返回 ”四大名著是《西游记》《水浒传》《红楼梦》《三国演义》“,否则返回 ”what?“
5、接收端和发送端程序退出
6.2.2 代码实现
接收端代码如下
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
// 1、创建一个 DatagramSocket 对象,准备在 9999 接收数据
DatagramSocket socket = new DatagramSocket(8888);
// 2、创建一个 DatagramPacket 对象,准备接收数据
byte [] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 3、调用接收方法,将通过网络传输的 DatagramPacket 对象 填充到 packet 对象
System.out.println("接收端A等待接收数据......");
socket.receive(packet);
// 4、对 packet 进行拆包,取出数据并显示
int length = packet.getLength();// 获取实际接收到的数据字节长度
byte[] data = packet.getData();// 获取实际接收到的数据
String s = new String(data, 0, length);
System.out.println(s);
String answer = "";
if("四大名著有哪些".equals(s)){
answer = "四大名著是《西游记》《水浒传》《红楼梦》《三国演义》";
}else{
answer = "what?";
}
/** 回复消息给 B 端 **/
// 将需要发送的数据,封装到 DatagramPacket 对象
data = answer.getBytes();
packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"),8887);
// 发送数据
socket.send(packet);
// 关闭资源
socket.close();
System.out.println("A端退出");
}
}
发送端代码如下
public class UDPReceiverB {
public static void main(String[] args) throws IOException {
// 1、创建一个 DatagramSocket 对象,准备在 9998 接收数据
DatagramSocket socket = new DatagramSocket(8887);
Scanner scanner = new Scanner(System.in);
System.out.println("请输入内容");
String next = scanner.next();
// 2、将需要发送的数据,封装到 DatagramPacket 对象
byte[] data = next.getBytes();
// new DatagramPacket(data内容字节数组,data.length,主机(IP),端口号)
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"),8888);
// 3、发送数据
socket.send(packet);
/** 接收 A 端回复的消息 **/
// 创建一个 DatagramPacket 对象,准备接收数据
byte [] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
// 对 packet 进行拆包,取出数据并显示
int length = packet.getLength();// 获取实际接收到的数据字节长度
data = packet.getData();// 获取实际接收到的数据
String s = new String(data, 0, length);
System.out.println(s);
// 6、关闭资源
socket.close();
System.out.println("B端退出");
}
}
6.3 练习三
6.3.1 需求分析
1、编写客户端和服务端程序
2、客户端可以输入一个音乐文件名,比如 ”高山流水“,服务端收到音乐名后,可以给客户端返回这个音乐文件,如果服务器没有这个文件,返回一个默认的音乐即可
3、客户端收到文件后,保存到本地 e:\\
4、提示:该程序可以使用 StreamUtils.java
6.3.2 代码实现
客户端代码如下
public class SocketTcp03Client {
public static void main(String[] args) throws Exception {
// 1、连接服务端(ip,端口)
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
// 2、连接上后,生成 socket 对象
OutputStream outputStream = socket.getOutputStream();
// 3、通过输出流,写入数据到数据通道,使用字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
// 4、从键盘获取输入的值
Scanner scanner = new Scanner(System.in);
System.out.println("请输入音乐文件名");
String next = scanner.next();
bw.write(next);
bw.newLine();// 插入一个换行符,表示写入的内容结束
bw.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道
// 5、获取和 socket 关联的输入流
InputStream inputStream = socket.getInputStream();
BufferedInputStream br = new BufferedInputStream(inputStream);
byte[] bytes = StreamUtils.streamToByteArray(br);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("e:\\"+next+".ncm"));
bos.write(bytes);
// 6、关闭流对象和 socket
br.close();// 只关闭外层流即可
bw.close();// 只关闭外层流即可
socket.close();
System.out.println("客户端退出了");
}
}
服务端代码如下
public class SocketTcp03Server {
public static void main(String[] args) throws Exception {
// 1、监听本机的 9999 端口,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在 9999 端口等待连接");
// 2、等待客户端连接
Socket socket = serverSocket.accept();
// 3、读取客户端发送的文件名
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
System.out.println("服务端接收到的消息为:"+s);
// 4、判断指定目录下书否存在该音乐,如果存在则返回该音乐,如果不存在,则返回一首默认的音乐
File file = new File("src\\" + s+".ncm");
String fileName = "";
if(file.exists()){
fileName = "src\\"+s+".ncm";
}else{
fileName = "src\\不得不爱.ncm";
}
// 5、读取音频文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));
byte[] bytes = StreamUtils.streamToByteArray(bis);
// 6、获取 socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bws = new BufferedOutputStream(outputStream);
bws.write(bytes);
socket.shutdownOutput();
bws.flush();// 注意需要手动的 flush
// 7、关闭相关的流和 socket
bws.close();// 只关闭外层流即可
br.close();// 只关闭外层流即可
socket.close();
serverSocket.close();
}
}