반응형

DB 컬럼에 primary key, unique 등을 사용하여 값의 중복을 방지할 수 있는데, 여러개의 컬럼을 동시에 체크하여 중복을 체크해야 하는 경우 여러개를 묶어서 unique 처리를 하면 된다.

 

ALTER TABLE 테이블명 ADD UNIQUE (컬럼1, 컬럼2, 컬럼3);

동시에 관리할 컬럼들을 UNIQUE() 안에 넣어주면 된다.

 

 

사용 예시)

db schema

 

형태로 c1, c2가 모두 중복되는걸 방지하기 위해 UNIQUE 처리하였다.

ALTER TABLE dupli ADD UNIQUE (c1, c2);

 

아래와 같은 샘플 데이터가 존재하는 상태에서

db sample

 

 

c1, c2에 1, 5를 각각 입력해보면 아래와 같은 유니크키 에러가 발생하는 것을 볼 수 있다.

1, 5를 똑같이 입력
중복 에러

 

반응형
반응형

Tomcat으로 서버에 반영할 때, 개발에서 사용한 Sysout을 최대한 안찍히게 하기 위해 소스에서 다 제거하기 바쁜데 이번 경우는 Sysout을 다 로그에 남기게 해야 하는 상황이었다.

 

개발소스가 없어서 에러발생시 원인분석도 어렵고 최대한 도움을 받기위해서 Sysout까지 남겨야 하는 아이러니 한 상황... 

 

찾아보니 리눅스환경에서의 처리방법은 많이 나오는데 Window Server에서 처리방법은 많지 않았다.

 

첫번째 처리방법 데몬 동작

일단 제일 쉬운 방법은 톰캣 데몬(UI환경)으로 구동하는 방법이다.

logging탭에 보면 Redirect Stdout:

 

Redirect Stderror 부분이 보이는데 이부분이 auto면 자동으로 System.out.print로 처리된 부분들이 다 출력된다

 

위치는 LogPath를 수정해주면 된다.

 

반대로 아무것도 안나오게 하고싶으면 해당 부분을 비워주면 된다.

 

 

두번째 처리방법 .bat 동작시

데몬이 아니라 .bat파일로 동작해야 하는 경우

명령창으로 실행되는 .bat파일로 출력해야 하는 경우  Sysout 메시지들이 콘솔에 출력되고 있지만 로그파일로 남기지 않고 있을텐데, 설정을 변경해주면 로그파일로 출력시킬 수 있습니다.

 

${catalina.base}/conf/context.xml

<Context swallowOutput="true">

Context부분에 swallowOutput="true" 로 설정을 변경하고 저장합니다.

 

 

${catalina.base}/lib/log4j.properties

log4j.properties를 lib 디렉토리에 존재하지 않는다면 생성 후에 아래의 설정을 입력합니다.

log4j.rootLogger = INFO, CATALINA
 
# Define all the appenders
log4j.appender.CATALINA = org.apache.log4j.DailyRollingFileAppender
log4j.appender.CATALINA.File = ${catalina.base}/logs/catalina
log4j.appender.CATALINA.Append = true
log4j.appender.CATALINA.Encoding = UTF-8
# Roll-over the log once per day
log4j.appender.CATALINA.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.CATALINA.layout = org.apache.log4j.PatternLayout
log4j.appender.CATALINA.layout.ConversionPattern = %d [%t] %-5p %c- %m%n
 
log4j.appender.LOCALHOST = org.apache.log4j.DailyRollingFileAppender
log4j.appender.LOCALHOST.File = ${catalina.base}/logs/localhost
log4j.appender.LOCALHOST.Append = true
log4j.appender.LOCALHOST.Encoding = UTF-8
log4j.appender.LOCALHOST.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.LOCALHOST.layout = org.apache.log4j.PatternLayout
log4j.appender.LOCALHOST.layout.ConversionPattern = %d [%t] %-5p %c- %m%n
 
log4j.appender.MANAGER = org.apache.log4j.DailyRollingFileAppender
log4j.appender.MANAGER.File = ${catalina.base}/logs/manager
log4j.appender.MANAGER.Append = true
log4j.appender.MANAGER.Encoding = UTF-8
log4j.appender.MANAGER.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n
 
