Docker学习(一)Docker入门、部署一个简单的基于TCP的Java应用——Windows
目录
在上云计算这门课——Docker部署实验,因为要求是部署自己的java应用,就做个平平无奇java的TCP应用部署的笔记和分享。
环境准备
- 操作系统:Windows10专业版
- 使用软件:Docker-for-windows 19.03.8、Eclipse photon(其他java ide也可以)
安装Dcoker
-
安装过程中忘记截图了,这篇文章写得就挺详细的,跟着安装应该不会有大问题: https://www.runoob.com/docker/windows-docker-install.html
-
注意事项:
1) Windows家庭版需要先安装Docker ToolBox。防止Windows的平台虚拟化与Docker ToolBox互相冲突:控制面板–>程序和功能–>启用或关闭Windows功能–>Windows功能–>关闭Virtual Machine Platform、Windows Hyper-V platform。
2) Windows专业版在安装之前需要在本地先开启Windows自带的Hyper-V服务,开启后重启电脑服务才能生效。
3) VMware的虚拟机服务与Hyper-V不能并存,这时候你开启虚拟机会出现下面的报错。解决办法…后续尝试了再补充吧…
补充:由于目前用的Hyper-V和VMware的虚拟化技术无法兼容,还是分开用吧……其他人的答案也是启动的时候选择加载Hyper-V还是VMware,既然不能同时用就需要用谁再开吧,但是看到VMware的20H1好像支持同时使用,期待后面的版本吧。
-
Dcoker 成功安装后,使用
dcoker --version
查看可安装的Dcoekr版本,验证Docker是否成功安装:
运行hello-world
- 在CMD运行命令
docker run hello-world
,成功运行的情况截图如下:
- 如果出现拉取超时导致失败的情况,一般是网络问题,再次运行该命令可以成功,或者搜索net/http: TLS handshake timeout更换国内的镜像源:
部署自己的java应用
准备可运行jar包
1. 项目结构
编写自己的java应用,一个非常简单的项目(之前学java写的代码逻辑可能不太严谨凑合用吧),项目结构如下:
2. Server.java
package tcp;
/* 服务器接收客户端发送的in.txt文件中的内容,并向out.txt中写入
* 当连接服务器的客户端数量达到5时不再接受连接,关闭服务器的socket */
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Server {
public static int client_num = 0;//初始化客户端连接数量
private static int MAX_NUM = 5;//可连接的客户端数量上限为5
public static boolean flag = true;//flag用于表示是否接受连接
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(6666); // 监听6666端口的服务器Socket
} catch (IOException e1) {
System.out.println("ERROR:In Server Socket Creation.");
e1.printStackTrace();
}
System.out.println("Listening on port 6666");// 输出监听端口号
/* 达到一定数量的客户端连接后拒绝连接,否则始终保持监听连接状态 */
while (flag) {
if (client_num == MAX_NUM) {
flag = false;
break;
}else {
Socket socket = null; // 创建服务于当前连接的socket
try {
socket = server.accept(); // 接受一个客户端的连接请求
client_num++;
System.out.println("Client " + client_num + " connected"); // 输出连接的客户号
} catch (IOException e) {
e.printStackTrace();
}
serverThread thread = new serverThread(socket,client_num); // 创建一个新的线程进行服务
thread.start(); // 开启服务线程
}
}
/*关闭服务器socket,不再接受连接请求,但是还有未完成工作的服务线程*/
try {
server.close(); // 当连接的客户端数量达到5时关闭整个服务器
System.out.println("Max connection,server closed");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class serverThread extends Thread {
private int client_num;// 每个服务线程所服务的客户序号
private Socket socket;// 每个服务线程私有的socket
public serverThread(Socket socket,int client_num) {
this.client_num = client_num;
this.socket = socket;
}
@Override
public void run() {
try {
String path = "out.txt";
byte []buf = new byte[1024];
InputStream in = socket.getInputStream(); // 接收来自客户端的输入数据流
DataInputStream dis = new DataInputStream(in);
Scanner scanner = new Scanner(System.in);
File file2 = new File(path);//写入文件,如果不存在则创建一个新文件
if (!file2.exists()) {
try {
file2.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileOutputStream fos = new FileOutputStream(file2);
int size = 0;
while((size = dis.read(buf, 0, buf.length)) != -1) {
fos.write(buf, 0, size);
fos.flush();
}
//写完数据后关闭相关的流
scanner.close();
in.close();
dis.close();
fos.close();
socket.close();
System.out.println("\tSocket " + client_num + " closed");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. Client.java
package tcp;
/*客户端向6666号端口发起连接请求,传输in.txt文件内容*/
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
Socket socket = null;
Scanner scanner = new Scanner (System.in);
byte[] buf = new byte[1024];//用于读取文件流中的内容
String path = "in.txt";
try {
socket = new Socket("localhost",6666);
System.out.println("Conneted to server.");
OutputStream out = socket.getOutputStream(); // 客户端的输出数据流
//文件--》缓冲--》字节流--》字节数组--》由socket的输出流发送
//从文件读取数据为字节流
BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));
ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
int size = 0;
while (-1 != (size = in.read(buf))) {
output.write(buf, 0, size);
}
in.close();
byte[] content = output.toByteArray();// 将读取的字节流转换为字节数组
System.out.println("Sent bytes :" + content.length);
//客户端输入write之后向服务器发送数据
if(scanner.nextLine().equals("write")) {
out.write(buf); // 将字节数组写到输出流中进行发送
}
//客户端输入end关闭当前client的socket连接
if(scanner.nextLine().equals("end")) {
socket.close();
System.out.println("\tSocket closed.");
scanner.close();
}
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
4. 将服务端代码导出为可运行jar包
- 编译两个类并顺利运行后导出,在Eclipse中,按照
File-->Export-->输入jar-->选择Runnable JAR file-->Next
的顺序:
- 选择你要导出的类和保存的位置,选一个空的文件夹方便后面整理Docker需要的文件,为你导出的包自定义名字:
- 提示:如果想测试导出jar包是否成功,则同时导出client的jar包,在cmd中使用
java -jar server-demo1.jar(你的jar包名称)
,同时打开另一个cmd使用java -jar client-demo1.jar(你的jar包名称)
测试导出后的情况。 - 在运行客户端之前记得创建自己的
in.txt
写入内容,在eclipse里面客户端运行的情况和实验用的in.txt内容如下:
为Docker容器准备java环境
- 由于本项目只需一个可以运行jvm的环境,所以理论上只需要jre的镜像也能运行,为了后面的开发方便我拉取了一个jdk8的镜像,当然你也可以把自己机器上的环境打包成一个镜像。运行
docker search jdk
可以看到一些别人已经打包好的镜像,我选了Oracle的jdk8,也就是这个在gmaslowski库里的:
- 使用
docker pull gmaslowski/jdk
拉取到本地,docker images
查看本地现在有的镜像:
构建镜像并运行
1. 为镜像创建Dockerfile
在导出jar包的文件夹中创建一个Dcokerfile.txt,用于构建镜像,文件内容如下:
#指定以该jdk为基础镜像来构建这个app的镜像
FROM gmaslowski/jdk
#WORKDIR用于指定容器的的一个目录,容器启动时执行的命令会在该目录下执行
WORKDIR /
#将当前的测试jar复制到容器的根目录下
ADD . /
#暴露容器端口为50051,Docker镜像告知Docker宿主机docker镜像监听了50051端口
EXPOSE 50051
#指定容器启动的时候执行的命令
ENTRYPOINT ["java","-jar","server-demo1.jar"]
2.从Dcokerfile构建镜像
- 进入存有Dockerfile的文件夹中,运行
docker build -t server_demo1 -f ./Dockerfile.txt .
来构建镜像,不要忘记最后的.
- 命令中
-t
参数后面写你要创建的镜像名称,-f
后写Dockerfile,Dockerfile的位置要相对于你cmd启动路径。
- 控制台输出的都是Dockerfile里语句的执行过程,最后可能会出现图中的
SECURITY WRNING
,说在windows构建镜像时用了非windows的主机,在这里并不影响运行,略。
3.运行容器
- 使用
docker images
查看镜像,找到刚刚创建的镜像名称(没错的话就是你-t
后面的名字…) - 使用命令
docker run -p 4000:6666 server_demo1
来运行创建容器并运行,-p
做了一个指定的端口映射,将对宿主机4000
号端口的访问转发到容器的6666
号端口。这里端口的选取是因为写Server程序的时候开启了6666号端口,而主机的端口只要从65536个里选一个没有被占用的。别写成大写的P,那是内部端口随机映射到主机端口,写了的话就注意看映射到了哪个端口灵活处理。 - 首先起服务器,然后直接在Eclipse中运行客户端就行。这里我之前想得太复杂,还想做一个客户端的镜像,然后就陷入怎么通信的问题(修改固定IP和自定义桥接网络……),唉,虽然走了很多弯路,但还是学到了知识鸭!——不要在ddl当天放飞自我地发散👻。
- 既然我们做了端口映射,就要把客户端代码里连接的时候端口改成
4000
,运行情况和前面打成jar包里贴的一样。 - 为了测试数据是否成功发送,还需要查看容器内部文件的接收情况。运行命令
docker ps -a
查看所有的容器运行情况,找到serve_demov1
的容器ID
,再docker exec -it 容器ID /bin/sh
进入容器内部。这个问题上大部分人会说使用“exec……/bin/bash
”,或者attach,但是我这就无效,初步了解是由于镜像是基于alpine构建的,所以没有这个bash文件夹,而是sh?好的,如果你用这一条找不到就换docker exec -it 容器ID /bin/bash
🤥
到这里就做完了,下次还是要做容器间的通信!
附录
- 过程中频繁使用到的基本docker命令有:
docker images
查看所有的镜像docker ps
默认情况下,该命令仅显示正在运行的容器docker ps -a
查看所有的容器运行情况docker build -t 镜像名称 -f 创建用的Dockerfile .
用于从该Dockerfile构建镜像docker run -p 主机端口:容器端口 镜像名称
通过端口映射,在运行镜像的同时创建同名容器并运行docker stop 容器ID
停止正在运行的容器,先停止才能删除docker start 容器ID
重新启动已经停止运行的容器docker rm container 容器ID
删除容器docker rmi 镜像ID
删除镜像,在删除镜像前要先删除有以来的镜像,举例:先删server_demo1再删jdk
- 学习参考