자바에는 기본자료형인 primitive type과 boxed primitive type이 존재한다.

 

java 1.5 이상 부터 primitive type과 boxed primitive type간의 autoboxing과 autounboxing을 제공한다.

 

memory 영역으로 나뉘어 보면, primitive type은 stack 영역에 저장된다. 반면, boxed primitive type은 내부적으로 primitive type을 갖고 있는 wrapping된 class이므로 heap영역에 저장된다.

 

 

Primitive Type

- 스택 영역에 저장되기 때문에, 메모리 효율성과 접근면에서 뛰어나다.

 

 

 

boxed Primitive Type

- collection에서 사용할 떄는 Primitive type을 사용할 수 없으므로 boxed primitive type을 사용해야한다.

 

- 명시적으로 null을 주입해야 할 경우

    ex) db에 명시적으로 null을 넣고자 할 때 int(primitive type)를 사용하면 기본 값(0)이 적용된다. 

 

- parameterized type을 사용할 때

   ex) List<Long>

 

- boxed primitives는 Wrapping Object이므로, 비교연산(==)에 주의하자.

 

 

Caching

- boxed primitive type에서는 자주 사용하는 값에 대해 Caching 기능을 갖고 있다.

 

 

 

Long 객체의 내부 코드를 살펴보면, -128~127 사이의 값을 Caching 해두고 있는데 범위 내의 값을 비교할 땐 미리 생성해 둔 객체로 비교를 한다.

 

    public static void main(String[] args){
        Long a = 127l;
        Long b = 128l;

        Long boxA = Long.valueOf(127);
        Long boxB = Long.valueOf(128);

        System.err.println(a==boxA);    //TRUE
        System.err.println(b==boxB);    //FALSE    

    }

 

 

위의 예제를 통해 살펴보면, 해당 값의 범위를 벗어 나면 비교연산에 실패하게 되므로 주의 해야한다.

Reflection

- Java에서 지원하는 API로, 객체를 통해 클래스의 정보를 분석해 낼 수 있다.
- 즉, 실행중인 Java Application의 Class 정보를 동적으로 접근하여 메소드, 필드 등에 접근하거나 인스턴스를 다룬다.

- 대표적인 예로 Spring의 Bean들은 Reflection을 통해 인스턴스들이 다뤄진다.

 

실제로 org.springframework.beans.annotation.AnnotationBeanUtils 클래스 내부를 보면 bean의 properties를 copy하는 메소드를 찾을 수 있는데, Object 타입의 bean과 java.lang.reflection.Method를 통해 bean의 메소드들이 다뤄지는 것을 확인할 수 있다.

/**
	 * Copy the properties of the supplied {@link Annotation} to the supplied target bean.
	 * Any properties defined in {@code excludedProperties} will not be copied.
	 * <p>A specified value resolver may resolve placeholders in property values, for example.
	 * @param ann the annotation to copy from
	 * @param bean the bean instance to copy to
	 * @param valueResolver a resolve to post-process String property values (may be {@code null})
	 * @param excludedProperties the names of excluded properties, if any
	 * @see org.springframework.beans.BeanWrapper
	 */
	public static void copyPropertiesToBean(Annotation ann, Object bean, @Nullable StringValueResolver valueResolver,
			String... excludedProperties) {

		Set<String> excluded = new HashSet<>(Arrays.asList(excludedProperties));
		Method[] annotationProperties = ann.annotationType().getDeclaredMethods();
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(bean);
		for (Method annotationProperty : annotationProperties) {
			String propertyName = annotationProperty.getName();
			if (!excluded.contains(propertyName) && bw.isWritableProperty(propertyName)) {
				Object value = ReflectionUtils.invokeMethod(annotationProperty, ann);
				if (valueResolver != null && value instanceof String) {
					value = valueResolver.resolveStringValue((String) value);
				}
				bw.setPropertyValue(propertyName, value);
			}
		}
	}

 

 

