IPC별 특성 


 

메일슬롯 

이름없는파이프 

이름있는파이프 

방향성 

단방향 ,브로드캐스팅

단방향 

양방향 

통신범위 

제한 없음 

부모 자식 프로세스 

제한 없음 





예제8-1


이름없는 파이프


#include <windows.h>

#include <tchar.h>

#include <stdio.h>


int _tmain(int argc, LPTSTR argv[])

{

HANDLE hReadPipe, hWritePipe; //pipe handle


TCHAR sendString[] = _T("anonymous pipe");

TCHAR recvString[100];


DWORD bytesWritten;

DWORD bytesRead;          


/* pipe 생성 */

CreatePipe(&hReadPipe, &hWritePipe, NULL, 0);

/* 이름없는 파이프는 단방향성이기에 함수를 호출하면 파이프의 쓰는 부분과 읽는 부분을 반환받는다. */


/* pipe의 한쪽 끝을 이용한 데이터 송신 */ 

WriteFile(hWritePipe, sendString, lstrlen(sendString)*sizeof(TCHAR), &bytesWritten,

                                                                                                                     NULL);

_tprintf( _T("string send: %s \n"), sendString);



/* pipe의 다른 한쪽 끝을 이용한 데이터 수신 */ 

ReadFile(hReadPipe, recvString, bytesWritten, &bytesRead, NULL);

recvString[bytesRead/sizeof(TCHAR)] = 0;

_tprintf( _T("string recv: %s \n"), recvString);



CloseHandle(hReadPipe);

CloseHandle(hWritePipe);


return 0;

}


예제를 보면 CreatePipe함수를 호출하는 프로세스가 파이프의 읽고 쓰는 핸들을 모두 가지고 있다. 

이름없는 파이프는 부모 자식 프로세스간 에만  통신이 가능하다고 위에 나왔다.

 부모 프로세스는 자식 프로세스에 핸들을 상속 할 수 있기에 자식프로세스에 핸들을 상속하여  통신이 가능하다.




이름있는 파이프 프로그래밍 모델





1. CreateNamedPipe 함수에 의해 생성

2. ConnectNamedPipe 함수에 의해 연결 대기 상태로 전환

3. Client의 CreateFile 함수 호출에 의한 파이프 오픈


이름 있는 파이프는 양방향 통신이다.

먼저 서버가 파이프를 생성 한다.

CreateNamePipe함수를 호출하면 서버는 파이프를 가지고 있는다. 하지만 말그대로 가지고만 있을 뿐 외부와의 연결은 되지않는 상태이다.

외부와의 연결을 위해

ConnectNamePipe 함수를 호출통하여 외부에 파이프를 노출시킨다.

외부에서 파이프에 연결은 CreateFile 함수를 이용한다.




예제 8-2

이름있는파이프 서버


#include <stdio.h>

#include <stdlib.h>

#include <windows.h> 


#define BUF_SIZE 1024


int CommToClient(HANDLE);


int _tmain(int argc, TCHAR* argv[]) 

{

LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe"); 


HANDLE hPipe;

while(1)

{

  hPipe = CreateNamedPipe ( 

  pipeName,            // 파이프 이름

  PIPE_ACCESS_DUPLEX,       // 읽기,쓰기 모드 지정

  PIPE_TYPE_MESSAGE |

  PIPE_READMODE_MESSAGE | PIPE_WAIT,

  PIPE_UNLIMITED_INSTANCES, // 최대 인스턴스 개수.

  BUF_SIZE,                  // 출력버퍼 사이즈.

  BUF_SIZE,                  // 입력버퍼 사이즈 

  20000,                   // 클라이언트 타임-아웃  

  NULL                    // 디폴트 보안 속성

  );

  

  if (hPipe == INVALID_HANDLE_VALUE) 

  {

  _tprintf( _T("CreatePipe failed")); 

  return -1;

  }

  

  BOOL isSuccess; 

  isSuccess = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); 

  

  if (isSuccess) 

  CommToClient(hPipe);

  else 

  CloseHandle(hPipe); 

}

return 1; 


int CommToClient(HANDLE hPipe)

TCHAR fileName[MAX_PATH];

TCHAR dataBuf[BUF_SIZE];


BOOL isSuccess;

DWORD fileNameSize;


isSuccess = ReadFile ( 

hPipe,        // 파이프 핸들 

fileName,    // read 버퍼 지정.

MAX_PATH * sizeof(TCHAR), // read 버퍼 사이즈 

&fileNameSize,  // 수신한 데이터 크기

NULL);       


if (!isSuccess || fileNameSize == 0) 

{

_tprintf( _T("Pipe read message error! \n") );

return -1; 

}


FILE * filePtr = _tfopen(fileName, _T("r") );


if(filePtr == NULL)

{

_tprintf( _T("File open fault! \n") );

return -1; 

}


DWORD bytesWritten = 0;

DWORD bytesRead = 0;


while( !feof(filePtr) )

{

bytesRead = fread(dataBuf, 1, BUF_SIZE, filePtr);


WriteFile ( 

hPipe, // 파이프 핸들

dataBuf, // 전송할 데이터 버퍼  

bytesRead, // 전송할 데이터 크기 

&bytesWritten, // 전송된 데이터 크기 

NULL);


       if (bytesRead != bytesWritten)

{

   _tprintf( _T("Pipe write message error! \n") );

   break; 

}

}


