Docker学习(一)在Docker容器中部署一个基于TCP的简单Java应用——Windows

目录

在上云计算这门课——Docker部署实验,因为要求是部署自己的java应用,就做个平平无奇java的TCP应用部署的笔记和分享。

环境准备

  1. 操作系统:Windows10专业版
  2. 使用软件:Docker-for-windows 19.03.8、Eclipse photon(其他java ide也可以)

安装Dcoker

  1. 安装过程中忘记截图了,这篇文章写得就挺详细的,跟着安装应该不会有大问题: https://www.runoob.com/docker/windows-docker-install.html

  2. 注意事项:
    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好像支持同时使用,期待后面的版本吧。
    VMware的报错VMware20H1

  3. Dcoker 成功安装后,使用dcoker --version查看可安装的Dcoekr版本,验证Docker是否成功安装:
    Dcocker安装版本测试

运行hello-world

  1. 在CMD运行命令docker run hello-world ,成功运行的情况截图如下:
    在这里插入图片描述
  2. 如果出现拉取超时导致失败的情况,一般是网络问题,再次运行该命令可以成功,或者搜索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的顺序:
    导出可运行jar包
  • 选择你要导出的类和保存的位置,选一个空的文件夹方便后面整理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环境

  1. 由于本项目只需一个可以运行jvm的环境,所以理论上只需要jre的镜像也能运行,为了后面的开发方便我拉取了一个jdk8的镜像,当然你也可以把自己机器上的环境打包成一个镜像。运行docker search jdk可以看到一些别人已经打包好的镜像,我选了Oracle的jdk8,也就是这个在gmaslowski库里的:
    搜索可用jdk
  2. 使用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命令有:
  1. docker images查看所有的镜像
  2. docker ps默认情况下,该命令仅显示正在运行的容器
  3. docker ps -a查看所有的容器运行情况
  4. docker build -t 镜像名称 -f 创建用的Dockerfile .用于从该Dockerfile构建镜像
  5. docker run -p 主机端口:容器端口 镜像名称通过端口映射,在运行镜像的同时创建同名容器并运行
  6. docker stop 容器ID停止正在运行的容器,先停止才能删除
  7. docker start 容器ID重新启动已经停止运行的容器
  8. docker rm container 容器ID删除容器
  9. docker rmi 镜像ID删除镜像,在删除镜像前要先删除有以来的镜像,举例:先删server_demo1再删jdk
  • 学习参考
  1. Docker官方文档
  2. Dockerfile学习
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值