샘플 예제

 

간단하게 샘플 예제를 구현해보자.

 

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
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
class Calculator {
    private String name;
      
    public Calculator() {
        System.out.println("call Calculator()");
    }
    
    public int add(int a, int b) {
        System.out.println("call add()");
        int result = a + b;
        return result;
    }
    public int minus(int a, int b) {
        System.out.println("call minus()");
        int result = a - b;
        return result;
    }
}
 
public class ReflectionTest {
    
    public static void main(String[] args) throws Exception {
    
        
        Class<?> cls = Class.forName("Calculator");
        
        Field[] fields = cls.getDeclaredFields();
        
        for(Field field: fields) {
            System.out.println("field="+field.getName());
        }
        
        Calculator calculator = new Calculator();
        Method[] methods = cls.getDeclaredMethods();
        
        for(Method method: methods) {
            Object s = method.invoke(calculator, 4,3);
            System.out.println("result=" + s);
        }
        
        System.out.println("Class="+cls.getName());
    }
 
}
 
cs

 

field=name

call Calculator()

call add()

result=7

call minus()

result=1

Class=Calculator

 

 

 

 

장점

 

확장: 확장 가능한 인스턴스를 만들어 외부 사용자 정의 클래스를 작성 할 수 있다.
디버깅 및 테스트: 디버거는 리플렉션 속성을 사용하여 클래스의 private memeber를 체크할 수 있다.

 

 

단점

 

성능 : reflection은 성능이 느리므로 성능에 민감한 프로그램이라면, 자주 호출되는 코드에서는 피하는 것이 좋다.
내부 노출 : reflection은 클래스 추상화를 손상 시킬 수 있다. 즉, 클래스 코드의 변경이 있으면 동작의 변경이 따를 수 있다.

 

 

 

https://docs.oracle.com/javase/tutorial/reflect/index.html

 

Trail: The Reflection API (The Java™ Tutorials)

Uses of Reflection Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers

docs.oracle.com

 


Defualt Method

- 디폴트 메소드는 자바8 에서 더 유연하게 인터페이스를 만들 수 있도록 추가한 방법이다.

- 자바 8 이전에는 인터페이스의 메소드는 반드시 모두 상속해야만 했다. 

- 자바8 에서는 default라는 키워드를 통해 디폴트 메소드를 지정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

interface Calcuator {
 
 default void add(int a, int b) {
        System.out.println((a+b));
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b));
    }
}
 
 
 
 
public class Test implements  Calcuator{
      public void cal(){
           add(3,5);
       } 
}
    
 
cs

위와 같은 방법으로 상속받은 Calculator 인터페이스는 add, minus 라는 디폴트 메소드를 호출 할 수 있다.

또한 그동안 자바에서는 다중상속이 불가능한 것으로 알려져 있던 것을, 디폴트 메소드를 통해 어느정도 구현할 수 있다. 하지만 다이아몬드 상속 문제를 반드시 피해야 하는데 만일 인지하지 못할 경우 다음과 같은 컴파일 에러가 날 수 있다.

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
 

interface
 Calcuator {
 
    default void add(int a, int b) {
        System.out.println((a+b));
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b));
    }
 
}
 
interface  DoubleCalculator {
    default void add(int a, int b) {
        System.out.println((a+b)*2);
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b)*2);
    }
 
 
}
 
/**
 * 디폴트 메소드로 인해 어느 정도 다중 상속 개념이 될 수 있다. 다이아모몬드 상속 문제를 피해야한다.
 */
public class DefaultMethod implements  Calcuator, DoubleCalculator{
 
   
    public void cal() {
        add(3,5);
        minus(3,6);
    }
}
 
cs


위와 같은 경우, 컴파일 시 에러가 나는데   Interface의 상속을 통해 Override를 통해 문제를 해결할 수 있다.

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
 
 
interface Calcuator {
 
