반응형

삽입 정렬

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

 

{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(", ");
			}
		}
	}
}

 

반응형
반응형

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

 

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

 

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

 

선택 정렬

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

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(", ");
			}
		}
	}
}

반응형
반응형

자바로 소켓 프로그램 연동 프로젝트를 구성하고 테스트를 하면서 이상한 버그를 발견하였다.

 

소켓 서버에서 클라이언트 연결을 하고 전송되는 데이터를 기다리는데 연결 클라이언트 정보 이력은 확인이 되지만, 클라이언트에서 전송한 문자열 데이터를 받지 못하고 무한 대기하는 현상이였다.

 

기존 소스

public void run(){
	BufferedReader br = null;
	PrintWriter pw = null;
	
	try{
		String connIp = socket.getInetAddress().getHostAddress();
		logger.info(StringUtils.logStr(connIp + "에서 연결 시도."));
		int BUF_SIZE = 1024*7;
		br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf8"), BUF_SIZE);
		pw = new PrintWriter(socket.getOutputStream());
		
		String receiveStr = ""; 
		String tmp = "";
		while((tmp = br.readLine()) != null) {
			receiveStr += tmp;
		}
		logger.info(StringUtils.logStr("receive msg : " + receiveStr));

위 와 같은 형태로 BufferedReader를 통해 readLine형태로 메시지를 전달받고 null일때까지 데이터를 임시 변수에 받아서 저장하는 형태였다.

 

하지만 전송하는 클라이언트에서 문자열 끝에 개행문자 "\n" 를 처리해주거나 BufferedWriter를 사용해 newLine() 처리를 해주지 않으면 영원히 머물러 있는 무한 대기 현상을 겪었고, 클라이언트측에서는 개행을 보장해주지 않는 상황이라 받는 부분을 개선해야 했다.

 

BufferedReader의  read()를 사용하여 개선하였다.

 

개선 소스

public void run(){
	BufferedReader br = null;
	PrintWriter pw = null;
	
	try{
		String connIp = socket.getInetAddress().getHostAddress();
		logger.info(StringUtils.logStr(connIp + "에서 연결 시도."));
		int BUF_SIZE = 1024*7;
		br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf8"), BUF_SIZE);
		pw = new PrintWriter(socket.getOutputStream());
		
		// 클라이언트에서 보낸 문자열 출력
		StringBuffer buffer = new StringBuffer();
		while(true) {
			int ch = br.read();
			if((ch<0) || (ch == '\n')) {
				break;
			}
			buffer.append((char) ch);
		}
		String receiveStr = buffer.toString();
		logger.info(StringUtils.logStr("receive msg : " + receiveStr));
반응형
반응형

처음 대학에 들어가 c언어를 배우면서 일상에서 쉽게 사용하던 문자열들이 굉장히 어렵게 처리했던것들이 기억난다. char형의 배열로 문자열형태를 만들고 제어했는데, 자바는 String 클래스 하나면 문자열을 저장하고 합치고 필요한 메소드를 사용해 제어가 가능하다.

 

불과 작년까지 String을 자바의 기본 자료형(primitive type)으로 착각하고 문자열처리는 String 클래스로만 가능할 것이라고 생각했던 것에 반성하며 String, StringBuffer, StringBuilder클래스에 대해 정리해본다.

 

String

String 클래스는 일단 변경 불가능한 클래스(immutable)이다.

쉽게 String클래스에 문자열을 넣어 사용하지만 실제로는 다른 언어처럼 char[] 배열 변수를 인스턴스 변수로 받아서 저장이 된다.

final 상수로 String을 처리하였다.(불변 객체 String)

"Test"+ "Sum" = "TestSum" 이런식으로 String으로 쉽게 문자열들을 합쳐왔을텐데, 인스턴스 내의 문자열이 바뀌는게 아닌 TestSum이라는 새로운 문자열이 생성되는 것이다.

 

String의 문자열 비교는 equals()

여기서 우리가 자주 쓰던 조건문의 eqauls() 사용하는 이유가 나오는데, 문자열은 생성할때 새로운 인스턴스를 생성하고 메모리에 저장한다.

 

public class Test {

	public static void main(String[] args) {
		String a = "test1";
		String b = "test";
		String c = "1";
		String d = b + c;
		
		if(a == d) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
		
		if(a.equals(d)) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
	}
}

같지 않아요 // 같아요

 

당연히 equals로 비교하지 않았기 때문에 else를 탄다는것을 알지만 왜? 가 여기서 이해가 될 것이다.

test라는 문자열을 생성하고 1을 생성 후 둘을 합치면서 새로운 test1이라는 문자열을 만들었기때문에 둘의 생성된 메모리의 주소값은 다르다. 그래서 두 변수의 문자열의 값만 비교하는 eqauls()를 통해 boolean값을 얻는다.

 

계속해서 말하는 내용이지만 결합이나 추출 등의 작업이 나오면 새로운 문자열이 생성된다. 즉, 문자열을 합칠때마다 새로운 문자열을 만들기 위해 인스턴스를 생성하고 메모리를 잡아먹는다.

문자열의 결합, 추출 등의 작업이 필요하다면 String이 아닌 StringBuffer를 통해 처리하는것이 시스템상 좋을 것이다. 왜? 인스턴스를 매번 생성하고 메모(간단하게 합치고 추출정도는 현대의 컴퓨터 연산과 메모리에 큰 영향이 없으므로 상관 없을것같다.)

 

 

 

StringBuffer

StringBuffer는 불변객체가 아니다. 내부적으로 buffer를 가지고 있는데,

StringBuffer 인스턴스를 생성할때는 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집할때 사용하는 buffer가 된다.

 

기본 사이즈는 16

여기서 적절한 길이를 지정해야 하는 이유는, 수정하는 문자열이 버퍼의 길이를 넘어가면 버퍼의 길이를 늘려주는 작업이 추가되기 때문이다. 배열은 길이를 바꿀수 없기때문에 이전에 작업하던 배열의 값을 복사해서 넣어준다.

 

StringBuffer의 문자열 비교

StringBuffer의 equals는 String의 equals와는 다르게 (==) 비교 연산이라 문자열 비교시 원하는 결과를 얻을 수 없다.

toString()을 통해 String인스턴스화를 거친 후 비교를 한다.

public class Test {

	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer("test1");
		StringBuffer sb2 = new StringBuffer("test1");
		if(sb.equals(sb2)) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
		
		String str1 = sb.toString();
		String str2 = sb.toString();
		if(str1.equals(str2)) {
			System.out.println("같아요");
		}else {
			System.out.println("같지 않아요");
		}
	}
}

같지 않아요 // 같아요

 

StringBuffer의 문자열 합치기, 추출, 제거하기

문자열 합치기

StringBuffer에는 append()메소드가 존재한다. 해당 메소드를 통해 문자열을 추가할 수 있다.

StringBuffer sb = new StringBuffer("a");
sb.append("b"); //ab
sb.append("c").append("d").append("e"); //abcde
System.out.println(sb); //abcde

"abcde"

 

위 캡처처럼 계속 연결해서 사용할 수도 있다.

 

 

문자열 제거

delete(), deleteCharAt() 메소드를 통해 문자열을 지우거나 특정 문자를 제거할 수도 있다.

StringBuffer sb = new StringBuffer("abcde");
sb.delete(0, 2);
System.out.println(sb); //cde

"cde"
"abd"

 

 

문자열 추출

String의 substring의 StringBuffer에도 존재한다. 사용법은 동일하다.

StringBuffer sb = new StringBuffer("hello world!");
String str1 = sb.substring(6); //world!
String str2 = sb.substring(0, 5); //hello

"hello"

 

 

문자열 역순처리

reverse()메소드는 문자열을 거꾸로 나열할 수있다.

StringBuffer sb = new StringBuffer("!dlrow olleh");
sb.reverse(); //reverse
System.out.println(sb); //hello world!

문자열 역순처리로 hello world! 출력

 

 

 

StringBuilder

StringBuilder는 기본적으로 사용하는 메소드나 사용법은 동일하여 서로 호환이 됩니다.(append(), delete(), reverse() 등)

StringBuffer와의 차이점은 동기화를 하는지 않하는지의 차이인데

StringBuffer - 동기화O // StringBuilder - 동기화X

싱글쓰레드 환경에서의 개발이라면 StringBuilder를 멀티쓰레드 환경이라면 동기화처리가된 StringBuffer를 사용하면 됩니다.

즉, StringBuffer의 동기화 처리를 빼고 만들어진 클래스가 StringBuilder입니다.

 

그럼 무시하고 무조건 StringBuffer만 쓰면 되는것이 아니냐? 라고 물어볼 수도 있지만...

동기화처리는 시스템의 불필요하게 성능만 느리게 하므로 차이를 알고 사용하는게 시스템 성능향상에 도움이 될 것입니다.

 

 

반응형
반응형

log4j.properties를 사용하여 log4j 설정을 하고 Mybatis의 동작 로그를 출력하도록 설정을 해보겠습니다.

 

mybatis-configs.xml

<configuration>

	<settings>
		<setting name="cacheEnabled" value="false"/>
		<setting name="useGeneratedKeys" value="ture"/>
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="defaultStatementTimeout" value="3000"/>
		<setting name="logImpl" value="LOG4J"/>  <!-- log4j log setting  -->
	</settings>

mybatis 설정의 settings 부분에 <setting name="logImpl" value="LOG4J"/> 를 추가합니다.

 

 

log4j.properties

log4j.rootLogger=DEBUG, stdout, logfile

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p (%C{2}:%L) - %m%n

# Direct log message to log file
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.DatePattern='.'yyyy-MM-dd
log4j.appender.logfile.File=C:/javaProcess/dailyLog.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %5p (%C{2} - %M:%L) - %m%n
log4j.appender.logfile.Append=true
log4j.appender.logfile.encoding=UTF-8

 

rootLogger 부분에 DEBUG를 추가해주면 정상적으로 mybatis 동작에 대해서 출력하는 모습을 볼 수 있습니다.

반응형
반응형

JAVA 서버를 작성해야 할 일이 생겨서 Maven 프로젝트로 생성하여 진행을 하고 있었다. DB에 insert와 select처리가 필요해서 Mybatis 설정작업까지 완료 후, select는 정상동작하는 걸 봤는데 insert처리를 아무리해도 에러가 발생하였다.

 

설정에 문제가 있나???... 점점 당황스러운 시점에 동작하는 insert 쿼리 자체를 넣어도 동작하지 않았다.

 

insert된 결과값을 찍어보니 -2147482646 라는 값이 찍히고 있었다.

 

<setting name="defaultExecutorType" value="BATCH" /> //제거

Mybatis 설정을 하면서 setting부분한 BATCH부분이 문제라고 해서 해당부분을 제거하고 재구동을 해봤지만, 결과값은 정상적으로 1로 나오지만 DB에 반영은 안되고 있었다.

 

 

점점 멘탈이 나가려고 하는 순간 최초에 Mybatis 설정을 하던 부분에서 주석부분이 생각났다.

 

SqlSessionFactory와 SqlSession을 static변수로 처리하고 싱글톤 형태로 특정 객체에서 관리하도록 하였는데,

SqlSession session = sqlSessionFactory.openSession(false);	 //false = Not autoCommit

openSession(false);처리는 autoCommit을 끄는 옵션이였다.

 

해답은 commit이 동작하지 않아서 반영이 안되었기 때문이었다...

순간적으로 부끄러움과 아직 멀었구나가 느껴지는 순간이었다~

 

항상 스프링에서 Mybatis를 사용하다보면 Spring이 알아서 전부 관리해주다보니 까맣게 잊고 있었던 것 같다.

 

Maven프로젝트에서 Mybatis insert처리 후 commit하기

이번일을 계기로 다시한번 공부를 하게되었고, 처리한 방법을 올려볼까 합니다.

 

SqlsessionFactorys.java

package kr.or.kisa.ktoaInterlock.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;

public class SqlSessionFactorys {
	
	private static final Logger logger = Logger.getLogger(SqlSessionFactorys.class);
	
	private SqlSessionFactorys() {	}
	
	static SqlSessionFactory sqlSessionFactory;
	
	private static SqlSessionFactory setSqlSessionFactory() {
		SqlSessionFactory sqlSessionFactory = null;
		String resource = "프로젝트내 xml 위치/mybatis-config.xml";
		
		PropertyReader pr = PropertyReader.getInstance();
		Properties props = new Properties();
		props.put("driver"      , pr.get("mybatis.driver"));
		props.put("url"         , pr.get("mybatis.url"));
		props.put("username"    , pr.get("mybatis.user"));		
		props.put("password"    , pr.get("mybatis.password"));	
		
		InputStream inputStream = null;
		try {
		    inputStream = Resources.getResourceAsStream(resource);
		    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, props);
		    props.clear();
		} catch (IOException e) { 	
			logger.error(e);
		} finally {
			if(inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return sqlSessionFactory;
	}
	
	public static SqlSession setSqlSession() {
		if(sqlSessionFactory == null) {
			sqlSessionFactory = setSqlSessionFactory();
		}
		SqlSession session = sqlSessionFactory.openSession(false);	 //false = Not autoCommit
		return session;
	}
}

SqlSessionFactory는 싱글톤 형태로 유지하고 쿼리를 진행할때마다 열고 닫아가면서 사용할 SqlSessionFactory를 싱글톤 형태로 관리하는 부분이다.

 

 

SqlSessionUtils.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;

public class SqlSessionUtils {
	private static final Logger logger = Logger.getLogger(SqlSessionUtils.class);
	
	private SqlSessionUtils() {}
	public static SqlSessionUtils instance;
	public static SqlSessionUtils getInstance() {
		if(instance == null) {
			instance = new SqlSessionUtils();
		}
		return instance;
	}
	
	public int insert(String statement, Object params) {
		int res = 0;
		SqlSession session = null;
		try {
			session = SqlSessionFactorys.setSqlSession();
			res = session.insert(statement, params);
			if(res > 0) {
				session.commit();
			}else {
				session.rollback();
			}
		}catch(Exception e) {
			logger.error(e);
			session.rollback();
		}
		session.close();
		return res;
	}
	
	public List<Map<Object, Object>> selectList(String statement, Object params) {
		List<Map<Object, Object>> res = new ArrayList<>();
		SqlSession session = null;
		try {
			session = SqlSessionFactorys.setSqlSession();
			res = session.selectList(statement, params);
		}catch(Exception e) {
			logger.error(e);
		}
		session.close();
		return res;
	}
}

SqlSession의 메소드와 일부러 이름을 맞추고 파라미터 또한 통일시켰다. 다만 리턴값은 좀 다르게 설정하였다.

예제에서는 insert, select만 존재하며, 추가적으로 update, delete, selectOne 등은 추가적으로 작성이 필요하다.

 

매번 동작할때마다 SqlSession에 SqlSessionFactorys에서 처리한 싱글톤을 통해 SqlSession을 열어주고

쿼리 동작이 끝나면 정상적으로 처리된 경우 commit()을 한다.

정상 동작이 아니거나 예외처리가 발생하면 rollback()처리를 해주고, 동작이 끝나면 해당 SqlSession을 닫아준다.

 

 

사용한 Dao 예제 소스)

import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import kr.or.kisa.ktoaInterlock.utils.SqlSessionUtils;

public class InterlockDao {
	private static final Logger logger = Logger.getLogger(InterlockDao.class);
	
	private InterlockDao() {}
	private static InterlockDao instance;
	static SqlSessionUtils session = SqlSessionUtils.getInstance();
	
	public static InterlockDao getInstance() {
		if(instance == null) {
			instance = new InterlockDao();
		}
		return instance;
	}
	
	//테이블 데이터 저장
	public int insertRequestTrack(Map<Object, Object> params) throws Exception{
		return session.insert("track.insertTest", params);
	}
}

로그를 확인해보면 오토커밋은 꺼진상태로 정상적으로 JDBC 커넥션이 열리는 모습을 볼 수 있고, insert 동작이 끝나면 commit을 처리하고 JDBC연결을 닫아준다.

 

추가적으로 commit을 한번 처리하면 autoCommit이 켜지는것 같은데, SqlSession을 닫아버리고 다시 꺼진 옵션의 SqlSession을 가져오기 때문에 큰 문제는 없이 계속해서 해당 로직을 사용하여 insert 처리를 할 수 있었다.

반응형
반응형

이번엔 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 리스트를 역직렬화하여 다시 받고 출력한다.

 

반응형