기술면접

#4 신입 프로그래머 기술(실무) 면접준비 [네트워크]

91MS 2017. 10. 28. 23:07

OSI 7 Layer와 각 계층에 대한 설명을 해보아라

  • OSI 7 Layer 란 통신 접속에서 완료까지의 과정을 7단계로 정의한 국제 통신 표준 규약으로 다음과 같이 분류된다. 
  • 물리계층 : 전송하는데 필요한 기능을 제공. 장비로는 통신 케이블, 허브가 존재한다.
  • 데이터링크계층 : 송/수신을 확인. MAC Address를 가지고 통신. 장비로는 브릿지와 스위치가 존재한다.
  • 네트워크계층 : 패킷을 네트워크 간의 IP를 통하여 데이터를 전달, 장비로는 라우팅이 존재한다.
  • 전송계층 : 두 호스트 시스템으로부터 발생하는 데이터의 흐름을 제공한다.
  • 세션계층 : 통신 시스템 사용자간의 연결을 유지 및 설정한다.
  • 표현계층 : 세션 계층 간의 주고받는 인터페이스를 일관성 있게 제공한다.
  • 응용계층 : 사용자가 네트워크에 접근할 수 있도록 서비스를 제공한다.

 

TCP/IP 프로토콜 스택 4계층으로 구분짓고 설명하라

  • LINK 계층
    • 물리적인 영역의 표준화에 대한 결과. 가장 기본이 되는 영역으로 LAN, WAN, MAN과 같은 네트워크 표준과 관련된 프로토콜을 정의하는 영역.
  • IP 계층
    • 경로검색을 해주는 계층. IP 자체는 비연결지향적이며 신뢰할 수 없는 프로토콜이다. 데이터를 전송할 때마다 거쳐야 할 경로를 선택해주지만, 그 경로는 일정치 않다. 특히 데이터 전송 도중에 경로상에 문제가 발생하면 다른 경로를 선택해 주는데, 이 과정에서 데이터가 손실되거나 오류가 발생하는 등의 문제가 발생한다고 해서 이를 해결해주지 않는다. 즉, 오류발생에 대한 대비가 되어있지 않은 프로토콜이다.
  • TCP/UDP(전송) 계층
    • 데이터의 실제 송수신을 담당한다. UDP는 TCP에 비해 상대적으로 간단하며, TCP는 신뢰성 있는 데이터의 전송을 담당한다. 그런데 TCP가 데이터를 보낼 때 기반이 되는 프로토콜이 IP이다. 앞서 말했듯이 IP 계층은 문제가 발생한다면 해결해주지 않는 신뢰되지 않은 프로토콜이다. 그 문제를 해결해 주는 것이 TCP. 데이터가 순서에 맞게 올바르게 전송이 갔는지 확인을 해주며 대화를 주고받는다. 확인절차를 걸쳐서 신뢰성 없는 IP에 신뢰성을 부여한 프로토콜이라 생각하면 됨.
  • APPLICATION 계층
    • 이러한 서버와 클라이언트를 만드는 과정에서 프로그램의 성격에 따라 데이터 송수신에 대한 약속(규칙)들이 정해지기 마련인데, 이를 가리켜 Aplication 프로토콜이라한다.

 

