InetAddress 类
public static void main(String[] args) throws UnknownHostException {
// 获取本机的 InetAddress 对象
InetAddress localhost = InetAddress.getLocalHost();
System.out.println("localhost = " + localhost);
// 根据主机名获取 InetAddress 对象
InetAddress host1 = InetAddress.getByName("BLACK"); // 此处为我的主机名
System.out.println("host1 = " + host1);
// 根据域名返回 InetAddress 对象
// 比如百度的
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2 = " + host2);
// 通过 InetAddress 对象获取对应主机的地址
String hostAddress = host2.getHostAddress();
System.out.println("hostAddress = " + hostAddress);
// 通过 InetAddress 对象获取对应主机的域名/主机名
String hostName = host2.getHostName();
System.out.println("hostName = " + hostName);
}
输出结果:
localhost = Black/192.168.199.99
host1 = Black/192.168.199.99
host2 = www.baidu.com/14.215.177.39
hostAddress = 14.215.177.39
hostName = www.baidu.com
Socket
Socket 套接字应用广泛,是事实上网络通信的标准
- 通信的两端都要有 Socket,是两台机器通信的端点
- 网络通信其实就是 Socket 的通信
- Socket 允许程序把网络连接当成一个流,数据在两个 Socket直接通过 IO 传输
- 一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端
编程方式有两种
- TCP 编程,可靠
- UDP 编程,不可靠
TCP 网络通信编程
- 基于客户端-服务端的网络通信
- 底层使用 TCP/IP 协议
- 是基于 Socket 的 TCP 编程
例子1:
服务器监听 9999 端口,客户端连接服务端,向服务端发送字节流的消息,然后服务端显示请求内容
服务端:
public class SocketTCPServer01 {
public static void main(String[] args) throws IOException {
// 监听本机 9999 端口,等待连接
// 注意,不能有其它服务也在监听 9999 端口
// ServerSocket 流可以对应多个 Socket 流,就像多个客户端连接服务器
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在监听 9999 端口,等待连接");
// 当没有客户端连接时,程序会阻塞
// 如果有程序连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务器端 socket = " + socket);
// 连接成功后,获取输入流对象
InputStream inputStream = socket.getInputStream();
// IO 读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
// 根据读取到的长度显示内容
System.out.println(new String(buf, 0, readLen));
}
// 关闭流
inputStream.close();
socket.close();
// 服务器流也关闭
serverSocket.close();
System.out.println("服务器退出");
}
}
客户端:
public class SocketTCPClient01 {
public static void main(String[] args) throws IOException {
// 连接服务器
// 连接 InetAddress.getLocalHost() 主机的 9999 端口
// 连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket = " + socket);
// 连接上后,生成 Socket
// 通过 getOutputStream() 获取输出流对象
OutputStream outputStream = socket.getOutputStream();
// 通过输出流写入数据到通道
outputStream.write("杰瑞狗666".getBytes());
// 使用完毕后关闭流
outputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
例子2:
在上个例子的基础上,让服务端收到客户端的字节流信息后,给客户端回复字节流的信息,客户端将收到的内容打印出来。注意:需要在输出流之后加上结束标记,关闭TCP连接
服务端
public class SocketTCPServer02 {
public static void main(String[] args) throws IOException {
// 监听本机 9999 端口,等待连接
// 注意,不能有其它服务也在监听 9999 端口
// ServerSocket 流可以对应多个 Socket 流,就像多个客户端连接服务器
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在监听 9999 端口,等待连接");
// 当没有客户端连接时,程序会阻塞
// 如果有程序连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务器端 socket = " + socket);
// 连接成功后,获取输入流对象
InputStream inputStream = socket.getInputStream();
// IO 读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
// 根据读取到的长度显示内容
System.out.println(new String(buf, 0, readLen));
}
// 服务端向客户端发送回复
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello杰瑞狗".getBytes());
// 设置结束标记
socket.shutdownOutput();
// 关闭流
inputStream.close();
outputStream.close();
socket.close();
// 服务器流也关闭
serverSocket.close();
System.out.println("服务器退出");
}
}
客户端
public class SocketTCPClient02 {
public static void main(String[] args) throws IOException {
// 连接服务器
// 连接 InetAddress.getLocalHost() 主机的 9999 端口
// 连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket = " + socket);
// 连接上后,生成 Socket
// 通过 getOutputStream() 获取输出流对象
OutputStream outputStream = socket.getOutputStream();
// 通过输出流写入数据到通道
outputStream.write("杰瑞狗666".getBytes());
// 设置结束标记,防止程序陷入无限的等待
socket.shutdownOutput();
// 获取服务端的回复
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
// 使用完毕后关闭流
outputStream.close();
inputStream.close();
socket.close();
System.out.println("客户端退出");
}
}
例子3:
在第二个例子的基础上,使用字符流完成消息的发送接收
服务端:
public class SocketTCPServer03 {
public static void main(String[] args) throws IOException {
// 监听本机 9999 端口,等待连接
// 注意,不能有其它服务也在监听 9999 端口
// ServerSocket 流可以对应多个 Socket 流,就像多个客户端连接服务器
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在监听 9999 端口,等待连接");
// 当没有客户端连接时,程序会阻塞
// 如果有程序连接,则会返回 Socket 对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务器端 socket = " + socket);
// 连接成功后,获取输入流对象
InputStream inputStream = socket.getInputStream();
// 使用字符流读取
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println("s = " + s);
// 服务端向客户端发送回复
OutputStream outputStream = socket.getOutputStream();
// 使用字符流回复信息
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("回复杰瑞狗777 字符流");
bufferedWriter.newLine();// 插入换行符,表示写入内容结束
// 需要手动刷新,否则数据不会写入通道
bufferedWriter.flush();
// 关闭流
bufferedWriter.close();
bufferedReader.close();
socket.close();
// 服务器流也关闭
serverSocket.close();
System.out.println("服务器退出");
}
}
客户端:
public class SocketTCPClient03 {
public static void main(String[] args) throws IOException {
// 连接服务器
// 连接 InetAddress.getLocalHost() 主机的 9999 端口
// 连接成功,返回 Socket 对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket = " + socket);
// 连接上后,生成 Socket
// 通过 getOutputStream() 获取输出流对象
OutputStream outputStream = socket.getOutputStream();
// 通过输出流写入数据到通道
// 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("杰瑞狗666 字符流");
bufferedWriter.newLine();// 插入换行符,表示写入内容结束
// 需要手动刷新,否则数据不会写入通道
bufferedWriter.flush();
// 获取服务端的回复
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println("s = " + s);
// 使用完毕后关闭流
bufferedReader.close();
bufferedWriter.close();
socket.close();
System.out.println("客户端退出");
}
}
例子 4:
使用客户端读取图片发送给服务端,服务端收到后回复字符流的消息
服务端:
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在监听 8888 端口");
// 等待连接
Socket accept = serverSocket.accept();
// 读取客户端发送的数据
BufferedInputStream bufferedInputStream = new BufferedInputStream(accept.getInputStream());
// 把数据转化为 byte 数组
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
// 把 byte 数组写入到文件
String filePath = "src\\cat.jpg";
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filePath));
bufferedOutputStream.write(bytes);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bufferedWriter.write("服务器收到图片");
bufferedWriter.newLine();
bufferedWriter.flush();
// 关闭流
bufferedWriter.close();
bufferedOutputStream.close();
bufferedInputStream.close();
accept.close();
serverSocket.close();
}
客户端:
public static void main(String[] args) throws Exception {
// 客户端连接 8888 服务端
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 创建读取磁盘文件的输入流
String filePath = "e:\\cat.jpg";
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
// 使用工具类方法,把文件输入流转为 byte 数组
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
// 通过 socket 获取输出流,把 byte 数组发给服务端
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
// 把 byte 数组发给服务端
bufferedOutputStream.write(bytes);
// 结束标记
socket.shutdownOutput();
BufferedInputStream bufferedInputStream1 = new BufferedInputStream(socket.getInputStream());
String s = StreamUtils.streamToString(bufferedInputStream1);
System.out.println("客户端收到回复:" + s);
// 关闭流
bufferedInputStream.close();
bufferedInputStream1.close();
bufferedInputStream.close();
socket.close();
}
工具类 StreamUtils:
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;
}
/**
* 功能:将InputStream转换成String
* @param is
* @return
* @throws Exception
*/
public static String streamToString(InputStream is) throws Exception{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder= new StringBuilder();
String line;
while((line=reader.readLine())!=null){ //当读取到 null时,就表示结束
builder.append(line+"\r\n");
}
return builder.toString();
}
TCP 其它知识点
- 客户端也是通过端口与服务器进行通信的,端口是 TCP/IP 协议分配的,不是确定的
比如在上面的例子 4 中,在获取客户端连接后加上一句输出语句,可以看到客户端的 socket 的信息,里面包含端口号:
UDP
- 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议的网络程序
- UDP 数据报通过数据报套接字 DatagramSocket 发送和接受,系统不保证一定能到达目的地,也不保证到达的时间
- DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端和接收端的 IP 地址以及端口
- UDP 协议中每个数据报都给出了完整的地址信息,所以无需建立连接
原理示意图:
例子 1:
发送端向接收到发送消息,接收端收到消息后发送回复后退出,发送端收到回复后退出
发送端:
public class UDPSender01 {
public static void main(String[] args) throws IOException {
// 创建一个 DatagramSocket 对象,在端口 7777 准备接收数据
DatagramSocket datagramSocket = new DatagramSocket(7777);
// 将需要发送的数据封装到 DatagramPacket 对象中
byte[] data = "杰瑞狗7777".getBytes();
// params: 内容字节数组 数组长度 目的IP地址 端口
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.125.29"), 9999);
// 发送
datagramSocket.send(datagramPacket);
// 设置获取回复的数据包
byte[] replyData = new byte[1024];
DatagramPacket packet = new DatagramPacket(replyData, replyData.length);
// 获取回复
datagramSocket.receive(packet);
// 拆包
int len = packet.getLength();
byte[] data2 = packet.getData();
String s = new String(data2, 0, len);
System.out.println("s = " + s);
datagramSocket.close();
System.out.println("发送端退出");
}
}
接收端:
public class UDPReciever01 {
public static void main(String[] args) throws IOException {
// 创建一个 DatagramSocket 准备在端口 9999 接收数据
DatagramSocket socket = new DatagramSocket(9999);
// 创建一个 DatagramPacket 准备接受数据包
// UDP 报文最大为 64 k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 调用接收方法,将网络上传输的 DatagramPacket 对象填充到 packet 对象
// 如果没有数据包发送到 9999 端口。就会一直阻塞
System.out.println("接收端等待接收数据");
socket.receive(packet);
// 拆包,取出数据并显示
// 实际收到的数据长度
int len = packet.getLength();
// 获取数据
byte[] data = packet.getData();
String s = new String(data, 0, len);
System.out.println("s = " + s);
// 接收端发送回复
byte[] replyData = "已收到杰瑞给777".getBytes();
// 新建一个数据包
DatagramPacket packet1 = new DatagramPacket(replyData, replyData.length, InetAddress.getByName("192.168.125.29"), 7777);
socket.send(packet1);
System.out.println("接收端退出");
socket.close();
}
}
TCP 文件下载
客户端发送要获取的文件名,服务端根据文件名发送文件
服务端:
public class TCPFileUploadServerHW03 {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在监听 8888 端口");
// 等待连接
Socket accept = serverSocket.accept();
// 读取客户端发送的数据
InputStream inputStream = accept.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
String downloadName = "";
while ((len = inputStream.read(buf)) != -1) {
downloadName += new String(buf, 0, len);
}
System.out.println("客户端请求下载的文件名:" + downloadName);
// 选择返回的文件名
String resFileName = downloadName.equals("高山流水") ? "src\\高山流水.mp3" : "src\\无名.mp3";
// 创建输入流,读取磁盘文件
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(resFileName));
// 转化为字节数组,用于传输
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
// 获取 socket 对应的输出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(accept.getOutputStream());
// 发送给客户端
bufferedOutputStream.write(bytes);
// 设置结束标记
accept.shutdownOutput();
// 关闭流
bufferedOutputStream.close();
inputStream.close();
bufferedInputStream.close();
accept.close();
serverSocket.close();
}
}
客户端:
public class TCPFileUploadClientHW03 {
public static void main(String[] args) throws Exception {
// 客户端连接 8888 服务端
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 接受用户输入,指定下载的文件名
System.out.println("输出要下载的文件名:");
Scanner scanner = new Scanner(System.in);
String msg = scanner.next();
// 获取 socket 的输出流
OutputStream outputStream = socket.getOutputStream();
// 把消息发送给服务端
outputStream.write(msg.getBytes());
// 设置结束标记
socket.shutdownOutput();
// 读取服务端返回的文件
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
// 输入流转化为字节数组
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
// 获取一个输出流,把字节数组写到磁盘
String desFilePath = "e:\\" + msg + ".mp3";
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(desFilePath));
bufferedOutputStream.write(bytes);
System.out.println("客户端成功保存!");
// 关闭流
bufferedInputStream.close();
bufferedOutputStream.close();
outputStream.close();
socket.close();
}
}