반응형

기존에 운영중이던 프로젝트를 고도화 시키면서 특정 서비스단에서 전달된 값을 DB에 저장하고 저장된 정보를 바로 특정 소켓 서버로 전달을 해줘야 하는 기능을 작성해야 했습니다.

이미 tomcat으로 was서버를 운영중인데 소켓 서버를 운영하는건 여러가지로 문제가 있을것이라 판단하여 별도로 빼려고 했지만, 실시간성이 좀 더 중요시 되었고, 한 곳이 망가져도 바로 재부팅으로 올려도 괜찮다는 확인을 받아 바로 소켓처리를 진행해보았습니다.

 

먼저 단순 소켓서버만 올린다면 굳이 netty까진 쓰지 않아도 됩니다. io.Socket만으로도 충분히 훌륭하게 작성 할 수 있었지만 몇몇 요구사항들을 좀 더 쉽게 구현하기 위해 netty를 공부해보고 적용해보았던 소스를 공유하고자 합니다.

 

아래는 실제로 프로젝트에 적용해본 Socket Server, Socket Client 소스들입니다.

 

Netty Server

netty사용을 위해 메이븐 추가를 먼저 해줍니다.

 

pom.xml

<!-- netty -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.0.33.Final</version>
</dependency>

먼저 Controller에@PostConstruct어노테이션을 활용하여 was 서비스 구동시netty가 동작하도록 별도의 쓰레드를 돌렸습니다.

 

NettyController.java

@Controller
public class NettyController {

    @PostConstruct
    private void start() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    new NettySocketServer(5010).run();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }

    @PreDestroy
    private void destory() {

    }
}

서비스가 구동되면 start()가 동작되면서 별도로 작성한 NettySocketServer가 동작합니다.

내부에 5010은 임시로 지정한 소켓 포트입니다.

다음은 netty 소켓 서버를 확인해보겠습니다.

 

NettySocketServer.java

public class NettySocketServer {
    private int port;

    public NettySocketServer(int port) {
        this.port = port;
    }

    public void run() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class)
         .childHandler(new ChannelInitializer<SocketChannel>() {
             @Override
             public void initChannel(SocketChannel ch) throws Exception {
                 ChannelPipeline pipeline = ch.pipeline();
                 //handler setting
                 pipeline.addLast(new NettySocketServerHandler());
             }
         })
         .option(ChannelOption.SO_BACKLOG, 128)
         .childOption(ChannelOption.SO_KEEPALIVE, true);


        try {
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

생성자를 통해 port값을 처리하고 run메소드를 통해 서버가 동작시 응답을 처리할 핸들러를 연결합니다.

다음은 핸들러입니다.

 

NettySocketServerHandler.java

@Sharable
public class NettySocketServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String readMessage = ((ByteBuf) msg).toString(Charset.forName("UTF8"));
        ctx.write(msg);
        System.out.println("message from received: " + readMessage);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

@Sharable 어노테이션을 활용하여 안전하게 데이터를 처리하도록 변경하였습니다.

 

테스트결과

톰캣을 구동하고 서비스를 올립니다.

정상적으로 netty가 동작하는지 확인하기 위해 테스트툴로 5010포트를 통해 연결하고 메시지가 출력되는지 확인하겠습니다.

 

 

Netty Client

다음은 client를 구성해보겠습니다.

 

NettySocketClient.java

public class NettySocketClient {
	
	private String msg;
	private String host;
	private int port;
	
	public NettySocketClient(String host, int port, String msg){
		this.msg = msg;
		this.host = host;
		this.port = port;
	}
	
	public void run() {
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		
		try {
			Bootstrap b = new Bootstrap();
			b.group(workerGroup);
			b.channel(NioSocketChannel.class);
			b.option(ChannelOption.SO_KEEPALIVE, true);
			b.handler(new ChannelInitializer<SocketChannel>() {
	            @Override
	            public void initChannel(SocketChannel ch) throws Exception {
	                ch.pipeline().addLast(new NettySocketClientHandler(msg));
	            }
	        });
	        
	        //client connect
			try {
				ChannelFuture f = b.connect(host, port).sync();
				f.channel().closeFuture().sync();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}finally {
			workerGroup.shutdownGracefully();
		}
	}
}

생성자를 통해 연결할 host, ip, 발신할 message를 담는 문자열 msg값을 처리합니다.

 

해당 값들을 통해 run()메소드가 동작하면 연결정보로 연결이 됩니다.

다음으로 클라이언트 핸들러를 보겠습니다.

 

NettySocketClientHandler.java

public class NettySocketClientHandler extends ChannelInboundHandlerAdapter {

	private String msg;
	
	public NettySocketClientHandler(String msg) {
		this.msg = msg;
	}
	
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ByteBuf messageBuffer = Unpooled.buffer();
		messageBuffer.writeBytes(msg.getBytes());
	
		ctx.writeAndFlush(messageBuffer);
	
		System.out.println("send message {" + msg + "}");
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		System.out.println("receive message {" + ((ByteBuf) msg).toString(Charset.defaultCharset()) +"}");
	}
	
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.close();
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println(cause);
		ctx.close();
	}
}

 

