반응형
SMALL

'분류 전체보기'에 해당되는 글 209건

반응형
LIST
반응형
SMALL

이번시간엔 Nio java를 이용한 에코서버 만들기에 대해 포스팅 해보려고 한다. 사실 별내용은 없다. 기존에 만들었었던 기초적인 서버만들기와 비슷한 점이 많다. 에코서버의 전체 그림은 다음과 같다.



1. Serverinitializer class를 만들고 다음과 같이 코딩한다.


import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.channels.AsynchronousChannelGroup;

import java.nio.channels.AsynchronousServerSocketChannel;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


public class ServerInitializer 

{

  private static int PORT = 5000; ->포트수설정

  private static int threadPoolSize = 8; ->스레드풀사이즈 설정

  private static int initialSize = 4; ->이니셜 사이즈 설정

  private static int backlog = 50; 

  public static void main(String[] args)

{

  System.out.println("SERVER START!");

  ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize); ->threadPoolSize 공간 할당

try

{

  AsynchronousChannelGroup group = AsynchronousChannelGroup.withCachedThreadPool(executor, initialSize);->캐시스레드 풀생성

  AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open(group);-> 스트림 지향의 리스닝 소켓을 위한 비동기 생성

  listener.bind(new InetSocketAddress(PORT), backlog);

  listener.accept(listener, new Dispatcher()); -> 리스닝 수용

}

  catch (IOException e)

{

   e.printStackTrace();

}

}

}


2. Dispatcher class를 생성하고 다음과 같이 코딩한다.

import java.nio.ByteBuffer;

import java.nio.channels.AsynchronousServerSocketChannel;

import java.nio.channels.AsynchronousSocketChannel;

import java.nio.channels.CompletionHandler;



public class Dispatcher implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel>