log4j.appender.HOST-MANAGER = org.apache.log4j.DailyRollingFileAppender
log4j.appender.HOST-MANAGER.File = ${catalina.base}/logs/host-manager
log4j.appender.HOST-MANAGER.Append = true
log4j.appender.HOST-MANAGER.Encoding = UTF-8
log4j.appender.HOST-MANAGER.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.HOST-MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.HOST-MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n
 
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Encoding = UTF-8
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = %d [%t] %-5p %c- %m%n
 
# Configure which loggers log to which appenders
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost] = INFO, LOCALHOST
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager] =\
  INFO, MANAGER
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager] =\
  INFO, HOST-MANAGER
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost]=DEBUG
log4j.logger.org.apache.catalina.core=DEBUG
log4j.logger.org.apache.catalina.session=DEBUG

 

로그4 설정을 설정을 마치고 톰캣을 구동한 다음 확인을 해봅니다.

 

logs/localhost.일자.log를 열어보니 아래처럼 메시지가 출력되는걸 확인 할 수 있었습니다.

 

 

ps. /lib/log4j 설정파일은 꼬이지 않았다면 굳이 추가하지 않아도 쌓이고 있는걸 볼 수 있습니다.

반응형
반응형

입사하고 인수인계를 받으면서 이건~ 원본소스도 없구요 예전에 개발된건데 가끔 ftp 연결 key만 바꿔주면 되는거에요~ 라고 "???" 물음표 3개가 생기는 프로젝트를 얼렁뚱단 받아버린게 하나 있었는데, 이번회사에서 약 1년정도 있다보니 역시나 장애가 터졌다...

 

가장 큰 문제점은 역시... "스케줄러 프로그램인데 원본소스가 없어요~" 이부분 일 것이다,

 

일단 무작정 서버에 붙어서 확인을 해보기 시작했다. 다행히 인수인계 문서에 해당 서버 접속정보는 있었다.

(인수인계 제대로 안받은 내잘못이지 ㅠㅠ)

 

윈도우 서버고 서비스중인 프로세스를 확인해보니 톰캣이 떡하니 있다.

 

오 톰캣으로 뭔가 하는구나! 설치된 톰캣 위치로 가서 webapps에 가보니 war파일이 5년전에 배포 된게 있고, 실행중으로 판단되는 디렉토리 하나가 있다.

 

압축파일로 가져와서 개발PC 디컴파일러(jd-gui-windows-1.4.0)를 하나 설치하고 압축상태로 열어본다.

 

구조를 보니 스프링 구조.. view페이지도 있고;; 단순 스케줄러로 파일 내려주는 것만 있다고 들었는데 심각하다. 분명히 웹의 기능은 없다고 들었는데... 컨트롤러에 필요없는 vo 서비스 dao 등등 그냥 어디서 구조하나 가져와서 스케줄링을 위해 quartz만 설정해서 쓰고 있는것으로 판단되었다.

 

그래 뭐 다 좋다...

 

스케줄링이 도는 동작시간을 확인하고 log를 확인하기 위해 log4j가 설정된 부분을 찾아봤다...

우왁... 파일 하나당 10기가 16기가 8기가 엄청나다;

당연히 열리지도 않는다.

 

gvim, gsplit  등 에디터와 파일쪼개는 프로세스를 활용해서 로그를 분석해본다...

 

로그가 미친듯이 쌓인 이유를 찾았다. mybatis 설정부분인 Connection, Statement, PreparedStatement, ResultSet 모든 레벨이 다 debug로 되어있다. 실서버 등록하면서 개발자가 까먹은건지... 몰랐던건지 그냥 다 때려박도록 되어있다.

 

세월아 네월아 천천히 로그들을 분석해보니 특정 쿼리가 도는데 where절이 없이 데이터가 9만개 가량 들어있는 테이블을 조회하고 있다.

테이블 자체에 데이터도 많지만 컬럼양도 약 70개가 넘어가고 중간에 Text형도 있기에 모든 결과행을 출력하면 데이터가 어마어마하다.

한 27만줄씩 찍어대는거 같다..

 

그런데 그런짓을 한번 돌때마다 9~10번씩 하니 로그만 30분이 넘게 찍다가 OutOfMemory가 뜨고 서버 hdd에도 용량이 심심하면 꽉차고 난리가 난 것 같다...

 

