출처: http://blog.woniper.net/271

junit을 java에서 사용하기 위해서는 2가지 라이브러리가 필요하다.

junit.jar와 hamcrest-core.jar가 필요하다. 다운은 https://github.com/junit-team/junit/wiki/Download-and-Install 여기서 받을 수 있고, junit 공식 사이트는 http://junit.org/


junit 사용법을 설명하기 전에 Printer 예제를 만들어보았다. 

Printer 예제는 git소스를 참고하고 설명은 하지 않겠다. 사실 junit 사용법만 익히기 위해서 Printer 예제는 볼 필요없다. 아래 설명한 어노테이션만 잘 숙지하면 된다.

아래는 Printer를 테스트하는 테스트 코드이다.

  • @Before : @Test 메소드가 있는 클래스는 @Test 메소드가 실행 될 때마다 객체가 생성되고 실행된다. 쉽게 말해서 @Test 메소드가 2개라면 2번 실행하기 위해 객체 생성을 2번한다는 말이다. @Before 메소드는 @Test 메소드가 실행되기 전에 반드시 실행되게한다. @Test 메소드가 마다 공통으로 사용하는 데이터를 @Before 메소드에서 실행하는 것인데, 여기서 공통으로 사용하는 데이터를 fixture라고 한다.
  • @BeforeClass : @Before에서 설명 했듯이 @Test 메소드 실행 할 때마다 객체를 생성한다고 설명했는데, 이때 생성되는 객체가 최초에 한번만 실행되는 어노테이션이다.
  • @After : @Test 메소드가 실행된 후 실행된다. 
  • @AfterClass : 객체가 최초 한번 생성 된 후 실행된다.
  • @Test : @Test 메소드는 테스트를 수행하는 메소드다. 아래 예제에 expected가 붙은 @Test가 있는데 이는 예외 상황을 테스트 하기 위한 기능이다.
  • @Ignore : 테스트를 실행하지 않게 한다. @Test가 붙어 있지만, @Ignore가 붙어 있는 메소드라면 테스트 대상 메소드가 아니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package net.woniper.se.junit.test;
 
import net.woniper.se.junit.BlackPrint;
import net.woniper.se.junit.NotSupportImagePrint;
import net.woniper.se.junit.Printer;
import org.junit.*;
 
public class PrintTest {
 
    Printer printer;
 
    @Before
    public void setUp() throws Exception {
        System.out.println("setUp");
        printer = new Printer();
    }
 
    @BeforeClass
    public static void beforeClass() {
        System.out.println("beforeClass");
    }
 
    @Test
    public void testColorPrint() throws Exception {
        printer.btnPrint();
        Assert.assertEquals("COLOR", printer.getKind());
    }
 
    @Test
    public void testBlackPrint() throws Exception {
        printer.setPrint(new BlackPrint());
        Assert.assertEquals("BLACK", printer.getKind());
    }
 
    @Test
    @Ignore
    public void testIgnore() throws Exception {
        System.out.println("testIgnore");
    }
 
    @Test(expected = NotSupportImagePrint.class)
    public void testImagePrint() throws Exception {
        printer.setPrint(new BlackPrint());
        printer.imagePrint();
    }
 
    @After
    public void after() {
        System.out.println("after");
        printer.off();
    }
 
    @AfterClass
    public static void afterClass() {
        System.out.println("afterClass");
    }
}


'Programing > Java' 카테고리의 다른 글

Effective Java - 객체 비교, 복제  (0) 2016.03.04
Effective Java - 객체 소멸  (0) 2016.03.04
Effective Java - 객체 생성  (0) 2016.03.03
Java Reference와 GC  (0) 2016.02.29
Effective Java - Reference  (0) 2016.02.29
  • 1_생성자
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 객체를 생성하는 방법.
    // 1. 생성자
    //  a. 생성자 메소드의 이름은 클래스의 이름과 같다.
    //  b. 생성자의 오버로딩은 한계가 있다.
    class RandomIntGenerator {
    	final int min;
    	final int max;
    	
    	public RandomIntGenerator(int min, int max) {
    		this.min = min;
    		this.max = max;
    	}
    
    	public RandomIntGenerator(int min) {
    		this.min = min;
    		this.max = Integer.MAX_VALUE;
    	}
    	
    	// 1. 동일한 인자를 받는 다른 형태의 객체 생성 방법을 제공할 수 없다.
    	// public RandomIntGenerator(int max) {}
    }
    
    public class Example1 {
    	public static void main(String[] args) {
    		// 2. 객체를 생성하는 코드를 통해서 어떤 객체가 생성되는지를 절대 알 수 없다.
    		RandomIntGenerator obj1 = new RandomIntGenerator(0, 100);
    		RandomIntGenerator obj2 = new RandomIntGenerator(0);
    	}
    }
    

  • 2_정적 팩토리 메소드(static factory method)
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 2. 객체를 생성하는 방법 2. 
    //  : 정적 팩토리 메소드(static factory method)
    //   정의 : 객체를 생성할 때 생성자가 아닌 정적 메소드를 통해 생성하자.
    
    //  단점
    //  1. 상속을 통한 기능 변경이 불가능하다. (테스트가 힘들다)
    //  2. 이름을 잘 지어야 한다. 
    
    class RandomIntGenerator {
    	final int min;
    	final int max;
    
    	// private 이므로 외부에서 접근이 불가능하다.
    	private RandomIntGenerator(int min, int max) {
    		this.min = min;
    		this.max = max;
    	}
    
    	// 객체를 생성하는 다양한 정적 메소드를 제공하자.
    	// 장점 1. 정적 팩토리 메소드는 결국 메소드 이므로 이름에 제한이 없다.
    	public static RandomIntGenerator between(int min, int max) {
    		return new RandomIntGenerator(min, max);
    	}
    
    	public static RandomIntGenerator biggerThan(int min) {
    		return new RandomIntGenerator(min, Integer.MAX_VALUE);
    	}
    
    	public static RandomIntGenerator smallerThan(int max) {
    		return new RandomIntGenerator(Integer.MIN_VALUE, max);
    	}
    
    	// 장점 3. 생성자처럼 매번 생성할 필요가 없다.
    	// Integer.valueOf(3), Long.valueOf(3)
    	private static final RandomIntGenerator INSTANCE 
    	 = new RandomIntGenerator(Integer.MIN_VALUE, Integer.MAX_VALUE);
    	public static RandomIntGenerator getInstance() {
    		return INSTANCE;
    	}
    }
    
    public class Example2 {
    	public static void main(String[] args) {
    		// 장점 2. 라이브러리 사용자들은 객체가 어떤 정책을 가지고 생성되는지
    		// 이름을 통해 쉽게 이해할 수 있다.
    		RandomIntGenerator obj1 = RandomIntGenerator.between(0, 100);
    		RandomIntGenerator obj2 = RandomIntGenerator.smallerThan(100);
    		RandomIntGenerator obj3 = RandomIntGenerator.biggerThan(0);
    	}
    }
    

  • 3_불필요한 객체 생성
    : Junit test 결과 testLong2() 는 오토박싱을 통해 객체를 계속 생성한다. -> testLong()에 비해 현저히 느리다.

     
     package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import org.junit.Test;
    
    public class Example3 {
    	public static void main(String[] args) {
    
    		// 불필요한 객체가 생성된다.
    		String s1 = new String("hello");
    
    		String s2 = "hello";
    		// 같은 가상 머신에서 실행되는 모든 코드가 해당 객체를 재사용한다.
    		// 불변 객체 ( Immutable object ): 불변객체는 생성 후에는 변하지 않는 object 로, 언제든 재사용이 가능
    		// 불변객체의 불필요한 객체 생성을 막으려면 생성자보다는 static 팩토리 메소드를 사용하는 것이 좋습니다. ( Factory 에서 관리 )
    
    		Boolean b1 = new Boolean(true);
    		Boolean b2 = Boolean.TRUE;
    	}
    
    	@Test
    	public void testLong2() {
    		Long sum = 0L;
    		for (long i = 0; i < Integer.MAX_VALUE; ++i)
    			sum += i;
    		
    		System.out.println(sum);
    	}
    	
    	@Test
    	public void testLong() {
    		long sum = 0L;
    		for (long i = 0; i < Integer.MAX_VALUE; ++i)
    			sum += i;
    		
    		System.out.println(sum);
    	}
    }
    

  • 4_싱글톤(Singleton)

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 싱글톤(Singleton)
    // 개념 : 하나의 인스턴스만 생성되는 클래스
    
    // 장점 : 어디에서든 동일한 방법으로 동일한 객체를 얻을 수 있다.
    // 단점 : 테스트 용이성 낮다.
    //       객체와 객체간의 결합도가 높아진다.
    
    // 자바에서 가장 많이 사용하는 싱글톤의 형태
    // -> 자바 5 이상에서는 스레드 안전성도 보장된다.
    class Cursor {
    	// 방법 1. private 생성자
    	private Cursor() {
    	}
    
    	// 방법 2.
    	// public static final Cursor INSTANCE1 = new Cursor();
    
    	// private static final Cursor INSTANCE = new Cursor();
    	private static final ThreadLocal<Cursor> INSTANCE
    		= new ThreadLocal<Cursor>() {
    		@Override
    		protected Cursor initialValue() {
    			return new Cursor();
    		}
    	};
    
    	public static Cursor getInstance() {
    		return INSTANCE.get();
    	}
    }
    
    public class Example4 {
    	public static void main(String[] args) {
    		System.out.println(Cursor.getInstance());
    		new Thread(new Runnable() {			
    			@Override
    			public void run() {
    				System.out.println(Cursor.getInstance());
    			}
    		}).start();
    	
    	}
    }
    

  • 5_enum을 이용한 싱글톤
    Serialize()는 객체를 쓰고, 읽어도 하나의 객체로 유지된다.
    : Refelction()는 객체가 생성되지 않는다.
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    import java.lang.reflect.Constructor;
    
    import org.junit.Test;
    
    // 아래의 싱글톤은 두 가지 경우에 객체가 두개이상 생성될 수 있습니다.
    // 1. Reflection
    // 2. 직렬화
    //class Cursor implements Serializable {
    //	private Cursor() {
    //	}
    //	
    //	private Object readResolve() {
    //		return INSTANCE;
    //	}
    //	
    //	private static final Cursor INSTANCE = new Cursor();
    //
    //	public static Cursor getInstance() {
    //		return INSTANCE;
    //	}
    //}
    
    // 위의 문제점을 해결하기 위해 새롭게 제안된 싱글톤
    //  원소가 하나뿐인 enum 을 이용하자.
    // 리플렉션을 통해 생성하는 것도 하는 것 불가능하고
    // 직렬화에 의한 객체 생성도 자동적으로 처리해준다.
    enum Cursor {
    	INSTANCE;
    	
    	public static Cursor getInstance() {
    		return INSTANCE;
    	}
    }
    
    public class Example5 {
    	@Test
    	public void Serialize() throws Exception {
    		String filename = "cursor.dat";
    		
    		FileOutputStream fos = new FileOutputStream(filename);
    		ObjectOutputStream oos = new ObjectOutputStream(fos);
    		oos.writeObject(Cursor.getInstance());
    		
    		fos.close();
    		oos.close();
    		
    		FileInputStream fis = new FileInputStream(filename);
    		ObjectInputStream ois = new ObjectInputStream(fis);
    		
    		Cursor c = (Cursor) ois.readObject();
    		
    		System.out.println(c);
    		System.out.println(Cursor.getInstance());
    	}
    	
    	
    	@Test
    	public void Refelction() throws Exception {
    		Constructor<?> con = Cursor.class.getDeclaredConstructors()[0];
    		con.setAccessible(true);
    
    		Cursor c = (Cursor) con.newInstance();
    
    		System.out.println(c);
    		System.out.println(Cursor.getInstance());
    	}
    
    }
    

  • 6_지연된 초기화

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 자바의 싱글톤은 객체가 클래스 로더에 의해 로딩될 때 생성된다.
    // : 객체의 생성 비용이 크거나, 객체를 사용하지 않을 경우 리소스 낭비가 심하다.
    //  (싱글톤은 가비지 컬렉션의 대상이 아니다.)
    
    // 지연된 초기화의 필요성
    // 1. 메모리 낭비 제거
    // 2. 애플리케이션의 로딩 속도 개선
    
    class Cursor {
    	public static final Cursor INSTANCE = new Cursor();
    	private Cursor() {
    		System.out.println("Cursor created!");
    	}
    	
    	public static void foo() {
    		System.out.println("foo");
    	}
    }
    
    public class Example6 {
    	public static void main(String[] args) {
    		Cursor.foo();
    	}
    }
    

  • 7_지연된 초기화2
    : DCLP(Double Checked Locking Pattern)
    IODH(Initialization On Demand Holder) - 클래스가 최초로 초기화 되는 시점

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    class Cursor {
    	// private static Cursor instance = null;
    
    	private Cursor() {
    		System.out.println("Cursor");
    	};
    
    	// public static Cursor getInstance() {
    	// if (instance == null)
    	// instance = new Cursor();
    	//
    	// return instance;
    	// }
    
    	// 위의 지연된 초기화는 스레드 안전성이 없습니다.
    	// 해결해야 합니다.
    	// 1. 엄격한 동기화
    	// public synchronized static Cursor getInstance() {
    	// if (instance == null)
    	// instance = new Cursor();
    	//
    	// return instance;
    	// }
    
    	// 2. 위의 코드는 계속 동기화를 수행하기 때문에 느립니다.
    	// 생성 시점에만 동기화를 적용하고, 생성 이후에는 동기화를 제거하는 방법.
    	// DCLP(Double Checked Locking Pattern)
    //	public static Cursor getInstance() {
    //		if (instance == null) {
    //			synchronized (Cursor.class) {
    //				if (instance == null)
    //					instance = new Cursor();
    //			}
    //		}
    //		return instance;
    //	}
    
    	// 3. IODH(Initialization On Demand Holder)
    	// 클래스가 최초로 초기화 되는 시점
    	// 1) 클래스 T의 인스턴스가 생성될 때
    	// 2) 클래스 T의 정적 메소드가 호출되었을 때
    	// 3) 클래스 T의 정적 필드에 값이 할당 되었을 때     *
    	// 4) 클래스 T의 정적 필드가 상수 필드가 아니고 사용되었을 때  *
    	private static class Singleton {
    		private static final Cursor INSTANCE = new Cursor();
    	}
    	
    	public static Cursor getInstance() {
    		return Singleton.INSTANCE;
    	}
    		
    	public static void foo() {
    		System.out.println("foo");
    	}
    }
    
    public class Example7 {
    	public static void main(String[] args) {
    		Cursor.foo();
    		Cursor.getInstance();
    	}
    }
    

  • 8_정적필드 초기화

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.util.Calendar;
    
    // 정적 필드는 위에서부터 아래로 차근차근 초기화 합니다.
    // 사용하기 전에 초기화 할 수 있도록 앞에 배치해야 합니다.
    class Person {
    	public static final Person INSTANCE = new Person();
    	private final int weight;
    
    	public static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR);
    	
    	private Person() {
    		weight = CURRENT_YEAR - 1984;
    	}
    
    	public int weight() {
    		return weight;
    	}
    }
    
    public class Example8 {
    	public static void main(String[] args) {
    		System.out.println(Person.INSTANCE.weight());
    		System.out.println(Person.INSTANCE.CURRENT_YEAR);
    	}
    }
    

  • 9_정적필드 초기화2
    : isTeenager()는 메소드 호출 당 Calendar, TimeZone, Date 2개의 객체가 생성된다.

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimeZone;
    
    import org.junit.Test;
    
    class Person {
    	private final Date birthDate;
    
    	public Person(Date b) {
    		this.birthDate = b;
    	}
    
    	// 아래 메소드가 호출될 때마다 생성되는 객체는 다음과 같다.
    	// 1. Calendar
    	// 2. TimeZone
    	// 3. Date 2개
    	public boolean isTeenager() {
    		Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    		gmtCal.set(1996, Calendar.JANUARY, 1, 0, 0, 0);
    		Date start = gmtCal.getTime();
    
    		gmtCal.set(2005, Calendar.JANUARY, 1, 0, 0, 0);
    		Date end = gmtCal.getTime();
    
    		return birthDate.compareTo(start) >= 0 && birthDate.compareTo(end) < 0;
    	}
    
    	private static final Date START;
    	private static final Date END;
    
    	static {
    		Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    		gmtCal.set(1996, Calendar.JANUARY, 1, 0, 0, 0);
    		START = gmtCal.getTime();
    
    		gmtCal.set(2005, Calendar.JANUARY, 1, 0, 0, 0);
    		END = gmtCal.getTime();
    	}
    
    	public boolean isTeenager2() {
    		return birthDate.compareTo(START) >= 0 && birthDate.compareTo(END) < 0;
    	}
    }
    
    public class Example9 {
    	@Test
    	public void test_isTeen() {
    		Calendar c = Calendar.getInstance();
    		c.set(1995, Calendar.MAY, 5);
    
    		Person p = new Person(c.getTime());
    
    		for (int i = 0; i < 100000000; ++i) {
    			p.isTeenager();
    		}
    	}
    
    	@Test
    	public void test_isTeen2() {
    		Calendar c = Calendar.getInstance();
    		c.set(1995, Calendar.MAY, 5);
    
    		Person p = new Person(c.getTime());
    
    		for (int i = 0; i < 100000000; ++i) {
    			p.isTeenager2();
    		}
    	}
    }
    

  • 10_StringBuilder
    + 연산자에 대한 오버로딩은 문자열만 제공. 
    : 매번 String 인스턴스를 생성하는 방식이라 성능 이슈 발생. 이를 개선하기 위해 JDK 1.5 버전 이후에는 컴파일 단계에서 StringBuilder로 컴파일 되도록 변경되었다. 그래서 JDK 1.5 이후부터는 +를 활용해도 성능상에 큰 이슈는 없다.

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // + 연산자에 대한 오버로딩은 문자열만 제공하고 있다.
    // : '+' 연산자는 피연산자로 문자열이 있을 때만 문자열 연결 연산을 수행합니다.
    //      문자열이 없으면 그냥 덧셈을 수행합니다.
    public class Example10 {
    	public static void main(String[] args) {
    		System.out.print("H" + "A");
    		// System.out.print('H' + 'A');
    		
    		// 문자를 연결하는 방법 1.
    		StringBuilder sb = new StringBuilder();
    		sb.append('H').append('A');
    		
    		System.out.print(sb);
    		
    		// 방법 2.
    		System.out.println("" + 'H' + 'A');
    		
    		// 방법 3.
    		System.out.printf("%c%c", 'H', 'A');
    	}
    }
    

  • 11_부호확장, 제로 확장
    : 부호확장은 상위 비트를 부호에 따라 0이나 으로 채우는 것
    : 제로확장은 상위 비트를 0으로 채우는 것  


     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // int, long, byte : signed
    // char : unsigned
    
    // 변환되는 자료형에 상관없이 원래 부호가 있는 타입이면 부호 확장이 일어나고,
    // char 자료형이면 0의 확장이 일어난다.
    
    public class Example11 {
    	public static void main(String[] args) {
    		System.out.println((int)(char)(byte)-1);
    		// int(4byte) -> byte(1byte) -> char(2byte) -> int(4byte)
    		
    		// -1 -> (byte) -1
    		// ff ff ff ff -> ff
    		
    		// -> (char)(byte) -1
    		// 부호 확장
    		// ff -> ff ff 
    		
    		// -> (int)(char)(byte) -1
    		// 제로 확장
    		// ff ff -> 00 00 ff ff
    	}
    }
    