{

private int DATA_SIZE = 1024;


@Override

public void completed(AsynchronousSocketChannel channel, AsynchronousServerSocketChannel listener

{

listener.accept(listener, this);

ByteBuffer buffer = ByteBuffer.allocate(DATA_SIZE);

channel.read(buffer, buffer, new EchoHandler(channel));

}


@Override

public void failed(Throwable exc, AsynchronousServerSocketChannel listener

{

}


}


3. EchoHandler class를 생성하고 다음과 같이 코딩해준다.

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.channels.AsynchronousSocketChannel;

import java.nio.channels.CompletionHandler;



public class EchoHandler implements CompletionHandler<Integer, ByteBuffer>

{

private AsynchronousSocketChannel channel; ->비동기식소켓채널 설정

public EchoHandler(AsynchronousSocketChannel channel)

{

this.channel = channel;

}


@Override

public void completed(Integer result, ByteBuffer buffer)

{

if (result == -1)

{

try

{

channel.close(); ->close안해주면 메모리에 내용이 그대로 남아있다. 꼭 닫아주자.

}

catch (IOException e)

{

   e.printStackTrace();

}

}

else if (result > 0)

{

   buffer.flip();

   String msg = new String(buffer.array());

   System.out.println("echo: " + msg);

}

}


@Override

public void failed(Throwable exc, ByteBuffer attachment) {

}


}


4. 이대로 정확히 다 설정해주면 다음과같이 실행되게 된다.



앞에 에코가 나오면서 정확히 잘 동작되고 있다. 


반응형
LIST

'Server > Server의 기초' 카테고리의 다른 글

서버의 전체적인 그림  (0) 2016.08.10
Dispatcher을 이용한 또하나의 Protocol생성  (0) 2016.08.07
Dispatcher  (0) 2016.08.07
블로그 이미지

만년필석사

,

Reactor Thread

Server/Reactor 2016. 8. 11. 00:20
반응형
SMALL

이번 시간엔 Reactor Thread에 대해 포스팅 해보려고 한다. 서버는 한번에 한가지 일만을 처리하지 않는다. 동시다발적으로 여러가지 프로토콜을 처리하는것이 서버이다. 이렇게 동시에 처리 할 수 있도록 해주는 것이 Thread기능이다. 하지만 Thread도 적당히 있으면 좋은데 이것도 너무 많아지게 되면 갑자기 처리량이 증가해서 서버는 당연히 느려지게 된다. 그러다보면 과부하가 발생하는것은 기본이다. 그러다보면 메모리에도 부담이 가게되고 여러모로 악재가 겹친다. 그래서 사용되는 것이 Thread Pool기능이다. 접속이 들어오기 전에 미리 스레드를 만들어서 스레드를 생성하는 지연을 줄이고 스레드의 갯수를 제한하고 다 쓴 스레드를 재활용한다. 



스레드풀을 만드는 방법은 다음과 같다.


1. Demultiplexer class파일을 만들고 다음과같이 코딩한다.


import java.io.IOException;

import java.io.InputStream;

import java.net.Socket;


public class Demultiplexer implements Runnable

{

   private final int HEADER_SIZE = 6;

   private Socket socket;

   private HandleMap handleMap;

public Demultiplexer(Socket socket, HandleMap handleMap)

{

    this.socket = socket;

    this.handleMap = handleMap;

}


public void run()

{

try

{

   InputStream inputStream = socket.getInputStream();

   byte[] buffer = new byte[HEADER_SIZE];

   inputStream.read(buffer);

   String header = new String(buffer);

   handleMap.get(header).hanedleEvent(inputStream);

   socket.close();

}

catch (IOException e)

{

    e.printStackTrace();

   }

  }

}


2. Dispatcher interface를 만들어주고 다음과 같이 코딩한다.


import java.net.ServerSocket;


public interface Dispatcher 

{

   public void dispatch(ServerSocket serverSocket, HandleMap handler);


}


3. EventHandler interface를 만들어주고 다음과 같이 코딩한다.

import java.io.InputStream;


public interface EventHandler 

{

    public String getHandler();

    public void handleEvent(InputStream inputStream);


}


4.  HandleMap class를 만들고 다음과 같이 코딩한다.


import java.util.HashMap;


public class HandleMap extends HashMap<String, EventHandler> 

{


}


5. Reactor class를 만들어주고 다음과 같이 코딩한다.


import java.io.IOException;

import java.net.ServerSocket;


public class Reactor 

{

   private ServerSocket serverSocket;

   private HandleMap handleMap;

public Reactor(int port)

{

    handleMap = new HandleMap();

try

{

    serverSocket = new ServerSocket(port);

}

catch (IOException e)

{

   e.printStackTrace();

}

}

public void startServer() 

{

   Dispatcher dispatcher = new ThreadPoolDispatcher();

   dispatcher.dispatch(serverSocket, handleMap);

}

public void registerHandler(EventHandler handler)

{

   handleMap.put(handler.getHandler(), handler);

}

public void removeHandler(EventHandler handler)

{

   handleMap.remove(handler.getHandler());

}

}


6. Serverinitializer class를 만들어주고 다음과 같이 코딩한다.

import java.io.IOException;

import java.net.ServerSocket;



public class Serverinitializer 

{


public static void main(String[] args

{

  int port = 5000;

  System.out.println("Server ON :" + port);

  Reactor reactor = new Reactor(port);

  reactor.registerHandler(new StreamSayHelloEventHandler());

  reactor.registerHandler(new StreamUpdateProfileEventHandler());

  reactor.startServer();

}

}


7. StreamSayHelloEventHandler class를 만들어주고 다음과 같이 코딩한다.


import java.io.IOException;

import java.io.InputStream;

import java.util.StringTokenizer;


public class StreamSayHelloEventHandler implements EventHandler

{

   private static final int DATA_SIZE = 512;

   private static final int TOKEN_NUM = 2;

@Override

public String getHandler() 

{

   return "0x5001";

}

public void handleEvent(InputStream inputStream)

{

try

{

   byte[] buffer = new byte[DATA_SIZE];

   inputStream.read(buffer);

   String data = new String(buffer);

   String[] params = new String[TOKEN_NUM];

   StringTokenizer token = new StringTokenizer(data, "|");

   int i = 0;

 while(token.hasMoreTokens())

{

   params[i] = token.nextToken();

   ++i;

}

   sayHello(params);

}

catch (IOException e)

{

   e.printStackTrace();

}

}


8. StreamUpdateProfileEventHandler class를 생성하고 다음과 같이 코딩한다.


import java.io.IOException;

import java.io.InputStream;

import java.util.StringTokenizer;


public class StreamUpdateProfileEventHandler implements EventHandler

{

private static final int DATA_SIZE = 1024;

private static final int TOKEN_NUM = 5;

@Override

public String getHandler() 

{

return "0x6001";

}

public void handleEvent(InputStream inputStream)

{

try

{

byte[] buffer = new byte[DATA_SIZE];

inputStream.read(buffer);

String data = new String(buffer);

String[] params = new String[TOKEN_NUM];

StringTokenizer token = new StringTokenizer(data, "|");

int i = 0;

while(token.hasMoreTokens())

{

params[i] = token.nextToken();

++i;

}

updateProfile(params);

}

catch (IOException e)

{

e.printStackTrace();

}

private void updateProfile(String[] params)

{

System.out.println("UpdateProfile ->" +

            " id :" + params[0] +

            " password : " + params[1] +

            " name : " + params[2] +

            " age : " + params[3] + 

            " gender: " + params[4]);

}

}

private void sayHello(String[] params)

{

   System.out.println("SayHello -> name: " + params[0] + "age : " + params[1]);

}

}


9. TestClient class를 생성하고 다음과 같이 코딩해준다.


import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;


public class TestClient {


public static void main(String[] args

{

   System.out.println("Client ON");

   try

   {

      String message;

      Socket socket = new Socket("127.0.0.1", 5000);

      OutputStream out = socket.getOutputStream();

      message = "0x5001|이건주|26";

      out.write(message.getBytes());

      socket.close();


      Socket socket2 = new Socket("127.0.0.1", 5000);

      OutputStream out2 = socket2.getOutputStream();

      message = "0x6001|Lee|1234|이건주|26|남성";

      out2.write(message.getBytes());

      socket2.close();

    } 

    catch (UnknownHostException e)

    {

        e.printStackTrace();

    } 

    catch (IOException e)

   {

       e.printStackTrace();

   }

}

}


10. ThreadPerDispatcher class를 생성하고 다음과 같이 코딩한다.

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;


public class ThreadPerDispatcher implements Dispatcher

{

  public void dispatch(ServerSocket serverSocket, HandleMap handleMap)

{

while(true)

{

try

{

   Socket socket = serverSocket.accept();

   Runnable demultiplexer = new Demultiplexer(socket, handleMap);

   Thread thread = new Thread(demultiplexer);

   thread.start();

}

catch (IOException e)

{

   e.printStackTrace();

}

}

}

}


11. ThreadPoolDispatcher class를 생성하고 다음과 같이 코딩한다.


import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;


public class ThreadPoolDispatcher implements Dispatcher

{

static final String NUMTHREADS = "8";

static final String THREADPROP = "Threads";

private int numThreads;

public ThreadPoolDispatcher()

{

   numThreads = Integer.parseInt(System.getProperty(THREADPROP, NUMTHREADS));

}

public void dispatch(final ServerSocket serverSocket, final HandleMap handleMap)

{

for(int i = 0; i < (numThreads - 1); i++)

{

   Thread thread = new Thread()

{

public void run()

{

   dispatchLoop(serverSocket, handleMap);

}

};

  thread.start();

  System.out.println("Created and started Thread = " + thread.getName());

}

System.out.println("Iterative server starting in main thread" +

        Thread.currentThread().getName());

dispatchLoop(serverSocket, handleMap);

}

private void dispatchLoop(ServerSocket serverSocket, HandleMap handleMap

{

while(true)

{

try

{

   Socket socket = serverSocket.accept();

   Runnable demultiplexer = new Demultiplexer(socket, handleMap);

   demultiplexer.run();

}

catch (IOException e)

{

   e.printStackTrace();

}

}

}

}


12. 정상적으로 동작하면 다음과 같이 동작되어야 한다. 동시Pool의 갯수만큼의 스레드로 동시에 처리하고 나머지 접속에 대해서는 처리를 미룬다.




반응형
LIST

'Server > Reactor' 카테고리의 다른 글

Reactor의 기능  (0) 2016.08.10
블로그 이미지

만년필석사

,
반응형
SMALL

서버는 공부해보면 생각외로 리눅스도 많이 사용하고 어렵게 느껴질때가 많았다. 그래서 도데체 뭐가 어떻게 만들어지고 이게 왜 만들어져야 돌아가고 이게 무슨 역할을 하는지 모르는 경우가 허다했다. 그래서 전체적으로 서버실행의 전체적인 그림이 어떻게 그려지는지에 대해 알아보았다.


1. ServerInitializer(서버를 on한다. initializer의 뜻만 봐도 유추는 가능하다. ) 2. Demultiplexer(데이터를 받아서(accept) dispatcher에게 넘긴다. 항상 이것도 서버를 만들다보면 자주 접한다. 다른 클래스파일들 상속시켜주는 곳이기도 하다.) 

3. Dispatcher(해당 프로토콜에 해당하는 eventHandler에게 넘겨준다.

4. EventHandler(프로토콜에 해당하는 작업을 수행 하고 클라이언트에게 정보를 전달해준다.)

5. Client server(말그대로 클라이언트 서버. 어떤걸 동작시킬 것인지, 화면에 어떤                           것을 출력해 줄지를 결정시켜준다.)


전체적인 큰 그림은 위에 설명한 바와 같다. 하지만 경우에 따라 그 구조는 또 달라질 수 있다. 하지만 저 위에 써놓은 5개의 큰 그림만 그려놔도 세세한것이 나왔을때도 유연하게 이해를 할 수 있다. 그 전에 포스팅에서도 몇개 전체적인 구조에 대한 그림을 올려놓은 적이 있다. 앞으로 포스팅 할 때도 전체적인 그림을 올려놓을텐데 저 위에 5개를 기본으로 해서 이해를 하면 아주 수월하게 서버만들기를 진행할 수 있을 것이다.




반응형
LIST

'Server > Server의 기초' 카테고리의 다른 글

nio java를 이용한 에코서버 만들기  (0) 2016.08.12
Dispatcher을 이용한 또하나의 Protocol생성  (0) 2016.08.07
Dispatcher  (0) 2016.08.07
블로그 이미지

만년필석사

,

Reactor의 기능

Server/Reactor 2016. 8. 10. 12:23
반응형
SMALL

이번시간엔 Reactor에 대해 포스팅해보려고 한다. 앞서 포스팅에서 Protocol을 2개로 만들어서 서버를 만들어 봤었다. 하지만 문제가 되는게 또다른 프로토콜을 추가하게 되면 또 클래스를 정의하고 스위치문에다 포트번호를 넣어주고 일일히 그렇게 다 만들어 줘야한다. 아래와 같은 그림처럼;;



이렇게 해서 어느세월에 프로토콜을 정의 하려 하는지... 그래서 필요한 것이 바로 Reaactor 기능이다. 



Reactor의 구조를 대강 살펴보면.... 아래 그림과 같다.








- Reactor Pattern을 사용하여 프로토콜 추가에 유연한 서버를 만든다.

- Map을 이용하여 편하게 클래스 관리를 해본다.


이것이 이번 포스팅의 목표다!! 리엑터기능을 이용한 프로토콜 정의하기!!

만드는 법은 아래와 같다.


1. EventHandler라는 interface를 생성해주고 다음과 같이 코딩한다.

import java.io.InputStream;


public interface EventHandler 

{

   public String getHandler();

   public void handleEvent(InputStream inputStream);


}


2.  HandleMap이라는 클래스를 정의해주고 다음과 같이 코딩한다.

import java.util.HashMap;


public class HandleMap extends HashMap<String, EventHandler>  ->HandleMap이라고 하는 HashMap을 상속

{


}


3. 기존에 있었던 StreamSayHelloProtocol을 StreamSayHelloEventHandler로 리액터 시켜 이름을 바꿔주고 다음과 같이 코딩해준다.

import java.io.IOException;

import java.io.InputStream;

import java.util.StringTokenizer;


public class StreamSayHelloEventHandler implements EventHandler -> 상속받은것

{

private static final int DATA_SIZE = 512;

private static final int TOKEN_NUM = 2;

@Override

public String getHandler() 

{

   return "0x5001";

}

public void handleEvent(InputStream inputStream)

{

try

{

byte[] buffer = new byte[DATA_SIZE];

inputStream.read(buffer);

String data = new String(buffer);

String[] params = new String[TOKEN_NUM];

StringTokenizer token = new StringTokenizer(data, "|");

int i = 0;

while(token.hasMoreTokens())

{

     params[i] = token.nextToken();

     ++i;

}

sayHello(params);

}

catch (IOException e)

{

   e.printStackTrace();

}

}

private void sayHello(String[] params)

{

     System.out.println("SayHello -> name: " + params[0] + "age : " + params[1]);

}

}


4. StreamUpdateProfileProtocol도 리액터 시켜 StreamUpdateProfileEventHandler로 이름을 바꿔주고 다음과 같이 코딩해준다.

import java.io.IOException;

import java.io.InputStream;

import java.util.StringTokenizer;


public class StreamUpdateProfileEventHandler implements EventHandler

{

  private static final int DATA_SIZE = 1024;

  private static final int TOKEN_NUM = 5;

  @Override

  public String getHandler() 

{

   return "0x6001";

}

public void handleEvent(InputStream inputStream)

{

try

{

byte[] buffer = new byte[DATA_SIZE];

inputStream.read(buffer);

String data = new String(buffer);

String[] params = new String[TOKEN_NUM];

StringTokenizer token = new StringTokenizer(data, "|");

int i = 0;

while(token.hasMoreTokens())

{

   params[i] = token.nextToken();

   ++i;

}

updateProfile(params);

}

catch (IOException e)

{

   e.printStackTrace();

}

}

private void updateProfile(String[] params)

{

System.out.println("UpdateProfile ->" +

            " id :" + params[0] +

            " password : " + params[1] +

            " name : " + params[2] +

            " age : " + params[3] + 

            " gender: " + params[4]);

}

}


5. Reactor 클래스를 만들어주고 다음과 같이 코딩해준다.

import java.io.IOException;

import java.net.ServerSocket;


public class Reactor 

{

private ServerSocket serverSocket;

private HandleMap handleMap;  ->리액터에서 관리할 HandleMap 등록

public Reactor(int port)

{

   handleMap = new HandleMap();  ->리액터에서 관리할 HandleMap등록

try

{

   serverSocket = new ServerSocket(port);

}

catch (IOException e)

{

   e.printStackTrace();

}

}

public void startServer() 

{

  Dispatcher dispatcher = new Dispatcher();

while(true)

{

  dispatcher.dispatch(serverSocket, handleMap);

}

}

public void registerHandler(EventHandler handler)

{

  handleMap.put(handler.getHandler(), handler); ->핸들러의 헤더등록, 핸들러등록

}

public void removeHandler(EventHandler handler)

{

  handleMap.remove(handler.getHandler()); ->핸들러의 헤더등록, 핸들러등록

}

}


6. Serverinitializer로 돌아와 리액터를 등록해 준다.

import java.io.IOException;

import java.net.ServerSocket;



public class Serverinitializer 

{


public static void main(String[] args

{

  int port = 5000;

  System.out.println("Server ON :" + port);

  Reactor reactor = new Reactor(port);

  reactor.registerHandler(new StreamSayHelloEventHandler());

  reactor.registerHandler(new StreamUpdateProfileEventHandler());

  reactor.startServer();

}

}


7. Dispacher 클래스를 만들고 다음과 같이 코딩해준다.

import java.io.IOException;

import java.io.InputStream;

import java.net.ServerSocket;

import java.net.Socket;


public class Dispatcher

{

private final int HEADER_SIZE = 6;

public void dispatch(ServerSocket serverSocket, HandleMap handleMap)

{

try

{

Socket socket = serverSocket.accept();

demultiplex(socket, handleMap);

}

catch (IOException e)

{

e.printStackTrace();

}

}

public void demultiplex(Socket socket, HandleMap handleMap)

{

try

{

InputStream inputStream = socket.getInputStream();

byte[] buffer = new byte[HEADER_SIZE];

inputStream.read(buffer);

String header = new String(buffer);

handleMap.get(header).handleEvent(inputStream);

}

catch(IOException e)

{

e.printStackTrace();

}

}

}


8. 기존에 만들어 놨던 TestClient를 실행해보면....


다음과 같이 그 전에 만들어 놨던 것과 같이 잘 실행된다는 걸 볼 수 있다. 이젠 만들어논 클래스 파일들을 핸들러맵으로 상속시켜서 리액터 시켜놓으면 이상한 번거로움 없이 잘 실행 시킬 수 있다는 것을 보여주고 있다.




반응형
LIST

'Server > Reactor' 카테고리의 다른 글

Reactor Thread  (0) 2016.08.11
블로그 이미지

만년필석사

,

가상메모리

운영체제 2016. 8. 10. 00:17
반응형
SMALL

1. 프로세스와 가상 메모리


모든 프로세스는 자신만의 가상 주소 공간을 가지고 있다.

32비트/64비트 프로세스는 각 비트수에 맞게 최대 4GB/16GB의 주소 공간을 가진다.


모든 프로세스들은 자신만의 주소 공간을 가지기 때문에,

특정 프로세스 내에서 쓰레드가 수행될 때 해당 쓰레드는 프로세스가 소유하고 있는 메모리에 대해서만 접근이 가능하다. 다른 프로세스에 의해 소유된 메모리는 숨겨져 있으며, 접근이 불가능하다.


윈도우에서는 운영체제 자체가 소유하고 있는 메모리 또한 숨겨져 있다.

이는 특정 프로세스의 수행 중인 쓰레드가 운영체제의 데이터에 접근하는 것이 불가능함을 의미한다.


가상 메모리는 프로세스의 logical memory와 physical memory를 분리하기 위해 만들었다.


이를 이용하여, logical memory가 physical memory보다 커지는 것을 가능케 할 수 있다.


하나의 프로세스 logical memory가 physical memory보다 커지는 것도 가능하며,

여러 프로세스의 logical memory 총합이 physical memory보다 커지는 것도 가능한 것이다.


이처럼, 프로세스가 실제 필요로 하는 부분만 메모리로 올리는 Demand-Paging 기법을 사용한다.


이를 기억해두기 위해 최근의 운영체제들은 디스크 공간을 메모리처럼 활용할 수 있는 기능을 가지고 있다.

디스크 상에 존재하는 이러한 파일을 paging file (또는 swap file)이라고 하며, 

모든 프로세스가 사용할 수 있는 가상 메모리로 사용된다.


여기서 말하는 하드 스왑이라는 페이징 파일에서 실제 물리 메모리로 올리고 내리고 하는 일련의 작업을 이야기하는 것이다. 애플리케이션 관점에서 보면, 페이징 파일을 사용하면 애플리케이션이 사용할 수 있는 램의 크기가 증가한 것과 같은 효과를 가져온다. 만일 PC에 4GB의 램이 있고, 디스크 상에 4GB의 페이징 파일이 있다면, 수행 중인 애플리케이션은 PC에 8GB의 램이 있는 것과 동일한 효과를 누릴 수 있다.


한정된 물리 메모리의 한계를 극복하고자 디스크와 같은 느린 저장장치를 활용해,

애플리케이션들이 더 많은 메모리를 활용할 수 있게 해 주는 것이 가상 메모리이다.



2. 프로세스 가상 주소 공간의 분할


각 프로세스의 가상 주소 공간은 분할되어 있으며, 각각의 분할 공간을 파티션(partition)이라고 한다.


주소 공간의 분할 방식은 운영체제의 구현 방식에 따라 서로 다를 수 있으며,

윈도우 계열에서도 커널이 달라지면, 그 구조가 조금씩 달라지곤 한다.


처음 32비트 프로세스는 4GB의 주소 공간을 가질 수 있다고 했다.

하지만, 사용자가 이 4GB를 모두 사용할 수 있는 것은 아니고, 약 2GB밖에 사용하지 못한다.

32비트 주소 공간이 아래와 같이 분할되어 있기 때문이다.

Null 포인터 할당 파티션 : 0x00000000 ~ 0x0000FFFF

유저 모드 파티션 : 0x00010000 ~ 0x7FFEFFFF

64KB 접근 금지 파티션 : 0x7FFF0000 ~ 0x7FFFFFF

커널 모드 파티션 : 0x80000000 ~ 0xFFFFFFFF


1) Null 포인터 할당 파티션


프로그래머가 NULL 포인터 할당 연산을 수행할 경우를 대비하기 위해 준비된 영역이다.

만일 프로세스의 특정 쓰레드가 이 파티션에 대해 읽거나 쓰기를 시도하게 되면 접근 위반(access violation)이 발생한다.


2) 유저모드 파티션


프로세스의 주소 공간 내에서 유일하게 자유롭게 활용될 수 있는 파티션이다.

0x00010000 ~ 0x7FFEFFFF의 범위이므로, 2047MB의 크기이다.


모든 애플리케이션에 대해 프로세스가 유지해야 되는 대부분의 데이터가 저장되는 영역이며,

.exe / .dll 파일이 이 파티션에 로드된다.



3. Page


Page란, 가상 메모리를 사용하는 최소 크기 단위이다.

최근의 윈도우 운영체제는 모두 4096 (4KB)의 페이지 크기를 사용한다.


만약, 페이징 파일에서 물리 메모리로 데이터를 로드할 때, 

아무 위치에나 필요한 크기 만큼(무작위) 로드한다고 가정을 해 보자.


이런 경우, 로드하고 언로드하는 데이터의 크기가 모두 달라서,

이를 반복하다 보면 메모리 공간에 fragmentation이 발생하게 된다.


결국 메모리는 남아 있지만, 정작 원하는 크기의 데이터를 물리 메모리로 로드하지 못하게 되는 상황이 생길 수 있는 것이다.


이를 막기 위해, 운영체제가 만든 것이 page라는 최소 크기 단위인 것이다.



4. Demanding-Page


Demanding-page는 실제로 필요한 page만 물리 메모리로 가져오는 방식을 이야기한다.


필요 page에 접근하려 하면, 결국 가상 메모리 주소에 대응하는 물리 메모리 주소를 찾아내어,

물리 메모리 주소를 얻어와 하는데, 이 때 필요한 것이 페이지 테이블(page table)이다.


페이지 테이블에 valid bit 라는 것을 두고, 해당 page가 물리 메모리에 있으면 set, 그렇지 않으면 invalid로 설정한다.


5. page fault


프로그램이 자신의 주소공간에는 존재하지만 시스템의 RAM에는 현재 없는 데이터나 코드에 접근 시도하였을 경우 발생하는 현상이다. 페이지 폴트가 발생하면 운영 체제는 그 데이터를 메모리로 가져와서 마치 페이지 폴트가 전혀 발생하지 않은 것처럼 프로그램이 계속적으로 작동하게 해준다.


<페이지폴트 처리과정>


Page 접근 요청을 하였는데, physical memory에 없는 상태, 

즉 valid bit가 clear 되어있는 상황을 Page fault 라 하며 아래와 같은 처리 과정을 거친다.


1) 페이징 하드웨어는 page table entry를 보고 invalid인 것을 확인한 후 OS에게 trap으로 알린다.

2) OS는 정말로 메모리에 없는 것인지 아니면 잘못된 접근인지 확인한 후 잘못된 접근이었으면 종료시킨다.

3) Empty frame (free page)을 얻는다.

4) Page를 frame으로 swap한다.

5) 프로세스의 page table과 TLB를 page-in/page-out 된 것을 토대로 재설정한다.

6) Page fault를 야기했던 인스트럭션부터 다시 수행한다.