FlushFileBuffers(hPipe); 

DisconnectNamedPipe(hPipe); 

CloseHandle(hPipe); 

return 1;

}




예제 8-3

이름있는 파이프 클라이언트



#include <stdio.h>

#include <stdlib.h>

#include <windows.h>


#define BUF_SIZE 1024


int _tmain(int argc, TCHAR *argv[]) 

{

    HANDLE hPipe;

    TCHAR readDataBuf[BUF_SIZE + 1];

    LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe"); 

    

    while(1)

    {

      hPipe = CreateFile( 

      pipeName,             // 파이프 이름 

      GENERIC_READ | GENERIC_WRITE, // 읽기, 쓰기 모드 동                         

                                                                                                               시 지정 

      0,

      NULL,

      OPEN_EXISTING,

      0,

      NULL

      );

     

      if (hPipe != INVALID_HANDLE_VALUE)

         break;

         

      if (GetLastError() != ERROR_PIPE_BUSY)

      {

         _tprintf( _T("Could not open pipe \n") );        

         return 0;

      }

     

      if (!WaitNamedPipe(pipeName, 20000))

      {

         _tprintf( _T("Could not open pipe \n") );

         return 0;

      }

    }

    

    DWORD pipeMode = PIPE_READMODE_MESSAGE|PIPE_WAIT; // 메시지 기반으로 모드 변경.

    BOOL isSuccess = SetNamedPipeHandleState (

                       hPipe,      // 파이프 핸들

                       &pipeMode,  // 변경할 모드 정보.  

                       NULL,     // 설정하지 않는다. 

                       NULL);    // 설정하지 않는다. 

    

    if (!isSuccess)

    {

        _tprintf( _T("SetNamedPipeHandleState failed") ); 

        return 0;

    }

    

    LPCTSTR fileName = _T("news.txt");

    DWORD bytesWritten = 0;

    

    isSuccess = WriteFile (

                    hPipe,                // 파이프 핸들

                    fileName,             // 전송할 메시지 

                    (lstrlen(fileName)+1) * sizeof(TCHAR), // 메시지 길이 

                    &bytesWritten,             // 전송된 바이트 수

                    NULL);

    

    if (!isSuccess) 

    {

      _tprintf( _T("WriteFile failed") ); 

      return 0;

    }

    

    DWORD bytesRead = 0;

    while(1) 

    {

        isSuccess = ReadFile(

                        hPipe, // 파이프 핸들

                        readDataBuf, // 데이터 수신할 버퍼

                        BUF_SIZE * sizeof(TCHAR),  // 버퍼 사이즈

                        &bytesRead, // 수신한 바이트 수

                        NULL);      

        if (! isSuccess && GetLastError() != ERROR_MORE_DATA) 

            break; 

        

        readDataBuf[bytesRead] = 0; 

        _tprintf( _T("%s \n"), readDataBuf ); 

    }

    

    CloseHandle(hPipe); 

    return 0; 

}






핸들 테이블의 상속


BOOL CreateProcess(

LPCTSTR lpApplicationName , 

LPTSTR lpCommandLine ,                                                                                    

LPSECURITY_ATTRIBUTES lpProcessAttributes ,

LPSECURITY_ATTRIBUTES lpTreadAttributes ,

BOOL binheritHandles , // 핸들테이블의 상속여부

DWORD dwCreationFlags ,

LPVOID lpEnvironment ,

LPCTSTR lpCurrentDirectory ,

LPSTARTUPINFO lpstrartupInfo ,

LPPROCESS_INFORMATION lpProcessInformation

);


핸들테이블은 5장 과 6장에서 각 리소스에 독립적이라 하였는데 핸들테이블은 조건에 따라 상속도 가능하다. 

상속될때 핸들같이 동일하게 상속된다.

핸들테이블에 핸들 정보에 상속가능여부가 있는데 이 값이 가능상태여야 상속이가능하다.

ex)


 핸들

주소 

상속여부 

127 

0x1200 

168 

0x1680 

 ......

 ......

 ......

이런 식으로 상속 가능여부가 핸들값마다 저장이 되어진다.



CreateProcess함수를 살펴보면 5번째 인자에 BOOL binheritHandles 가있는데 이 인자값을 true 을 주면 부모프로세스의 핸들테이블에 상속가능한 핸들들이 상속된다.



그러하다면 어떻게하면 핸들값의 상속여부를 가능하게 할 수 있을 것인가.

 

ex)


SECURITY_ATTRIBUTES sa;

sa.nLength=sizeof(sa);

sa.lpSecurityDescriptor=NULL;

sa.bInheritHandle=TRUE;  // 이런식으로 하면 상속가능하게 할 수 있다.


.................


CreateMailslot(......... &sa); //4번째 전달인자이다.


대부분의 리소스 생성과정에서 보면 SECURITY_ATTRIBUTES 즉 보안관리자 설정을 위한 구조체가 사용된다.

이보안설정을 하는과정에서 앞의 강의에서 NULL 설정을 하였다.

보안 설정을 하지 않을 경우 상속가능여부는 가능하지 않게 된다.


메일 슬롯 뿐아리라 대부분 다른 리소스를 생성할 경우에도 보안관리구조체의 포인터를 전달하면 동일하게 설정 할 수 있다.



핸들의 상속과 UC