일단 찾은 부분까지 상황을 팀장님께 보고를 드리고 대기하고 있었다.

 

그리고 답변이 왔다 "우리가 조치할 수 방법이 뭐가 있나요..?"

 

원본 소스가 없는데 뭘 어떻게 조치하란 말인가... 프로세스도 미친듯한 절차식들로 인해 분석도 힘들고 심지어 디컴파일러를 통해 보다보니 제대로 안된부분도 많았다.

 

반나절 정도 고민해보고 소스와 로그를 분석해 본 결과

1. log4j.xml의 저 쓸데없는 로그 레벨을 낮추자

   -> IO를 엄청나게 쓰면서 부하 및 용량 이슈를 해결 할 수 있다.

   -> 다만 원본소스가 없어서 뭐가 어떻게 동작하는지 모르니 ResultSet만 error로 바꾸자

 

2. where절이 없이 돌아가는 mapper부분을 분석해보자

   -> 다행이 해당 쿼리를 타는 부분을 역추적하여 찾아보니 mapper에서 사용하는 key값이 processId라면 Map에 담아주는 key값이 processid인 것을 확인하였다. 당연히 자바 소스 수정은 불가능하여 choose when으로 mapper부분의 xml을 바꾸기로 하였다. 그럼 8만개를 검색하여 40초걸리던 부분이 약 1초로 줄어들것으로 판단 되었다.

 

3. tomcat jvm을 강제로 50퍼정도까지 사용하도록 설정하기

 

이정도였고 실서버에 해당 3가지 내용을 적용하고 엄청난 부하가 오던 이슈가 해결이 되었다.

 

ps. 사실 중간에 스킵된 내용이 많은데 log분석이 정말 너무 힘들었다... 장애가 터졌던 날의 로그파일이 12GB였는데 500MB씩 쪼개어 23개 가량의 로그파일들을 하나하나 읽어보면서 찾기기능을 쓴다고 해도 어떤 키워드로 어떤 장애가 터졌을지 감도 안왔고, 너무 힘들어 선임님의 도움으로 java로 디렉토리의 모든 파일들을 쭈르륵 읽어들여 특정 키워드가 발생한 문장만 따로 가져오도록 프로세스도 짜고 아주 신박한 경험을 해보았다.

반응형
반응형

특정 영역을 지정하여 해당 영역에 달력을 생성하고 클릭에 따라 선택 이벤트, 이전달, 다음달로 이동되는 달력 code를 작성해보겠습니다.

아래는 동작 달력입니다.

 

 

 

 

 

 

 

달력 만들기

 

 

 

달력만들기

calendar.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>달력 만들기</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
    <div id="calendarForm"></div>
</body>
</html>

 

calendar.css

* {
    margin: 0;
    padding: 0
}

.custom_calendar_table td {
    text-align: center;
}

.custom_calendar_table thead.cal_date th {
    font-size: 1.5rem;
}

.custom_calendar_table thead.cal_date th button {
    font-size: 1.5rem;
    background: none;
    border: none;
}

.custom_calendar_table thead.cal_week th {
    background-color: #288CFF;
    color: #fff;
}

.custom_calendar_table tbody td {
    cursor: pointer;
}

.custom_calendar_table tbody td:nth-child(1) {
    color: red;
}

.custom_calendar_table tbody td:nth-child(7) {
    color: #288CFF;
}

.custom_calendar_table tbody td.select_day {
    background-color: #288CFF;
    color: #fff;
}

 

calendar.js

(function () {
    calendarMaker($("#calendarForm"), new Date());
})();