3번 과정에서 empty frame을 얻어와야 하는 상황에서 물리 메모리가 이미 모두 사용중이라면,

그 사용중인 frame 중 하나를 선택해서 page-out (페이징 파일로 이동) 시키고, 그 frame을 사용해야 한다.


이와 같이 victim frame을 선택하는 과정에서 Page Replacement Algorithm을 사용한다.




5. 페이지 교체 알고리즘


1. FIFO


가장 먼저 page-in 되어, 오래된 page를 page-out 시키는 방식.

단순하고 깔금하나, memory locality 측면에서는 좀 아니다.


2. LRU (Least Recent Used)


가장 오랫동안 사용되지 않았던 녀석은 앞으로도 사용되지 않을 것이라는 기대로 page-out 시키는 방식.

LRU는 두 가지 방식으로 보통 구현하는데, 카운트 방식 : 4. Counting에서 이 방식을 쓴다.

스택 방식 : 그 많은 페이지를 관리하기 위해, doubly-linked-list를 차용한 스택을 쓰기엔 메모리, 연산량이 아깝다.

3. LRU approximation


LRU 개념이긴 하지만, 하드웨어의 도움을 받아 reference bit을 사용한다.


페이지들을 circular queue 형태로 나타내고 만약 reference가 일어나면 이 bit 를 set 한다. 