    default void add(int a, int b) {
        System.out.println((a+b));
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b));
    }
 
}
 
interface  DoubleCalculator {
    default void add(int a, int b) {
        System.out.println((a+b)*2);
    }
 
    default void minus(int a, int b) {
        System.out.println((a-b)*2);
    }
 
 
}
 
interface CalAndDoubleCal extends Calcuator,DoubleCalculator{
 
    @Override
    default void add(int a, int b){
        Calcuator.super.add(a,b);
        DoubleCalculator.super.add(a,b);
    };
    @Override
    default void minus(int a, int b) {
        Calcuator.super.minus(a,b);
        DoubleCalculator.super.minus(a,b);
    }
 
}
 
 
public class DefaultMethod implements CalAndDoubleCal{
 
    public void cal(){
        add(3,5);
    }
}
cs




함수형 인터페이스(Functinal Interface)

- 오직 하나의 추상 메소드를 지정한다.

- @Functional Interface 를 붙이는 것을 권장한다.

- 람다식 사용 가능


Predicate<T>

Java.util API 내부를 살펴보면 test라는 하나의 추상 메소드만 지정된다.  test는 제네릭 형식의 T 객체를 인수로 받아 boolean을 반환한다.

또한 Predicate 인터페이스는 복작한 predicate를 만들 수 있도록 negate, and, or 메소드를 제공하는데 이는 default 메소드이다.


1
2
3
4
5
6
7
8
        //predicate
       Predicate<String> nonEmptyString = s -> !s.isEmpty();
       Predicate<String> emptyString = s -> s.isEmpty();
       System.err.println(nonEmptyString.test("")); //false
       System.err.println(nonEmptyString.and(emptyString).test("")); //두개의 조건을 모두 만족해야하므로 false
       System.err.println(nonEmptyString.or(emptyString).test("")); //둘중 하나만 만족해도 되므로 true
       System.err.println(nonEmptyString.negate().test("")); // nonEmpytyString predicate의 반대상황을 테스트 true
 
cs



Consumer<T>

Java.util API 내부를 살펴보면 accept라는 하나의 추상 메소드만 지정된다.  accept는 제네릭 형식의 T 객체를 인수로 받아 void을 반환한다.


1
2
3
4
5
6
7
        //consumer
        Consumer<String> outConsumer = s -> System.out.println(s);
        Consumer<String> errConsumer = s -> System.err.println(s);
 
        outConsumer.accept("k");  //k
        outConsumer.andThen(errConsumer).accept("g"); // g, g
cs


Function<T,R>

Java.util API 내부를 살펴보면 aaply라는 하나의 추상 메소드만 지정된다.  appy는 제네릭 형식의 T 객체를 인수로 받아 제네릭 형태의 R객체를 반환한다.



1
2
3
4
5
6
7
    //function
        Function<String,String> lowerFunction = s -> s.toLowerCase();
        Function<String,String> upperFunction = s -> s.toUpperCase();
 
        System.out.println(lowerFunction.apply("S"));       //s
        System.out.println(lowerFunction.andThen(upperFunction).apply("S"));    // S
 
cs


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

primitive type과 boxed primitives  (0) 2019.11.24
Reflection  (0) 2019.09.20
[JAVA 8] - 함수형 프로그래밍(Functional Programming)  (0) 2019.02.17
# Enum (열거형)  (0) 2018.05.20
# NIO - Selector  (0) 2018.05.19

함수형 프로그래밍 (Functional-style programming)

부수 효과를 없애고 순수 함수를 만들어 모듈화 수준을 높이는 프로그래밍 패러다임

: 부수효과란  주어진 값 이외의 외부 변수 및 프로그램 실행에 영향을 끼치지 않아야 된다는 의미이며, 부수효과를 만족하는 함수를 순수함수라고 한다.


- 순수함수 : 모든 입력이 입력으로만, 모든 출력이 출력으로만 사용
- 비순수함수: 숨겨진 입력이나 출력이 존재