핸들 값을 상속하게 되면 당연하게도 UC값이 증가한다.








Pseudo 핸들과 핸들의 중복


6장에 나왓듯이 프로세스가 자기자신의 핸들값은 핸들테이블에 저장시켜서 사용하는것이 아니라 GetCurrentProcess함수를 이용하여 특정 상수를 이용한다라고 하였는데 이를 Pseudo 핸들 이라한다.즉 가짜핸들이다.


하지만 이런 일이 일어난다고 해보자.


A프로세스가 B라는 자식프로세스에게 A프로세스의 핸들을 상속하고싶다.

하지만 A프로세스의 핸들테이블에는 A의 핸들정보가 존재하지않는다.

그래서 A프로세스의 핸들테이블에 자기자신의 핸들정보를 저장하여야 한다.



DuplicateHandle 함수가 있다


대략 하는일은 DulpicateHandle(A 핸들, 256,B핸들,&val,.......)  

                                                                        HANDLE val=364

이런식으로 함수를 호출하면 밑에 처럼 A프로세스의 핸들테이블에 있는 256 핸들이    B프로세스의 핸들테이블에 364 값으로 복사가 된다. 

여기서 다시한번 핸들테이블은 각 리소스에 독립적이라는 것을 인식하자.


프로세스 A     

 프로세스B

 

핸들 

주소 

 

핸들 

주소 

... 

... 

 

..0

... 

256 

0x2400 

 ------------>

364 

0x2400 

 ...

... 

 복사

... 

 ...


이것으로 이제 Pseudo 핸들을 핸들테이블에 저장해보자.



DuplicateHandle(

프로세스A핸들,

256,

프로세스A핸들,

&val,

.....

);


 핸들 

주소 

 

 

... 

...   

 256

0x2400 

 ---------------->

 Usage count : 2

 ...

 ...  

 284

  0x2400  ----------------> 

 ...

...   


한테이블에서 핸들을 복사 할 수도 있다.

이러면 같은 주소를 가리키는 핸들이 하나 더 생긴다. 

※물론  UC도 늘어남으로 나중에 핸들을 닫을때 두번 닫아야 한다.


이제 자신의 핸들값을 상속 할 수있게 Pseudo 핸들을 핸들테이블에 저장해보자.


DuplicateHandle(

GetCurrentProcess(),

GetCurrentProcess(),

GetCurrentProcess(),

&hProcess,

0,

TRUE,

DUPLICATE_SAME_ACCESS

);


앞에 3인자에 모두 GetCurrentProcess함수를 넣으면 함수를 호출한 프로세스의 핸들이 반환되니까 호출한 프로세스의 핸들테이블에 호출한프로세스의 핸들값이 저장이 되는것이다. 





 Signaled vs Non-Signaled



커널 오브젝트의 상태


이곳은 프로세스 기준이다.

커널오브젝트의 구성멤버 변수중에 커널오브젝의 상태정보 변수가있는데 이 변수는

프로세스가 실행중이면 프로세스의 커널오브젝트가 Non-signaled 상태 이고

                실행 종료되면  프로세스의 커널오브젝트가 signaled 상태가 된다.  


프로세스가 실행되면 그 프로세스의 커널 오브젝트는 바로  Non-signaled 가 된다.


커널 오브젝트의 상태가 필요한 이유는 프로세스의 핸들을 가지고 있는 무언가가 프로세스의 실행 여부를 판단하기 위해서이다.


상태관찰 시나리오


부모프로세스가 자식 프로세스 생성 - >자식프로세스의 핸들값반환 


WaitForSingleObject(자식프로세스핸들인자){

Non-signaled 면 멈춰있는다.

signaled 상태면 동작한다.

}


윈속을 위한 헤더 및 라이브러리 설정


winsock2.h 헤더 파일을 포함 한다.


ws2_32.lib 라이브러리를 링크 시킨다.


윈속 사용을 위한 라이브러리 초기화 및 해제



윈속 관련 라이브러리 초기화 함수


#include<winsock2.h>


int WSAStartup(

WORD wVersionRequested,

LPWSADATA lpWSAData

);

WORD wVersionRequested 에 버전에 맞는 16진수를 전달해야한다.

예 Version 3.4 이면 0x0403 식으로 주버전이 뒤로간다.

 WORD MAKEWORD(BYTE bLow,BYTE bHigh);

bLow에 주 버전 bHigh 에 부 버전을 넣으면 16진수로 반환된다.

윈속 관련 라이브러리 해제 함수


int WSACleanup(void);



윈속 프로그램 코드 Template


#include<winsock2.h>


............


int main(int argc, char **argv)

{

WSADATA wsaData;

...............

if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)

error_handling("WSAStartup() error!");

..................


WSACleanup()

retune 0;

}



윈속 기반의 소켓 관련 함수


SOCKET socket(int af, int type ,int protocol);

윈도우에서도 socket을 생성하면 리눅스와 마찬가지로 파일(소켓)핸들이 만들어져서 리턴된다. 

※파일(소켓) 핸들도 정수형 데이터이다.

int bind(SOCKET s,const struct sockaddr FAR * name, int namelen);


int listen(SOCKET s, int backlog);


SOCKET accept(SOCKET s, struct sockaddr FAR *addr,int FAR *addrlen);


int connect(SOCKET s, const sockaddr FAR * name, int namelen);