그리고 victim 페이지를 선정할 때는 하나씩 접근하여 reference bit 를 확인한다.

만약 bit 가 clear하면 그 페이지를 victim으로 선정하며, set 되어 있으면 그 bit 를 clear시키고 다음 페이지를 확인한다. 해당 페이지에 Second-chance를 주는 것이므로 Second-chance algorithm이라고도 한다.


이 방식이 가장 많이 사용되고 효율이 좋은 것으로 알려져 있다.


4. Counting


Page가 참조될 때마다 카운팅을 하는 방식

MFU : Most frequent used

LFU : Least frequent used


5. Thrashing


가상 메모리를 사용하다 보면 실제 물리 메모리 공간보다 논리적 메모리 공간이 큰 것 처럼 사용될 수 있기 때문에 효율적이다. 


그런데, 효율적이라는 이유로 멀티 프로세싱의 degree를 계속 늘리다 보면, 

실제 실행하는 시간보다 page replacement를 하는 시간이 더 많아지는 순간이 오며, 결국엔 CPU 사용율이 떨어지게 된다. 하드웨어가 엄청많이 돌아가게된다.


이와 같이 프로그램을 제대로 수행하지 못하고, 실행 시간보다 페이지 교체 시간이 많아지는 현상을 Thrashing이라 한다.