클라이언트 테스트

서버툴에서 4564포트를 열어두고 연결하여 test msg send!!!라는 문구를 발송하였습니다.

캡처와 같이 서버에 정상적으로 메시지가 수신된 것을 볼 수 있습니다.

반응형
반응형

이미지 뷰어형태로 당장 올릴 이미지를 미리보기형태로 작은모습으로 보여주는 기능을 만들고 있었는데, 스마트폰에서 찍은 사진을 테스트하다 보면 자동으로 회전되어 보이는 현상이 있었습니다.

 

감자♥ 정상적으로 나옵니다.

 

 

꼬미♥ 꼬미야 왜 돌아갔니...

 

업로드 하고 페이지에 노출하는 형태라면 서버단에서 저장할 때, 처리하면 되지만 업로드 뷰어의 경우에는 스크립트단에서 해결이 필요하였습니다.

 

 

스크립트 라이브러리 중 Load-Image의 힘을 빌려 해결하였습니다.

github.com/blueimp/JavaScript-Load-Image#image-loading

 

blueimp/JavaScript-Load-Image

Load images provided as File or Blob objects or via URL. Retrieve an optionally scaled, cropped or rotated HTML img or canvas element. Use methods to parse image metadata to extract IPTC and Exif t...

github.com

 

위 github에서 라이브러리를 받을 수 있습니다.

 

EXIF 

디지털 카메라와 스마트폰등에서 이미지 등의 정보를 기록할 수 있습니다.

해당 메타정보 중에 회전 방향 옵션을 받아올 수 있는데 이 회전 방향값인 orientation을 추출하고 처리하여 강제로 고정시키는 방식입니다.

단순하게 업로드 한 데이터에서는 회전정보를 알 수가 없다보니 blob을 통해 바이너리 데이터로 변환하여 데이터를 확인하는 절차를 따른다고 합니다.

 

Load-Image

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
    integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="./load_image/load-image.all.min.js"></script>
<body>
    <input type="file" id="upload">
    <img src="" alt="" id="preview" style="width:300px;height:300px;">
</body>

</html>

 

javascript

$(function () {
    $("#upload").on("change", function (e) {
        var files = e.target.files;
        var fileType = files[0].type;
        var limg = loadImage(files[0], function (img, data) {
            img.toBlob(function (blob) {
                var rotateFile = new File([blob], files[0].name, { type: fileType });
                var reader = new FileReader();
                reader.onload = function (e) { $("#preview").attr("src", e.target.result); }

                reader.readAsDataURL(rotateFile);
            }, fileType)
        }, { orientation: 1 });
    });
})

file 태그에 이미지가 올라가면 change이벤트에 의해 파일 정보를 받아오고 라이브러리 함수 loadImage를 통해 데이터를 blob으로 읽고 image태그에 처리를 해줍니다.

 

여기서 포인트는 2번째 파라미터 object값에 orientation값을 1로 해주면 세로 이미지로 볼 수 있었습니다.

여기서 값을 2로 처리하시면 좌우 반전되어 나오는 것을 볼 수 있습니다.

 

 

처리 동작 후 미리보기 이미지가 정상적인 회전으로 나옵니다.

반응형
반응형

삽입 정렬

삽입 정렬은 아래의 알고리즘을 따릅니다.

 

{8, 2, 1, 4, 5} 형태의 배열을 정렬해보는 예제입니다.

두번째 인덱스인 1의 데이터를 하위 인덱스 0까지 비교합니다.

비교하는 하위 인덱스의 값이 임시 temp값보다 작을때까지 비교하고 작지 않다면 다음 인덱스로 넘깁니다.

arr[i+1] = arr[i];

이 행동으로 배열 끝까지 진행합니다.

처음 큰 한바퀴가 진행되면 위 그림의 순서대로 정렬이 진행됩니다.

 

 

다음은 기준 인덱스가 하나 증가하여 2번째 인덱스를 0~1번째 인덱스까지 비교하고 정렬합니다.

 

 

다음은 기준 인덱스가 하나 증가하여 3번째 인덱스부터 하위 인덱스까지 비교합니다.

 