즉 함수형 프로그래밍은 순수함수를 지향, 최대한 숨겨진 입력과 출력을 제거하여 가능한  코드를 입력과 출력의 관계로 사용해야 한다.


객체지향 프로그래밍과 함수형 프로그래밍

1) 객체 지향은 명령형 프로그래밍이고, 함수형 프로그래밍은 선언형 프로그래밍이다.


: 우선 명령형 프로그래밍과 함수형프로그래밍의 차이의 핵심은 문제해결의 관점이다. 기존의 우리가 객체지향 프로그래밍을 할 때는 데이터를 어떻게 처리할 지에 대해 명령을 통해 풀어 나아갔다면, 함수형 프로그래밍은 선언적 함수를 통해 무엇을 풀어나아갈지 결정하는 것이다.


아직 Stream API에 대해 다뤄보진 않았지만, 간단히 샘플 코드를 통해 비교해본다면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
        List<String> myList =
                Arrays.asList("c1""a2""b3""4""5");
 
        // 기존방식
        for(int i=0; i<myList.size(); i++){
            String s = myList.get(i);
            if(s.startsWith("c")){
                System.out.println(s.toUpperCase());
            }
        }
 
        // stream API를 이용한 방식
        myList.stream()
              .filter(s -> s.startsWith("c"))
              .map(String::toUpperCase)
              .forEach(System.out::println);
 
 
cs


처음보면 크게 와 닿지 않을 수 있지만, 기존 방식과는 많이 다르다. 중요한 점은 프로그래밍 패러다임의 변화이다. 단순하게 함수를 선언함으로써 데이터들을 내가 원하는 방향으로 처리해 나아가고 있다는 점이다. 한눈에 보더라도 함수형프로그래밍을 통해 구현한 아래코드가 무엇을 하고있는지에 대해 명확하다.


2) 함수형 프로그래밍에서 함수는 1급객체 여야 한다.

위키피디아의 정의된 1급객체의 의미를 인용하면..

특정 언어의 일급 객체 (first-class citizens, 일급 값, 일급 엔티티, 혹은 일급 시민)이라 함은 컴퓨터 프로그래밍 언어 디자인에서 일반적으로 다른 객체들에 적용 가능한 연산을 모두 지원하는 객체를 가리킨다.


1급 객체(First class object)란 다음과 같은 조건을 만족하는 객체이다.

  • 변수나 데이터 구조안에 담을 수 있다.
  • 파라미터로 전달 할 수 있다.
  • 반환값(return value)으로 사용할 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
  • 동적으로 프로퍼티 할당이 가능하다.
  • 기존 데이터의 불변성(Immutable)


자바에서 그동안 함수는 메소드를 값으로 표현할 수 없어 1급 시민이 아닌 2급 시민으로써 역할 해왔다. 하지만 자바8에서 함수는 1급시민이 되어 함수형 프로그래밍의 핵심이 되었다.


사실 위의 2가지 특징이 함수형 프로그래밍의 모든 것을 나타낸다고 생각하는데, 위의 특징이 단점이 될 수도 있다. 예를 들면, 덕분에 코드의 간결성이 증가되었지만 그만큼 학습의 필요로 한다. 함수형 프로그래밍을 학습하는 것은 결코 쉽지 않기 때문에 오랜 학습이 필요하다.



Java 8만의 3가지 기술

- 인터페이스의 디폴트 메소드

- 메소드 레퍼런스, 람다

- Stream API




참조

- https://velog.io/@kyusung/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9A%94%EC%95%BD#%EB%AA%85%EB%A0%B9%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EA%B3%BC%EC%9D%98-%EB%B9%84%EA%B5%90

- Java8 in action


Enum (열거형) - 서로 연관된 Constants(상수)


Java에서도 Enum을 지원하고 다양한 장점을 갖고 있다.


