Without a Break

BSD Sockets API-based Network Programming 본문

Network/네트워크보안과프로그래밍

BSD Sockets API-based Network Programming

와븨 2022. 11. 6. 23:27

파일 입출력

파일모드 기능 설명
"r" 읽기 전용 파일을 읽기 전용으로 연다. 단, 파일이 반드시 있어야 한다.
"w" 쓰기 전용 새 파일을 생성한다. 만약 파일이 있으면 내용을 덮어쓴다.
"a" 추가 파일을 열어 파일 끝에 값을 이어 쓴다. 만약 파일이 없으면 파일을 생성한다.
"r+" 읽기/쓰기 파일을 읽기/쓰기 용으로 연다. 단, 파일이 반드시 있어야 하며 파일이 없으면 NULL을 반환한다.
"w+" 읽기/쓰기 파일을 읽기/쓰기 용으로 연다. 파일이 없으면 파일을 생성하고, 파일이 있으면 내용을 덮어쓴다.
"a+" 추가(읽기/쓰기) 파일을 열어 파일 끝에 값을 이어 쓴다. 만약 파일이 없으면 파일을 생성한다. 읽기는 파일의 모든 구간에서 가능하지만, 쓰기는 파일의 끝에서만 가능하다.
"t" 텍스트 모드 파일을 읽거나 쓸 때 개행 문자 \n과 \r\n을 서로 변환한다.
^Z 파일의 끝으로 인식하므로 ^Z까지만 파일을 읽는다.
"b" 바이너리 모드 파일의 내용을 그대로 읽고, 값을 그대로 쓴다.

*"t"와 "b"는 Windows전용

 

1. 서식을 지정하여 파일에 문자열 읽기

#define _CRT_SECURE_NO_WARNINGS //fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> //fopen, fscanf, fclose 함수가 선언된 헤더 파일

int main()
{
    char s1[10];
    int num1;
    
    FILE *fp = fopen("hello.txt", "r"); //hello.txt 파일을 읽기 모드(r)로 열기
                                        // 파일 포인터 반환
    fscanf(fp, "%s %d", s1, &num1); //서식을 지정하여 파일에서 문자열 읽기
    printf("%s %d\n", s1, num1); //Hello 100: 파일에서 읽은 값을 출력
    
    fclose(fp); //파일 포인터 닫기
    
    return 0;
}

 

2. 제한된 버퍼로 파일 전체 읽기

#define _CRT_SECURE_NO_WARNINGS	 //fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> //fopen, feof, fread, fclose 함수가 선언된 헤더 파일
#include <string.h> //strlen, memset 함수가 선언된 헤더 파일

int main()
{
    char buffer[5] = {0, }; //문자열 데이터 4바이트 NULL 1바이트. 4+1=5
    int count = 0;
    int total = 0;
    
    FILE *fp = fopen("hello2.txt", "r"); //hello2.txt 파일을 읽기 모드(r)로 열기
                                         //파일 포인터 반환
    while(feof(fp) == 0)
    {
        count = fread(buffer, sizeof(char), 4, fp); //1바이트씩 4번(4바이트) 읽기
        printf("%s", buffer); //읽은 내용 출력
        memset{buffer, 0, 5) //버퍼를 0으로 출력
        total += count; //읽은 크기 누적
    }
    
    printf("\ntotal: %d\n", total): //total: 13: 파일을 읽은 전체 크기 출력
    
    fclose(fp); //파일 포인터 닫기
    
    return 0;
}

 

3. 파일에서 구조체 읽고 쓰기

3-1.ANSI C 표준

앞에 제시한 코드들은 텍스트 형식의 파일로부터 문자열을 읽는다고 가정하였으나, 현재 많은 파일들은 바이너리 형식으로 작성되어 있다. (바이너리 파일이 공간 활용 효율이 훨씬 좋기 때문)

=> 실무에서는 구조체를 활용하여 바이너리 파일을 처리함.

(단, 호환성이 중요한 웹 기술에선 문자열을 이용해 정보를 저장하는 경우도 많음)

 

<stdio.h>에 정의된, ANSI C 표준 라이브러리의 파일 입출력 함수들은 FILE이라는 구조체를 fopen()을 이용해 메모리 상에 생성한 뒤, 그 시작 주소를 리턴값으로 받아 쓰는 파일 포인터 FILE *를 참조하는 함수들임

  • UNIX 스타일의 파일 입출력이 마음에 들지 않았던 표준 라이브러리 설계자들이 사용자 프로세스의 공간(heap 영역)에 FILE 구조체를 할당해서 직접 관리하는 접근을 사용하였음.
  • 파일의  포인터, 구조체를 사용하는 이유 : 서로 다른 운영체제를 고려한 것

 

3-2. 유닉스 스타일