TCP에 대해 설명하시오

  • TCP 서버의 함수호출 순서 : socket() 소켓생성 -> bind() 소켓 주소할당 -> listen() 연결요청 대기상태 -> accept() 연결허용 -> read()/write() 데이터 송수신 -> close() 연결종료
  • TCP 클라이언트의 함수호출 순서 : socket() 소켓생성 -> connect() 연결요청 -> read()/write() 데이터 송수신 -> close() 연결종료
  • 서버와 클라이언트의 차이점은 ‘연결요청’이라는 과정이다. 이는 클라이언트 소켓을 생성한 후에 서버로 연결을 요청하는 과정. 서버는 listen()를 호출한 이후부터 연결요청 대기 큐를 만들어 놓는다. 따라서 그 이후부터 클라이언트는 연결요청을 할 수 있다. 이 때, 서버가 바로 accept()를 호출할 수 있는데 이때는, 연결되기 전까지 호출된 위치에서 블로킹 상태에 놓이게 된다.
  • 3-way handshaking : TCP 소켓은 연결설정 과정에서 총 세번의 대화를 주고 받는다.
    • SYN :: Synchronize Sequence Number 연결 요청 플래그
    • ACK :: Acknoledgement 응답
    • 클라이언트는 서버에 접속을 요청하는 SYN(M) 패킷을 보낸다. 
    • 서버는 클라이언트의 요청인 SYN(M)을 받고 클라이언트에게 요청을 수락한다는 ACK(M+1)와 SYN(N)이 설정된 패킷을 발송한다.
    • 클라이언트는 서버의 수락 응답인 ACK(M+1)와 SYN(N) 패킷을 받고 ACK(N+1)를 서버로 보내면 연결이 성립된다.
    • 클라이언트가 연결을 종료하겠다는 FIN플래그를 전송한다.
    • 서버는 클라이언트의 요청(FIN)을 받고 알겠다는 확인 메세지로 ACK를 보낸다. 그리고나서는 데이터를 모두 보낼 때까지 잠깐 TIME_OUT이 된다.
    • 데이터를 모두 보내고 통신이 끝났으면 연결이 종료되었다고 클라이언트에게 FIN플래그를 전송한다.
    • 클라이언트는 FIN 메세지를 확인했다는 메세지(ACK)를 보낸다.
    • 클라이언트의 ACK 메세지를 받은 서버는 소켓 연결을 Close한다.
    • 클라이언트는 아직 서버로부터 받지 못한 데이터가 있을 것을 대비해 일정 시간 동안 세션을 남겨놓고 잉여 패킷을 기다리는 과정을 거친다. (TIME_WAIT)

 

UDP에 대해 설명하시오

  • UDP는 TCP의 대안이며, IP와 함께 쓰일 때에는 UDP/IP라고 표현하기도 한다.
  • TCP와 마찬가지로 실제 데이터 단위를 받기위해 IP를 사용한다. 그러나 TCP와 달리, 메세지를 패킷으로 나누고, 반대편에서 재조립하는 등의 서비스를 제공하지 않는다. 즉, 여러 컴퓨터를 거치지 않고 데이터를 주고 받을 컴퓨터끼리 직접 연결하고자 할때 UDP를 사용한다.
  • UDP를 사용해서 목적지(IP)로 메세지를 보낼 수 있고, 컴퓨터를 거쳐서 목적지까지 도달할 수도 있다. 허나 도착하지 않을 수도 있다. 정보를 받는 컴퓨터에서는 포트를 열어두고 패킷이 올 때까지 기다리며 데이터가 온다면 모두 다 받아들인다. 패킷이 도착했을 때 출발지에 대한 정보(IP, PORT)를 알 수 있다.
  • UDP는 이러한 특성 때문에 안정적이지 않은 프로토콜이다. 하지만 TCP에 비해서 속도가 빠른편이기에 데이터의 유실이 일어나도 큰 상관이 없는 스트리밍이나 화면전송에 사용된다.

 

Multi-Thread 서버에 대해 설명하시오 

  • 듣기 소켓을 통해서 새로운 클라이언트가 들어오면 fork(:2) 함수를 이용해서 자식 프로세스를 만드는 대신에, pthread_create(:3) 함수를 이용해서 새로운 스레드를 만드는 것이다. 이 스레드는 문맥을 포함한 코드 조각으로 듣기 소켓의 소켓 지정 번호를 매개 변수로 받아들일 수 있다. 이 듣기 소켓을 이용해서 클라이언트를 처리하는 식이다.
  • 핵심은 서버 프로그램이 듣기 소켓과 연결 소켓이 분리되어 있는데, 듣기 소켓에 클라이언트 연결이 들어와서 연결 소켓이 만들어 지면, 스레드를 만들어 클라이언트 요청을 처리하는데 있다. (대리자)
  • 스레드는 코드 조각이므로 프로세스를 복사하는 멀티 프로세스 방식보다 좀 더 작고 빠르게 작동하는 프로그램을 만들 수 있다. 반면 독립된 프로세스 단위로 구동되지 않기 때문에, 디버깅이 힘들다는 단점이 있다. 또한 하나의 스레드에 생긴 문제가 전체 프로세스에 문제를 줄 수 있다는 문제점도 있다.

 