이 thrashing이 발생하는 이유는 locality의 크기의 합이 실제 메모리의 크기보다 커졌기 때문이다.따라서, 이 thrashing을 해결하기 위해서, Working set model 이란 것을 적용한다.

Working set model 은 locality 를 approximate한 것으로 페이지 넘버로 관리한다.그러다 working set의 크기가 실제 메모리보다 커지면 하나의 프로세스를 종료하는 방식으로 thrashing이 생기지 않도록 할 수 있다.하지만, 위 방법은 접근되는 페이지를 관리해야 하므로 불편한 감이 있다.따라서, thrashing이 생겼다는 것은 즉, Page fault 가 많아졌다는 것이기 때문에 그것의 정도를 지정하고 Page fault 의 횟수가 어느 한계점을 넘어가면 프로세스를 종료하는 방식으로 구현한다.


그래서 가장 좋은 Thrashing 해결방안은 Thrashing이 발생할 때 가장 좋은 솔루션은 램을 추가로 설치하는 것이다.



7. 가상 메모리의 부가 장점


가상 메모리를 사용하면서 생기는 부가 장점으로 다음과 같은 것들이 있다.

- 공유 메모리 사용

- Copy-on-write 메커니즘

- Memory mapped file


1. 공유 메모리 사용


여러 프로세스 간의 communication의 한 가지 방법으로 공유 메모리를 사용할 수 있는데, 