var nowDate = new Date();
function calendarMaker(target, date) {
    if (date == null || date == undefined) {
        date = new Date();
    }
    nowDate = date;
    if ($(target).length > 0) {
        var year = nowDate.getFullYear();
        var month = nowDate.getMonth() + 1;
        $(target).empty().append(assembly(year, month));
    } else {
        console.error("custom_calendar Target is empty!!!");
        return;
    }

    var thisMonth = new Date(nowDate.getFullYear(), nowDate.getMonth(), 1);
    var thisLastDay = new Date(nowDate.getFullYear(), nowDate.getMonth() + 1, 0);


    var tag = "<tr>";
    var cnt = 0;
    //빈 공백 만들어주기
    for (i = 0; i < thisMonth.getDay(); i++) {
        tag += "<td></td>";
        cnt++;
    }

    //날짜 채우기
    for (i = 1; i <= thisLastDay.getDate(); i++) {
        if (cnt % 7 == 0) { tag += "<tr>"; }

        tag += "<td>" + i + "</td>";
        cnt++;
        if (cnt % 7 == 0) {
            tag += "</tr>";
        }
    }
    $(target).find("#custom_set_date").append(tag);
    calMoveEvtFn();

    function assembly(year, month) {
        var calendar_html_code =
            "<table class='custom_calendar_table'>" +
            "<colgroup>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "<col style='width:81px'/>" +
            "</colgroup>" +
            "<thead class='cal_date'>" +
            "<th><button type='button' class='prev'><</button></th>" +
            "<th colspan='5'><p><span>" + year + "</span>년 <span>" + month + "</span>월</p></th>" +
            "<th><button type='button' class='next'>></button></th>" +
            "</thead>" +
            "<thead  class='cal_week'>" +
            "<th>일</th><th>월</th><th>화</th><th>수</th><th>목</th><th>금</th><th>토</th>" +
            "</thead>" +
            "<tbody id='custom_set_date'>" +
            "</tbody>" +
            "</table>";
        return calendar_html_code;
    }

    function calMoveEvtFn() {
        //전달 클릭
        $(".custom_calendar_table").on("click", ".prev", function () {
            nowDate = new Date(nowDate.getFullYear(), nowDate.getMonth() - 1, nowDate.getDate());
            calendarMaker($(target), nowDate);
        });
        //다음날 클릭
        $(".custom_calendar_table").on("click", ".next", function () {
            nowDate = new Date(nowDate.getFullYear(), nowDate.getMonth() + 1, nowDate.getDate());
            calendarMaker($(target), nowDate);
        });
        //일자 선택 클릭
        $(".custom_calendar_table").on("click", "td", function () {
            $(".custom_calendar_table .select_day").removeClass("select_day");
            $(this).removeClass("select_day").addClass("select_day");
        });
    }
}

 

반응형
반응형

크롬 브라우저가 이번에 버전업이 되었다.

 

버전업이 되고 기본 default 옵션으로 input이나 select박스와 같은 입력창에 outline으로 검색은 선이 생기게 되어 뭔가 이상한느낌이다.

 

아래 CSS를 공통으로 처리하여 전부 제거되도록 설정하였다.

(크롬 관련 페이지들은 이미 처리가 되어있는지 해당 현상이 없다...)

input:focus, select:focus, option:focus, textarea:focus, button:focus{
	outline: none;
}
반응형
반응형

이번엔 JSON형태로 데이터를 넘기고 받는 방법을 알아보겠습니다.

 

객체를 JSON처리하기 위해 GSON과 SIMPLE JSON 라이브러리의 힘을 빌렸습니다.

처음에 구성은 Simple Json만 사용하여 구성해봤지만, 단순 Array에 담은 데이터를 넘기는건 문제가 없지만 Vo객체를 JSONObject에 넣어서 넘기게되면 "key" 처리가 정상적으로 되지 않아 파싱부분에서 에러가 발생하는 것을 발견하였고, 넘기기전에는 Gson을 통해 JSON화하여 넘기도록 하였습니다.

 

JSON처리하여 소켓통신하기

Server

 

이번엔 Gson과 simple Json을 사용할 예정이므로 pom.xml에 아래 정보를 추가해야 합니다.

pom.xml

<!-- JSON SIMPLE -->
<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>

<!-- gson -->
<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
	<version>2.8.5</version>
</dependency>

 

SokcetThreadServer.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.json.simple.JSONObject;

import com.google.gson.Gson;

public class SocketThreadServer extends Thread {
	
	private static final Logger logger = Logger.getLogger(SocketThreadServer.class);
	
	private Socket socket;
	public SocketThreadServer(Socket socket){
		this.socket=socket;
	}
	
	private static final InterlockDao interlockDao = InterlockDao.getIntstance();
	