IOCP에 대해 설명하시오

  • IOCP는 어떤 I/O 핸들에 대해서, 블록 되지 않게 비동기 IO작업 함으로 프로그램 대기시간을 줄이는 목적으로 사용된다. 우선 Overlapped IO의 개념이 기반이 된다. IOCP는 이런 Overlapped IO가 실행이 되고 알려주는 방법에 대한 것이다.
  • 동기화 오브젝트 세마포어의 특성과 큐(Queue)를 가진 커널 오브젝트이다. 스레드상에서 사용되며 대부분 여러 스레드, 즉 멀티스레드 상에서 사용된다. 큐를 자체적으로 운영하는 특징때문에 스레드 풀링에 적합하다.
  • 동기화와 동시에 큐를 통한 데이터전달(완료알림) IOCP는 스레드 풀링을 위한 것이라 할수 있다.
    • 여기서 풀링이란, 여러 스레드를 생성하여 대기시켜놓고 필요할 때 가져다가 사용 후 다시 반납하는 과정이다. 스레드 생성과 파괴에는 상당한오버헤드가 있기 때문.
  • 장점
    • 사용자가 설정한 버퍼만을 사용하기 때문에 더 효율적으로 작동한다. 기존에는 OS버퍼, 사용자 버퍼로 따로 분리되는 개념이었다.
    • IO요청에 대해서 효율적으로 접근한다. 디스크 IO의 경우 디스크에 접근을 효율적으로 한다. 순서대로가 아닌 효율적인 순서에 따라 접근하기도 함. 즉, 커널레벨에서는 모든 IO를 비동기적으로 처리한다.
  • 주의사항
    • 소켓생성(WSASocket)을 할 때 마지막에 반드시 WSA_FLAG_OVERLAPPED를 넣어줘야함
    • WSASend, WSARecv 사용 할 때 Overlapped구조체를 넣어줄것
  • Server 소스
#define _WINSOCK_DEPRECATED_NO_WARNINGS 

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <process.h> 

#define BUFSIZE 1024 

typedef struct {
    SOCKET hClntSock;
    SOCKADDR_IN clntAddr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct {
    OVERLAPPED overlapped;
    char buffer[BUFSIZE];
    WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;

unsigned int __stdcall CompletionThread(LPVOID pComPort);
void ErrorHandling(char *message);

#pragma comment(lib, "ws2_32.lib")

int main(int argc, char** argv) {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    	ErrorHandling("WSAStartup() error!");

    HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    SYSTEM_INFO SystemInfo;
    GetSystemInfo(&SystemInfo);

    for (int i = 0; i<SystemInfo.dwNumberOfProcessors; ++i)
    	_beginthreadex(NULL, 0, CompletionThread, (LPVOID)hCompletionPort, 0, NULL); 

    SOCKET hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

    SOCKADDR_IN servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //servAddr.sin_port = htons(atoi("2738"));
    servAddr.sin_port = htons(2738);

    bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr));
    listen(hServSock, 5);

    LPPER_IO_DATA PerIoData;
    LPPER_HANDLE_DATA PerHandleData;	
    
    int RecvBytes;
    int i, Flags;

    while(TRUE) {
    	SOCKADDR_IN clntAddr;
        int addrLen = sizeof(clntAddr);
        SOCKET hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &addrLen);
        PerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
        PerHandleData->hClntSock = hClntSock;
        memcpy(&(PerHandleData->clntAddr), &clntAddr, addrLen);
        CreateIoCompletionPort((HANDLE)hClntSock, hCompletionPort, (DWORD)PerHandleData, 0);
        
        PerIoData = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
        memset(&(PerIoData->overlapped), 0, sizeof(OVERLAPPED));
        PerIoData->wsaBuf.len = BUFSIZE;
        PerIoData->wsaBuf.buf = PerIoData->buffer;
        Flags = 0;
        WSARecv(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, (LPDWORD)&RecvBytes, (LPDWORD)&Flags, &(PerIoData->overlapped), NULL);
    }

    return 0;
}