다음은 기준 인덱스가 하나 증가하여 4번째 인덱스부터 하위 인덱스까지 비교합니다.

정렬이 끝나면 아래와 같이 1,2,4,5,8의 형태로 배열이 정리가 됩니다.

 

JAVA Source

public class Sorting2 {
	public static void main(String[] args) {
		int[] arr = {8, 2, 1, 4, 5};
		
		if(arr.length > 1) {
			int i, j;
			for(i=1; i<arr.length; i++) {
				int tmp = arr[i];
				for(j=i-1; j>=0; j--) {
					arr[j+1] = arr[j];
					if(tmp > arr[j]) {
						break;
					}
				}
				arr[j+1] = tmp;
			}
		}
		
		for(int i=0; i<arr.length; i++) {
			System.out.print(arr[i]);
			if(i+1 != arr.length) {
				System.out.print(", ");
			}
		}
	}
}

 

반응형
반응형

입력폼에 컨텐츠에 해당하는 내용을 적도록 할때 textarea 또는 에디터 툴을 적용하여 장문을 지원하는데, 이때 사용자가 엔터와 같은 개행문자를 입력하지 않고, 연속되게 이어서 입력하게되면 나중에 데이터를 받아와서 parsing시 그냥 한줄로 쭉 들어와서 정상적으로 노출되지 않는 현상이 존재한다.

 

이 때, CSS의 word-wrap옵션을 통해 처리가 가능하다.

 

 

word-wrap

word-wrap:break-word;

 

적용전 문제가 되는 모습

개행이 없다보니 그대로 넣게되면 깨지는 현상을 볼 수 있다.

 

word-wrap: break-word;적용

.contents p {word-wrap: break-word;}

적용 후에는 넓이값에 도달하면 알아서 줄바꿈이 일어난다.

 

normal 옵션도 존재하는데, 이 경우에는 기존처럼 문자열이 뚫고 나온다.

반응형
반응형

자료구조의 정렬 알고리즘을 다시 한번 복습해보고, 소스로 구현해보기 위해 공부삼아 포스팅을 진행해보겠습니다.

 

자바 자체 컬렉션이나 스트림의 정렬의 도움을 받지 않고 삽입정렬, 선택정렬, 버블정렬 등을 차례대로 공부해보면서 적용해볼 예정이며, 잘 못된 방식이 있다면 답글 부탁드립니다.

 

먼저 선택 정렬을 통해 오름차순, 내림차순 처리를 해보겠습니다.

 

선택 정렬

선택정렬은 맨 앞의 인덱스부터 차례대로 값을 확인 해나갑니다.

n, n+1을 비교하고 만족하면 서로의 위치를 교체해주는 방식입니다.

 

 

조건문에 따라 내림차순 오름차순 결과값을 확인 할 수 있습니다.

public class Sorting1 {
	public static void main(String[] args) {
		int[] arr = {42, 456, 7898, 156, 123, 77, 9, 498, 2142, 2, 3, 241, 65756, 553, 767};
		
		for(int i=0; i<arr.length; i++) {
			for(int j=i+1; j<arr.length; j++) {
				if(arr[i] > arr[j]) { //오름차순
					int tmp = arr[i];
					arr[i] = arr[j];
					arr[j] = tmp;
				}
			}
		}
		
		for(int i=0; i<arr.length; i++) {
			System.out.print(arr[i]);
			if(i+1 != arr.length) {
				System.out.print(", ");
			}
		}
	}
}

 

public class Sorting1 {
	public static void main(String[] args) {
		int[] arr = {42, 456, 7898, 156, 123, 77, 9, 498, 2142, 2, 3, 241, 65756, 553, 767};
		
		for(int i=0; i<arr.length; i++) {
			for(int j=i+1; j<arr.length; j++) {
				if(arr[i] < arr[j]) { //내림차순
					int tmp = arr[i];
					arr[i] = arr[j];
					arr[j] = tmp;
				}
			}
		}
		
		for(int i=0; i<arr.length; i++) {
			System.out.print(arr[i]);
			if(i+1 != arr.length) {
				System.out.print(", ");
			}
		}
	}
}

반응형
반응형

사실 단순한 pdf파일을 보이게만 하는건 몇몇 태그를 활용하여 쉽게 처리가 가능합니다.

 

iframe

<iframe src="/image/test.pdf" style="width:700px;height:700px;"></iframe>

IE를 제외하고 firefox, chrome, edge등 모두 정상적으로 뷰어 동작을 하는것을 볼 수 있습니다.