	//JSON 데이터 넘기기
	public void run(){
		BufferedReader br = null;
		PrintWriter pw = null;
		
		try{
			String connIp = socket.getInetAddress().getHostAddress();
			System.out.println(connIp + "에서 연결 시도.");

			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pw = new PrintWriter(socket.getOutputStream());
			
			// 클라이언트에서 보낸 문자열 출력
			String returnMessage = br.readLine();
			System.out.println(returnMessage);
			
			Gson gson = new Gson();
			JSONObject jo = gson.fromJson(returnMessage, JSONObject.class);
			List<Map<Object, Object>> list = (ArrayList<Map<Object, Object>>) jo.get("list");
			
			for(int i=0; i<list.size(); i++) {
				System.out.println(list.get(i).toString());
			}
			// 클라이언트에 문자열 전송
			pw.println("수신되었다. 오버!");
			pw.flush();
			
			HashMap<String, Object> params = new HashMap<String, Object>();
			List<Map<String, Object>> test = interlockDao.selectTest(params);
			for(int i=0; i<test.size(); i++) {
				System.out.println(test.get(i));
			}
		}catch(IOException e){
			logger.error(e);
		}finally{
			try{
				if(pw != null) { pw.close();}
				if(br != null) { br.close();}
				if(socket != null){socket.close();}
			}catch(IOException e){
				logger.error(e);
			}
		}
	}
}

이번에도 마찬가지로 데이터를 먼저 받고 응답을 하는 서버 코드입니다.

작성하게될 클라이언트에서 JSONObject에 ArrayList<vo> 컬렉션을 "list" 키에 담아서 발송하는 코드를 작성 예정인데, 파싱하는 부분은 Map형태로 되어있습니다. JSON으로 파싱하면서 VO 객체를 단순 Map의 컬렉션처럼 key, value화 시켰기 때문입니다. 같은 VO로 파싱하려고 하면 파싱에러가 발생하는것을 볼 수 있습니다.

 

App.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.log4j.Logger;

import kr.or.kisa.ktoaInterlock.socket.SocketThreadServer;

public class App {
	
	private static final Logger logger = Logger.getLogger(App.class);
	
	private static final int PORT_NUMBER = 4432;
	
	public static void main(String[] args) throws IOException{
		
		logger.info(":::                                                :::");
		logger.info(":::       Socket Application  Process Start        :::");
		logger.info(":::                                                :::");
		
		try(ServerSocket server = new ServerSocket(PORT_NUMBER)){
			while(true){
				Socket connection = server.accept();
				Thread task = new SocketThreadServer(connection);
				task.start();
			}
		}catch(IOException e){
			logger.error(e);
		}
	}
}

작성 후 소켓서버를 동작시킵니다.

 

Client

클라이언트 프로젝트도 별도로 빼셨다면 server 코드에서 추가한 pom.xml 정보를 입력하여 Gson과 simple Json을 추가해주세요.

 

Client3.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;

import org.json.simple.JSONObject;

import com.google.gson.Gson;

public class Client3 {
    private Socket socket;
    private BufferedReader br;
    private PrintWriter pw;
    
    public Client3(String ip, int port) {
		try {
			// 서버에 요청 보내기
			socket = new Socket(ip, port);
			System.out.println(socket.getInetAddress().getHostAddress() + " 연결됨");
			
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pw = new PrintWriter(socket.getOutputStream());
			
			ArrayList<BoardVO> list = new ArrayList<>();
			for(int i=0; i<5; i++) {
				BoardVO vo = new BoardVO();
				vo.setTitle(i+"번째 제목입니다!");
				vo.setContent("1234567890_testtest String 문자열 테스트 컨텐츠츠츠");
				vo.setIdx(i);
				vo.setWriter("홍길동");
				list.add(vo);
			}
			
			JSONObject jo = new JSONObject();
			jo.put("list", list);
			
			//VO 메시지 발송
			pw.println(new Gson().toJson(jo));
			pw.flush();
			
			//발송 후 메시지 받기
			System.out.println(br.readLine());
		} catch (IOException e) {
		    System.out.println(e);
		} finally {
			// 소켓 닫기 (연결 끊기)
			try {
			    if(socket != null) { socket.close(); }
			    if(br != null) { br.close(); }
			    if(pw != null) { pw.close(); }
			} catch (IOException e) {
			    System.out.println(e);
			}
		}
    }
}

 