unsigned int __stdcall CompletionThread(LPVOID pComPort) {
    HANDLE hCompletionPort = (HANDLE)pComPort;
    DWORD BytesTransferred;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_DATA PerIoData;
    DWORD flags;

    while (1) {
    	GetQueuedCompletionStatus(hCompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE);
        
        if (BytesTransferred == 0) {
            closesocket(PerHandleData->hClntSock);
            free(PerHandleData);
            free(PerIoData);
            continue;
        }
        
        PerIoData->wsaBuf.buf[BytesTransferred] = '\0';
        printf("Recv[%s]\n", PerIoData->wsaBuf.buf);
        
        PerIoData->wsaBuf.len = BytesTransferred;
        WSASend(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, NULL, 0, NULL, NULL);
        memset(&(PerIoData->overlapped), 0, sizeof(OVERLAPPED));
        PerIoData->wsaBuf.len = BUFSIZE;
        PerIoData->wsaBuf.buf = PerIoData->buffer;
        flags = 0;
        WSARecv(PerHandleData->hClntSock, &(PerIoData->wsaBuf), 1, NULL, &flags, &(PerIoData->overlapped), NULL);
    }

    return 0;
} 

void ErrorHandling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

  • Client 소스
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS 

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h> 

void ErrorHandling(char *message); 

#pragma comment(lib, "ws2_32.lib") 

int main(){
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    	ErrorHandling("WSAStartup() error!"); 

    SOCKET hSocket = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (hSocket == INVALID_SOCKET)
    	ErrorHandling("socket() error"); 

    SOCKADDR_IN recvAddr;
    memset(&recvAddr, 0, sizeof(recvAddr));
    recvAddr.sin_family = AF_INET;
    recvAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    recvAddr.sin_port = htons(2738); 

    if (connect(hSocket, (SOCKADDR*)&recvAddr, sizeof(recvAddr)) == SOCKET_ERROR)
    	ErrorHandling("connect() error!"); 

    WSAEVENT event = WSACreateEvent();
    WSAOVERLAPPED overlapped;
    memset(&overlapped, 0, sizeof(overlapped));
    overlapped.hEvent = event; 

    WSABUF dataBuf;
    char message[1024] = { 0, };
    int sendBytes = 0;
    int recvBytes = 0;
    int flags = 0; 

    while (true){
    	flags = 0;
        printf("전송할데이터(종료를원할시exit)\n");
        scanf("%s", message);
        
        if (!strcmp(message, "exit")) break;
        
        dataBuf.len = strlen(message);
        dataBuf.buf = message;
        
        if (WSASend(hSocket, &dataBuf, 1, (LPDWORD)&sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR){
        	if (WSAGetLastError() != WSA_IO_PENDING)
                	ErrorHandling("WSASend() error");
        }
        
        WSAWaitForMultipleEvents(1, &event, TRUE, WSA_INFINITE, FALSE);
        WSAGetOverlappedResult(hSocket, &overlapped, (LPDWORD)&sendBytes, FALSE, NULL);
        printf("전송된바이트수: %d \n", sendBytes);
        
        if (WSARecv(hSocket, &dataBuf, 1, (LPDWORD)&recvBytes, (LPDWORD)&flags, &overlapped, NULL) == SOCKET_ERROR){
        	if (WSAGetLastError() != WSA_IO_PENDING)
            		ErrorHandling("WSASend() error");
        }
        
        printf("Recv[%s]\n", dataBuf.buf);
    } 

    closesocket(hSocket);
    WSACleanup(); 

    return 0;
} 

void ErrorHandling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);

    exit(1);
}