윈속 기반의 데이터 입출력 함수


데이터 출력함수 

#include<winsock2.h>


int send(SOCKET s, const char FAR *buf, int len, int flags);


데이터 입력함수

#include<winsock2,h>


int recv(SOCKET s, char FAR *buf, int len, int flags);


send,recv 함수는 리눅스에도 존재하는 함수로 윈도우에서는 리눅스의 read,write함수가 없기에 send,recv함수를 사용한다.




예제

서버

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<winsock2.h>


void ErrorHandling(char *message);


int main(int argc, char **argv)

{

WSADATA wsaData;

SOCKET hServSock;

SOCKET hClntSock;

SOCKADDR_IN servAddr;

SOCKADDR_IN clntAddr;

int szClntAddr;

char message[]="hello World\n";


if(argc!=2){

printf("Usage : %s <port>\n",argv[0]);

}


if(WSAStartup(MAKEWORD(2,2), &wsaData) !=0)

ErrorHandling("WSAStartup() error!");


hServSock=socket(PF_INET, SOCK_STREAM, 0); //서버 소켓 생성

if(hServSock==INVALID_SOCKET)

ErrorHandling("socket() error");


memset(&servAddr, 0 ,sizeof(servAddr));

servAddr.sin_family=AF_INET;

servAddr.sin_addr.s_addr=htonl(INADDR_ANY);

servAddr.sin_port=htons(atoi(argv[1]));


if(bind(hServSock, (SOCKADDR*) &servAddr,sizeof(servAddr))==SOCKET_ERROR)

ErrorHandling("bind() error");


if(listen(hServSock,5)==SOCKET_ERROR)

ErrorHandling("listen() error");                  //연결 요청 대기


szClntAddr=sizeof(clntAddr);

hClntSock=accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); //연결요청수락

if(hClntSock==INVALID_SOCKET)

ErrorHandling("accept() error");


send(hClntSock, message, sizof(message), 0);


closesocket(hClntSock);

WASCleanup();


클라이언트


#include<stdio,h>

#include<stdlib.h>

#incldue<string.h>

#incldue<winsock2.h>


void ErrorHandling(char *message);


int main(int argc, char **argv)