1) C/C++ 에서의 Enum의 사용은 단순 상수로서의 기능을 갖고 있었다면, Java에서는 여러 기능을 갖춘 클래스로서의 역할을 수행한다.

2) 불필요한 혹은 중복된 코드를 줄일 수 있다.

3) 리팩토링시 변경하고자 하는 범위를 최소화할 수 있다.




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
public enum HttpStatusCode {
    
    Success(200), Forbidden(403),NotFound(404), InternalServerError(500);
    
    private int num;
    
    HttpStatusCode(int num){
        this.num = num;
    }
    
    public static HttpStatusCode valueOf(int num) {
        switch (num) {
            case 200 :
                return Success;
            case 403 :
                return Forbidden;
            case 404 :
                return NotFound;
            case 500:
                return InternalServerError;
            default :
                return null;
        }
    }
    
    public int getStatusCode(){
        return this.num;
    }    
}
 
 
cs



-> 위 예제처럼, httpStatus 상태코드를 미리 지정해 놓으면 효율적으로 관리 할 수 있다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
public class Main {
    
    public static void main(String[] args){
        
        System.out.println(HttpStatusCode.valueOf("Success"));
        System.out.println(HttpStatusCode.valueOf(200));
        System.out.println(HttpStatusCode.valueOf(200)==HttpStatusCode.valueOf("Success"));
    
        // 해당하는 값이 없으면 IllegalArgumentException 발생
        System.out.println(HttpStatusCode.valueOf("success"));
    }
}
 
cs




 정의 되지 않은 값을 가져올 경우 Exception이 발생한다.





=> enum을 사용한다면, 상태코드만으로는 알 수 없는 오류에 대해 명확히 알 수 있었을 뿐 아니라 사용되는 값들의 범위에 대해 용이하게 관리 할 수 있다.  하지만, 빈번하게 추가/변경/삭제가 이뤄진다면 기존에 작성된 코드와의 문제가 발생할 요소가 있으니 주의해야 한다.













NIO (New IO)


자바에서 Socket 통신을 할 때 기존 IO 방식에서는 네트워크 처리와 속도에 대해 매우 제한적이었다.


하지만 JDK1.3 버전 이후 Java IO 대신 NIO를 사용하여 한계를 보안하였다.





[IO Process]

 



: 블로킹 문제를 해결하기 위해 클라이언트마다 쓰레드가 추가 되었고 멀티쓰레드 환경에서 수많은 쓰레드로 인해 메모리의 오버헤드가 발생할 수 있다.


  






[NIO Process]



: Selector는 멀티쓰레드 환경에서 여러 클라이언트의 작업요청에 대응할 수 있는 좋은 방법이다. 

 작업을 수행하는 work 쓰레드는 1개 이상이 될 수 있다.   

 물론 클라이언트가 너무 많으면, 이 과정에서 selector와 work 쓰레드가 문제가 발생할 수 있다.

 Accept와 Read 단계에서 SelectorPoolThreadPool을 사용하여 성능을 고려할 수 있다.






Selector Server 간단한 예제



    