UNIX에서는 포인터 대신 int형인 file descriptor(fd)를 사용해 파일 접근

  • 커널은 프로세스 별로 프로세슷 제어 블록(PCB)라는 공간에 fd와 파일의 포인터를 기록한 file descriptor 테이블을 유지

 

ANSI C 스타일이 파일에 대한 정보를 구조체의 형태로 사용자 프로그램에 노출시키는 반면, 유닉스 스타일은 번호표만 제공하여 실제 일은 커널이 함

 

 

BSD SOCKETS

1. 유닉스(UNIX)와 유닉스 계열(Unix-like) 운영체제

유닉스(UNIX)

  • 1969년 AT&T Bell Laboratory에서 일하는 Dennis Ritchie와 Ken Thompson이 만든 운영체제
  • AT&T(현재는 The Open Group)에서 해당 용어를 UNIX라는 트레이드마크로 등록해서 사용을 제한하였기 때문에, 사용 허락을 따로 맡지 않았다면 Unix로 기재하는 게 바람직함

유닉스 계열 운영체제 (Unix-like System)

  • AT&T에 라이선스 비용을 내고 코드를 사서 자체 운영체제로 개조함
  • 리누스 토발즈가 처음부터 새로 작성한, 유닉스의 기능을 흉내낸 운영체제

 

1-1. 유닉스 계열 운영체제의 표준화 작업

여러 운영체제의 난립으로 인해 여러 단체에서 프로그래밍 인터페이스를 표준화함

 

 

2. 네트워크 프로그래밍과 소켓 프로그래밍

2-1. 소켓 프로그래밍이란?

유닉스 계열 운영체제인 BSD에 포함된 TCP/IP 네트워킹 구현에서 사용된 BSD Sockets Application PRogramming Interface(API)를 바탕으로 한 네트워크 프로그래밍

 

현재까지 쓰이는 사실 상 모든 소켓 API는 BSD Socket API 또는 BSD Sockets API의 표준화 버전인 POSIX Sockets API를 따르고 있음

 

2-2. 소켓 및 BSD 소켓이란?

Socket : 특정 포트 식별자를 포함하는 주소, 즉 인터넷 주소와 TCP 포트의 연결

  • 소켓을 네트워크 주소(IP주소)와 호스트 주소(포트 번호)를 연접(concatenate)한 것으로 정의됨
  • 한 쌍의 소켓이 각 TCP 연결을 유일하게 특정함
  • 프로세스 간 통신에 사용되는 일시적인 개체이며, 일부 프로세스가 소켓을 참조하는 디스크립터를 보유할 경우에만 존재
  • 소켓은 소켓 시스템 콜에 의해 생성되며, 소켓에 대한 디스크립터를 반환
  • 통신 도메인과 주소를 담는 구조체의 형태로 구성되어 있음
  • 소켓은 소켓 디스크립터로 특정되는 임시적으로 존재하는 객체임

 

2-3. 소켓이란?

소켓 : 컴퓨터 네트워크를 통한 프로세스 간 통신 흐름의 endpoint

  • Local socket address : 로컬 IP주소와 포트 번호
  • Remote socket address : TCP 서버가 여러 클라이언트를 동시에 서비스할 수 있으므로 설정된 TCP 소켓에만 해당함. 서버는 각 클라이언트 당 하나의 소켓이 생성하고 이 소켓들은 같은 로컬 소켓 주소를 공유함
  • Protocol : 전송 프로토콜 ex)TCP, UDP

소켓 API : 응용 프로그램을 제어하고 네트워크 소켓을 사용하는 것을 허락함. 주로 운영체제에서 제공

 

 

3. Windows Sockets API(WSA, Winsock)

3-1. 윈도우의 네트워킹

윈도우는 산업계에서 요구했던 여러 종류의 네트워킹 API를 제공하고 있음 (이 중 일부는 유닉스 계열 운영체제에서도 지원됨)

  • Windows Sockets API(Winsock) : 응용 프로그램이 BSD Sockets API 스타일로 네트워크를 활용하기 위한 API
  • Winsock Kernel(WSK) : 커널 모듈이 네트워크를 활용하기 위한 API
  • Remote procedure call(RPC) : 분산 컴퓨팅 환경에서 사용되는 네트워크
  • Web accress APIs : HTTP, FTP, Gopher 등의 인터넷 응용 계층에 대한 네트워킹 API 제공
  • NetBIOS : 예전 IBM PC에서 Local Area Network (LAN)에 접속하기 위해 만들어진 네트워킹 API

 

3-2. Windows Sockets API의 특징

- API를 사용하는 코드 상단에 아래 코드를 기재해 명시적으로 정적 라이브러리를 링크해야 함

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