ArrayList<BoardVO> 형태의 컬렉션 리스트에 데이터를 담고 JSONObject 키값 list에 담은 후 Gson을 활용하여 JSON화한 문자열을 발송합니다.

 

App.java

public class App{
    public static void main( String[] args ) {
    	String ip = "서버의 IP";
        int port = 서버의 포트;
        new Client3(ip, port);
    }
}

작성이 완료되었으면, 서버를 동작시키고 클라이언트에서 JSON을 발송해본다.

 

 

동작결과

서버에 정상적으로 vo별로 담은 정보가 파싱되는것을 볼 수 있습니다.

 

클라이언트에서도 결과 문자열이 받아진것을 볼 수 있습니다.

 

 

주의사항

JSON파싱 로직시 GSON의 도움 없이 Simple JSON만으로 처리시 아래처럼 데이터가 들어오는것을 볼 수 있습니다.

JSONObject jo = new JSONObject();
jo.put("list", list);
pw.println(jo.toJSONString());

"idx":"0", "title":"0번째 제목입니다!" 인 JSON형태가 아니다.

 

꼭 Gson의 toJSON메소드를 활용하여 전체적으로 JSON형태가 될 수 있게 처리하여 발송하도록 해야 받는 곳에서 파싱하는 경우 문제가 없습니다.

JSONObject jo = new JSONObject();
jo.put("list", list);
pw.println(new Gson().toJson(jo));

 

반응형
반응형

객체 VO를 통신으로 넘기고 싶다면 직렬화(Serialization)라는 과정을 거치고 전송 해야합니다.

직렬화를 위해선 객체가 Serializable을 상속받고 각각 고유한 serialVersionUID를 가지고 있어야하며, 각각 처리하는 VO의 패키지명과 객체명까지 일치시킬 필요성이 있습니다.

 

바로 예제를 보겠습니다.

 

VO객체를 직렬화하여 전송하기

Client

전송할 Client 부분부터 정의하겠습니다. 

TEST를 위한 BoardVO로 서버와 클라이언트의 패키지 구조를 동일하게 작성해주시고 serialVersionUID값을 꼭 맞춰줘야합니다.(VO를 덩어리로 복사하는게 속편합니다...)

 

클라이언트쪽 vo

Client vo path

 

서버쪽 vo

Server vo path

 

 

BoardVO.java

import java.io.Serializable;

public class BoardVO implements Serializable {
	
	private static final long serialVersionUID = 1234567890L;
	
	private int idx;
	private String title;
	private String content;
	private String writer;
	private String reason;

	public int getIdx() {
		return idx;
	}
	public void setIdx(int idx) {
		this.idx = idx;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public String getReason() {
		return reason;
	}
	public void setReason(String reason) {
		this.reason = reason;
	}
	
	@Override
	public String toString() {
		return "BoardVO [idx=" + idx + ", title=" + title + ", content=" + content + ", writer=" + writer + ", reason="
				+ reason + "]";
	}
}

 

Client2.java

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

public class Client2 {
	private Socket socket;
	private ObjectOutputStream oos;
	private ObjectInputStream ois;
    