{

WSADATA wsaData;

SOCKET hSocket;

char message[30];

int strLen;

SOCKADDR_IN servAddr;


if(argc!=3){

printf("Usage : %s <IP> <port> \n",argv[0]);

exit(1);

}


if(WSAStartup(MAKEWORD(2,2), &wsadata) !=0)

ErrorHandling(WSAStartup() error!");


hSocket=socket(PF_INET, SOCK_STREAM, 0);

if(hSocket==INVALID_SOCKET)

ErrorHandling("hSocketet() error");


memset(&servAddr, 0, sizeof(servAddr));

servAddr.sin_family=AF_INET;

servAddr.sin_addr,s_addr=inet_addr(argv[1]);

servaddr.sin_port=htons(atoi(argv[2]));


if(connect(hSocket,  (SOCKADDR*)&servAddr, sizeof(servAddr))==SOCKET_ERROR)

Erroerhandling(:connect() error!);


strLen=recv(hSocket, message, sizeof(message)-1,0);

if(strLen==-1)

ErrorHandling("read() error!");

message[strLen]=0;

printf("Message from server : %s \n", message);


closesocket(hSocket);

WSACleanup();

return 0;

}


void ErrorHandling(char *message)

{

fputs(message, stderr);

fputc('\n',stderr);

exit(1);

}


파일의 조작

저 수준 파일 입출력 (Low-Level File Access)


리눅스 혹은 윈도우즈 자체에서 제공해 주는 파일 입출력 함수를 사용하여 파일을 관리

(파일의 생성 및 삭제, 데이터 입력 및 출력) 하는것을 의미함.


리눅스에서는 모든 것을 파일로 관리한다.

-파일,소켓,표준 입력(키보드),표준 출력(모니터)

※흔히 표준 입출력 함수라 함은 ANSI 표준에서 제공하는 함수를 말한다.


파일에 파일 디스크립터를 할당해서 관리(파일 디스크립터는 정수)

※여기서 파일은 단순 파일 뿐아니라 소켓,표준 입출력을 포함한 의미이다.


 리눅스는 모든 것을 파일로 간주하기 때문에 파일 입.출력 함수로 소켓을 통한 데이터 전송 및 모니터나 키보드로 부터 데이터를 입.출력 또한 가능하다.

여기서 말하는 입출력함수는 표준입출력 함수가 아니라 리눅스에서 제공하는 입출력함수이다.



파일 디스크립터(File Descriptor)


파일을 관리하기 위해서 모든 파일(파일,소켓,표준 입력,표준 출력)에 파일 디스크립터를 할당 해 준다.


파일 디스크립터 

대상 

표준 입력 (Standard Input)

표준 출력 (Standard Output)

표준 에러 출력(Standard Error)


기본적으로  파일디스크립터는 정수형으로 이루어지면 0~2까지는 위 표처럼 기본적으로 할당 되어있다.


      Program                            Operating System

 




5





7






 <--------------
 





<--------------

 

파일 A


소켓 B


소켓 C


파일 D


만약 위처럼 요청에 따라 운영체제에서 소켓과 파일에 번호를 붙여 주면 프로그램에서 파일의 이름이아닌 넘버링된 숫자만으로 운영체제에 파일의 입력이나 출력등을 요청할수있다.

비유를 하자면 도서관에서 책에 번호를 붙여서 관리를 하는 것이라 보면된다.



File open 및 close


#include<fcntl.h>

#include<sys/types.h>

#include<sys/stat.h>


int open(const char *path, int flag);

open 함수의 const char *path 인자는 열려고하는 파일의 위치와 이름을  함께지니고 있는 문자열을 전달하고 

int flag는 파일을 열때의 모드를 설정하는것이다.

보면 함수의 리턴형이 int 형이다.

이것이 바로 파일 디스크립터 이다.

#include<unistd.h>


int close(int fildes);

닫고자하는 파일의 디스크립터를 인자로 전달하면 된다.


MODE 

의미 

O_CREAT 

 파일이 없으면 생성

O_TRUNC 

존재하던 데이터를 모두 삭제 ,다시 기록

O_RDONLY 

읽기 전용 모드로 파일을 오픈 

 O_WRONLY

쓰기 전용 모드로 파일을 오픈 

O_RDWR 

읽기와 쓰기 모두 가능한 모드로 파일을 오픈 

O_APPEND 

파일이 '추가모드'로 열린다. 파일 포인터가 열린 파일의 마지막 부분을 가리킨다.

 O_EXCL

 

O_NOCTTY 

 

 O_SYNC

 

 O_NOFOLLOW

 결로명이 심볼릭 링크라면,파일오픈이 안된다.

O_DIRECTORY 

경로명이 디렉토리가 아니면,파일오픈이 안된다. 



Data read & write


#include<unistd.h>


ssize_t write(int fildes, const void *buf, size_t nbytes);

ssize_t : signed int ,   size_t : unsigned int 

int fildes 는 데이터를 보낼 파일 디스크립터를 전달하면 된다.

void *buf 는 전달하고자 원하는 데이터의 배열정보

※배열 이름은 상수 포인터이다.

size_t nbytes 전달하고자 원하는 데이터의 크기

#include<unistd.h>


ssize_t read(int fildes, void *buf, size_t nbytes);

위와 같은데 size_t nbytes 는 읽어들일 데이터의 최대크기로 더 큰 데이터가 와도 여기서 지정한 크기까지만 읽어들인다.








서버 소켓 구현의 이해



Telephone

전화기 구입


전화번호 할당


케이블에 연결


수화기를 든다



<-------------------->


<-------------------->


<-------------------->


<-------------------->

 Server Socket


소켓 생성


IP주소 할당


연결 요청 대기 상태


연결 수락



서버를 전화받는 상황에 비유를 해보면 소켓을 전화기로 볼 수 있다.


일단 전화를 하려면 전화기가 필요하듯이 서버에서 데이터를 주고 받기위해서는 데이터를 주고받을 매개체인 소켓이 일단 필요하다. 


전화기에 전화번호가 있듯이 당연하게 IP주소가 할당되어져야하고 연결 요청 대기 상태로 되있게끔 해야하는데 말하자면 전화기에 케이블을 연결하는 과정이라고 생각하면 쉽다.


그뒤로 전화가 왔을때 받으면 된다.

서버는 클라이언트가 연결을 요청했을때 이를 수락하면 된다.


일반적인 서버프로그래밍은 이러한 큰 틀을 가지고 있다.



일단 소켓통신을 위한 함수들은 간단하게 살펴보자.


소켓 생성


#include<sys/types.h>

#include<sys/socket.h>


int socket(int domain, int type, int protocol)


IP 주소, Port 정보


#include<sys/socket.h>


int bind(int socket, struct sockaddr *myaddr, int addrlen)


연결 요청 대기 상태 진입


#include<sys/socket.h>


int listen(int sockfd, int backlog)


연결 요청 수락 


#include<sys/socket.h>


int accept(int sockfd, struct sockaddr *addr, int *addrlen)




클라이언트 소켓 구현의 이해


전화를 거는 사람도 전화기와 전화번호가 필요한 것 처럼 

서버 뿐만아니라 클라이어트도 소켓이 필요하다.


소켓 생성


위와 동일


연결요청


#include<sys/socket.h>


int connet(int socket, struct sockaddr *serv_addr, int addrlen)













IPC(interprocessor communication)


하나의 프로그램에서 여러게의 프로세스를 형성 할 수있다.


프로세스간 통신

프로세스간 데이터 송 수신→메모리 공유 


A에서 B로 주고 받는다는 느낌보다는 메모리 공유 기법에서 이루어진다.

process A ------[memory]--------process B

각 프로세스의 메모리영역이 분리 되어있고 그 범위를 벗어날 수 없기때문에 프로세스들은 직접 서로 데이터를 주고 받을 수 없다. 

다른 프로세스의 메모리에 접근은 안전성을 위해서 막혀있다. 

[출일슬롯 방식의 IPC|작성자 F



메일슬롯(Mail Slot)


Receiver process<----- Receiver's Mail slot <------- Sender process


데이터를 받기 위해서 프로세스는 접근가능한 공간이 필요하다. 그렇기에 OS는 메일슬롯을 생성하고 받는프로세서는 함수호출을 통한 간접적 방법으로 이 영역에 접근이 가능하다. 그리고 이 영역에 주소를 할당하여 데이터를 전송하는 프로세스는 이 주소를 통하여 데이터를 전송한다.

우체통에 편지를 보내서 받는 개념이다. 

그렇게 때문에 양방향성의 통신이 아니라 단방향 통신이다.



Process A <--------  A Mail slot    <------------

Process B <--------  A Mail slot    <------------            Sender Process

Process C <--------  A Mail slot    <------------


현실에서는 우체통 마다 다른 주소를 같지만 윈도우즈에서는 메일슬롯의 주소가 같을 수 도 있고 또한 데이터를 보내는 프로세스가 한번의 보내기를 통하여 각각의 프로세스에 모두 전달 할 수 있다. 




Receiver 가 우체통을 생성하면 Sender 는 우체통에 접근하기위하여 연결을 하기 위해 어떻한 함수를 호출하는데 이런 연결과정을 거친후에 데이터를 전송 할 수 있다.

위에서 한번 나왔듯이 Receiver에서의 데이터 전송은 가능하지 않다.


잘 보면 여기서 사용하는 함수들이 파일 관련함수들이다. 

 A 가 파일을 생성하여 데이터 작성 --> B가 파일을 읽어서 데이터 받음

이런식의 파일을 이용한 단순하고 불안정한 통신이 가능한데 

실제로 메일슬롯 방식이 이러한 방식을 기반으로 만들어졌기 때문에  파일관련함수를 사용한다.



예제



메일슬롯 Receiver



#include <windows.h>

#include <tchar.h>

#include <stdio.h>


#define SLOT_NAME    _T("\\\\.\\mailslot\\mailbox")//이곳에 컴퓨터 이름이 들어간다.

/*이 주소를 이용하여 분리된 컴퓨터간에서 프로세스간 통신가능 하지만 실제로는 거의 이용되지 않는다*/

int _tmain(int argc, LPTSTR argv[])

{

HANDLE hMailSlot;  //mailslot 핸들

TCHAR messageBox[50];

  DWORD bytesRead;  // number of bytes read


/* mailslot 생성 */

hMailSlot=CreateMailslot(

SLOT_NAME, 

0, //메일슬롯 버퍼크기

MAILSLOT_WAIT_FOREVER,

NULL);

if(hMailSlot==INVALID_HANDLE_VALUE)

{

_fputts(_T("Unable to create mailslot!\n"), stdout);

return 1;

}


/* Message 수신 */

_fputts(_T("******** Message ********\n"), stdout);

while(1)

{

if(!ReadFile(hMailSlot, messageBox, sizeof(TCHAR)*50, &bytesRead, NULL))

{

_fputts(_T("Unable to read!"), stdout);

CloseHandle(hMailSlot);

return 1;

}


if(!_tcsncmp(messageBox, _T("exit"), 4))

{

_fputts(_T("Good Bye!"), stdout);

break;

}


messageBox[bytesRead/sizeof(TCHAR)]=0; //NULL 문자 삽입.

_fputts(messageBox, stdout);

}


CloseHandle(hMailSlot);

return 0;

}


-----------------------------------------------------------------------------------



 메일슬롯 Sender


#include <windows.h>

#include <tchar.h>

#include <stdio.h>


#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")


int _tmain(int argc, LPTSTR argv[])

{

HANDLE hMailSlot;  //mailslot 핸들

TCHAR message[50];

    DWORD bytesWritten;  // number of bytes write


hMailSlot=CreateFile(SLOT_NAME, GENERIC_WRITE, FILE_SHARE_READ, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if(hMailSlot==INVALID_HANDLE_VALUE)

{

_fputts(_T("Unable to create mailslot!\n"), stdout);

return 1;

}


while(1)

{

_fputts(_T("MY CMD>"), stdout);

_fgetts(message, sizeof(message)/sizeof(TCHAR), stdin);


if(!WriteFile(hMailSlot, message, _tcslen(message)*sizeof(TCHAR), &bytesWritten, NULL))

{

_fputts(_T("Unable to write!"), stdout);

CloseHandle(hMailSlot);

return 1;

}


if(!_tcscmp(message, _T("exit")))

{

_fputts(_T("Good Bye!"), stdout);

break;

}


}


CloseHandle(hMailSlot);

return 0;

}










네트워크의 이해

네트워크란 End-System 들을 연결하는 하나의 System을 의미한다.

End-System : "호스트"라 한다.

ex)PC,프린터,핸드폰 등등등



End-System 는 종단 시스템으로 쉽게 말하면 네트워크에 연결된 단말기. 

호스트 라한다. 네트워크에 모바일로 연결된 핸드폰,PDA도 호스트가 된다. 


Internet의 이해

멀리 떨어진 둘 이상의 네트워크가 연결되어 이뤄지는 거대한 네트워크를 의미한다.

인터넷의 구축을 위해서는 서로 다른 네트워크를 연결하는 장비가 필요한다.

이를 두고 라우터(Router)라 한다.




Client / Server 모델

Server와 Client는 프로그램이다.(서버 장비를 말하는 것이 아니다)

Server는 Client의 연결요청을 기다린다.

Client는 Server에 요청을 하고 응답을 기다리는 호스트를 의미한다.



Client는 Server에 요청을 하면 Server는 요청에 대해 응답을 해주고 Client는 이 응답을 기다리는 모델이다. 일반적으로 Server가 먼저 요청하는 경우는 없다.


Server의 종류

Server는 일반적으로 Client에 비해 복잡하다.

두 가지 종류의 서버

Iterative Server : 한 순간에 하나의 클라이언트에게 응답한다.

Concurrent Server : 동시에 여러 클라이언트에게 응답한다.

 




 




네트워크 프로그래밍의 이해

네트워크로 연결되어 있는 두 호스트간의 데이터 송수신.

파일 입출력과의 차이점은 데이터를 주고 받는 대상에 있다.

소켓(soket)이라는 장치를 사용하여 프로그래밍 한다.

소켓이란 원격에 존재하는 두 호스트를 연결시켜 주는 매개체 역할을 한다.





커널 오브젝트와 핸들의 관계



프로세스가 생성될때 커널 오브젝트가 생성된다. 또한 그 프로세스의 핸들 테이블도 생성된다. 

핸들 테이블에 핸들 값이 있고 그 핸들값이 가리키는 대상 즉 커널 오브젝트가 저장된다. 

※핸들 테이블은 각 프로세스 영역에서만 의미를 지니기 때문에 가리키는대상이 같아도 핸들값이 다를 수 있다.


커널 오브젝트에는 그 커널오브젝트에 접근가능한 사용자의 수를 카운트하는 값이 존재한다. 만약 프로세스A가 생성된다면 그 값은 2가될겄이다. 하나는 자기자신일 것이도 다른 하나는 프로세스A를 실행시킨 그 무엇(마우스로 실행시키면 탐색기가 될수도있고 명령프롬프트가 될 수도 있다.)이 될 것이다.

자식 프로세스는 실행될때 부모 프로세스로 자신의 핸들값을 집어던진다.


프로세스가 소멸 될지라도 커널 오브젝트는 사라지지 않는다. 

다른 프로세스가 그 커널 프로세스를 참조 할 수도 있기때문이다.

하지만 이미 그 프로세스가 소멸 했는데 다른 프로세스가 참조한다해도 이미 할 수있는 일은 아무것도 없지않을까 ?

여기서 잠깐 C언어 프로그래밍을 보면 return 0,1,-1 또는 exit(-1),exit(1) 이런 식으로 종료되는 경우가 많다. 어차피 뭘 넣던 종료는 된다.

하지만 

뒤에 오는 값은 종료되는 프로세스의 부모프로세스에게 자신이 정상 종료인지 비정상 종료인지 알려주는 것이다.

부모 프로세스와 자식 프로세스가 완전 별도의 프로세스 일 수도 있지만 자식 프로세스의 결과에따라 부모 프로세스의 실행을 결정 할 수도 있기에 자식 프로세스가 정상적 종료인지 아닌지 궁금해 할 수도 있다.


프로세스가 종료될때 종료코드값를 리턴하는데 어디로 하느냐 바로 커널 오브젝트이다.  그래서 프로세스가 종료되어도 커널 오브젝트는 남아있는다.

그 커널 오브젝에 아무도 관심이 없어진 상태가 된야만 운영체제가 그 커널 오브젝트를 소멸 시킨다.



파일에 접근 할 때 ANSI 함수를 사용 하거나 시스템 함수를 사용 할 수 있다.

ANSI 함수를 사용하면 파일의 커널오브젝트가 생성되지 않는다고 알고있는 사람도 있다고하는데 ANSI 함수는 각 OS에 시스템 함수를 호출하는 방법으로 사용된다. 

즉 파일이 생성되면 반드시 커널 오브젝트는 생성된다.


프로세스가 자기 자신의 커널 오브젝트에 접근 할때는 핸들 테이블에 등록되어있는 값을 사용하는것이 아니라 자기자신을 의미하는 상수값을 반환받는다.



예제


#include <stdio.h>

#include <windows.h>

#include <tchar.h>


int _tmain(int argc, TCHAR* argv[])

{

STARTUPINFO si={0,};

PROCESS_INFORMATION pi;

si.cb=sizeof(si);


TCHAR command[]=_T("Operation2.exe");


CreateProcess(NULL,     // 프로세스 생성.

     command,

 NULL,

 NULL, 

 TRUE, 

 0, 

 NULL, 

 NULL, 

 &si, 

 &pi

);  //CreateProcess


while(1)

{

for(DWORD i=0; i<10000; i++)

for(DWORD i=0; i<10000; i++); //Busy Waiting!!

_fputts(_T("Operation1.exe \n"), stdout); 

timing+=1;

if(timing==2)

 SetPriorityClass(pi.hProcess,

 NORMAL_PRIORITY_CLASS);   //우선순위를 다시 낮춘다.


}


return 0;

}


--------------------------------------------------------------------------------

Operation2.exe


#include <stdio.h>

#include <windows.h>

#include <tchar.h>


int _tmain(int argc, TCHAR* argv[])

{

SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);


while(1)

{

for(DWORD i=0; i<10000; i++)

for(DWORD i=0; i<10000; i++); //Busy Waiting!!


_fputts(_T("Operation2.exe \n"), stdout); 

}


return 0;

}


저번 예제와 거의 같다. 달라진 점은 프로세스를 실행시키면 자식프로세스인 operation.exe는 자신의 우선순위를 높인다. 그후에 부모프로세스가 우선순위를 다시 일반적 우선순위로 바꾼다. 

프로세스를 실행시킬때 PROCESS_INFORMATION pi; 구조체를 전달해주는데 

pi 구조체의 pi.hProcess를 통해 해당 프로세스를 통해 접근하게 된다.





커널 오브젝트(Kernel Object)에 대한 이해


커널오브젝트(Kernel Object)

커널에 의해 관리되는 리소스 정보를 담고 잇는 데이터 블록



         Kernel Object                                                 Resource

 

파이프 커널 오브젝트


프로세스 커널 오브젝트


쓰레드 커널 오브젝트


<---------------->





<---------------->





<---------------->

 파이프 


프로세스


쓰레드




커널은 쉽게 말해서 OS의 핵심 이다.

파일의 생성,접근,삭제등을 할때 우리가집접하는 것이 아니라 우리가 OS에 명령을 내리면 OS가 이러저러한 잡업을 해준다.

이런 리소스를 컨트롤하기 위해서 필요한 정보를 담고있는 것이 커널오브젝트이다.


프로세스 기반 커널 오브젝트



CreateProcess 함수를 호출한다고 한다면 OS에 요청을 하면 OS는 프로세스만 생성하는 것이아니라 프로세스의 정보와 프로세스의관리에 필요한 정보들로 이루어진 커널 오브젝트를 생성한다.

OS에는 커널 오브젝트를 관리하기위한 구조체가 존재한다.




커널 오브젝트와 핸들의 관계


         Handle                                    Kernel Object                         Resource

 

파이프 핸들


프로세스 핸들


쓰레드 핸들




<----->





<----->




 

<----->


 

 

파이프 커널 오브젝트


프로세스 커널 오브젝트


쓰레드 커널 오브젝트





<--->





<--->




 

<--->


  

 

파이프


프로세스


쓰레드



커널 오브젝트에는 여러가지 정보가 담겨있지만 커널 오브젝트에는 직접접근이 허용되지 않는다.

그렇기에 커널 오브젝트에 정보를 바꿀 수 있게 하기위해 관련된 시스템 함수를 제공한다.

이러한 함수를 사용하기위해서 커널 오브젝트를 지정해야하는데 직접접근 할 수 있는 어떻한 정보도 얻지못한다  이러한 관계로 

OS는 커널 오브젝트를 만들때 각각 고유한 번호를 매긴다 이를 핸들(Handle)이라한다. 핸들은 우리가 반환받을 수있다.


단순하게 보면

커널 오브젝트는 구조체 변수이고 핸들은 숫자 일 뿐이다.

핸들을 얻는 방법은 리소스 별로 각각다르기 때문에 일일이 알아 두어야 한다.


※커널 오브젝트마다 고유 핸들 값이있는것은아니다.

자세한 내용은 다음장에 계속...





밑의 예제는 커널 오브젝트에 접근하여 프로세스의 우선순위를 변경하는 예제이다.


예제6-1]