'Programing > Java' 카테고리의 다른 글

Effective Java - 객체 소멸  (0) 2016.03.04
Junit 사용하기  (0) 2016.03.03
Java Reference와 GC  (0) 2016.02.29
Effective Java - Reference  (0) 2016.02.29
Garbage Collection  (0) 2015.11.15

출처 : http://d2.naver.com/helloworld/329631

Java의 가비지 컬렉터(Garbage Collector)는 그 동작 방식에 따라 매우 다양한 종류가 있지만 공통적으로 크게 다음 2가지 작업을 수행한다고 볼 수 있습니다.

  1. 힙(heap) 내의 객체 중에서 가비지(garbage)를 찾아낸다.
  2. 찾아낸 가비지를 처리해서 힙의 메모리를 회수한다.

최초의 Java에서는 이들 가비지 컬렉션(Garbage Collection, 이하 GC) 작업에 애플리케이션의 사용자 코드가 관여하지 않도록 구현되어 있었습니다. 그러나 위 2가지 작업에서 좀 더 다양한 방법으로 객체를 처리하려는 요구가 있었습니다. 이에 따라 JDK 1.2부터는 java.lang.ref 패키지를 추가해 제한적이나마 사용자 코드와 GC가 상호작용할 수 있게 하고 있습니다.

java.lang.ref 패키지는 전형적인 객체 참조인 strong reference 외에도 soft, weak, phantom 3가지의 새로운 참조 방식을 각각의 Reference 클래스로 제공합니다. 이 3가지 Reference 클래스를 애플리케이션에 사용하면 앞서 설명하였듯이 GC에 일정 부분 관여할 수 있고, LRU(Least Recently Used) 캐시 같이 특별한 작업을 하는 애플리케이션을 더 쉽게 작성할 수 있습니다. 이를 위해서는 GC에 대해서도 잘 이해해야 할 뿐 아니라, 이들 참조 방식의 동작도 잘 이해할 필요가 있습니다.

GC와 Reachability

Java GC는 객체가 가비지인지 판별하기 위해서 reachability라는 개념을 사용한다. 어떤 객체에 유효한 참조가 있으면 'reachable'로, 없으면 'unreachable'로 구별하고, unreachable 객체를 가비지로 간주해 GC를 수행한다. 한 객체는 여러 다른 객체를 참조하고, 참조된 다른 객체들도 마찬가지로 또 다른 객체들을 참조할 수 있으므로 객체들은 참조 사슬을 이룬다. 이런 상황에서 유효한 참조 여부를 파악하려면 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 root set이라고 한다.

JVM에서 메모리 영역인 런타임 데이터 영역(runtime data area)의 구조를 그림으로 그리면 다음과 같다.

javareference1

그림 1 런타임 데이터 영역(Oracle HotSpot VM 기준)

런타임 데이터 영역은 위와 같이 스레드가 차지하는 영역들과, 객체를 생성 및 보관하는 하나의 큰 힙, 클래스 정보가 차지하는 영역인 메서드 영역, 크게 세 부분으로 나눌 수 있다. 위 그림에서 객체에 대한 참조는 화살표로 표시되어 있다.

힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나이다.

  • 힙 내의 다른 객체에 의한 참조
  • Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
  • 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
  • 메서드 영역의 정적 변수에 의한 참조

이들 중 힙 내의 다른 객체에 의한 참조를 제외한 나머지 3개가 root set으로, reachability를 판가름하는 기준이 된다.

reachability를 더 자세히 설명하기 위해 root set과 힙 내의 객체를 중심으로 다시 그리면 다음과 같다.

javareference2

그림 2 Reachable 객체와 Unreachable 객체

위 그림에서 보듯, root set으로부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고, 이 참조 사슬과 무관한 객체들이 unreachable 객체로 GC 대상이다. 오른쪽 아래 객체처럼 reachable 객체를 참조하더라도, 다른 reachable 객체가 이 객체를 참조하지 않는다면 이 객체는 unreachable 객체이다.

이 그림에서 참조는 모두 java.lang.ref 패키지를 사용하지 않은 일반적인 참조이며, 이를 흔히 strong reference라 부른다.

Soft, Weak, Phantom Reference

java.lang.ref는 soft reference와 weak reference, phantom reference를 클래스 형태로 제공한다. 예를 들면, java.lang.ref.WeakReference 클래스는 참조 대상인 객체를 캡슐화(encapsulate)한 WeakReference 객체를 생성한다. 이렇게 생성된 WeakReference 객체는 다른 객체와 달리 Java GC가 특별하게 취급한다(이에 대한 내용은 뒤에서 다룬다). 캡슐화된 내부 객체는 weak reference에 의해 참조된다.

다음은 WeakReference 클래스가 객체를 생성하는 예이다.

WeakReference<Sample> wr = new WeakReference<Sample>( new Sample());  
Sample ex = wr.get();  
...
ex = null;  

위 코드의 첫 번째 줄에서 생성한 WeakReference 클래스의 객체는 new() 메서드로 생성된 Sample 객체를 캡슐화한 객체이다. 참조된 Sample 객체는 두 번째 줄에서 get() 메서드를 통해 다른 참조에 대입된다. 이 시점에서는 WeakReference 객체 내의 참조와 ex 참조, 두 개의 참조가 처음 생성한 Sample 객체를 가리킨다.