demand-paging 기법을 사용할 경우 다른 프로세스의 각각의 페이지가 같은 프레임을 가리키도록 하면 공유 메모리를 사용할 수 있다.


윈도우의 dll이나 리눅스의 so 역시 이 방식으로 물리 메모리 프레임을 같이 가리키게 하여, 전체 메모리를 절약하게 되는 것이다.

아래 그림에서 프로세스 A의 1번 페이지, 프로세스 B의 7번 페이지가 물리 메모리 5번 프레임을 같이 가리키는 것을 볼 수 있다.



2. Copy-on-write 메커니즘


부모 프로세스를 clone하여 자식 프로세스를 생성하였을 때, 

처음에는 같은 메모리를 사용하도록 하다가 그곳에 Write가 발생하였을 때 메모리를 copy하는 것으로 이것 또한 공유 메모리처럼 같은 프레임을 가리키도록 하였다가 복사가 되었을때 새로운 프레임을 할당하면 된다.


3. Memory mapped file

메모리 맵 파일 기능은 가상 메모리처럼 프로세스 주소 공간을 예약하고, 
예약한 영역에 물리 저장소를 commit하는 기능을 제공한다.

가상 메모리와 유일한 차이점이라면, 시스템의 페이징 파일을 사용하는 대신
디스크 상에 존재하는 어떤 파일이라도 물리 저장소로 사용 가능하다는 점이다.

메모리 맵 파일은 아래 세 가지 목적으로 사용될 수 있다.
  • - 실행 파일(.exe)과 DLL 파일을 읽고 수행
  • - 디스크에 있는 데이터에 접근
  • - 동일 머신에서 수행중인 다수의 프로세스 간 데이터 공유


출저: 수까락에 프로그래밍 이야기

반응형
LIST

'운영체제 ' 카테고리의 다른 글

이중동작모드(Dual-Mode operation)  (0) 2017.01.15
멀티프로세싱(Multi Processing)이란?  (0) 2016.09.20
임계구역(Critical section)  (0) 2016.09.11
Context Switching  (0) 2016.09.08
DeadLock 특징들  (0) 2016.08.19
블로그 이미지

만년필석사

,
반응형
SMALL

컴퓨터에는 여러가지 지식들이 있다. 사실 컴퓨터라는 세계는 끝이 안보이는 미지의 세계일 수도 있지만 모든걸 다 안다기 보다는 그래도 컴퓨터 전공자들이 개발을 하던지, 보안을하던지 등간에 꼭 알아야할 것들이 몇가지가 있다. 지금 그걸 몇가지 소개해보려고 한다.


1. 하드디스크 = ARM 작동될 때 기계로 하는 방식보다 (전기) 하는 방식이 훨씬 빠르기  때문에 컴퓨터 부팅속도                          도  빨라진다.

 -> 당연히 이렇게 될 수 밖에없다. 이 세상에서 가장 빠른건 빛밖에 없기 때문에  빛의 속도로 하드디스크에 접근을 하다면 어떻게 되겠는가? 예전엔 기술의 발전이 없을 때는 당연히 기계식으로 돌렸고, 빛보다 현저히 느릴 수 밖에 없는 구조였다. 현대에는 빛을 이용한 방식을 많이 사용하기 때문에 당연히 모든 면에서 빨라질 수 밖에 없는 구조이다.


2. 라우터 = 일종의 공유기이다. LAN과 LAN, LAN과 WAN을 서로 연결하기 위한 인터넷 프로토콜이며, 데이터의 전송을 위한 경로지정을 해주는 기능을한다. 또한 가장 중요한 것은 현존하는 네트워크 연결 장비중에 가장 속도가 빠르며 전세계는 이러한 라우터들로 이루어져 통신하고 있다. 가장 최적의 경로를 찾아 가장 빨리 송수신 해주는 장비가 바로 라우터이다. 

 



3. TCP/UDP의 차이점?


TCP(Transmission Control Protocol)

연결형 서비스를 지원하는 전송계층 프로토콜이며,  인터넷 환경에서 기본이 된다. 호스트간       신뢰성 있는 데이터 전달과 흐름제어 및 혼잡제어 등을 제공하는 전송계층이다.


TCP의 특징

- 가상 회선 연결 방식, 연결형 서비스를 제공

- 높은 신뢰성(Sequence Number, Ack Number를 통한 신뢰성 보장)