	public Client2(String ip, int port) {
		try {
			// 서버에 요청 보내기
			socket = new Socket(ip, port);
			System.out.println(socket.getInetAddress().getHostAddress() + " 연결됨");
			
			oos = new ObjectOutputStream(socket.getOutputStream());
			
			ArrayList<BoardVO> list = new ArrayList<>();
			for(int i=0; i<5; i++) {
				BoardVO vo = new BoardVO();
				vo.setTitle(i+"번째 제목입니다!");
				vo.setContent("1234567890_testtest String 문자열 테스트 컨텐츠츠츠");
				vo.setIdx(i);
				vo.setWriter("홍길동");
				list.add(vo);
			}
			
			HashMap<Object, Object> map = new HashMap<Object, Object>();
			map.put("list", list);
			//VO 메시지 발송
			oos.writeObject(map);
			oos.flush();
			
			//발송 후 메시지 받기
			ois = new ObjectInputStream (socket.getInputStream());
			// 응답 출력
			HashMap<Object, Object> returnMap = (HashMap<Object, Object>) ois.readObject();
			ArrayList<BoardVO> retrunList = (ArrayList<BoardVO>) returnMap.get("list");
			for(int i=0; i<retrunList.size(); i++) {
				System.out.println(retrunList.get(i).toString());
			}
		} catch (IOException | ClassNotFoundException e) {
		    System.out.println(e.getMessage());
		} finally {
		    // 소켓 닫기 (연결 끊기)
			try {
				if(socket != null) { socket.close();}
				if(oos != null) { oos.close(); }
				if(ois != null) { ois.close(); }
			} catch (IOException e) {
			    System.out.println(e.getMessage());
			}
		}
	}
}

BoardVO는 방금 작성했던 위치로 import처리 해주시고, 가데이터 처리 5개정도를 넣어준다음 VO객체를 발송하고 응답받는 예제입니다.

이번에는 ObjectInputStream과 ObjectOuptStream을 사용하여 객체를 발송하고 발신받는 예제입니다.

ObjectInputStream은 객체를 직렬화하여 전송하여 주고 ObjectOuptStream은 객체의 직렬화를 역직렬화 해줍니다.

해당 클래스의 readObject() 메소드는 스트림에서 객체를 읽는데 사용되며, 전송한 객체의 유형과 동일한 형태로 꼭 캐스팅을 해주어야 문제없이 파싱이 되는것을 볼 수 있습니다.

 

App.java

public class App{
    public static void main( String[] args ) {
    	String ip = "연결할 주소(ip)";
        int port = 연결할 포트;
        new Client2(ip, port);
    }
}

 

 

Server

서버에서도 동일한 VO를 사용할 예정이므로 Client쪽에서 발신하는 VO와 동일하게 패키지 구조를 구성하시고 serialserialVersionUID 또한 맞춰줍니다.

VO객체는 생성하였다고 치고 Server Socket코드를 작성하겠습니다.

 

SocketThreadServer.java

package kr.or.kisa.ktoaInterlock.socket;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

import org.apache.log4j.Logger;

public class SocketThreadServer extends Thread {
	
	private static final Logger logger = Logger.getLogger(SocketThreadServer.class);
	
	private Socket socket;

	public SocketThreadServer(Socket socket){
		this.socket=socket;
	}
	
	//vo형태 Serializable 데이터 넘기기
	public void run(){
		ObjectOutputStream oos = null;
		ObjectInputStream  ois = null;
		try{
			ois = new ObjectInputStream (socket.getInputStream());  
			oos = new ObjectOutputStream(socket.getOutputStream());
			
			HashMap<Object, Object> map = (HashMap<Object, Object>) ois.readObject();
			ArrayList<BoardVO> list = (ArrayList<BoardVO>) map.get("list");
			for(int i=0; i<list.size(); i++) {
				System.out.println(list.get(i).toString());
			}
			
			oos.writeObject(map);
			oos.flush();
		}catch(IOException e){
			logger.error(e);
		}catch (ClassNotFoundException e) {
			logger.error(e);
		}finally{
			try{
				if(oos != null) { oos.close();}
				if(ois != null) { ois.close();}
				if(socket != null){socket.close();}
			}catch(IOException e){
				logger.error(e);
			}
		}
	}
}

서버 코드 또한 ObjectInputStream / ObjectOutputStream 를 통해 데이터를 역직렬화하여 가져오거나 직렬화하여 넘깁니다.

 

App.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.log4j.Logger;

import com.psw.socket.SocketThreadServer;

public class App {
	
	private static final Logger logger = Logger.getLogger(App.class);
	
	private static final int PORT_NUMBER = 4432;
	