javareference3

그림 3 Weak Reference 예 1

위 코드의 마지막 줄에서 ex 참조에 null을 대입하면 처음 생성한 Sample 객체는 오직 WeakReference 내부에서만 참조된다. 이 상태의 객체를 weakly reachable 객체라고 하는데, 이에 대한 자세한 내용은 뒤에서 다룬다.

javareference4

그림 4 Weak Reference 예 2

Java 스펙에서는 SoftReference, WeakReference, PhantomReference 3가지 클래스에 의해 생성된 객체를 "reference object"라고 부른다. 이는 흔히 strong reference로 표현되는 일반적인 참조나 다른 클래스의 객체와는 달리 3가지 Reference 클래스의 객체에 대해서만 사용하는 용어이다. 또한 이들 reference object에 의해 참조된 객체는 "referent"라고 부른다. Java 스펙 문서를 참조할 때 이들 용어를 명확히 알면 좀 더 이해하기 쉽다. 위의 소스 코드에서 new WeakReference() 생성자로 생성된 객체는 reference object이고, new Sample() 생성자로 생성된 객체는 referent이다.

Reference와 Reachability

앞에서 설명한 것처럼, 원래 GC 대상 여부는 reachable인가 unreachable인가로만 구분하였고 이를 사용자 코드에서는 관여할 수 없었다. 그러나 java.lang.ref 패키지를 이용하여 reachable 객체들을 strongly reachable, softly reachable, weakly reachable, phantomly reachable로 더 자세히 구별하여 GC 때의 동작을 다르게 지정할 수 있게 되었다. 다시 말해, GC 대상 여부를 판별하는 부분에 사용자 코드가 개입할 수 있게 되었다.

두 번째 그림에서 몇몇 객체들을 WeakReference로 바꾸어서 예를 들어보면 다음과 같다.

javareference5

그림 5 Reachable, Unreachable, Weakly Reachable 예제

녹색으로 표시한 중간의 두 객체는 WeakReference로만 참조된 weakly reachable 객체이고, 파란색 객체는 strongly reachable 객체이다. GC가 동작할 때, unreachable 객체뿐만 아니라 weakly reachable 객체도 가비지 객체로 간주되어 메모리에서 회수된다. root set으로부터 시작된 참조 사슬에 포함되어 있음에도 불구하고 GC가 동작할 때 회수되므로, 참조는 가능하지만 반드시 항상 유효할 필요는 없는 LRU 캐시와 같은 임시 객체들을 저장하는 구조를 쉽게 만들 수 있다.

위 그림에서 WeakReference 객체 자체는 weakly reachable 객체가 아니라 strongly reachable 객체이다. 또한, 그림에서 A로 표시한 객체와 같이 WeakReference에 의해 참조되고 있으면서 동시에 root set에서 시작한 참조 사슬에 포함되어 있는 경우에는 weakly reachable 객체가 아니라 strongly reachable 객체이다.

GC가 동작하여 어떤 객체를 weakly reachable 객체로 판명하면, GC는 WeakReference 객체에 있는 weakly reachable 객체에 대한 참조를 null로 설정한다. 이에 따라 weakly reachable 객체는 unreachable 객체와 마찬가지 상태가 되고, 가비지로 판명된 다른 객체들과 함께 메모리 회수 대상이 된다.

Strengths of Reachability

앞에서 설명한 것처럼 reachability는 총 5종류가 있고 이는 GC가 객체를 처리하는 기준이 된다. Java 스펙에서는 이들 5종류의 reachability를 "Strengths of Reachability"라 부른다. 앞의 예제 그림에서는 weakly reachable만 예를 들었기 때문에 WeakReference만 표시하였으나, SoftReference, PhantomReference 등을 이용하여 여러 가지 방식으로 reachability를 지정할 수 있고 이에 따라 각 객체들의 GC 여부는 다양하게 달라지게 된다. 하나의 객체에 대한 참조의 개수나 참조 형태에는 아무런 제한이 없으므로, 하나의 객체는 여러 strong reference, soft reference, weak reference, phantom reference의 다양한 조합으로 참조될 수 있다.

Java GC는 root set으로부터 시작해서 객체에 대한 모든 경로를 탐색하고 그 경로에 있는 reference object들을 조사하여 그 객체에 대한 reachability를 결정한다. 다양한 참조 관계의 결과, 하나의 객체는 다음 5가지 reachability 중 하나가 될 수 있다.

  • strongly reachable: root set으로부터 시작해서 어떤 reference object도 중간에 끼지 않은 상태로 참조 가능한 객체, 다시 말해, 객체까지 도달하는 여러 참조 사슬 중 reference object가 없는 사슬이 하나라도 있는 객체
  • softly reachable: strongly reachable 객체가 아닌 객체 중에서 weak reference, phantom reference 없이 soft reference만 통과하는 참조 사슬이 하나라도 있는 객체
  • weakly reachable: strongly reachable 객체도 softly reachable 객체도 아닌 객체 중에서, phantom reference 없이 weak reference만 통과하는 참조 사슬이 하나라도 있는 객체
  • phantomly reachable: strongly reachable 객체, softly reachable 객체, weakly reachable 객체 모두 해당되지 않는 객체. 이 객체는 파이널라이즈(finalize)되었지만 아직 메모리가 회수되지 않은 상태이다.
  • unreachable: root set으로부터 시작되는 참조 사슬로 참조되지 않는 객체

다음 예의 경우 객체 B의 reachability는 softly reachable이다.

javareference6

그림 6 Softly Reachable

root set으로부터 바로 SoftReference를 통해서 B를 참조할 수 있기 때문이다. 만약 root set의 SoftReference에 대한 참조가 없다면(즉, 왼쪽 아래 화살표를 삭제한다면), 객체 B는 phantomly reachable이 된다.

Softly Reachable과 SoftReference

softly reachable 객체, 즉 strong reachable이 아니면서 오직 SoftReferencce 객체로만 참조된 객체는 힙에 남아 있는 메모리의 크기와 해당 객체의 사용 빈도에 따라 GC 여부가 결정된다. 그래서 softly reachable 객체는 weakly reachable 객체와는 달리 GC가 동작할 때마다 회수되지 않으며 자주 사용될수록 더 오래 살아남게 된다. Oracle HotSpot VM에서는 softly reachable 객체의 GC를 조절하기 위해 다음 JVM 옵션을 제공한다.

-XX:SoftRefLRUPolicyMSPerMB=<N>

이 옵션의 기본값은 1000이다.

softly reachable 객체의 GC 여부는 위 옵션의 에 설정한 숫자에 따라 다음 수식에 의해 결정된다.

(마지막 strong reference가 GC된 때로부터 지금까지의 시간) > (옵션 설정값 N) * (힙에 남아있는 메모리 크기)

어떤 객체가 사용된다는 것은 strong reference에 의해 참조되는 것이므로 위 수식의 좌변은 해당 객체가 얼마나 자주 사용되는지를 의미한다. 옵션 설정값이 1000이고 남아 있는 메모리가 100MB이면, 수식의 우변은 1,000ms/MB * 100MB = 100,000ms = 100sec, 즉 100초가 된다(옵션 이름 마지막이 MSPerMB로 끝나므로 옵션 설정값의 단위는 ms/MB임을 알 수 있다). 따라서 softly reachable 객체가 100초 이상 사용되지 않으면 GC에 의해 회수 대상이 된다. 힙에 남아있는 메모리가 작을수록 우변의 값이 작아지므로, 힙이 거의 소진되면 대부분의 softly reachable 객체는 모두 메모리에서 회수되어 OutOfMemoryError를 막게 될 것이다.

softly reachable 객체를 GC하기로 결정되면 앞서 설명한 WeakReference 경우와 마찬가지로 참조 사슬에 존재하는 SoftReference 객체 내의 softly reachable 객체에 대한 참조가 null로 설정되며, 이후 이 softly reachable객체는 unreachable 객체와 마찬가지가 되어 GC의해 메모리가 회수된다.

Weakly Reachable과 WeakReference

weakly reachable 객체는 특별한 정책에 의해 GC 여부가 결정되는 softly reachable 객체와는 달리 GC를 수행할 때마다 회수 대상이 된다. 앞서 설명한 것처럼 WeakReference 내의 참조가 null로 설정되고 weakly reachable 객체는 unreachable 객체와 마찬가지 상태가 되어 GC에 의해 메모리가 회수된다. 그러나 GC가 실제로 언제 객체를 회수할지는 GC 알고리즘에 따라 모두 다르므로, GC가 수행될 때마다 반드시 메모리까지 회수된다고 보장하지는 않는다. 이는 softly reachable 객체는 물론 unreachable 객체도 마찬가지이다. GC가 GC 대상인 객체를 찾는 작업과 GC 대상인 객체를 처리하여 메모리를 회수하는 작업은 즉각적인 연속 작업이 아니며, GC 대상 객체의 메모리를 한 번에 모두 회수하지도 않는다.

LRU 캐시와 같은 애플리케이션에서는 softly reachable 객체보다는 weakly reachable 객체가 유리하므로 LRU 캐시를 구현할 때에는 대체로 WeakReference를 사용한다. softly reachable 객체는 힙에 남아 있는 메모리가 많을수록 회수 가능성이 낮기 때문에, 다른 비즈니스 로직 객체들을 위해 어느 정도 비워두어야 할 힙 공간이 softly reachable 객체에 의해 일정 부분 점유된다. 따라서 전체 메모리 사용량이 높아지고 GC가 더 자주 일어나며 GC에 걸리는 시간도 상대적으로 길어지는 문제가 있다.

ReferenceQueue

phantomly reachable 객체의 동작과 PhantomReference를 설명하기 전에 java.lang.ref 패키지에서 제공하는 ReferenceQueue 클래스에 대해 설명할 필요가 있다.

SoftReference 객체나 WeakReference 객체가 참조하는 객체가 GC 대상이 되면 SoftReference 객체, WeakReference 객체 내의 참조는 null로 설정되고 SoftReference 객체, WeakReference 객체 자체는 ReferenceQueue에 enqueue된다. ReferenceQueue에 enqueue하는 작업은 GC에 의해 자동으로 수행된다. ReferenceQueue의 poll() 메서드나 remove() 메서드를 이용해 ReferenceQueue에 이들 reference object가 enqueue되었는지 확인하면 softly reachable 객체나 weakly reachable 객체가 GC되었는지를 파악할 수 있고, 이에 따라 관련된 리소스나 객체에 대한 후처리 작업을 할 수 있다. 어떤 객체가 더 이상 필요 없게 되었을 때 관련된 후처리를 해야 하는 애플리케이션에서 이 ReferenceQueue를 유용하게 사용할 수 있다. Java Collections 클래스 중에서 간단한 캐시를 구현하는 용도로 자주 사용되는 WeakHashMap 클래스는 이 ReferenceQueue와 WeakReference를 사용하여 구현되어 있다.

SoftReference와 WeakReference는 ReferenceQueue를 사용할 수도 있고 사용하지 않을 수도 있다. 이는 이들 클래스의 생성자 중에서 ReferenceQueue를 인자로 받는 생성자를 사용하느냐 아니냐로 결정한다. 그러나 PhantomReference는 반드시 ReferenceQueue를 사용해야만 한다. PhantomReference의 생성자는 단 하나이며 항상 ReferenceQueue를 인자로 받는다.

ReferenceQueue<Object> rq = new ReferenceQueue<Object>(); PhantomReference<Object> pr = new PhantomReference<Object>(referent, rq);  

SoftReference, WeakReference는 객체 내부의 참조가 null로 설정된 이후에 ReferenceQueue에 enqueue되지만, PhantomReference는 객체 내부의 참조를 null로 설정하지 않고 참조된 객체를 phantomly reachable 객체로 만든 이후에 ReferenceQueue에 enqueue된다. 이를 통해 애플리케이션은 객체의 파이널라이즈 이후에 필요한 작업들을 처리할 수 있게 된다. 더 자세한 내용은 다음 절에서 설명한다.

Phantomly Reachable과 PhantomReference