- 연결의 설정(3-way handshaking)과 해제(4-way handshaking)

-> 여기서 가장 중요한 사실은 높은 신뢰성보장이다.


* UDP(User Datagram Protocol)

비연결형 서비스를 지원하는 전송계층 프로토콜이며 사용자 데이터그램형 프로토콜이다. 인터넷상에서 서로 정보를 주고받을 때 정보를 보낸다는 신호나 받는다는 신호 절차를 거치지 않는 것이 특징이며, 보내는 쪽에서 일방적으로 데이터를 전달하는 통신 프로토콜


UDP의 특징


-  비연결형(port만 확인하여 소켓을 식별하고 송수신)

-  패킷 오버헤드가 적어 네트워크 부하 감소

-  비신뢰성

-  오류검출(헤더에 오류 검출 필드를 포함하여 무결성 검사)

-  TCP의 handshaking 같은 연결 설정이 없다

-> 여기서 가장 중요한건 비신뢰성이며 핸드쉐이킹같은 연결설정이 없다는 것이다.


three way handshaking/four way handshaking = three way handshaking은 서로간의 송수신을 서로 제대 인하면서 진행한다. four way handshaking 송수신을 끊을때 사용한다.

※ 프로세스: 프로그램이 실행되고 있는 것


4.  좋은 코딩을 위한 최적화 이슈


    - int arr[800*480] = [0]; -> 배열까진 좋다. 이정도를 메모리공간에 할당한다는거니까.

    - void draw(int x), int y1, int x2, int y2, int type, int color)-> 근데 파라미터가 너무 많다는       느낌 안받는가? 파라미터가 많을수록 레지스터에 대한 부담은 점점 커진다. 그렇게 되                   면 컴파일도 당연히 느릴수밖에 없고 여러모로 불리한 점이 많아진다. 그래서 좋은 코딩을 하         기 위해선 파라미터를 4개정도까지만 하는 것을 권장하고 있다.

    - for (int i = 0;  i < sizeof(arr), i++) -> sizeof 런타임이 아니다. 몇번 호출되든 상관 없다.





반응형
LIST
블로그 이미지

만년필석사

,
반응형
SMALL


지난번 포스팅에서 간단한 서버를 구현해보았다. 하지만 서버라는것은 저렇게 하나만 간단히 돌아가는 것이 아니다. 항상 많은 서버들이 맞물려서 돌아가게 되기 마련이다. 그러기때문에 지난번에 했던 서버 내용을 기본으로 이번에는 Dispatcher에 여러 protocol을 넣어서 서버를 구현해보았다. 이번에 만들고자 하는 서버의 구성은 다음과 같다.



1.  Server1이라는 프로젝트를 만들고 그 안에 Serverinitializer class를 만들고 다음과 같이 코      딩해준다.


import java.io.IOException;

import java.net.ServerSocket;



public class Serverinitializer 

{


public static void main(String[] args

{

int port = 5000;

System.out.println("Server ON :" + port);

try

{


ServerSocket serverSocket = new ServerSocket(port);


Dispatcher dispatcher = new Dispatcher();


while (true)

{

      dispatcher.dispatch(serverSocket);

}

}

catch (IOException e)

{

    e.printStackTrace();

}

}


}


2.  Dispatcher이라는 class를 만들고 다음과 같이 코딩한다.

import java.io.IOException;

import java.io.InputStream;

import java.net.ServerSocket;

import java.net.Socket;


public class Dispatcher 

{

   private final int HEADER_SIZE = 6; -> "0X5001"은 헤더 6글자를 의미(헤더설정)

   public void dispatch(ServerSocket serverSocket)

{

  try

  {

  Socket socket = serverSocket.accept();

  demultiplex(socket);

  }

  catch (IOException e)

  {

     e.printStackTrace();

  }

 }


   public void demultiplex(Socket socket) ->Dispatcher에서 받아들인 데이터를 분배해주는 것이 demultiplex다.

   {

   try

   {

      InputStream inputStream = socket.getInputStream();


      byte[] buffer = new byte[HEADER_SIZE];

      inputStream.read(buffer);

      String header = new String(buffer); -> 헤더사이즈만큼 받아온다.



     switch (header) -> 전체적으로 헤더를 2개로 나누어서 표현한것이다.(이것이 프로토콜)

     {

     case "0X5001":

          StreamSayHelloProtocol sayHelloProtocol = new StreamSayHelloProtocol();

          sayHelloProtocol.handleEvent(inputStream); -> "0x5001이면 sayHelloProtocol 실행

          break;

     case "0X6001":

          StreamUpdateProfileProtocol updateProtocol = new StreamUpdateProfileProtocol();

          updateProtocol.handleEvent(inputStream); -> 0x6001이면 updateProtocol  실행

          break;

    }

}

    catch (IOException e)

   {

       e.printStackTrace();

   }

  }

}


2. StreamSayHelloProtocol 이름으로 class를 생성하고 다음과 같이 코딩한다.

import java.io.IOException;

import java.io.InputStream;

import java.util.StringTokenizer;


public class StreamSayHelloProtocol 

{

    private static final int DATA_SIZE = 512; -> ex) 홍길동|26 여기서 최대로 쓸수 있는 사이즈는 512

    private static final int TOKEN_NUM = 2;-> 위 예에서 데이터 종류는 2개

    public void handleEvent(InputStream inputStream)

{

  try 

  {

      byte[] buffer = new byte[DATA_SIZE];

      inputStream.read(buffer);

      String data = new String(buffer);


      String[] params = new String[TOKEN_NUM];

      StringTokenizer token = new StringTokenizer(data, "ㅣ"); ->데이터 512를 받아온다음 StringTokenizer를 이용                                                                                                     해서 파이프(|)를 파싱함

      int i = 0;

      while (token.hasMoreTokens())

      {

         params[i] = token.nextToken();

         ++i;

      }

 }

   catch (IOException e)

  {

      e.printStackTrace();

  }

 }

   private void sayHello(String[] params) 

   {

       System.out.println("SayHello -> name : " +params[0] + "age : "+params[1]);

   }

 } 