package selector; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.Iterator; public class Server implements Runnable { private Selector select; private InetSocketAddress sockAddr; public Server(String host, int port) { sockAddr = new InetSocketAddress(host, port); } public void accept(SelectionKey key) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel channel; try { channel = serverChannel.accept(); channel.configureBlocking(false); Socket socket = channel.socket(); SocketAddress remoteAddr = socket.getRemoteSocketAddress(); System.out.println("Connected to: " + remoteAddr); channel.register(key.selector(), SelectionKey.OP_READ); // 읽을 수 있는 모드로 전환 } catch (Exception e) { // TODO: handle exception } } public void read(SelectionKey key) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int numRead = -1; try { numRead = channel.read(buffer); if (numRead == -1) { // 아직 읽지 않았다면 읽는다. channel.close(); key.cancel(); return; } else { System.out.println("read:" + new String(buffer.array())); channel.close(); key.cancel(); return; //읽고 종료 } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { // TODO Auto-generated method stub try { // selector 열기 select = Selector.open(); // 서버 소켓 채널 열기 ServerSocketChannel sockChannel = ServerSocketChannel.open(); sockChannel.configureBlocking(false); // blocking 모드 false sockChannel.bind(sockAddr); System.out.println("Start Server:"+sockChannel.getLocalAddress()); // 서버 셀렉터를 클라이언트 연결을 수용하기 위한 키로 등록합니다. sockChannel.register(select, SelectionKey.OP_ACCEPT); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } while (true) { try { select.select(); // 여기서 Blocking 됨. 등록된 키를 가져온다. // select.selectNow(); // 비동기 처리. Iterator<?> keys = select.selectedKeys().iterator(); while (keys.hasNext()) { SelectionKey key = (SelectionKey) keys.next(); if (!key.isValid()) { // 사용가능한 상태가 아니면 그냥 넘어감. continue; } if (key.isAcceptable()) { // select가 accept 모드이면 accept(key); } else if (key.isReadable()) { // select가 read 모드이면 read(key); } keys.remove(); // 중요함 . 처리한 키는 제거 } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) { // TODO Auto-generated method stub Server server = new Server("localhost", 8080); server.run(); } }






 간단한 채팅 프로그램 만들기


- 다대다 클라이언트 간의 채팅

- 서버는 클라이언트에게 메시지를 뿌려주는 역할




[Server]

package tcp4;


import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.ArrayList;

import java.util.List;


public class Server {


ServerSocket serSock;

List<Socket> sockList;


public Server() throws IOException {

serSock = new ServerSocket(10081);

sockList = new ArrayList<>();

}


public void all_client_write(Socket sock, String msg) {


BufferedOutputStream bos = null;

try {

String info = "[" + sock.getRemoteSocketAddress() + "] : ";

for (int i = 0; i < sockList.size(); i++) {

bos = new BufferedOutputStream(sockList.get(i).getOutputStream());

System.out.println("send to " + sockList.get(i).toString() + ":" + msg);


String inFoMsg = info + msg;

bos.write(inFoMsg.getBytes("UTF-8"));

bos.flush();


}


} catch (IOException e) {

// TODO Auto-generated catch block

try {

bos.close();

sock.close();

removeSock(sock);

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}


System.out.println("server write exception!");


e.printStackTrace();

}


}


public void stop(Thread thread) {

thread.interrupt();

}

public void removeSock(Socket sock) {

System.out.println("socket is close");

System.out.println("====================================");

System.out.println("현재 접속: " + sockList.size());

for (int i = 0; i < sockList.size(); i++) {

if (sockList.get(i) == sock) {

try {

sockList.get(i).close();

sockList.remove(i);

break;

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}


}

}

for (int i = 0; i < sockList.size(); i++) {

System.out.println(sockList.get(i).toString());

}

System.out.println("====================================\n\n");


}


public void read(Socket sock) {

Thread thread = null;


thread = new Thread(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

BufferedInputStream bis = null;

boolean flag = true;

try {

while (flag) {


bis = new BufferedInputStream(sock.getInputStream());

byte b[] = new byte[256];

int data = bis.read(b);


if (data != 0) {

String msg = new String(b);

System.out.println("[" + sock.getLocalAddress() + "] : " + msg);


all_client_write(sock, msg);

}


}

} catch (IOException e) {

// TODO Auto-generated catch block

try {

sock.close();

removeSock(sock);

} catch (IOException e2) {

// TODO Auto-generated catch block

e2.printStackTrace();

}

}


}


});


thread.start();


}


public void startServer() {

System.out.println("start Server!");

Thread thread = new Thread(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

try {

while (true) {

Socket sock = null;

sock = serSock.accept();

sockList.add(sock);


System.out.println("====================================");

System.out.println("현재 접속: " + sockList.size());

for (int i = 0; i < sockList.size(); i++) {

System.out.println(sockList.get(i).toString());

}

System.out.println("====================================\n\n");


read(sock);

}


} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

System.out.println("socket accept excpetion");

return;

}

}

});