softly reachable과 weakly reachable, phantomly reachable은 많이 다르다. 이를 설명하기 위해서는 먼저 GC 동작을 설명해야 한다. GC 대상 객체를 찾는 작업과 GC 대상 객체를 처리하는 작업이 연속적이지 않 듯이, GC 대상 객체를 처리하는 작업과 할당된 메모리를 회수하는 작업도 연속된 작업이 아니다. GC 대상 객체를 처리하는 작업, 즉 객체의 파이널라이즈 작업이 이루어진 후에 GC 알고리즘에 따라 할당된 메모리를 회수한다.

GC 대상 여부를 결정하는 부분에 관여하는 softly reachable, weakly reachable과는 달리, phantomly reachable은 파이널라이즈와 메모리 회수 사이에 관여한다. strongly reachable, softly reachable, weakly reachable에 해당하지 않고 PhantomReference로만 참조되는 객체는 먼저 파이널라이즈된 이후에 phantomly reachable로 간주된다. 다시 말해, 객체에 대한 참조가 PhantomReference만 남게 되면 해당 객체는 바로 파이널라이즈된다. GC가 객체를 처리하는 순서는 항상 다음과 같다.

  1. soft references
  2. weak references
  3. 파이널라이즈
  4. phantom references
  5. 메모리 회수

즉, 어떤 객체에 대해 GC 여부를 판별하는 작업은 이 객체의 reachability를 strongly, softly, weakly 순서로 먼저 판별하고, 모두 아니면 phantomly reachable 여부를 판별하기 전에 파이널라이즈를 진행한다. 그리고 대상 객체를 참조하는 PhantomReference가 있다면 phantomly reachable로 간주하여 PhantomReference를 ReferenceQueue에 넣고 파이널라이즈 이후 작업을 애플리케이션이 수행하게 하고 메모리 회수는 지연시킨다.

앞서 설명한 것처럼 PhatomReference는 항상 ReferenceQueue를 필요로 한다. 그리고 PhantomReference의 get() 메서드는 SoftReference, WeakReference와 달리 항상 null을 반환한다. 따라서 한 번 phantomly reachable로 판명된 객체는 더 이상 사용될 수 없게 된다. 그리고 phantomly reachable로 판명된 객체에 대한 참조를 GC가 자동으로 null로 설정하지 않으므로, 후처리 작업 후에 사용자 코드에서 명시적으로 clear() 메서드를 실행하여 null로 설정해야 메모리 회수가 진행된다.

이와 같이, PhantomReference를 사용하면 어떤 객체가 파이널라이즈된 이후에 할당된 메모리가 회수되는 시점에 사용자 코드가 관여할 수 있게 된다. 파이널라이즈 이후에 처리해야 하는 리소스 정리 등의 작업이 있다면 유용하게 사용할 수 있다. 그러나 개인적으로는 PhantomReference를 사용하는 코드를 거의 본 적이 없으며, 그 효용성에 대해서는 의문이 있다.

마치며

Java의 Reference는 그 선후 관계와 용어가 복잡해서 글로 쉽게 풀어쓰기가 어려워 본문이 꽤 장황해졌다. 본문의 내용을 간단히 요약하면 다음과 같다.

  • Java GC는 GC 대상 객체를 찾고, 대상 객체를 처리(finalization)하고, 할당된 메모리를 회수하는 작업으로 구성된다.
  • 애플리케이션은 사용자 코드에서 객체의 reachability를 조절하여 Java GC에 일부 관여할 수 있다.
  • 객체의 reachability를 조절하기 위해서 java.lang.ref 패키지의 SoftReference, WeakReference, PhantomReference, ReferenceQueue 등을 사용한다.

개인적으로는 내부 캐시 등을 구현하고자 하는 대부분의 애플리케이션에서는 WeakReference 혹은 이를 이용한 WeakHashMap만으로도 충분하다고 생각한다. 다른 애플리케이션에서는 가끔 SoftReference를 사용하는 경우도 있지만, PhantomReference는 거의 예제가 없으며 그만큼 불필요할 것이다. 이들 Java Reference들과 관련된 GC 동작을 잘 이해하면 Java의 heap 메모리 문제에서 더욱 유연한 애플리케이션 작성에 크게 도움이 될 것이다.

'Programing > Java' 카테고리의 다른 글

Junit 사용하기  (0) 2016.03.03
Effective Java - 객체 생성  (0) 2016.03.03
Effective Java - Reference  (0) 2016.02.29
Garbage Collection  (0) 2015.11.15
Wrapper클래스,박싱(boxing),언박싱(unboxing)  (0) 2015.11.15
  • JAVA의 객체가 garbage collection 대상이 되는 순간은?
    1. reference가 영원히 영역을 벗어남 (ex. 지역 변수로 선언한 객체의 메소드의 영역을 벗어남)
    2. reference에 다른 객체를 대입
    3. reference를 직접 'null'로 설정
    but, 위 방법 외에 참조를 약하게 만들어 GC 대상이 되도록 만들 수 있다.

Strong Reference > Soft Reference > Weak Reference > Phantom Reference

  • Strong Reference
    : 일반적으로 new를 통해서 객체를 생성하게 되면 생기게 되는 참조.
    : 참조에 대해서 해지하지 않는다면 그 메모리는 절대 수거되지 않는다.

  • Soft Reference
    : GC에 의해 수거될 수도 있고, 수거되지 않을 수도 있다.
    : 메모리 상태에 따라 결정

  • Weak Reference
    : GC가 발생하기 전까지는 참조를 유지한다.
    : GC가 발생하는 순간 무조건 수거된다.
    : 객체 캐시에 유용 (안드로이드에서 그림이 포함된 listView 등)

  • Phantom Reference
    : 가장 약한 참조
    : GC가 발생 전 메모리에서 정리된다. (finalize() 호출 후)
    : 내부적으로 유지하고 있지만, 객체를 다시 꺼내오면 null 이다 

    - ReferenceTest.java
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.lang.ref.SoftReference;
    import java.lang.ref.WeakReference;
    import java.util.LinkedList;
    import java.util.List;
    
    // 강한 참조(Strong Reference)
    // 일반적으로 new를 통해서 객체를 생성하게 되면 생기게 되는 참조.
    
    // 강한 참조를 통해 참조되고 있는 객체는 절대 가비지 컬렉션의 대상에서
    // 제외된다.
    // 자바에서 아무리 자동적으로 메모리가 수거된다고 하지만, 
    // 참조에 대해서 해지하지 않는 다면 그 메모리는 절대 수거되지 않는다.
    //  (참조 = null , 참조 = 다른객체)
    
    // OutOfMemory를 방지하기 위해서는 약한 형태의 참조를 사용해야 한다.
    //  SoftReference<>, WeakReference<>
    
    // Soft Reference
    // 생성 방법 : SoftReference<Data> r
                // = new SoftReference<Data>(new Data);
    // 동작 : 강한 참조와 다르게 GC에 의해 수거될 수도 있고, 수거되지 않을 수도 있다.
    //  메모리에 충분한 여유가 있다면 GC가 수행되고 있다고 하더라도 수거되지 않는다.
    //  하지만 out of memory의 시점이 가깝다면, 수거될 확률이 높다.
    
    // Weak Reference
    //생성 방법 : WeakReference<Data> r
    // = new WeakReference<Data>(new Data);
    
    // WeakReference에 의해 참조된 객체는 가비지 컬렉션이 발생하기 전까지는 참조를 유지
    // 하지만 GC가 발생하는 순간 무조건 수거된다.
    // WeakReference가 사라지는 시점이 GC의 실행 주기와 일치한다.
    // 이를 이용하면 짧은 주기에 자주 사용되는 객체를 캐시할 때 유용하다.
    // => WeakHashMap
    // WeakHashMap<K,V>
    
    // PhantomReference
    
    class BigData {
    	private int[] array = new int[5000]; // 20000byte, 20K
    }
    
    public class ReferenceTest {
    
    	private List<WeakReference<BigData>> refs = new LinkedList<>();
    
    	public void referenceTest() {
    		try {
    			for (int i = 0; true; i++) {
    				refs.add(new WeakReference<BigData>(new BigData()));
    			}
    		} catch (OutOfMemoryError ofm) { // Strong일 경우 out of memory 발생
    			System.out.println("out of memory!");
    		}
    	}
    
    	public static void main(String[] args) {
    		System.out.println("run");
    		// Thread.sleep(10 * 1000);
    		ReferenceTest test = new ReferenceTest();
    		test.referenceTest();
    		System.out.println("finish");
    	}
    	
    }
    

    - WeakSingleton.java
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    import java.lang.ref.WeakReference;
    
    public class WeakSingleton {
    	private static WeakReference<WeakSingleton> instance
    	 = new WeakReference<WeakSingleton>(null);
    
    	public static WeakSingleton getInstance()
    	{
    		WeakSingleton m = instance.get();
    		if (m != null)
    			return m;
    		
    		synchronized (WeakSingleton.class) {	
    			
    			System.out.println("Create new instance");
    			
    			m = new WeakSingleton();
    			instance = new WeakReference<WeakSingleton>(m);		
    		}
    		
    		return m;
    	}
    	
    	public static void main(String[] args) {
    		WeakSingleton w = WeakSingleton.getInstance();
    		
    		// System.gc();
    		
    		w = WeakSingleton.getInstance();
    	}
    }
    


'Programing > Java' 카테고리의 다른 글