#include <stdio.h>

#include <windows.h>

#include <tchar.h>


int _tmain(int argc, TCHAR* argv[])

{

STARTUPINFO si={0,};

PROCESS_INFORMATION pi;

si.cb=sizeof(si);


TCHAR command[]=_T("Operation2.exe");


CreateProcess(NULL,     // 프로세스 생성.

     command,

 NULL,

 NULL, 

 TRUE, 

 0, 

 NULL, 

 NULL, 

 &si, 

 &pi

);  //CreateProcess


while(1)

{

for(DWORD i=0; i<10000; i++)

for(DWORD i=0; i<10000; i++); //Busy Waiting!!

_fputts(_T("Operation1.exe \n"), stdout); 

}


return 0;

}


예제6-2]


#include <stdio.h>

#include <windows.h>

#include <tchar.h>


int _tmain(int argc, TCHAR* argv[])

{

SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);

            //GetCurrentProcess함수는 프로세스자신의 핸들을 얻어오는 함수이다.


while(1)

{

for(DWORD i=0; i<10000; i++)

for(DWORD i=0; i<10000; i++); //Busy Waiting!!


_fputts(_T("Operation2.exe \n"), stdout); 

}


return 0;

}


6-1과 6-2모두 단순 출력 프로그램이다.

예제6-1은 예제 6-2를 생성시키는데  6-2는 자신의 우선순위를 올리는 함수를 호출한다.

Busy Waiting은 출력결과가 너무빠르기때문에 출력을 늦추기위함인데 

sleep함수도 있긴하지만 sleep함수로 프로세스를 늦추면 Running상태에서 Blocked상태로 된다. 

하지만 Busy Waiting을 시키면 프로세스가 Blocked상태가아니라 스케줄러에 의해서 

Ready 상태로 된다.

예제가 우선순위에 의한 프로세스의 실행량을 보기 위함이기 때문에 sleep함수보다

Busy Waiting을 사용해야한다.


실행결과 

Operation2.exe

OpOperation2.exe

Operation2.exe

Operation2.exe

Operation2.exe

Operation2.exe

Opraeration2.exe

Operation2.exe

Operation2.exe

Operation2.exe

Operation2.exe

Opertion1.eation2.exe

Operation2.exe

중략.....................

결과를 보면 예제6-1보다 확실히 예제6-2가 많이 실행된것을 알 수있다.
또한 함수호출이 완료되기 이전에도 우선순위가 넘어갈 수 있다.

우선순위의 기준은 함수 단위가아닌 명령어 단위로 이루어 지기때문이다.




+ Recent posts