IE에서는 다운로드 이벤트가 발생합니다.

 

 

 

embed

<embed src="/image/test.pdf" type="application/pdf" width=700px height=700px/>

iframe과 비슷하지만 type과 넓이 높이를 따로 지정해줘야 합니다.

마찬가지로 IE를 제외하고 전부 정상 동작하는것을 볼 수 있습니다.

 

 

 

 

PDFObject

pdfobject.com/

 

PDFObject: A JavaScript utility for embedding PDFs

width [string]. Default: "100%" Will insert the width as an inline style via the style attribute on the element. If left unspecified, PDFObject will default to 100%. Is standard CSS, supports all units, including px, %, em, and rem. Tip: It's safer to spec

pdfobject.com

PDF를 읽어서 뷰어 역할을 해주는 훌륭한 js 라이브러리입니다.

해당 브라우저에서 PDF를 읽지못한다면 특정 메시지와 함께 다운로드를 제공하고 있습니다.

 

IE에서 동작이 안되는 브라우저는 친절하게 다운로드 링크를 제공한다.

 

사용법은 생각보다 간단합니다.

위의 URL을 통해 PDFObject를 다운로드 받은 후, PDFObject를 연결줍니다.

<div id="myPdf"></div>
<script type="text/javascript" src="/js/common/component/PDFObject/pdfobject.js"></script>
PDFObject.embed("/image/guide.pdf", "#myPdf");

첫번째 파라미터는 pdf파일 위치를 두번째 파라미터는 만들어줄 타겟 대상을 입력합니다.

 

 

full 버전으로 잘 읽힙니다.

 

세번째에는 옵션값을 넣을 수 있습니다.

var option = {
		height: "400px",
		pdfOpenParams: {view: 'FitV', page: '2'}
}
PDFObject.embed(RESOURCES_PATH + "/image/test.pdf", "#myPdf", option);

높이가 400으로 줄었습니다.

높이를 400으로 줄여보았습니다.

 

 

그럼 이쯤에서 IE에서는 pdf 뷰어를 못하나요? 궁금해하실텐데 IE11에서만 동작하긴 하지만 또 방법이 있습니다.

 

 

 

 

PDF.js + PDFObject.js

먼저 알아보았던 PDFObject에 Pdf.js를 활용하면 가능합니다.

뷰어처럼 동작하도록 뼈대의 프레임 html을 사용하고 각 태그별에 맞는 이벤트 동작을 제공합니다.

 

mozilla.github.io/pdf.js/

 

PDF.js

PDF.js A general-purpose, web standards-based platform for parsing and rendering PDFs. Download Demo GitHub Project

mozilla.github.io

해당 URL에서 PDF.js를 다운로드 하실 수 있습니다.

PDFObject와 같은 위치에 다운로드한 파일들을 옮겨줍니다.

 

이번에는 div태그에 사이즈를 입력해야합니다.

<div id="myPdf" style="width:550px;height:400px;"></div>
<script type="text/javascript" src="/js/common/component/PDFObject/pdfobject.js"></script>
var option = {
	pdfOpenParams: {
		navpanes: 0,
		toolbar: 0,
		statusbar: 0,
		view: "FitV",
		page: 1
	},
	width : "550px",
	height: "400px",
	forcePDFJS: true,
	PDFJS_URL: "/js/common/component/PdfJs/web/viewer.html"
}
PDFObject.embed("/image/test.pdf", "#myPdf", option);

 

 IE11기준에서만 동작하긴 하지만 아래와 같이 익스에서도 pdf뷰어 기능을 구현할 수 있습니다.

반응형
반응형

CentOS에 MariaDB 설치 후 한글이 깨지는 현상이 발견되었다.

 

인코딩 변환이 필요하였고, 처리 방법은 아래와 같다.

vim /etc/my.cnf.d/server.cnf

 

설정파일 열어주고 [mysqld] 아래쪽에 아래와같은 설정을 입력한다.

[mysqld]
init_connect="SET collation_connection=utf8_general_ci"
init_connect="SET NAMES utf8"
character-set-server=utf8
collation-server=utf8_general_ci

 

재시작을 한다.

systemctl restart mariadb

 

반응형
반응형

mariaDB를 이번에 centOS에 올리고 개발서버로 세팅 도중 프로시저만 복원하려고 하면 에러가 발생하였다.

 

에러 메시지처럼 아래의 명령어를 통해 업그레이드 후  mariaDB를 재부팅하여 해결하였다.

 

mysql_upgrade -u root -p

...

 

이후 서비스를 재시작한다.

 

systemctl restart mariadb

 

 

정상적으로 동작한다.

반응형