Effective Java - 객체 생성  (0) 2016.03.03
Java Reference와 GC  (0) 2016.02.29
Garbage Collection  (0) 2015.11.15
Wrapper클래스,박싱(boxing),언박싱(unboxing)  (0) 2015.11.15
Collection  (0) 2015.11.15
  • Protocol Buffers

    : 하나의 언어만으로 서버를 구성하는 것은 한계가 있다. 특히, java와 C#등으로 패킷을 만드는 것은 굉장히 불편하다.
    : 이런 언어적인 차이를 해결해주는 구글의 오픈소스 Protocol Buffers가 있다. 
    : 프로토콜 자체를 언어 독립적으로 구현할 수 있다.

    : node.js에는 npm
    https://developers.google.com/protocol-buffers/


  • Snappy
    : 패킷을 만드는 것 만큼 패킷의 양을 줄이는 것도 중요하다. 특히 모바일에서는 패킷의 양을 줄이는데 신경써야한다.
    : 패킷을 줄이려면 압축과 압축해제를 해야하는데 이 과정에서 속도도 중요하다.
    : 구글 오픈소스 Snappy
    - https://google.github.io/snappy/


  • IOCP
    Input/Ouptput Completion Port
    : Proactor 방식의 고성능 I/O Notification Model로 asynchronous I/O 지원
    : Windows OS가 직접 효율적인 스레드 풀링 제공으로 context switching을 줄임
    : Overlapped I/O를 확장 시킨 개념으로 커널영역과 유저영역의 버퍼 공유 (memory page-locking)

     

    - 기본 동작 구조 (출처: http://www.slideshare.net/sm9kr/windows-registered-io-rio)
     : I/O initiation -> I/O processing -> I/O completion





    - 1_IOCP
     : worker thread 도입

     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    #pragma comment(lib, "ws2_32.lib") 
    
    #pragma comment(linker, "/subsystem:console")
    
    // Overlapped 작업의 종료를 대기할 스레드
    DWORD __stdcall EchoThread( void* p )
    {
    	// 여기서 비동기작업의 종료를 대기하면 됩니다.
    	// 즉 WSAWait....()
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    	//-------------------------------------------
    	// 1. Overlapped io를 위한 소켓 생성
    	// 표준 C 네트워크 함수 : 소문자()
    	// WSAxxxx() 함수들 : windows socket 2.0 부터 지원되는
    	//					windows만의 개념들..
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    								
    
    
    
    	// 2. 소켓에 주소 지정(bind)
    	SOCKADDR_IN addr; 
    
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    
    
    	listen(listen_sock,  5); 
    
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    
    
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// Overlapped로 수신 하는 코드
    		WSAEVENT ev = WSACreateEvent();
    		WSAOVERLAPPED ov = {0};
    		ov.hEvent = ev;
    
    		// 수신 버퍼..
    		char s[1024] = {0};
    		WSABUF buf;
    		buf.buf = s;
    		buf.len = 1024;
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &buf, 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 &ov, // overlapped 구조체
    						 0);
    		// 새로운 스레드를 생성해서 비동기 작업의 완료를 대기한다.
    		CreateThread( 0, 0, EchoThread, (void*)ev, 0, 0);
    	}
    
    
    	closesocket( link_sock);
    	//------------------------------
    	WSACleanup(); 
    }
    

    - 2_IOCP
     : worker thread 구현
     : IOCP 생성
     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    #pragma comment(lib, "ws2_32.lib")
    
    DWORD __stdcall EchoThread( void* p )
    {
    	HANDLE hPort = (HANDLE)p;
    	WSAOVERLAPPED* pov; 
    	DWORD bytes, key;
    
    	while( 1 )
    	{
    		// IOCP에 있는 완료큐에 작업이 들어올때를 대기한다.
    		GetQueuedCompletionStatus( hPort, &bytes, &key,
    				&pov, INFINITE);
    
    		printf("비동기 작업 완료 : %d bytes,  key : %d\n",
    					bytes, key);
    	}
    	return 0;
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    
    	//--------------------
    	// 1. 입출력 완료 포트(IOCP)를 생성합니다.
    	HANDLE hPort = CreateIoCompletionPort( 
    						(HANDLE)-1, // IOCP에등록할 파일(소켓)
    						0, // 이미 존재 하는 IOCP핸들
    						0, // 완료키
    						2);// IOCP에서 비동기작업을 대기할 스레드
    						// 갯수 (CPU의 갯수만큼이 가장좋다.)
    
    	// 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    	HANDLE h1 = CreateThread( 0, 0, EchoThread, (void*)hPort,
    							0, 0);
    	HANDLE h2 = CreateThread( 0, 0, EchoThread, (void*)hPort, 0, 0);
    
    
    	//-------------------------------------------
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    	listen(listen_sock,  5); 
    
    	int cnt = 0;
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    		// Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
    		// IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
    		// 아래 함수를 사용합니다.
    		CreateIoCompletionPort( (HANDLE)link_sock,
    								hPort, 
    								cnt++,
    								2);
    
    
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// Overlapped로 수신 하는 코드
    		WSAEVENT ev = WSACreateEvent();
    		WSAOVERLAPPED ov = {0};
    		ov.hEvent = ev;
    
    		// 수신 버퍼..
    		char s[1024] = {0};
    		WSABUF buf;
    		buf.buf = s;
    		buf.len = 1024;
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &buf, 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 &ov, // overlapped 구조체
    						 0);
    	}
    	//------------------------------
    	WSACleanup(); 
    }
    

    - 3_IOCP
     : overlapped 구조체 확장 -> 입출력당 1개의 자료 구조
     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    #include <stdlib.h>  
    #pragma comment(lib, "ws2_32.lib")
    
    #define READ_MODE  1
    #define WRITE_MODE 2
    
    // OVERLAPPED 구조체는 보통 확장해서 사용하게 됩니다.
    // 입출력당 1개의 자료 구조
    struct IO_DATA
    {
    	WSAOVERLAPPED ov;
    	WSABUF        wsaBuf;
    	char          buff[1024];
    	int           mode;
    };
    
    DWORD __stdcall EchoThread( void* p )
    {
    	HANDLE hPort = (HANDLE)p;
    	WSAOVERLAPPED* pov; 
    	DWORD bytes, key;
    
    	while( 1 )
    	{
    		// IOCP에 있는 완료큐에 작업이 들어올때를 대기한다.
    		GetQueuedCompletionStatus( hPort, &bytes, &key,
    				&pov, INFINITE);
    
    		printf("비동기 작업 완료 : %d bytes,  key : %d\n",
    					bytes, key);
    
    		IO_DATA* ioData = (IO_DATA*)pov;
    
    		printf("수신된 data : %s\n", ioData->buff);
    
    		CloseHandle( ioData->ov.hEvent);
    		free(ioData);
    	}
    	return 0;
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    
    	//--------------------
    	// 1. 입출력 완료 포트(IOCP)를 생성합니다.
    	HANDLE hPort = CreateIoCompletionPort( 
    						(HANDLE)-1, // IOCP에등록할 파일(소켓)
    						0, // 이미 존재 하는 IOCP핸들
    						0, // 완료키
    						2);// IOCP에서 비동기작업을 대기할 스레드
    						// 갯수 (CPU의 갯수만큼이 가장좋다.)
    
    	// 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    	HANDLE h1 = CreateThread( 0, 0, EchoThread, (void*)hPort,
    							0, 0);
    	HANDLE h2 = CreateThread( 0, 0, EchoThread, (void*)hPort, 0, 0);
    
    
    	//-------------------------------------------
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    	listen(listen_sock,  5); 
    
    	int cnt = 0;
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    		// Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
    		// IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
    		// 아래 함수를 사용합니다.
    		CreateIoCompletionPort( (HANDLE)link_sock,
    								hPort, 
    								cnt++,
    								2);
    
    
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// IO작업당 아래 구조체를 만들어서 사용한다.
    		IO_DATA* ioData = (IO_DATA*)malloc(sizeof(IO_DATA));
    
    		memset(ioData, 0, sizeof(IO_DATA));
    
    
    		ioData->wsaBuf.buf = ioData->buff;
    		ioData->wsaBuf.len = 1024;
    		ioData->mode = READ_MODE;
    		ioData->ov.hEvent = WSACreateEvent();
    
    
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &(ioData->wsaBuf), 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 (WSAOVERLAPPED*)ioData, // overlapped 구조체
    						 0);
    	}
    	//------------------------------
    	WSACleanup(); 
    }
    

    - 4_IOCP
     : 소켓 구조체 구현 
     : write 구현 -> WSASend 도 완료되면 GetQueuedCompletionStatus로 통보된다.
     
    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>
    #include <stdlib.h>  
    #pragma comment(lib, "ws2_32.lib") 
    
    #define READ_MODE  1
    #define WRITE_MODE 2
    
    // 클라이언트와 연결된 소켓정보를 관리하는 구조체
    struct SOCKET_DATA
    {
    	int sock;
    	SOCKADDR_IN addr;
    };
    
    // OVERLAPPED 구조체는 보통 확장해서 사용하게 됩니다.
    // 입출력당 1개의 자료 구조
    struct IO_DATA
    {
    	WSAOVERLAPPED ov;
    	WSABUF        wsaBuf;
    	char          buff[1024];
    	int           mode;
    };
    
    DWORD __stdcall EchoThread( void* p )
    {
    	HANDLE hPort = (HANDLE)p;
    	WSAOVERLAPPED* pov; 
    	DWORD bytes, key;
    
    	while( 1 )
    	{
    		GetQueuedCompletionStatus( hPort, &bytes, &key,
    				&pov, INFINITE);
    
    		printf("비동기 작업 완료 : %d bytes,  key : %d\n",
    					bytes, key);
    
    		SOCKET_DATA* pSock = (SOCKET_DATA*)key;
    
    		IO_DATA* ioData = (IO_DATA*)pov;
    
    		if ( ioData->mode == READ_MODE )
    		{
    			printf("수신된 data : %s\n", ioData->buff);
    
    			// 수신에 사용한 버퍼를 사용해서 송신한다.
    			// 수신중에 overlapped 구조체의 내용은 변경되어 있게됩니다
    			memset( &(ioData->ov), 0, sizeof(WSAOVERLAPPED));
    
    			strcat( ioData->buff, " from server");
    			ioData->mode = WRITE_MODE;
    			//-----------------------------------
    			WSASend( pSock->sock, 
    				 &(ioData->wsaBuf), 1,
    				 &bytes, 0, &(ioData->ov), 0);
    			//-----------------------------
    		}
    		else
    		{
    			closesocket(pSock->sock);
    			free( pSock	);
    			CloseHandle( ioData->ov.hEvent);
    			free(ioData);
    		}
    	}
    	return 0;
    }
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    
    	//--------------------
    	// 1. 입출력 완료 포트(IOCP)를 생성합니다.
    	HANDLE hPort = CreateIoCompletionPort( 
    						(HANDLE)-1, // IOCP에등록할 파일(소켓)
    						0, // 이미 존재 하는 IOCP핸들
    						0, // 완료키
    						2);// IOCP에서 비동기작업을 대기할 스레드
    						// 갯수 (CPU의 갯수만큼이 가장좋다.)
    
    	// 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    	HANDLE h1 = CreateThread( 0, 0, EchoThread, (void*)hPort,
    							0, 0);
    	HANDLE h2 = CreateThread( 0, 0, EchoThread, (void*)hPort, 0, 0);
    
    
    	//-------------------------------------------
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    	listen(listen_sock,  5); 
    
    	int cnt = 0;
    	while(1)
    	{
    		struct sockaddr_in addr2;
    		int sz = sizeof addr2;
    
    		int link_sock = accept( listen_sock, 
    							  (struct sockaddr*)&addr2, &sz);
    
    
    		// 소켓당 하나의 구조체
    		SOCKET_DATA* pSock = (SOCKET_DATA*)malloc(
    									sizeof(SOCKET_DATA));
    		pSock->sock = link_sock;
    		pSock->addr = addr2;
    
    
    		// Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
    		// IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
    		// 아래 함수를 사용합니다.
    		CreateIoCompletionPort( (HANDLE)link_sock,
    								hPort, 
    								(ULONG_PTR)pSock,
    								2);
    
    
    
    		printf("클라이언트가 접속되었습니다\n");
    
    		// IO작업당 아래 구조체를 만들어서 사용한다.
    		IO_DATA* ioData = (IO_DATA*)malloc(sizeof(IO_DATA));
    
    		memset(ioData, 0, sizeof(IO_DATA));
    
    
    		ioData->wsaBuf.buf = ioData->buff;
    		ioData->wsaBuf.len = 1024;
    		ioData->mode = READ_MODE;
    		ioData->ov.hEvent = WSACreateEvent();
    
    
    		DWORD flag = 0;
    		DWORD recvBytes = 0;
    
    		int n = WSARecv( link_sock,
    						 &(ioData->wsaBuf), 1,  // 버퍼와 버퍼 갯수
    						 &recvBytes, // 받을 data 크기
    						 &flag,
    						 (WSAOVERLAPPED*)ioData, // overlapped 구조체
    						 0);
    	}
    	//------------------------------
    	WSACleanup(); 
    }


    - PAGE_LOCKING
    (참고: 
    http://www.slideshare.net/sm9kr/windows-registered-io-rio,
    http://ozt88.tistory.com/26)
     
     : 완벽한 통지모델로 보이는 IOCP에도 문제는 있다.
     : 하나의 I/O operation마다 버퍼 영역에 대한 page-lock/unlock
      -> 특정 메모리에 대한 pin/unpin은 많은 CPU cycle 요구
      -> 그래서 RECV를 posting 할 때, page-locking을 피하여 CPU cycle을 줄이기 위해 zero-byte recv로 최소화
     : 하나의 I/O operation마다 시스템콜 호출
      -> 유저모드-커널모드 전환 발생


     ■ Zero Byte Recv?
      : 실제 수행을 시작하는 때를 감지할 수 있다면 이 문제를 해결할 수 있다.
      : 비동기 작업을 명령하기 전에 먼저 0바이트를 읽는 Recv작업을 요청하는 것이다. 0바이트 읽는 작업이므로 필요한 버퍼도 0바이트, 그러므로 PAGE_LOCKING이 발생하지 않는다. 비동기 명령 프로세서를 사용하기 때문에 이 명령이 요청된 시점에는 작업이 바로 가능한지 불확실하지만, 이 명령이 완료된 시점에는 다음 작업을 바로 시작할 수 있을 가능성이 매우 높다. 그러니까 이때부터 본격적인(큰 용량의 버퍼를 사용하는) 작업을 요청하는 것이다. 이렇게 하면 최대한 쓸데없이 LOCKING된 메모리를 줄일 수 있다.

     ■ SO_RCVBUF 옵션
      이 옵션은 소켓 버퍼의 크기를 설정하는 옵션이다. 이 옵션을 사용하여 버퍼 크기를 0으로 설정하는 경우, 커널이 따로 소켓버퍼(send, recv 버퍼)를 사용하지않고 직접 유저가 설정한 버퍼(메모리)에 직접 I/O를 때려박는다. 필자는 Memory-mapped file같은 느낌이라고 이해하고 있다. 0으로 설정하면 커널 버퍼를 거치지 않고 직접 유저의 버퍼에 데이터가 저장되기 때문에, 불필요해 보이는 복사가 수행되지 않아서 성능상에 이점이 있다. 위에서 살짝 언급한것처럼 가상메모리의 영역이 하드웨어 메모리 영역과 깊은 커플링을 맺게되면서, PAGE_LOCKING의 원인이 된다. 그렇다면 유저는 복사를 하지 않는 성능 이점과 PAGE_LOCKING의 이슈를 저울질 하여 옵션을 선택해야 할 것이다.



  • 이벤트 모델
    - 1_event
    // 현재 프로젝트는 GUI 프로그램이 아니라고 링커에게 알려주는것
    #pragma comment(linker, "/subsystem:console")
    #include <stdio.h>
    #include <Windows.h>
    #include <conio.h>
    
    // 윈도우 "event"개념은 linux 의 "conditional variable"가 거의
    // 유사합니다. 
    
    // 실제로 포팅할때 서로 대응되는 개념입니다. 
    
    
    DWORD __stdcall foo( void* p )
    {
    	HANDLE h = (HANDLE)p;
    
    	// 이벤트이 signal 상태 조사하기
    	DWORD ret = WaitForSingleObject( h, 0);
    
    	if ( ret == WAIT_OBJECT_0)
    		printf("시그널 상태(1) 입니다.\n");
    	else
    		printf("시그널이 아닙니다.\n");
    
    	// 시그널 될때까지 대기하는 방법
    	ret = WaitForSingleObject( h, INFINITE); // 시간(무한)
    
    	if ( ret == WAIT_OBJECT_0)
    		printf("시그널 상태(1) 입니다.\n");
    	else
    		printf("시그널이 아닙니다.\n");
    
    	return 0;
    }
    
    int main()
    {
    	HANDLE hEvent = CreateEvent( 0, 0, FALSE, // 초기 signal을
    								0);			  // 0으로
    
    	CreateThread( 0, 0, foo, (void*)hEvent, 0, 0);
    	getch();
    
    	SetEvent( hEvent); // event의 signal을 1로 변경한다.
    
    	getch();
    }
    


    - 2_event
      
    WSAAsyncSelect
       : non-blocking, 비동기 통지방식 (통지 방식으로 윈도우 메시지를 사용하므로 윈도우 프로시저에서만 사용할 수 있다.)
       : 커널에게 미리 등록만 해두면 유저는 따로 커널에게 확인하여 동기화하지 않더라도 알아서 메시지가 날아온다.
       : 내부적으로 따로 체크하는 것이 아니라 운영체제가 I/O 상황이 될때 인터럽트를 사용하는 방식으로 구현되기 때문에 운영체제 수준에서도 연산량이 많이 줄어든다. 대신 다른 운영체제에서 지원하지 않는 기능이기 때문에, 다른 구동환경에서 같은 프로세스를 사용할 수가 없다는 단점은 있다.

     ■  WSAEventSelect
       이 통지방식은 다소 애매한 구석이 있다. 우선 이벤트 오브젝트를 사용하여 signal을 체크한다는 점이 동기랑 비슷한 점이 있어보인다. 그리고 wait함수로 이벤트가 발생할 때까지 대기한다는점이 blocking같기도 하다. 하지만 select나 epoll처럼 유저가 리소스를 사용하여 체크하는 것이 아니라 Wait함수를 사용하여 이벤트가 발생할 때 활성화 된다는 점이 비동기 방식에 가깝다고 생각한다.
       :
    WSAWaitForMultipleEvent() 함수에서 timeout 옵션이 있기 때문에 select나 epoll 처럼 어떻게 사용하느냐에 따라 blocking 방식으로 사용할 수도 non-blocking 방식으로 사용할 수도 있다.
       : 하지만 다른 점이 있다면, blocking 방식을 사용해도 멀티플렉싱이 가능하다는 점이다. 하나의 Wait에서 여러개의 I/O를 동시에 감지하고 있기 때문에, 쓰레드 하나만 wait를 통해 blocking한 상태에서 대기시키면 멀티플렉싱이 가능하다. 결과적으로는 Blocking 이면서 Non-Blocking이기도 하다.

    #pragma comment(linker,"/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h>
    #include <Windows.h>
    #include <conio.h>
    #pragma comment(lib, "ws2_32.lib") 
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    
    	int listen_sock = socket( PF_INET,  
    						SOCK_STREAM, 
    						0);
    
    	SOCKADDR_IN addr; 
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    
    	listen(listen_sock,  5); 
    	//------------------
    	WSAEVENT ev = WSACreateEvent(); // CreateEvent()의 네트워크
    									// 버전.
    	// 소켓을 비동기 소켓으로 변경한다.
    	WSAEventSelect( listen_sock, ev, FD_ACCEPT);
    	//-----------------------------
    
    	// 모든 소켓 핸들과 이벤트 핸들을 배열에 보관해야 합니다.
    	int sockArr[256] = { listen_sock };
    	WSAEVENT evArr[256] = { ev };
    
    	int cnt = 1;
    
    	while( 1 )
    	{
    		int pos = WSAWaitForMultipleEvents( cnt, evArr,  // event배열이름
    						FALSE, // 하나라도 signal 되면
    						WSA_INFINITE,0);
    
    
    		// 배열의 몇번째 요소인지 조사한다
    		int idx = pos - WSA_WAIT_EVENT_0;
    
    		printf("%d 번째 이벤트가 signal\n", idx);
    
    
    		WSANETWORKEVENTS netEv;
    		WSAEnumNetworkEvents( sockArr[idx], evArr[idx], &netEv);
    
    		
    		if ( netEv.lNetworkEvents & FD_READ )
    		{
    			char s[1024] = {0};
    			int n = recv( sockArr[idx], s, 1024, 0);
    			printf("도착한 data : %s\n", s);
    			strcat( s, " from Server");
    			send( sockArr[idx], s, 1024, 0);
    
    		}
    
    
    		if ( netEv.lNetworkEvents & FD_CLOSE )
    		{
    			closesocket( sockArr[idx]);
    			WSACloseEvent( evArr[idx]);
    
    			// 배열에서 제거 해야 합니다.
    			sockArr[idx] = sockArr[cnt-1];
    			evArr[idx] = evArr[cnt-1];
    
    			--cnt;
    			printf("Client 접속 끊어짐\n");
    			continue;
    		}
    
    
    
    
    		if ( netEv.lNetworkEvents & FD_ACCEPT)
    		{
    			printf("접속요청\n");
    
    
    			SOCKADDR_IN caddr;
    			int sz = sizeof caddr;
    
    			int link_sock = accept( listen_sock, 
    							(SOCKADDR*)&caddr, &sz);
    
    			WSAEVENT ev2 = WSACreateEvent();
    			WSAEventSelect( link_sock, ev2, 
    								FD_READ | FD_CLOSE);
    			
    			sockArr[cnt] = link_sock;
    			evArr[cnt] = ev2;
    			++cnt;
    
    			printf("클라이언트 접속 : %s\n", 
    						inet_ntoa( caddr.sin_addr));
    		}
    	}
    	//------------------------------
    	WSACleanup(); 
    }
    

  • Overlapped IO
    : 중첩 입출력 모델
    : non-blocking, asynchronous I/O (비동기적 완료 통보)
    : 데이터 입출력이 진행되는 동안에도 다른일을 할 수 있다.
    : 원리는 리눅스의 I/O multiplexing, RTS과 비슷하다.

     
    : 리눅스의 I/O multiplexing, RTS와 다른점은 모드 변환이 발생하지 않는다. (user <-> kernel 여러번 데이터 복사 x)


    #pragma comment(linker, "/subsystem:console")
    
    #define WIN32_LEAN_AND_MEAN  
    #include <stdio.h>
    #include <WinSock2.h> 
    #include <Windows.h>  
    
    #pragma comment(lib, "ws2_32.lib") 
    
    int main()
    {
    	WSADATA w;
    	int ret = WSAStartup( MAKEWORD(2,2), &w);    
    	
    	//-------------------------------------------
    	// 1. Overlapped io를 위한 소켓 생성
    	// 표준 C 네트워크 함수 : 소문자()
    	// WSAxxxx() 함수들 : windows socket 2.0 부터 지원되는
    	//					windows만의 개념들..
    	int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
    								0, 0, 0,
    								WSA_FLAG_OVERLAPPED);
    								
    
    
    
    	// 2. 소켓에 주소 지정(bind)
    	SOCKADDR_IN addr; 
    
    	addr.sin_family = AF_INET;
    	addr.sin_port   = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY; 
    	
    	bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    
    
    
    	listen(listen_sock,  5); 
    	struct sockaddr_in addr2;
    	int sz = sizeof addr2;
    
    
    	int link_sock = accept( listen_sock, 
    						  (struct sockaddr*)&addr2, &sz);
    
    	printf("클라이언트가 접속되었습니다\n");
    
    
    	closesocket(listen_sock); // 대기 소켓은 이제 필요없다.
    
    	// Overlapped로 수신 하는 코드
    	WSAEVENT ev = WSACreateEvent();
    	WSAOVERLAPPED ov = {0};
    	ov.hEvent = ev;
    
    
    	// 수신 버퍼..
    	char s[1024] = {0};
    	WSABUF buf;
    	buf.buf = s;
    	buf.len = 1024;
    
    	DWORD flag = 0;
    	DWORD recvBytes = 0;
    
    	int n = WSARecv( link_sock,
    					 &buf, 1,  // 버퍼와 버퍼 갯수
    					 &recvBytes, // 받을 data 크기
    					 &flag,
    					 &ov, // overlapped 구조체
    					 0);
    
    	if ( n == SOCKET_ERROR ) // -1 
    	{
    		if ( WSAGetLastError() == WSA_IO_PENDING )
    		{
    			printf("data 를 수신하고 있는 중입니다.\n");
    
    			// 비동기의 종료를 대기 해야 합니다.
    			// 비동기 IO의 종료시에는 overlapped구조체 안에 있는
    			// event가 signal 됩니다.
    			WSAWaitForMultipleEvents( 1, &ev, TRUE, 
    								WSA_INFINITE, 0);
    
    			printf("비동기 IO로 수신 완료\n");
    		}
    	}
    	else 
    		printf("data를 동기적으로 수신 : %d\n", n); 
    
    	printf("수신된 data : %s\n", buf.buf);
    
    	closesocket( link_sock);
    	//------------------------------
    	WSACleanup(); 
    }