- 실제 대부분의 Winsock 함수들은 Winsock 동적 라이브러리(WS2_32.DLL)에 정의되어 있으며, 함수 사용 전에 WSAStartup() 함수를 먼저 호출해 동적 라이브러리를 불러오고 (load) 초기화를 해야 함

WSADATA was;
if (WSAStartup (MAKEWORD(2,2), &wsa) != 0)
    exit(1);

- Winsock 함수들의 사용이 끝났으면 동적 라이브러리를 unload하는 WSACleanup() 함수를 호출해야 함

  • 호출 시 아무런 통보 없이 전송 중인 연결이 끊어지게 되므로, WSACleanup()를 호출하기 전에 각 소켓 연결 종료 시에는 close() 함수 대신 shutdown() 함수 같이 연결 종료를 기다리는 함수를 사용하는게 바람직함

 

4. Client-Server Model for Socket

4-1.Client-Server Model

- 두 프로세스 간에 통신 채널을 구축하려면 다른 프로세스가 대기하는 동안 한 프로세스가 주도권을 잡고 있어야 함

- 연결을 시작하는 것은 클라이언트이고, 서버는 시작되는 것을 기다림

  • peer-to-peer 연결에서, 프로그램은 클라이언트와 서버 모두로 작동할 수 있음
  • 클라이언트 : Sometimes on
  • 서버 : always on

- 서버는 응용 프로그램에 따라 연결 지향(TCP) 또는 connectionless(UDP)일 수 있음

 

4-2. Connection-oriented (TCP) vs. Connectionless(UDP)

- socket() 소켓을 만드는 함수

- conenct() : 원격 호스트에 연결하는 함수

- bind() : 소켓에 local address를 할당하고 해당 프로세스를 묶는 함수

- listen() : 소켓을 통해 들어오는 연결을 듣는 함수

- accept() : 연결이 들어왔을 때 이를 받아들이는 함수

- send() : 소켓 파일 디스크립터로 내용을 전송하는 함수

- recv() : 소켓 파일 디스크립터로부터 내용을 읽는 함수

 

4-3. 서비스를 식별하는 데 사용하는 포트

목적지 포트는 소켓의 유일무이한 식별자

 

4-4. 너무 많은 클라이언트가 도착한다면?

서버는 Multiple requests를 위해서 동시 접속을 제공해야 함

 - Process-based (fork)

  • 새로운 연결을 위해 fork()로 자식 프로세스를 생성
  • 단점 : Context switch를 위해 큰 오버헤드가 일어날 수 있음

 - Threading 

  • 각 사용자를 위해 Multiple threads 제공
  • 이해가 쉬움
  • 단점 : 복잡도 증가로 경쟁 상태 발생 가능, 의도치 않은 데이터 공유 에러 가능성이 있음

- 'Select' function (event-based method)

  • 멀티 디스크립터를 모니터하기 위한 소켓 세트 설정
  • 동시에 여러 소켓을 다루는 것이 아니라 여러 사용자로부터 하나의 event가 발생 시 이를 처리함
  • 명시적 제어 흐름, 경쟁상태 없음
  • 명시적 제어 흐름은 더 복잡함

몇 클라이언트는 연결이 block되거나 대기해야할 수 있음

 

 

 

5. BSD Sockets API : Daytime Protocol

5-1. Daytime Protocol

TCP 기반 Daytime 서비스 : 3단계로 이루어짐

 ① 클라이언트와 서버와 TCP 연결을 맺음 (TCP connection establishment)

    - 서버는 클라이언트가 13번 TCP 포트로 접속할거라 가정하고 들음

 ② 서버는 연결이 맺어졌으면 날짜와 시간 등의 정보를 ASCII 문자열의 형태로 보냄

 ③ 보낸 뒤에 곧바로 서버가 TCP 연결을 닫음 (TCP connection close)

 

UDP 기반 Daytime 서비스 : 2단계로 이루어짐

 ① 클라이언트가 서버에 아무 내용이나 담아서 UDP 데이터그램을 보냄

    - 서버는 클라이언트가 13번 UDP 포트로 접속할거라 가정하고 들음

 ② 서버는 받은 UDP 데이터그램의 내용은 무시하고 날짜오 시간 등의 정보를 ASCII 문자열의 형태로 보냄

 

5-2. Daytime TCP Client (daytimetcpcli.c)

#include "unp.h"