3. 이번엔 또다른 하나의 프로토콜인 StreamUpdateProfileProtocol이름으로 class를 생성하고 다음과 같이 코딩한다.


import java.io.IOException;

import java.io.InputStream;

import java.util.StringTokenizer;


public class StreamUpdateProfileProtocol 

{

   private static final int DATA_SIZE = 1024;

   private static final int TOKEN_NUM = 5;

   public void handleEvent(InputStream inputStream)

   {

   try 

   {

      byte[] buffer = new byte[DATA_SIZE];

      inputStream.read(buffer);

      String data = new String(buffer);

      String[] params = new String[TOKEN_NUM];

      StringTokenizer token = new StringTokenizer(data, "ㅣ");

      

      int i = 0;

      while (token.hasMoreTokens())

      {

          params[i] = token.nextToken();

          ++i;

      }

           updateProfile(params);

      }

      catch (IOException e)

      {

          e.printStackTrace();

      }

  }


private void updateProfile(String[] params

{

      System.out.println("UpdateProfile ->" +

            " id :" + params[0] +

            " password : " +params[1] +

            " name :" + params[2] +

            " age :" + params[3] +

            " gender: " + params[4]);

  }

}


전체적인 구조는 앞서 코딩한 2번과 유사하다. 단지 차이점이라고 하면 데이터사이즈와 데이터 최대종류가 달라졌다는 것이고, param구조를 이용해 출력내용을 조금 더 추가했다는 점이다.


4. 마지막으로 TestClient class를 생성하고 다음과 같이 코딩한다.


import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;


public class TestClient {


public static void main(String[] args

{

   System.out.println("Client ON");

   try

   {

      String message;

      Socket socket = new Socket("127.0.0.1", 5000);

      OutputStream out = socket.getOutputStream();

      message = "0x5001|이건주|26";

      out.write(message.getBytes());

      socket.close();


      Socket socket2 = new Socket("127.0.0.1", 5000);

      OutputStream out2 = socket2.getOutputStream();

      message = "0x6001|Lee|1234|이건주|26|남성";

      out2.write(message.getBytes());

      socket2.close();

    } 

    catch (UnknownHostException e)

    {

        e.printStackTrace();

    } 

    catch (IOException e)

   {

       e.printStackTrace();

   }

  }

}


5. 최종적으로 클라이언트를 만든것이다. 이제 이 상태에서 RUNAS를 해주면 다음과 같이 화면에 출력된다.



앞서 포스팅에도 이야기 했듯 사용중인 서버목록을 확인한 후 서버를 닫아주고 나서 실행해야 올려놓은 화면과 같이 출력이 되게된다.


반응형
LIST

'Server > Server의 기초' 카테고리의 다른 글

nio java를 이용한 에코서버 만들기  (0) 2016.08.12
서버의 전체적인 그림  (0) 2016.08.10
Dispatcher  (0) 2016.08.07
블로그 이미지

만년필석사

,
반응형
SMALL

<서버의 기초>

-Dispatcher의 필요성 -> Dispatcher은 데이터를 읽어들이는 역할을 하기때문에 서버에서 가장 중요하게                                      작용된다.

 서버의 문제점을 찾고 Reactor Pattern의 필요성을 확인할 수 있다.


○ 일단 가장 간단한 서버의 접속 프로그램을 만들어보았다.


1. 첫번째로 Serverinitializer class파일을 만든 후 다음과 같이 코딩한다.


import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.net.ServerSocket;

import java.net.Socket;


public class Serverinitializer 

{

      public static void main(String[] args

{

      int port = 5000; ->포트 설정 번호

      System.out.println("Server ON :" + port); -> 서버 on

  try

  {

      ServerSocket serverSocket = new ServerSocket(port); 

      Socket connection;  -> 서버 설정

     while (true)

     {

     connection = serverSocket.accept(); ->연결받기

     InputStreamReader inputStreamReader = new InputStreamReader(connection.getInputStream());

     BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 

     String line = bufferedReader.readLine(); -> 데이터를 읽는 부분

     System.out.println("READ:" + line); -> 받아들인 데이터를 처리하는 부분

     }

 }

  catch (IOException e)

{

   e.printStackTrace(); -> try문에서 에러 발생시 알려주는 부분

    }

}


2. 두번째로 client class를 만든다. 파일명은 TestClient로 한후 다음과 같이 코딩한다.

import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;


public class TestClient 

{


   public static void main(String[] args

  {

        System.out.println("Clinet ON"); -> 클라이언트 서버 on

  try

  {

     String message;

     Socket socket = new Socket("127.0.0.1", 5000); -> 받아들일 소켓 tcp설정

     OutputStream out = socket.getOutputStream();

     message = "0X5001|이건주|26"; -> 화면에 view할 메시지 입력

     out.write(message.getBytes()); ->메시지를 화면에 보내준다.

     socket.close(); -> 소켓 닫기

   }

   catch (UnknownHostException e)

   {

      e.printStackTrace();

   }

   catch (IOException e)

   {

     e.printStackTrace();

    }

  }

}


3. 이렇게 두가지를 완성해서 RUN AS를 해보면 콘솔창에 다음과 같이 나오게 된다.




이때 주의 사항이 있다. 분명 RUN AS를 해보게 된다면 Address already in use라고 경고표시가 나오면서 여러 사이트 비슷한 에러내용이 뜰 것이다. 그렇게 되면 iturm으로 가서 리눅스 명령어 sudo lsof -i tcp:5000 라고 입력을 하게 되면 현재 실행되고 있는 서버목록이 확인될 것이다. 현재 이렇게 포트 5000에서 이미 서버가 돌아가고 있기때문에 이미 사용중인 에러메시지가 나오는 것이다. 그러기때문에 현재 진행중인 서버를 닫아줘야 한다. 서버를 닫기위한 리눅스 명령어는 sudo kill -9 (PID번호)이다. 이걸 입력해주고 다시 돌아와서 RUN AS를 해주면 화면에 보이는 것과 같이 서버가 잘 돌아감을 볼 수 있다.


반응형
LIST
블로그 이미지

만년필석사

,