참고: http://ozt88.tistory.com/22

  • Server
    - 1_server ~ 3_server
     
    #define WIN32_LEAN_AND_MEAN
    #include <stdio.h>
    
    #include <Windows.h>
    
    #include <WinSock2.h>
    
    #pragma comment (lib, "ws2_32.lib")
    
    int main()
    {
    	WSADATA w;
    	WSAStartup(MAKEWORD(2, 2), &w);
    	//-----------------------------------
    
    	// 1. 소켓 생성
    	SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	// 2. 소켓에 주소 지정(bind)
    	SOCKADDR_IN addr = { 0, };
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(4000);
    	addr.sin_addr.s_addr = INADDR_ANY;
    
    	bind(sock, (SOCKADDR*)&addr, sizeof addr);
    
    	// 3. 소켓을 대기 상태로, 5라는 arg는 무시 됨
    	listen(sock, 5);
    
    	// 4. 클라이언트 요청을 수락
    	SOCKADDR_IN caddr = { 0, };
    	int sz = sizeof(caddr);
    
    	SOCKET csock = accept(sock, (SOCKADDR*)&caddr, &sz);
    
    	printf("클라이언트가 접속되었습니다.\n");
    
    
    	char buf[1024];
    	// 핵심 : read() 를 통해 수신하지 않습니다. 리턴값이 가장 중요하다.
    	int ret;
    	while (1) {
    		ret = recv(csock, buf, 1024, 0);
    		if (ret == 0) {
    			printf("연결이 정상적으로 종료되었습니다.\n");
    			break;
    		}
    		else if (ret == -1) {
    			printf("연결이 비정상적으로 종료되었습니다.\n");
    			break;
    		}
    
    		// ...
    		ret = send(csock, buf, ret, 0);
    		if (ret < 0)
    			break;
    	}
    
    	printf("클라이언트가 접속이 해지되었습니다.\n");
    
    	closesocket(sock);
    	closesocket(csock);
    
    	//-----------------------------------
    	WSACleanup();
    }
    

  • Client
    - 1_client
     : 초기 셋팅, 주의 사항

    // 1_client.cpp
    
    // 1. 윈도우즈의 모든 네트워크 관련 함수는
    //		winsock2.h에 있습니다. (헤더만)
    //		기존의 소켓 API를 확장한 형태
    // 2.모든 구현은 sw2_32.dll에 있습니다. 링킹이 필요
    // 3. Windows.h와 같이 사용할 경우 반드시 WinSock2.h를 먼저
    //	  포함해야 합니다.
    // 4. #define WIN32_LEAN_AND_MEAN
    // 5. 환경설정에서 링커 설정
    // 3~5 중 선택적으로 사용
    
    // Windows.h의 헤더에서 자주 사용하지 않는 것을 제외해달라.
    #define WIN32_LEAN_AND_MEAN
    
    #include <WinSock2.h>
    #pragma comment(lib, "ws2_32.lib") // 링킹
    
    #include <Windows.h> // 모든 시스템 함수가 헤더파일에 선언되어 있습니다.
    
    int main()
    {
    	return 0;
    }
    

    - 2_client

    #include <stdio.h>
    #include <WinSock2.h>
    #include <Windows.h> 
    
    #pragma comment(lib, "ws2_32.lib")
    
    // 1. Network 함수를 사용하기 전에 WSAStartup을 통해
    //	라이브러리 초기화를 수행해야 한다.
    
    // 2. WSA : Windows Socket API
    // 3. WSACleanup() 을 통해 자원을 정리해야 한다.
    
    int main()
    {
    	// 찰스 시모니의 헝가리안 표기법 이해 필요, 변수명에 타입에 대한 정보가 나타남
    	// JAVA의 m~은? 안드로이드 표준
    	WSADATA w;
    	int ret = WSAStartup(MAKEWORD(2, 2), &w);
    	// 0x0202 word가 2byte
    
    	if(ret != 0)
    		printf("소켓 라이브러리를 사용할 수 없다.\n");
    
    	// w에 있는 정보는 현재까지 정확하지 않습니다. 사용되지 않습니다.
    	printf("최대 소켓 갯수: %d\n", w.iMaxSockets);
    	printf("소켓 버전: %d.%d\n", w.wHighVersion, w.wVersion);
    
    	// 소켓 라이브러리가 사용하는 자원을 정리
    	WSACleanup();
    }
    

    - 3_client
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <WinSock2.h>
    #include <Windows.h>
    
    #pragma comment(lib, "ws2_32.lib")
    
    #include <locale.h>
    
    int main()
    {
    	setlocale(LC_ALL, "korean"); // 유니코드 셋팅
    	
    	WSADATA w;
    	WSAStartup(MAKEWORD(2, 2), &w);
    	//--------------
    	// 1. 소켓 생성, 리눅스는 fd가 retrun, 윈도우는 SOCKET return
    	SOCKET sock = socket(PF_INET, SOCK_STREAM, 0); // int도 되지만 정확한 타입을 사용!
    
    	// 2. 서버 주소 지정
    	// struct sockaddr_in addr = { 0, };
    	SOCKADDR_IN addr = { 0, };
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(4000);
    	//addr.sin_port = htons(80);
    	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    	//addr.sin_addr.s_addr = inet_addr("203.249.22.240");
    
    	// 3. 서버 접속
    	int ret = connect(sock, (SOCKADDR*)&addr, sizeof addr);
    	if (ret == 0)
    		printf("접속 성공\n");
    	else {
    		int err = WSAGetLastError(); // 함수 호출하면 
    		// 1. WSAGetLastError() / 도구 -> 오류 조희 
    		printf("접속 실패 : %d\n", err);
    
    		// 2. WSAGetLastError() 오류 번호를 저장하지 않고 다른 함수를 호출하면 오류 번호가 초기화 되버린다. 
    		WCHAR buf[512];
    		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
    					  0, 
    					  err, 
    					  LANG_SYSTEM_DEFAULT,
    					  buf, 
    					  512, 0);
    		// L은 유니코드라는 표기
    		wprintf(L"에러 발생 : %s", buf);
    
    		// 3. Debug Mode - 조사식
    		// @err
    		// @err, hr
    
    		// 비정상종료 테스트, 자원해지(소멸자 등) 테스트
    		//Sleep(1000 * 10); 
    		//exit(0);
    	}
    
    	//--------------
    
    	closesocket(sock);	// close(sock);
    	
    	WSACleanup();
    }
    


  •  Event-Driven Server Architectures
    : alternative to synchronous blocking I/O
    the mapping of a single thread to multiple connections
    new events are queued and the thread executes a so-called event loop--dequeuing events from the queue, processing the event, then taking the next event or waiting for new events to be pushed.

    - thread-based vs. event-driven

    thread-basedevent-driven
    connection/request statethread contextstate machine/continuation
    main I/O modelsynchronous/blockingasynchronous/non-blocking
    activity flowthread-per-connectionevents and associated handlers
    primary scheduling strategypreemptive (OS)cooperative
    scheduling componentscheduler (OS)event loop
    calling semanticsblockingdispatching/awaiting events
    Table 4.2: Main differences between thread-based and event-driven server architectures.


  • Non-blocking I/O Multiplexing Patterns
    event-based I/O multiplexing

    - Reactor Pattern
     : 주로 리눅스의 구현
     : select, epoll, kqueue, Node.js, Ruby EventMachine, Scala Akka’s IO 모듈 등
     : synchronous, non-blocking I/O handling and relies on an event notification interface.
     : event나 socket 같은 리소스들의 집합을 등록해놓고, 각 리소스들을 콜백 또 후킹하는 적절한 event handler가 필요하다.
     : 핵심 구성요소인 synchronous event demultiplexer blocking event notification interface를 사용하여 리소스의 이벤트를 기다린다.
     : synchronous event demultiplexer가 받는 이벤트는 언제든지 dispatcher로 알리고, 다음 event를 기다린다.
     : dispatcher는 관련 event handler 선택하고, callback/hook execution을 triggering함으로써 event를 처리한다.
     : 리엑터 패턴은 event handling 과 multiplexing 분리한다. 이 패턴은 싱글 스레드를 사용하므로 blocking 동작은 전체적은 application의 동작을 지연시킬 수 있다. 그래서 다양한 Reactor Pattern들이 event handler에 대한 thread poll(thread 개수는 CPU 개수*2 + 1)을 사용한다.

    *장점
    : 리엑터 패턴은 리엑터 구현에서 애플리케이션에 관련된 코드가 완벽하게 분리됨. 그래서 애플리케이션 컴포넌트들이 모듈화되고 재사용 가능한 부분들로 분리될 수 있음. 시스템을 멀티스레드로 인한 복잡성에서 보호해 줌.

    *단점
    : 디버깅하기 어려움. 요청 핸들러를 동기적으로 호출하기 때문에 최대 동시성에 제한이 있음. 요청 핸들러 뿐만 아니라 디멀티플렉서에 의해서도 확장성이 제한됨. (OS 스케줄링 이용 불가, event queue에 대한 성능 이슈)




      ■ 1_epoll
       : select, poll의 문제점 해결

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>   // epoll api
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    // select, poll 문제점
    // 1) 관찰 대상 디스크립터의 정보를 매번 운영체제에 전달해야 한다.
    // 2) 1024 개 이상의 디스크립터를 등록하는 것이 불가능하다.
    
    // epoll
    // - 2.5.44 부터 사용 가능하다.
    // - $ cat /proc/sys/kernel/osrelease
    
    // epoll api 3가지
    // 1) epoll_create : 파일 디스크립터 저장소 생성
    // 2) epoll_ctl    : 디스크립터 등록 & 삭제
    // 3) epoll_wait   : 파일 디스크립터의 변화를 감지하는 함수
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    
    	// 1. 초기화
    	const int EPOLL_SIZE = 1024;
    	int efd = epoll_create(EPOLL_SIZE);
    	// 최신 커널에서는 사이즈는 무시됩니다.
    
    	// 2. 이벤트 등록
    	struct epoll_event events[EPOLL_SIZE];
    
    	struct epoll_event event;
    	event.events = EPOLLIN;
    	event.data.fd = sock;
    	epoll_ctl(efd, EPOLL_CTL_ADD, sock, &event); 
    
    	while (1) 
    	{
    		int count = epoll_wait(efd, events, EPOLL_SIZE, -1);
    
    		for (int i = 0 ; i < count ; ++i)
    		{
    			if (events[i].data.fd == sock)
    			{
    				struct sockaddr_in caddr = {0, };
    				socklen_t clen = sizeof(caddr);
    				int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    				char* cip = inet_ntoa(caddr.sin_addr);
    				printf("Connected from %s\n", cip);
    
    				// 새로운 연결을 등록해주어야 한다.
    				event.events = EPOLLIN;
    				event.data.fd = csock;
    				epoll_ctl(efd, EPOLL_CTL_ADD, csock, &event); 
    			} 
    			else
    			{
    				int csock = events[i].data.fd;
    				char buf[1024];
    				int n = read(csock, buf, sizeof buf);
    
    				if (n <= 0)
    				{
    					printf("연결 종료!!\n");
    					close(csock);
    
    					// 종료된 디스크립터를 등록 해지해야 한다.
    					epoll_ctl(efd, EPOLL_CTL_DEL, csock, 0);
    				} 
    				else
    					write(csock, buf, n);
    			}
    		}
    	}
    
    	close(sock);
    }
    


      ■ 2_epoll
       : Level Triggering vs. Edge Triggering

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <errno.h>
    #include <fcntl.h>       // fcntl()
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>   // epoll api
    
    // epoll 이벤트 처리 방식
    // Level Triggering - Default
    // : 수신 버퍼에 데이터가 있다면, 이벤트가 발생
    // Edge Triggering
    // : 수신 버퍼에 데이터가 없다가, 존재하는 시점에 이벤트가 발생
    // 핵심 : 데이터를 처리하는 디스크립터를 Non-blokcing 모드로 설정해야 한다.
    
    
    // ET 장점
    // 1. 데이터를 처리하기 위해 커널에 진입하는 횟수가 적다.
    // 2. 데이터를 수신하는 로직과 데이터를 처리하는 로직을 분리할 수 있다.
    //   - 서버의 구조화에 유리하다.
    
    
    void setNonBlokcing(int fd)
    {
    	// 아래 코드는 문제점이 있습니다.
    	// fcntl(fd, F_SETFL, O_NONBLOCK);
    
    	// Edge Triggering은 연결에 대한 데이터가 들어오는 시점에서 발생하므로 다시 데이터가 올라올때까지 발생하지 않는다. 
    	// ex) aaaa -> aaaa, aaaab -> aaaa
    	// -> blocking이 생길 수 있다. -> read를 non-blocking 되도록 해야한다.
    	// -> Edge trigger + non-blocking -> fcntl
    
    	int flags = fcntl(fd, F_GETFL, 0);
    	flags |= O_NONBLOCK;
    
    	fcntl(fd, F_SETFL, flags);
    }
    
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    
    	// 1. 초기화
    	const int EPOLL_SIZE = 1024;
    	int efd = epoll_create(EPOLL_SIZE);
    	// 최신 커널에서는 사이즈는 무시됩니다.
    
    	// 2. 이벤트 등록
    	struct epoll_event events[EPOLL_SIZE];
    
    	struct epoll_event event;
    	event.events = EPOLLIN;
    	event.data.fd = sock;
    	epoll_ctl(efd, EPOLL_CTL_ADD, sock, &event); 
    
    	while (1) 
    	{
    		int count = epoll_wait(efd, events, EPOLL_SIZE, -1);
    		printf("epoll_wait()\n");
    
    		for (int i = 0 ; i < count ; ++i)
    		{
    			if (events[i].data.fd == sock)
    			{
    				struct sockaddr_in caddr = {0, };
    				socklen_t clen = sizeof(caddr);
    				int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    				char* cip = inet_ntoa(caddr.sin_addr);
    				printf("Connected from %s\n", cip);
    
    				// Non-Blocking 으로 설정  
    				setNonBlokcing(csock);
    
    				// 새로운 연결을 등록해주어야 한다.
    				event.events = EPOLLIN | EPOLLET;
    				event.data.fd = csock;
    				epoll_ctl(efd, EPOLL_CTL_ADD, csock, &event); 
    			} 
    			else
    			{
    				int csock = events[i].data.fd;
    				// char buf[1024];
    				char buf[4];
    				while (1)
    				{
    					int n = read(csock, buf, sizeof buf);
    					if (n == 0)
    					{
    						printf("연결 종료!!\n");
    						close(csock);
    
    						// 종료된 디스크립터를 등록 해지해야 한다.
    						epoll_ctl(efd, EPOLL_CTL_DEL, csock, 0);
    					} 
    					else if (n == -1)
    					{
    						int err = errno;
    						if (err == EAGAIN)   // 데이터가 존재하지 않는다.
    							break;
    						else {
    							close(csock);
    
    							epoll_ctl(efd, EPOLL_CTL_DEL, csock, 0);
    						}
    
    					}
    					else {
    						printf("read() : %d\n", n);
    						write(csock, buf, n);
    					}
    				}
    			}
    		}
    	}
    
    	close(sock);
    }


    - Proactor Pattern
     : 주로 윈도우의 구현
     : POSIX AIO, Boost.Asio (C++), 
    Adaptive Communication Environment (C++), libbitcoin (C++), RJR (Ruby), win32 IOCP
     : asynchronous, non-blocking I/O operations
     : Reactor pattern의 asynchronous 방식으로 보기도 함
     : blocking event notification interfaces 대신에 completion events
     : proactive initiator는 main application thread로 
    asynchronous I/O 동작들을 초기화하는 역할
     : 동작이 이슈될때 항상 completion handler와 completion dispatcher에 등록된다.
     : 비동기적인 동작의 수행은 asynchronous operation processor의해 관장된다. 이 프로세스는 OS에 의해 구현된다. 
     : I/O 작업이 완료되었을때, completion dispatcher로 notifiy 되고, 다음으로 completion handler는 그 결과를 처리한다.

     




    - 1_blocking_queue
     : mutex는 커널단 컨텍스트 스위칭이므로 오버헤드가 큼 -> atomic operation

     
    #include <unistd.h>
    
    #include <mutex>
    #include <condition_variable> // Event
    #include <deque>
    
    #include <thread>
    #include <iostream>
    using namespace std;
    
    template <typename T>
    class blocking_queue
    {
    	deque<T> d_queue;
    	condition_variable d_condition;
    	mutex d_mutex;
    
    public:
    	void push(const T& value)
    	{
    		{
    			unique_lock<mutex> lock(d_mutex);
    			d_queue.push_front(value);		
    		}
    		d_condition.notify_one();
    	}
    	
    	T pop()
    	{
    		unique_lock<mutex> lock(d_mutex);
    		d_condition.wait(lock, [=] { return !d_queue.empty(); } );
    
    		T rc(std::move(d_queue.back())); // move fast
    		d_queue.pop_back();
    
    		return rc;
    	}
    };
    

    - 2_lockfree_queue
     : boost를 이용한 생산자 소비자 패턴
     
    #include <iostream>
    
    #include <boost/lockfree/queue.hpp>
    // h + cpp = hpp
    
    #include <boost/thread/thread.hpp>
    #include <boost/atomic.hpp>
    
    using namespace std;
    using namespace boost;
    
    atomic_int producer_count(0);
    atomic_int consumer_count(0);
    
    lockfree::queue<int> queue(128);
    
    const int iterations = 100000000;
    const int producer_thread_count = 4;
    const int consumer_thread_count = 4;
    
    void producer(void)
    {
    	for (int i = 0 ; i < iterations ; ++i) {
    		int value = ++producer_count;
    		while (!queue.push(value))
    			;
    	}
    }
    
    atomic<bool> done(false);
    void consumer(void)
    {
    	int value;
    	while (!done)
    	{
    		while (queue.pop(value))
    			++consumer_count;
    	}
    
    	while (queue.pop(value))
    		++consumer_count;
    }
    
    int main()
    {
    	cout << "queue is ";
    	if (!queue.is_lock_free())
    		cout << "not ";
    	cout << "lockfree" << endl;
    
    	thread_group producer_threads, consumer_threads;
    	for (int i = 0 ; i < producer_thread_count ; ++i)
    		producer_threads.create_thread(producer);
    
    	for (int i = 0 ; i < consumer_thread_count ; ++i)
    		consumer_threads.create_thread(consumer);
    
    	producer_threads.join_all();
    	done = true;
    
    	consumer_threads.join_all();
    
    	cout << "produced " << producer_count << endl;
    	cout << "consumed " << consumer_count << endl;
    


    - 4_asio_echo_server
     : block에 대한 개념이 존재하지 않음
     : boost asio에서 핵심은 io_service
     : 각각의 연산 자체가 비동기로 이루어짐

    #include <boost/asio.hpp>
    using namespace boost;
    
    #include <iostream>
    #include <memory>    // shared_ptr<>
    #include <utility>
    using namespace std;
    
    using boost::asio::ip::tcp;
    class Session : public std::enable_shared_from_this<Session>
    {
    public:
    	Session(tcp::socket s) : socket(std::move(s)) {}
    
    	void start() { doRead(); }
    
    	void doRead() {
    		auto self(shared_from_this());  // 참조 계수 증가, self를 참조안하면 객체가 바로 파괴되버림
    		// 1. async_read      : readn
    		// 2. async_read_some : read
    
    		socket.async_read_some(
    				asio::buffer(data, 1024),
    				[this, self](system::error_code ec, size_t length) {
    				
    				if (!ec) 
    					doWrite(length);
    		});
    	}
    
    	void doWrite(size_t length)
    	{
    		auto self(shared_from_this());
    
    		asio::async_write(
    				socket, asio::buffer(data, length),
    				[this, self](system::error_code ec, size_t) {
    					if (!ec)
    						doRead();
    				});
    	}
    
    
    private:
    	tcp::socket socket;
    	char data[1024];
    };
    
    
    class Server
    {
    public:
    	Server(asio::io_service& io, short port)
    		: acceptor(io, tcp::endpoint(tcp::v4(), port)),
    			socket(io) {
    		
    		doAccept();
    	}
    
    	void doAccept()
    	{
    		acceptor.async_accept(socket, [this](system::error_code ec) {
    			// 클라이언트의 접속 요청이 완료된 시점에 호출되는 핸들러	
    			if (!ec) {
    				// socket...
    				make_shared<Session>(std::move(socket))->start();
    			}
    		});
    
    	}
    
    private:
    	tcp::acceptor acceptor;
    	tcp::socket socket;
    	
    };
    
    
    int main()
    {
    	try {
    		asio::io_service io_service;
    		Server s(io_service, 4000);
    		io_service.run();
    
    	} catch (std::exception& e) {
    		cerr << e.what() << endl;
    	}
    }

  • Reactor, Proactor 간단한 도식화
    - Reactor


    - Proactor




참고: http://berb.github.io/diploma-thesis/original/042_serverarch.html, http://ozt88.tistory.com/25

  • I/O multiplexing
    : synchronous, Blocking I/O 형태의 멀티 프로세스 서버, 멀티 스레드 서버 모두 고성능 서버로는 부적합하다.
    : 클라이언트마다 프로세스나 스레드를 할당하면 Context Switching 문제로 메모리적인 문제가 발생한다.
    -> 멀티플렉싱 개념 필요

    - poll
      ■ 1_poll
       : mkfifo fifo 파이프 만들기
       : IPC 전용 메커니즘
       : cat > myfifo로 리다이렉션

    #include <unistd.h>
    
    #include <stdio.h>
    #include <fcntl.h>
    
    int main()
    {
    	char buff[100];
    	int ret;
    
    	int fd = open("myfifo", O_RDWR);
    
    	while (1)
    	{
    		ret = read(0, buff, sizeof buff);
    		buff[ret] = '\0';
    		printf("Keyboard : %s\n", buff);
    
    		ret = read(fd, buff, sizeof buff);
    		buff[ret] = '\0';
    		printf("myfifo : %s\n", buff);
    	}
    }
    

      ■ 2_poll
    #include <unistd.h>
    
    #include <stdio.h>
    #include <fcntl.h>
    
    #include <poll.h>
    
    int main()
    {
    	char buff[100];
    	int ret;
    
    	int fd = open("myfifo", O_RDWR);
    
    
    	// 비동기적으로 이벤트를 처리할 디스크립터 배열
    	struct pollfd fds[2];
    
    	while (1)
    	{
    		fds[0].fd = 0;
    		fds[0].events = POLLIN;  // 관심 있는 이벤트의 종류
    														 // read  : POLLIN
    														 // write : POLLOUT
    
    		fds[1].fd = fd;
    		fds[1].events = POLLIN;
    
    
    		poll(fds, 2, -1);
    
    
    		// 주의할 점 : 이벤트가 동시에 발생할 수 있으므로
    		//             절대 else 로 묶으면 안된다.
    		if (fds[0].revents & POLLIN) 
    		{
    			ret = read(0, buff, sizeof buff);
    			buff[ret] = '\0';
    			printf("Keyboard : %s\n", buff);
    		}
    
    		if (fds[1].revents & POLLIN)
    		{
    			ret = read(fd, buff, sizeof buff);
    			buff[ret] = '\0';
    			printf("myfifo : %s\n", buff);
    		}
    	}
    }
    


    - select (참고: http://ozt88.tistory.com/21)
     : select는 싱글스레드로 다중 I/O를 처리하는 멀티플렉싱 통지모델의 가장 대표적인 방법이다.
     : 해당 파일 디스크립터가 I/O를 할 준비가 되었는지 알 수 있다면, 그 파일 디스크립터가 할당받은 커널 Buffer에 데이터를 복사해주기만 하면 된다. 이런 목적하에 통지모델은 파일디스크립터의 상황을 파악할 수 있게 하는 기능을 할 수 있어야한다. select는 많은 파일 디스크립터들을 한꺼번에 관찰하는 FD_SET 구조체를 사용하여 빠르고 간편하게 유저에게 파일 디스크립터의 상황을 알려준다.
     : select를 사용해서 I/O의 상황을 알기 위해서는 프로세스가 커널에게 직접 상황 체크를 요청해야한다. 프로세스가 커널의 상황을 지속적으로 확인하고 그에 맞는 대응을 하는 형태로 구성되기 때문에 프로세스와 커널이 서로 동기화된 상태에서 정보를 주고 받는 형태로 볼 수 있다. 따라서 select의 통지형태를 동기형 통지방식이라 부를 수 있다. 그리고 select 그 자체는 I/O를 담당하지 않지만, 통지하는 함수의 호출방식이 timeout에 따라 non-blocking 또는 blocking 형태가 된다. timeout을 설정하지 않으면, 관찰 대상이 변경되지 않는 이상 반환되지 않으므로 blocking 함수가 되고, timeout이 설정되면 주어진 시간이 지나면 시간이 다되었다는 정보를 반환하므로 non-blocking 함수가 된다. 

    #include  <stdio.h>
    
    #include  <unistd.h>
    #include  <sys/types.h>
    #include  <sys/select.h>
    #include  <sys/socket.h>
    
    #include  <arpa/inet.h>
    #include  <netinet/in.h>
    
    inline int max(int a, int b)
    {
    	return a > b ? a : b;
    }
    
    int main()
    {
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	listen(sock, 5);
    
    	// fd_set : bit array
    	fd_set socks;					// source data
    	fd_set readsocks;
    
    	FD_ZERO(&socks);
    	FD_SET(sock, &socks);
    
    	int maxfds = sock;
    
    	while (1) 
    	{
    		readsocks = socks;
    
    		int ret = select(maxfds + 1, &readsocks, 0, 0, 0);
    		printf("ret : %d\n", ret);
    
    		for (int i = 0 ; i < maxfds + 1 ; ++i)
    		{
    			if (FD_ISSET(i, &readsocks))
    			{
    				if (i == sock)
    				{
    					struct sockaddr_in caddr = {0, };
    					socklen_t clen = sizeof(caddr);
    					int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    					char* cip = inet_ntoa(caddr.sin_addr);
    					printf("Connected from %s\n", cip);
    
    					// 새로운 연결을 등록해주어야 한다.
    					FD_SET(csock, &socks); 
    					maxfds = max(maxfds, csock);
    				}
    				else
    				{
    					int csock = i;
    					char buf[1024];
    					int n = read(csock, buf, sizeof buf);
    					
    					if (n <= 0)
    					{
    						printf("연결 종료!!\n");
    						close(csock);
    
    						// 종료된 디스크립터를 등록 해지해야 한다.
    						FD_CLR(csock, &socks);
    					} 
    					else
    						write(csock, buf, n);
    
    				}
    			} 
    		}
    
    #if 0
    		while (1)
    		{
    			char buf[1024];
    			int n = read(csock, buf, sizeof buf);
    			if (n == 0)
    				close(csock);
    			else if (n == -1)
    				close(csock);
    
    			write(csock, buf, n);
    		}
    #endif
    	}
    
    	close(sock);
    }
    


+ Recent posts