int main(int argc, char *argv[])
{
    int sockfd, n; //sockfd 소켓 디스크립터(번호표), n은 시간 정보의 크기(바이트 단위)
    char recvline[MAXLINE+1]; //서버로부터 받을 시간 정보를 저장할 버퍼
    struct sockaddr_in servaddr; //서버의 주소를 저장한 인터넷 소켓 주소 구조체
                                 // 시간 서버의 IP주소가 인자로 주어졌는지 체크
    if(argc != 2)
        err_quit("usage: %s <IPaddres>", argv[0]);
#ifdef _WIN32
    WSAStartup(MAKEWORD(2,2), &wsaData); //Windows Sockets API Version 2.2
#endif
    //TCP 소켓을 생성해 socket descriptor를 받아와서 sockfd에 저장
    if ((sockfd=socket(AF_INET, SOCK_STREA,0)) < 0)
        err_sys("socket error");
    //서버 주소에 대한 구조체 servaddr에 주소를 설정
    bzero(&servaddr, sizeof(servaddr)); //0으로 제공
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(13); //daytime server port number
    if (inet_pton(AF_INET, argv[1], &servaddr, sin+addr) <= 0)
        err_quit("inet_pton error for %s", argv[1]); //argv[1] 문자열에 저장된 dotted
                                                     //decimal 형식의 서버 주소를 32비트 이진수로 변환
    //servaddr에 저장된 주소로 연결 시도
    if (connect(sockfd, (SA*) &servaddr, sizeof(servaddr)) < 0)
        err_sys("connect error");
        
    //연결된 소켓을 통해서버가 보낸 바이트 열 (즉, 읽을 문장)이 있으면
    while( (n = read(sockfd, recvline, MAXLINE)) > 0) { //이를 revline에 저장함
        recvline[n] = 0; //buffer overflowㄹ르 막기 위해 마지막 글자는 널 캐릭터로 바꿈
        if(fputs(revline, stdout) == EOF) //stdout에 revline을 밀어넣음
            err_sys("fputs error"); //코드 구조 상 fputs의 결과가 EOF가 나올 수 없음
    }
    
    if (n < 0) //정상적이었다면 n==0이어야 하며, n<0이면 while 루프에 문제가 있었음
        err_sys("read error");
        
#ifdef _WIN32
    WSAClenaup(); //Winsock 사용 후 정리
#endif
    exit(0);
}

 

5-3. Daytime TCP Server (daytimetcpcli.c)

#include "unp.h"
#include <time.h>

int main(int argc, char *argv[]) {
    int listenfd, connfd; //client를 기다리는 listenfd와, 연결 수립 후 사용하는 connfd
    char buff[MAXLINE]; //client에 보낼 내용을 저장하는 buffer
    struct sockaddr_in servaddr; //server가 기다리려는 client의 주소 범위를 저장함
    time_t ticks;
#ifdef _WIN32
    WSAStartup(MAKEWORD(2,2), &wsaData); //Windows Sockets API Version 2.2
#endif
    //TCP 소켓을 생성해 socket descriptor를 받아와서 sockfd에 저장
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");
    //서버 주소에 대한 구조체 servaddr에 주소를 설정함
    bzero(&servaddr, sizeof(servaddr)); //0으로 채움
    servaddr.sin_family = AF_INET; //Internet Protocol Suite 사용
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //0으로 채우는 것과 동일
    servaddr.sin_port = htons(13); //daytime server port number
    //serveraddr에 저장된 주소를 소켓에 bind함
    if(bind(listenfd, (SA*) &servaddr, sizeof(servaddr)) < 0)
        err_sys("bind error");
    //공간을 줄이기 위해 socket sockaddr는 SA로 치환함. conect는 SA* 타입을 받아야 하는데 &servaddr는 struct_sockaddr_in* 타입으로 다른 상황이므로, 타입 캐스팅이 필요
    
    //bind한 소켓을 통해 client의 접속을 대기함
    int backlog = LISTENQ; //listen 함수가 리턴될 때까지 받을 수 있는 클라이언트의 최대 수
    if (listen(listenfd, backlog) < 0)
        err_sys("listen error");
    for(;;){
    again:
        if((connfd = accept(listenfd, (SA*) NULL, NULL)) < 0)
            if(errno == EPROTO || errno == ECONNABORTED) //SW문제로 연결 중단
                goto again;
            else
                err_sys("accept error");
        ticks = time(NULL);
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        //수립된 연결에 대한 socket descriptor인 connfd에 저장된 시간 문자열을 기록
        if(write(connfd, buff, strlen(buff)) != strlen(Buff))
            err_sys("send error");
        if(close(connfd) == -1) //accpet로 얻은 소켓 connfd을 담음
            err_sys("close error");
    }
#ifdef _WIN32
    WSACleanup();
#endif
    exit(0);
}

 

 

 

'Network > 네트워크보안과프로그래밍' 카테고리의 다른 글

Firewall  (0) 2022.12.02
Attacks on the TCP protocol  (0) 2022.11.21
TCP Connection Establishment and Termination  (0) 2022.10.07
TCP: Transmission Control Protocol  (0) 2022.09.30
UDP: User Datagram Protocol  (0) 2022.09.30