此部分记录了使用Java进行网络编程所使用的基本类与方法,共分为「InetAddress类、UDP通信、TCP通信」三个基础部分,并通过两个实例展示「自定键盘输入、字符缓冲流封装、反馈、从文件到文件的发送接收」展示IO流与网络编程结合的拓展使用方法。
InetAddress类 此类的主要作用是产生主机的对象,拥有如下方法:
使用时,通常不会单独用构造函数创建一个对象,而是直接使用这个静态函数当作参数
方法名
类型
说明
static InetAddress.getByName(String host)
构造函数
确定主机名称的IP地址。 host 可以是机器名称,也可以是IP地址
String getHostName()
方法
获取此IP地址的主机名
String getHostAddress()
方法
返回文本显示中的IP地址字符串
UDP通信 概念
它在通信的两端各建立一个Socket对象,没有所谓的客户端和服务器
Java提供了DatagramSocket类作为基于UDP协议的Socket
UDP发送数据 构造方法
方法名
说明
DatagramSocket()
创建【数据报套接字】,并将其绑定到本机地址上的任何可用端口
DatagramPacket(byte[] buf,int len,InetAddress add,int port)
创建【数据包】,发送长度为len的数据包到指定主机的指定端口
相关方法
对应类
方法名
说明
DatagramSocket
void send(DatagramPacket p)
发送数据报包
DatagramSocket
void close()
关闭数据报套接字
表格描述
操作中需要两个对象:
发送端对象(DatagramSocket
):负责提供发送的方法
数据包对象(DatagramPacket
):负责指定数据包「内容、接收端主机对象、端口」
操作位置
操作方式
方法名/类名
说明
主类
new
DatagramSocket()
创建发送端的Socket对象
—
定义
byte[] bys = “xxx”.getBytes();
创建数据
—
new
DatagramPacket(byte[] buf,int len,InetAddress add,int port)
数据打包
—
调用
void send(DatagramPacket p)
调用DatagramSocket对象方法发送数据
—
调用
ds.close();
关闭接收端
代码描述
1 2 3 4 5 6 7 8 9 10 11 12 13 DatagramSocket ds = new DatagramSocket(); byte [] bys = "Udp 示例" .getBytes(); DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.1.3" ), 10086 ); ds.send(dp); ds.close();
UDP接收数据
构造方法
方法名
说明
DatagramPacket(byte[] buf, int len)
创建一个DatagramPacket用于接收长度为len的数据包
DatagramPacket相关方法
对应类
方法名
说明
DatagramPacket
byte[] getData()
返回数据缓冲区
DatagramPacket
int getLength()
返回要发送的数据的长度或接收的数据的长度
DatagramSocket
void receive(DatagramPacket p)
从此套接字接受数据报包给到DatagramPacket
表格描述
操作位置
操作方式
方法名/类名
说明
主类
new
DatagramPacket(byte[] buf, int len)
创建接收端的Socket对象
—
创建
byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length);
创建数据包,用于接收数据
—
调用
ds.receive(dp);
调用DatagramSocket对象的方法接收数据(上一节)
—
匿名内部类/new
String new String(dp.getData(), 0,dp.getLength())
解析数据
—
调用
ds.close();
关闭接收端
代码描述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ReceiveDemo { public static void main (String[] args) throws IOException { DatagramSocket ds = new DatagramSocket(10086 ); byte [] bys = new byte [1024 ]; DatagramPacket dp = new DatagramPacket(bys, bys.length); ds.receive(dp); byte [] datas = dp.getData(); int len = dp.getLength(); String dataString = new String(datas, 0 , len); System.out.println(dataString); System.out.println(new String(dp.getData(), 0 ,dp.getLength())); ds.close(); } }
TCP通信程序 Java中的TCP通信
Java使用Socket对象代表两端的通信端口,并通过Socket产生IO流 来通信。
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
TCP发送数据 表格描述
操作位置
操作方式
方法名/类名
说明
主类
new
Socket(String host, int port)
创建客户端Socket对象
—
调用Socket方法
OutputStream getOutputStream()
获取输出流…
—
调用OutputStream方法
void write(“TCP 示例”.getBytes())
…写数据
—
调用Socket方法
void close()
释放资源
※释放资源只要释放最顶层的对象,衍生的对象也会释放
代码描述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ClientDemo { public static void main (String[] args) throws IOException { Socket s = new Socket("127.0.0.1" , 10088 ); OutputStream os = s.getOutputStream(); os.write("hello,tcp!!" .getBytes()); s.close(); } }
TCP接收数据 表格描述
操作位置
操作方式
方法名/类名
说明
主类
new
ServerSocket(int port)
创建服务器端的Socket对象(ServerSocket)
—
调用ServerSocket方法
Socket accept()
侦听连接到此的套接字 接收成为【socket】对象
—
调用Socket方法
InputStream getInputStream()
获取输入流…
—
调用InputStream方法
byte[] bys = new byte[1024]; int len = is.read(bys)
…读数据…
—
new/匿名内部类
String new String(bys,0,len)
…数据处理
—
调用Socket与ServerSocket方法
close()
释放资源
※此处「字节流读数据」不能使用while循环
拓展操作实例(以TCP为例) 自定键盘输入+封装字符缓冲流 发送 案例需求
客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
服务端:接收到数据在控制台输出
案例分析
客户端创建对象,使用键盘录入循环接受数据,接受一行发送一行,直到键盘录入886为止
服务端创建对象,使用输入流按行循环接受数据,直到接受到null为止
代码实现
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ClientDemo { public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); Socket s = new Socket("127.0.0.1" , 10086 ); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line = br.readLine()) != null ){ bw.write(line); bw.newLine(); bw.flush(); if (line.equals("886" )){ break ; } } s.close(); br.close(); } }
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ServerDemo { public static void main (String[] args) throws IOException { ServerSocket ss = new ServerSocket(10086 ); Socket s = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String line; while ((line = br.readLine()) != null ){ System.out.println(line); } ss.close(); } }
文件到文件+反馈+多线程 案例需求
客户端:数据来自于文本文件,接收服务器反馈
服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程
案例分析
创建客户端对象,创建输入流对象指向文件,每读入一行数据就给服务器输出一行数据,输出结束后使 用shutdownOutput()方法告知服务端传输结束
创建多线程类,在run()方法中读取客户端发送的数据,为了防止文件重名,使用计数器给文件名编号, 接受结束后使用输出流给客户端发送反馈信息。
创建服务端对象,每监听到一个客户端则开启一个新的线程接受数据。 客户端接受服务端的回馈信息
代码实现
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.io.*;import java.net.Socket;public class ClientDemo { public static void main (String[] args) throws IOException { Socket s = new Socket("127.0.0.1" , 10090 ); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); BufferedReader br = new BufferedReader(new FileReader("C:\\test\\ArrayListToFileDemo.txt" )); String line; while ((line = br.readLine()) != null ){ bw.write(line); bw.newLine(); bw.flush(); } s.shutdownOutput(); BufferedReader receive = new BufferedReader(new InputStreamReader(s.getInputStream())); String receiveLine = receive.readLine(); System.out.println(receiveLine); s.close(); br.close(); } }
服务端线程类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import java.io.*;import java.net.Socket;public class ServerThread implements Runnable { private Socket s; public ServerThread (Socket s) { this .s = s; } @Override public void run () { int i = 0 ; File file = new File("C:\\test\\TCPTHREAD" + i + ".txt" ); while (file.exists()) { i++; file = new File("C:\\test\\TCPTHREAD" + i + ".txt" ); } try { BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String line; while ((line = br.readLine()) != null ) { BufferedWriter bw = new BufferedWriter(new FileWriter(file, true )); bw.write(line); bw.newLine(); bw.flush(); bw.close(); } BufferedWriter back = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); back.write("接收到文件" + i); back.newLine(); back.flush(); s.close(); } catch (IOException e) { e.printStackTrace(); } } }
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.io.*;import java.net.ServerSocket;public class ServerDemo { public static void main (String[] args) throws IOException { ServerSocket ss = new ServerSocket(10090 ); while (true ) { new Thread(new ServerThread(ss.accept())).start(); } } }
实例说明
线程类中:不可以在排除文件重名时创建文件,否则会一直循环。如果要创建,需要利用if去break
线程类中:之所以使用追加写入,就是为了能释放资源
服务端类中:开启线程使用的是缩略写法
此实例中如果排除“多线程”的话写法如下,可以更清晰的感知服务端的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ServerDemo { public static void main (String[] args) throws IOException { ServerSocket ss = new ServerSocket(10000 ); Socket s = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\test\\TCPWriterNoTwo.txt" )); String line; while ((line = br.readLine()) != null ){ bw.write(line); bw.newLine(); bw.flush(); } BufferedWriter back = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); back.write("接收完成" ); back.newLine(); back.flush(); ss.close(); br.close(); } }