네이버 클라우드 장비를 통해서 기본 application 설정을 진행합니다.
adduser user_test
passwd user_test
gpasswd -a user_test wheel
sudo yum update
sudo yum install tree
set nocompatible " 오리지날 VI와 호환하지 않음
set autoindent " 자동 들여쓰기
set cindent " C 프로그래밍용 자동 들여쓰기
set smartindent " 스마트한 들여쓰기
set wrap
set nowrapscan " 검색할 때 문서의 끝에서 처음으로 안돌아감
set nobackup " 백업 파일을 안만듬
set visualbell " 키를 잘못눌렀을 때 화면 프레시
set ruler " 화면 우측 하단에 현재 커서의 위치 표시
set shiftwidth=4 " 자동 들여쓰기 4칸
set number " 행번호 표시, set nu 도 가능
set fencs=ucs-bom,utf-8,euc-kr.latin1 " 한글 파일은 euc-kr로, 유니코드는 유니코드로
set fileencoding=utf-8 " 파일저장인코딩
set tenc=utf-8 " 터미널 인코딩
set expandtab " 탭대신 스페이스
set hlsearch " 검색어 강조, set hls 도 가능
set ignorecase " 검색시 대소문자 무시, set ic 도 가능
set tabstop=4 " 탭을 4칸으로
set lbr
set incsearch " 키워드 입력시 점진적 검색
syntax on " 구문강조 사용
filetype indent on " 파일 종류에 따른 구문강조
set background=dark " 하이라이팅 lihgt / dark
colorscheme desert " vi 색상 테마 설정
set backspace=eol,start,indent " 줄의 끝, 시작, 들여쓰기에서 백스페이스시 이전줄로
set history=1000 " vi 편집기록 기억갯수 .viminfo에 기록
wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.tar.gz
gunzip jdk-8u131-linux-x64.tar.gz
tar -xvf jdk-8u131-linux-x64.tar.gz
mv jdk1.8.0_131 ~/apps/
cd ~/apps
ln -s jdk1.8.0_131/ java
in .bashrc
JAVA_HOME=/home/test_user/apps/java
PATH="$JAVA_HOME/bin:$PATH"
##
wget http://apache.tt.co.kr/tomcat/tomcat-8/v8.5.14/bin/apache-tomcat-8.5.14.tar.gz
export CATALINA_HOME=/home/test_user/apps/tomcat
export JAVA_HOME=/home/test_user/apps/java
export PATH="$JAVA_HOME/bin:$CATALINA_HOME/bin:$PATH"
./configure –enable-module=so –enable-mods-shared=most –enable-maintainer-mode –enable-deflate –enable-headers –enable-rewrite –enable-ssl –enable-proxy –enable-proxy-http –enable-proxy-ajp –enable-proxy-balance –with-included-apr –with-pcre=/usr/local/pcre –prefix=/home/irteam/apps/apache-2.4.25
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
}
자동설정 관련한 어노테이션 특정 기준의 설정클래스를 로드
3.0에서는 잘 사용하지 않았다. 자바 Configuration 을 잘 사용하지 않았었다. 3.1로 넘어오면서 사용빈도가 높아진다.
예를 들어 EnableWebMvc 같은 어노테이션이 있다. EnableAspectJAutoProxy
@Import 와 함께 자신만의 모듈을 만들수 있다.
스프링부트의 설정의 시작
@Import({ EnableAutoConfigurationImportSelector.class,
AutoConfigurationPackages.Registrar.class })
public @interface EnableAutoConfiguration {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
}
제일 핵심인 코드
DeferredImportSelector 이 어노테이션을 통해서 설정정보를 가져온다.
@Order(Ordered.LOWEST_PRECEDENCE)
class EnableAutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware {
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
/*
* 문자열 배열의 의미는 package와 class명 정보를 가져온다.
*/
@Override
public String[] selectImports(AnnotationMetadata metadata) {
try {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(EnableAutoConfiguration.class.getName(), true));
// Find all possible auto configuration classes, filtering duplicates
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
// Remove those specifically disabled
factories.removeAll(Arrays.asList(attributes.getStringArray("exclude")));
// Sort
factories = new AutoConfigurationSorter(this.resourceLoader).getInPriorityOrder(factories);
return factories.toArray(new String[factories.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
설정리스트의 보관소 스프링 개발자들이 만들어놓은 노가다 작업
/META-INF/spring.factories 파일에 이미 정의 되어있다. 해당 파일은 boot-Autoconfiguration.jar 파일에 존재한다.
모든 클래스는 로딩이 되나 스프링 컨테이너에 올라가지는 않는다. 메모리에 올라가면 상당이 느려질 수 있다. @Conditional 이라는 옵션을 통해서 조건적으로 Bean에 등록한다. Spring 4.x.x 에서부터 지원하기 시작하는 어노테이션 그래서 스프링은 4.x.x 대부터 스프링부트를 사용할 수 있다.
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
특정 Bean을 등록할 때 특정 환경에서만 등록하고 싶다.
@Bean
@Conditional(Phase.class)
public CommandLineRunner ....
class Phase implements Condition {
public boolean match
}
대표적인 Conditional 에 대한 예제
첫째 많이 읽어야 잘 쓸 수 있다. 책을 많이 읽어도 글을 잘 쓰지 못할 수는 있다. 그러나 많이 읽지 않고도 잘 쓰는 것은 불가능하다.
둘때 많이 쓸수록 더 잘 쓰게 된다. 축구나 수영이 그런 것처럼 글도 근육이 있어야 잘 쓴다.
글쓰기 근육을 만드는 유일한 방법은 쓰는 것이다. 여기에 예외는 없다. 그래서 철칙
이다.
글쓰기에 도움이 되는 책을 고르는 3가지 조건
인간, 사회, 문화, 역사, 생명, 자연, 우주를 이해하는 꼭 필요한 개념과 지식을 담은 책이다. 글을 쓰는데 꼭 필요한 지식을 배울 수 있으며 독해력을 높일 수 있다.
정확하고 바른 문장을 구사한 책이여야 한다. 이런 책을 읽어야 자기생각을 효과적이고 아름답게 표현하는 문장 구사 능력이 높아진다.
지적 긴장과 흥미를 일으키는 책이다. 이런책이라야 즐겁게 읽을 수 있고 논리의 힘과 멋을 느낄 수 있다. 좋은 문장에 훌륭한 내용이 담긴 책을 즐거운 마음으로 읽으면 지식과 논리 구사 능력을 한꺼번에 얻게 된다.
책 목록 제안에 앞서 먼저 3권 추천한다. 토지, 자유론, 코스모스
토지 - 한국어의 정수 (2번) 자유론 - 어려운 어휘구사없이 철학적인 사유를 표현한 인문학 서적 코스모스 - 자연계 어휘의 학습과 어휘들의 연결을 이해할 수 있는 공학계 서적
도덕적 인간과 비도덕적 사회
, 문예출판사침묵의 봄
, 에코리브르만들어진 신
, 김영사이기적 유전자
, 을유문화사파인만의 여섯 가지 물리 이야기
, 승산정의란 무엇인가
, 김영사프로테스탄트 윤라와 자본주의 정신
, 다락원유한계급론
, 우물이있는집마음의 과학
, 와이즈베리다른 의견을 가질 권리
, 바오강의
, 돌배게역사의 연구
, 동서문화사권력이동
, 한국경제신문역사란 무엇인가
, 까치글방작은 것이 아름답다
, 문예출판사소유나 삶이냐
, 홍신문화사왜 세계의 절반은 굶주리는가
, 갈라파고스그들이 말하지 않는 23가지
, 부키총, 균, 쇠
, 문학사상정재승의 과학콘서트
, 어크로스가이아
, 갈라파고스자유론
, 책세상불확실성의 시대
, 홍신문화사미학 오디세이
, 휴머니스트생명이 있는 것은 다 아름답다
, 효형출판공산당선언
, 책세상코스모스
, 사이언스 북스성 정치학
, 이후유토피아
, 서해문집예루살렘의 아이히만
, 한길사시민의 불복종
, 은행나무진보와 빈곤
, 비봉출판사종류 | 키의 길이 | 블록길이 | 단수 |
---|---|---|---|
AES-128 | 128 | 128 | 10 |
AES-192 | 192 | 128 | 12 |
AES-256 | 256 | 128 | 14 |
자바 네트워크 소녀 Netty
책을 보고 정리한 글입니다. (책 내용이 참으로 좋습니다.)소켓 채널의 이벤트를 인터페이스로 정의하고 이 인터페이스를 상속받은 이벤트 핸들러를 작성하여 채널 파이프 라인에 등록한다. 채널 파이프라인으로 입력되는 이벤트를 이벤트 루프가 가로채어 이벤트에 해당하는 메소드를 수행하는 구조이다.
public interface ChannelInboundHandler extends ChannelHandler {
/**
* 1) 서버 기준으로 처음 서버소켓 채널이 생성될 때
* 2) 새로운 클라이언트가 서버에 접속하여 클라이언트 소켓 채널이 생성될때 이벤트가 발생한다.
*/
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
/**
* 이벤트 루프에서 채널이 제거되었을 때 발생하는 이벤트
*/
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
/**
* channelRegistered() 이벤트 후에 발생한다.
* 채널이 생성되어 이벤트 루프에 등록된 이후에 네티 API를 사용하여 채널 입출력을 수행할 상태가 되었다를 알려준다.
*
* ex) 서버 어플에 연결된 클라이언트의 연결 세기, 최초 연결메시지 전달
*/
void channelActive(ChannelHandlerContext ctx) throws Exception;
/**
* 채널이 비활성화 되었을때 발생하는 이벤트
* channelInactive 가 발생한 이후에는 채널에 대한 입출력 작업을 수행할 수 없다.
*/
void channelInactive(ChannelHandlerContext ctx) throws Exception;
/**
* 데이터가 수신되었음을 알려준다.
* 수신된 데이터는 네티의 ByteBuf 객체에 저장되어 있으며 이벤트 메서드의 두번째 인자인 msg를 통해서 접근할 수 있다.
*
*/
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
/**
* channelRead 이벤트는 채널에 데이터가 있을 때 발생하고
* 채널의 데이터를 다 읽어서 더 이상 데이타가 없을 때 channelReadComplete 이벤트가 발생한다.
*/
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
/**
* Gets called if an user event was triggered.
*/
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
/**
* Gets called once the writable state of a {@link Channel} changed. You can check the state with
* {@link Channel#isWritable()}.
*/
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
/**
* Gets called if a {@link Throwable} was thrown.
*/
@Override
@SuppressWarnings("deprecation")
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
public interface ChannelOutboundHandler extends ChannelHandler {
/**
* 서버 소켓 채널이 클라이언트의 연결을 대기하는 IP와 포트가 설정되었을 때 발생하는 이벤트
*/
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
/**
* 클라이언트 소켓 채널이 서버와 연결되었을 때 발생하는 이벤트
* 원격지의 SocketAddress, 로컬의 SocketAddress 정보가 인수로 입력된다.
*/
void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception;
/**
* 클라이언트 소켓 채널의 연결이 끊어졌을 때 발생
*/
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
/**
* 클라이언트 소켓 채널의 연결이 닫혔을 때 발생
*/
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
/**
* Called once a deregister operation is made from the current registered {@link EventLoop}.
*/
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
/**
*
*/
void read(ChannelHandlerContext ctx) throws Exception;
/**
* 소켓 채널에 데이터가 기록되었을 때 발생한다.
* wirte 이벤트에는 소켓 채널에 기록된 데이터 버퍼가 인수로 입력된다.
*/
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
/**
* 소켓 채널에 대한 flush 메소드가 호출되었을 때 발생한다.
*/
void flush(ChannelHandlerContext ctx) throws Exception;
}
하나의 특정 이벤트를 구현한 이벤트 핸들러 2개를 등록한 경우는 어떻게 동작할 것인가? 예를 들어 channelRead() 를 구현한 A, B를 모두 파이프라인에 등록하였다. 이 경우에는 앞에 등록한 A 의 channelRead()만 수행한다.
그 이유는 이벤트에 해당하는 이벤트 메소드를 수행하면서 이벤트가 사라졌기 때문이다.
그럼 두번째 이벤트 핸들러의 channelRead()를 수행하고 싶다면 아래의 코드를 수행하면 된다.
ctx.fireChannelRead(msg); 수행하여 강제적으로 channelRead 이벤트를 다시 발생시킵니다.
이벤트 루프란 이벤트를 실행하기 위한 무한루프 스레드를 지칭한다. 즉 이벤트가 발생하면 별도의 이벤트 큐에 이벤트를 던지게 되고 이벤트 루프는 현재 이벤트 큐에 들어있는 이벤트를 가져다가 수행하는 방식이다.
이 때 이벤트 루프가 지원하는 스레드 종류에 따라 단일 스레드 이벤트 루프
와 다중 스레드 이벤트 루프
로 분기된다.
이벤트 루프가 처리한 이벤트의 결과를 돌려주는 방식에 따라 콜백 패턴
과 퓨처 패턴
으로 나뉜다.
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
// 비동기 bind()를 호출한다. 포트 바인딩이 완료되기 전에 ChannelFuture 객체를 돌려준다.
ChannelFuture bindFuture = b.bind(8888);
// 주어진 ChannelFuture 객체의 작업이 완료될 때까지 블로킹한다.
// bind() 가 완료되면 sync() 도 완료된다.
bindFuture.sync();
// bindFuture 객체를 통해 채널을 얻어온다. 여기서 얻은 채널은 8888번 포트에 바인딩된 서버 채널이다.
Channel serverChannel = bindFuture.channel();
// 바인드가 완료된 서버 채널의 closeFuture 객체를 돌려준다.
// 내티 내부에서 채널이 생성될 때 closeFuture 객체도 같이 생성되므로
// closeFuture()가 돌려주는 closeFuture 객체는 항상 동일한 객체이다.
channelFuture closeFuture = serverChannel.closeFuture();
// closeFuture 객체는 채널의 연결이 종료될 때 연결 종료 이벤트를 받는다.
closeFuture.sync();
자바 네트워크 소녀 Netty
책을 보고 정리한 글입니다. (책 내용이 참으로 좋습니다.)public class BlockingServer {
public static void main(String[] args) throws Exception {
BlockingServer server = new BlockingServer();
server.run();
}
private void run() throws IOException {
ServerSocket server = new ServerSocket(8888);
System.out.println("접속 대기중");
while (true) {
Socket sock = server.accept();
System.out.println("클라이언트 연결됨");
OutputStream out = sock.getOutputStream();
InputStream in = sock.getInputStream();
while (true) {
try {
// blocking method, 운영체제의 송신버퍼에 전송할 데이터를 기록한다.
// 이때 송신버퍼의 남은 크기가 write 메소드에서 기록한 데이터의 크기보다 작다면
// 송신버퍼가 비워질 때까지 블로킹된다.
int request = in.read();
out.write(request);
}
catch (IOException e) {
break;
}
}
}
}
}
public class NonBlockingServer {
private Map<SocketChannel, List<byte[]>> keepDataTrack = new HashMap<>();
private ByteBuffer buffer = ByteBuffer.allocate(2 * 1024);
private void startEchoServer() {
try (
// Selector는 자신에게 등록된 채널에 변경 사항이 발생했는지 확인한다.
// 변경사항이 발생한 채널에 대한 접근을 가능하게 한다.
Selector selector = Selector.open();
// blocking 소켓의 ServerSocket에 대응하는 Non-Blocking 소켓클래스
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()
) {
if ((serverSocketChannel.isOpen()) && (selector.isOpen())) {
serverSocketChannel.configureBlocking(false); // default value is true. 비동기로 하려면 세팅필요
serverSocketChannel.bind(new InetSocketAddress(8888)); // port binding
// ServerSocketChannel 에 Selector 를 등록한다.
// Selector가 감지할 이벤트는 연결 요청에 해당하는 Accept() Operation 이다.
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("접속 대기중");
while (true) {
// Selector 에 등록된 채널에서 변경사항이 있는지 검사한다.
// Selector 에 아무런 I/O 이밴트도 발생하지 않으면 스레드는 이 부분에서 블로킹 된다.
// I/O 이벤트가 발생하지 않았을 때 블로킹을 피하고 싶다면 selectNow()를 사용하면 된다.
selector.select();
// Selector 에 등록돤 채널 중에서 I/O 이벤트가 발생한 채널들의 목록을 조회한다.
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
// I/O 이벤트가 발생한 채널에서 동일한 이벤트가 감지되는 것을 방지하기 위해 제거한다.
keys.remove();
if (!key.isValid()) {
continue;
}
// 연결요청
if (key.isAcceptable()) {
this.acceptOP(key, selector);
}
// 데이터 수신
else if (key.isReadable()) {
this.readOP(key);
}
// 데이터 쓰기 가능
else if (key.isWritable()) {
this.writeOP(key);
}
}
}
}
else {
System.out.println("서버 소캣을 생성하지 못했습니다.");
}
}
catch (IOException ex) {
System.err.println(ex);
}
}
private void acceptOP(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
// 클라이언트의 연결을 수락하고 연결된 소켓 채널을 가져온다.
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
System.out.println("클라이언트 연결됨 : " + socketChannel.getRemoteAddress());
keepDataTrack.put(socketChannel, new ArrayList<byte[]>());
// 클라이언트 소켓 채널을 Selector에 등록하여 I/O 이벤트를 감시한다.
socketChannel.register(selector, SelectionKey.OP_READ);
}
private void readOP(SelectionKey key) {
try {
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();
int numRead = -1;
try {
numRead = socketChannel.read(buffer);
}
catch (IOException e) {
System.err.println("데이터 읽기 에러!");
}
if (numRead == -1) {
this.keepDataTrack.remove(socketChannel);
System.out.println("클라이언트 연결 종료 : "
+ socketChannel.getRemoteAddress());
socketChannel.close();
key.cancel();
return;
}
byte[] data = new byte[numRead];
System.arraycopy(buffer.array(), 0, data, 0, numRead);
System.out.println(new String(data, "UTF-8")
+ " from " + socketChannel.getRemoteAddress());
doEchoJob(key, data);
}
catch (IOException ex) {
System.err.println(ex);
}
}
private void writeOP(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
List<byte[]> channelData = keepDataTrack.get(socketChannel);
Iterator<byte[]> its = channelData.iterator();
while (its.hasNext()) {
byte[] it = its.next();
its.remove();
socketChannel.write(ByteBuffer.wrap(it));
}
key.interestOps(SelectionKey.OP_READ);
}
private void doEchoJob(SelectionKey key, byte[] data) {
SocketChannel socketChannel = (SocketChannel) key.channel();
List<byte[]> channelData = keepDataTrack.get(socketChannel);
channelData.add(data);
key.interestOps(SelectionKey.OP_WRITE);
}
public static void main(String[] args) {
NonBlockingServer main = new NonBlockingServer();
main.startEchoServer();
}
}
소켓이란 데이터 송수신을 위한 네트워크 추상화 단위이다. 일반적으로 네트워크 프로그래밍에서 소켓은 IP주소와 포트를 가지고 있으며 양방향 네트워크 통신이 가능한 객체이다.
소켓에 데이터를 기록하거나 읽으려면 소켓에 연결된 소켓 채널(NIO) 이나 스트림(Old Blocking I.O)를 사용해야 한다. 네티가 제공하는 소켓 채널과 용어를 분리하고자 스트림으로 통칭한다. 클라이언트 어플리케이션이 소켓에 연결된 스트림에 데이터를 기록하면 소켓이 해당 데이터를 인터넷으로 연결된 서버로 전송한다.
클라이언트 | 서버 |
---|---|
- | 서버소켓생성 |
소켓생성 | 포트바인딩 |
연결요청 | 연결대기 |
- | 연결수락 |
- | 소켓생성 |
데이터 전송 | 데이터 수신 |
데이터 수신 | 데이터 전송 |
소켓 닫기 | 소켓 닫기 |
어떤 입장의 inbound, outbound인지를 명확하게 해야한다. (client 와 server는 정반대이다.)
부트스트랩은 네티로 작성한 네트워크 어플리케이션의 동작 방식과 환경을 설정하는 도우미 클래스이다. 예를 들어 네트워크에서 수신한 데이터를 단일 스레드로 데이터베이스에 저장하는 네트워크 프로그램을 작성한다고 가정