thread.start();


}


public static void main(String[] args) {

// TODO Auto-generated method stub

try {

new Server().startServer();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

System.out.println("server start exception");

}

}


}

[Client]

package tcp4;


import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.Socket;

import java.net.UnknownHostException;

import java.util.Scanner;


public class Client {


Socket sock;

String host;

int port;


public void connect() throws UnknownHostException, IOException {

host = "localhost";

port = 10081;

sock = new Socket();

sock.connect(new InetSocketAddress(host, port));

}


public void read() {

Thread thread = new Thread(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

BufferedInputStream bis = null;

try {

while (true) {


bis = new BufferedInputStream(sock.getInputStream());

byte[] b = new byte[256];

int data = bis.read(b);


if (data != 0) {

System.out.println("\n");

System.out.println(new String(b));

}

}

} catch (IOException e) {

// TODO Auto-generated catch block


System.out.println("client read exception!");

e.printStackTrace();


try {

bis.close();

sock.close();

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

}

}

});

thread.start();

}


public void chatStart() {

Scanner scan = new Scanner(System.in);

BufferedOutputStream bos=null;

while (true) {

System.out.print("입력>>");

String msg = scan.nextLine();


try {

bos = new BufferedOutputStream(sock.getOutputStream());

bos.write(msg.getBytes("UTF-8"));

bos.flush();

} catch (IOException e) {

// TODO Auto-generated catch block


System.out.println("client write exception!");

try {

bos.close();

sock.close();

break;

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

e.printStackTrace();

}

}

}


public static void main(String[] args) {

// TODO Auto-generated method stub

Client client = new Client();

try {

client.connect();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

System.out.println("connect exception");

}

client.read();

client.chatStart();

}


}










====> 결과







소켓이 close() 됐을 때의 예외처리는 무엇보다 중요하다.

반드시 예외를 잡고 처리해야한다. 

시간이 없어서 빈약하게 처리했지만, 로직을 작성하는 것보다 메모리 누수 혹은 정확한 처리가 더 중요한 것 같다.




다음은 selector 와  asyncrhronousServerSocket에 대해 마지막으로 알아보자.




https://docs.oracle.com/javase/7/docs/api/java/nio/channels/AsynchronousServerSocketChannel.html














client / server



server 기반 모델   vs   p2p 모델




server 기반 모델


- 전용 서버를 두는 모델

- 안정적인 서비스 제공

- 공유 데이터의 관리와 보안이 용이





P2P 모델


- 전용 서버없이 각 클라이언트가 서버역할까지 동시에 수행

- 자원 활용의 극대화

- 보안이 취약하고 자원관리의 어려움







TCP / UDP


소켓 : 프로세스간의 통신에 사용되는 end point ( 소켓간의 커넥션이 되면 point to point)


TCP/UDP 는  OSI 7 계층의 전송 계층에 해당( tcp/ip protocol 에 포함 , 4layer)



 항목

tcp 

udp 

 연결방식

 연결기반

- 연결 후 통신

- 1:1 통신

비 연결기반

- 1:1 , 1:n, n:n 통신

- 연결없이 통신 ( 소포를 예를 듬) 

 특징

 - 신뢰성 있는 데이터 전송

 - 데이터의 전송 순서 보장

 - 데이터의 수신 여부 확인

 - 패킷 관리 할 필요 없다.

 - udp 보다 느림

- 비신뢰성 전송

- 데이터의 전송 순서가 바뀔 수 있다.

- 수신 여부 확인하지 않음

- 패킷 관리해야함.

 

 관련클래스

  serversocket, socket

 datagramsocket, datagrampacket, multicastsocket












+ Recent posts