	public static void main(String[] args) throws IOException{
		
		logger.info(":::                                                :::");
		logger.info(":::       Socket Application  Process Start        :::");
		logger.info(":::                                                :::");
		
		try(ServerSocket server = new ServerSocket(PORT_NUMBER)){
			while(true){
				Socket connection = server.accept();
				Thread task = new SocketThreadServer(connection);
				task.start();
			}
		}catch(IOException e){
			logger.error(e);
		}
	}
}

서버 코드 작성이 끝났으면 실행동작을 확인해보겠습니다.

 

 

동작 결과

서버 로그

클라이언트에서 전송받은 vo객체를 역직렬화하여 ArrayList<BoardVO>에 받고 출력 > 그대로 직렬화하여 전달한다.

 

클라이언트 로그

직렬화하여 발신한 VO 리스트를 역직렬화하여 다시 받고 출력한다.

 

반응형
반응형

JAVA에서 소켓통신을 위한 예제입니다.

단순 문자열처리, VO객체 정보를 직렬화하여 처리하기, JSON형태로 데이터 주고 받기 3가지 방식에 대해 알아볼 예정이며 이번 포스팅에서는 단순 메시지를 주고 받는 예제를 진행하겠습니다.

 

Socket Server

SocketThreadServer.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import org.apache.log4j.Logger;

public class SocketThreadServer extends Thread {
	
	private static final Logger logger = Logger.getLogger(SocketThreadServer.class);
	
	private Socket socket;

	public SocketThreadServer(Socket socket){
		this.socket=socket;
	}
	
	//단순 문자열 Thread server
	public void run(){
		BufferedReader br = null;
		PrintWriter pw = null;
		try{
			String connIp = socket.getInetAddress().getHostAddress();
			System.out.println(connIp + "에서 연결 시도.");
			
			/*
			 * 접근한 소켓 계정의 ip를 체크한다. KTOA 연동 모듈인지 체크 
			 * 정상이면 먼저 정상 접근되었음을 알린다.
			 **/
			br = new BufferedReader(
			        new InputStreamReader(socket.getInputStream()));
			
			pw = new PrintWriter(socket.getOutputStream());
			
			// 클라이언트에서 보낸 문자열 출력
			System.out.println(br.readLine());
			
			// 클라이언트에 문자열 전송
			pw.println("수신되었다. 오버");
			pw.flush();		
		}catch(IOException e){
			logger.error(e);
		}finally{
			try{
				if(pw != null) { pw.close();}
				if(br != null) { br.close();}
				if(socket != null){socket.close();}
			}catch(IOException e){
				logger.error(e);
			}
		}
	}
}

 

App.java - main()

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.log4j.Logger;

import com.psw.socket.SocketThreadServer;

public class App {
	
	private static final Logger logger = Logger.getLogger(App.class);
	
	private static final int PORT_NUMBER = 4432;
	
	public static void main(String[] args) throws IOException{
		
		logger.info(":::                                                :::");
		logger.info(":::       Socket Application  Process Start        :::");
		logger.info(":::                                                :::");
		
		try(ServerSocket server = new ServerSocket(PORT_NUMBER)){
			while(true){
				Socket connection = server.accept();
				Thread task = new SocketThreadServer(connection);
				task.start();
			}
		}catch(IOException e){
			logger.error(e);
		}
	}
}

 

 

Socket Client

Client1.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client1 {
	private Socket socket;
	private BufferedReader br;
	private PrintWriter pw;
	
	public Client1(String ip, int port) {
		try {
			// 서버에 요청 보내기
			socket = new Socket(ip, port);
			System.out.println(socket.getInetAddress().getHostAddress() + "에 연결됨");
			
			// 메시지 받기
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pw = new PrintWriter(socket.getOutputStream());
			
			// 메세지 전달
			pw.println("메시지를 발송한다. 오버!");
			pw.flush();
			
			// 응답 출력
			System.out.println(br.readLine());
		} catch (IOException e) {
		    System.out.println(e.getMessage());
		} finally {
		    // 소켓 닫기 (연결 끊기)
		    try {
		    	if(socket != null) { socket.close(); }
		        if(br != null) { br.close(); }
		        if(pw != null) { pw.close(); }
		    } catch (IOException e) {
		        System.out.println(e.getMessage());
		    }
		}
	}
}

 

App.java

public class App{
    public static void main( String[] args ) {
    	String ip = "연결할 주소(ip)";
        int port = 4432;
        new Client1(ip, port);
    }
}

 

실행 결과

1. 서버를 먼저 실행합니다.

Socket Server start!

 

2. 소켓서버가 열리면 클라이언트에서 연결정보로 연결 후 메시지를 발송합니다.

클라이언트에서 서버에 연결
서버로 전달된 메시지

 

3. 서버에서는 클라이언트가 발송한 문자열을 출력하고 전달할 문자열을 발신합니다.

답신 도착

반응형