이더넷(Ethernet)이란 무엇인가?   

 

 데이터 링크 계층은 네트워크 장비 간에 신호를 주고 받는 규칙을 정하는 계층으로, 랜에서 데이터를 정상적으로 주고받기 위해 필요한 계층이다. 그 규칙들 중 일반적으로 많이 사용되는 규칙이 이더넷(Ethernet)이다. 즉, 랜에서 적용되는 규칙이다.

 

 

 

 

 이더넷은 허브와 같은 장비에 연결된 컴퓨터와 데이터를 주고 받을 때 사용한다. 하지만 허브는 전기 신호를 전달받을 포트뿐만 아니라 나머지 포트에도 전달한다는 문제점이 있다. 관계없는 다른 컴퓨터들도 데이터를 받으면 곤란한 상황이 발생할 수 있다.

 

 이러한 경우를 대비해 규칙을 정해둔다. 데이터에 목적지 정보를 추가해서 보내 목적지 이외의 컴퓨터는 데이터를 받더라도 무시하도록 말이다. 

 

 

 

 

 또한, 컴퓨터 여러 대가 동시에 데이터를 보내면 데이터들이 서로 부딪히는 충돌(collision)이 발생할 수 있다.

 

 

 

 이더넷은 데이터를 보내는 시점을 늦춰서 충돌을 방지하기 위한 구조로 설계되어 있다.  

 

 

 이러한 기법을 CSMA/CD라고 하는데 정의는 다음과 같다.

 

  • CS: 데이터를 보내려고 하는 컴퓨터가 케이블에 신호가 흐르고 있는지 아닌지를 확인하는 규칙
  • MA: 케이블에 데이터가 흐르고 있지 않다면 데이터를 보내도 좋다는 규칙
  • CD: 충돌이 발생하고 있는지를 확인하는 규칙

 하지만, 스위치(Switch)라는 네트워크 장비가 등장하면서 CSMA/CD는 거의 사용하지 않게 되었다. 

 


 

    MAC 주소란 무엇인가?   

 

 랜 카드는 비트열(0과 1)을 전기 신호로 변환한다. 이러한 랜 카드에는 MAC 주소라는 번호가 정해져 있는데, 이는 제조할 때 새겨지기 때문에 물리 주소라고 불리고 전 세계에서 유일한 번호로 할당되어 있다. MAC 주소는 48비트(6 바이트) 숫자로 구성되어 있으며, 그 중 앞 쪽 24비트는 랜 카드를 만든 제조사 번호이고 뒤 쪽 24비트는 제조사가 랜 카드에 붙인 일련 번호이다. 앞, 뒤쪽 모두 정해진 규칙이 있기 때문에 중복되지 않는다. 

 

  네트워크 통신에서 송신할 때에는 OSI 모델이나 TCP/IP 모델에서 필요한 정보를 헤더에 담아 데이터에 붙여나가는 작업인 캡슐화를 진행한다. OSI에서는 데이터 링크 계층, TCP/IP에서는 네트워크 계층에 해당하는데, 이 계층에서 이더넷 헤더와 트레일러를 붙이게 된다. 

 

 이더넷 헤더는 목적지의 MAC 주소(6 바이트), 출발지의 MAC 주소(6 바이트), 유형(2 바이트) 총 14바이트로 구성되어 있다. 

여기서 유형은 이더넷으로 전송되는 상위 계층 프로토콜의 종류를 나타낸다. 

 

 

 그리고 트레일러FCS(Frame Check Sequence)라고도 하는데, 데이터 전송 도중에 오류가 발생하는지 확인하는 용도로 사용한다. 

 

 이처럼 이더넷 헤더와 트레일러가 추가된 데이터를 프레임이라고 한다. 이 프레임은 네트워크를 통해 전송된다. 

 

 

 


 

    MAC 주소를 통한 프레임 전송 과정   

 

 

 아래 그림과 같이 컴퓨터 A에서 컴퓨터 B로 데이터 전송한다고 가정하면 이더넷 헤더에 목적지인 컴퓨터 B의 MAC 주소와 근원지인 컴퓨터 A의 MAC 주소 정보를 담는다. 그 후에 데이터에 이더넷 헤더와 트레일러를 추가하는 캡슐화 과정을 거쳐 프레임을 만들고 물리 계층에서 이 프레임 비트열을 전기 신호로 변환하여 네트워크를 통해 전송한다. 

 

 허브를 예로 들면 컴퓨터 A가 보낸 데이터를 컴퓨터 B~D에 다 전송하지만 컴퓨터 C, D는 이더넷 헤더에 붙어 있는 목적지 MAC 주소와 자신의 MAC 주소가 다르기 때문에 데이터를 파기한다. 컴퓨터 B는 목적지 MAC 주소와 자신의 MAC 주소가 일치하므로 데이터를 파기하지 않고 역캡슐화를 진행한다. 컴퓨터 B는 역캡슐화를 한 다음에 데이터를 수신한다.

 

 

 

 

 

 

 컴퓨터 A와 컴퓨터 C 동시에 컴퓨터 B로 데이터를 전송한다면 충돌이 발생할 것이다. 충돌을 방지하기 위해 CSMA/CD 규칙을 사용할 수 있다.

 


 

   스위치의 구조    

 

 스위치는 데이터 링크 계층에서 동작하며 레이어 2 스위치 또는 스위칭 허브라고 불린다. 겉모습만 봐서는 허브와 구별하기 힘들지만 허브와 다르게 데이터 충돌을 방지할 수 있다.

 

 

출처: 위키피디아

 

 

 

 스위치 내부에는 MAC 주소 테이블(Mac Address Table)이 존재하는데 이 MAC 주소 테이블은 스위치의 포트 번호와 해당 포트에 연결되어 있는 컴퓨터의 MAC 주소가 등록되는 데이터베이스이다. 

 

 맨 처음 전원을 킨 스위치 MAC 주소 테이블에는 아무것도 등록되어 있지 않지만 MAC 주소가 추가된 프레임이라는 데이터가 전송되면 MAC 주소 테이블에 포트 번호와 함께 추가가 된다. 만약 이미 등록되어 있는 MAC 주소라면 건너 뛴다. 이를 MAC 주소 학습 기능이라고 한다. 

 

 하지만 스위치도 피할 수 없는 것이 있다. MAC 주소 테이블에 MAC 주소가 등록되어 있지 않다면 허브와 같이 등록되어 있지 않은 포트 번호에 모두 전송된다는 것이다. 이를 플러딩(Flooding)이라고 부른다. 

 

 

 만약 MAC 주소 테이블에 컴퓨터 B의 MAC 주소가 등록되어 있고 컴퓨터 B로 데이터를 전송한다고 가정한다면 목적지 컴퓨터인 컴퓨터 B로만 전송하게 된다. 이를 MAC 주소 필터링이라고 한다. 즉, 불필요한 데이터를 네트워크에 전송하지 않게 되는 것이다. 

 

 

 그렇다면 지금의 MAC 주소 테이블 상태에서 만약 컴퓨터 A에서 컴퓨터 C로 데이터를 보낸다고 가정하면 어떻게 될까? MAC 주소에 해당 포트 번호의 목적지 MAC 주소가 없기 때문에 다시 플러딩이 발생한다.  

 

 



   데이터가 케이블에서 충돌하지 않는 구조   

 

 통신 방식에는 전이중 통신 방식과 반이중 통신 방식이 있다. 전이중 통신 방식은 데이터의 송, 수신을 동시에 통신하는 방식이고 반이중 통신 방식은 회선 하나로 송신과 수신을 번갈아가면서 통신하는 방식이다. 

 

 아래 그림과 같이 컴퓨터 1과 컴퓨터 2를 직접 랜 케이블로 연결한다면 선을 네 쌍을 사용하기 때문에 전이중 통신 방식이 된다. 

 

전이중 방식

 

 반면 허브 내부에는 송, 수신이 나누어져 있지 않기 때문에 컴퓨터 A와 B를 허브로 연결하면 동시에 데이터를 보낼 때 충돌이 난다. 이처럼 허브를 사용하면 회선 하나를 송신과 수신이 번갈아가면서 사용하는 반이중 통신 방식을 사용하게 된다. 

 

 

 스위치는 충돌이 일어나지 않는 구조로 되어 있기 때문에 전이중 통신 방식으로 데이터를 주고 받을 수 있다. 이처럼 허브를 사용하면 충돌로 인한 네트워크 지연이 발생하기에 최근에는 네트워크로 스위치를 사용한다. 

 

 

 

 


 

   충돌 도메인   

 

 충돌 도메인(Collision damain)은 충돌이 발생할 때 그 영향이 미치는 범위를 말한다. 허브와 스위치를 예로 들면 허브는 충돌의 영향이 모든 컴퓨터에 미치고 스위치는 전이중 통신 방식이며 접속되어 있는 모든 컴퓨터 영향을 미치지 않기에 충돌이 발생하지 않고 충돌 도메인의 범위도 컴퓨터 하나하나로 좁아진다. 

 

 

 

    (1)  그레이들(gradle) 버전 확인 및 변경   

 

  그레이들(gradle)의 버전을 확인하기 위해  [gradle]-[wrapper] 폴더의 gradle-wrapper.properties 파일을 찾아가 빨간색으로 표시된 부분을 확인한다. 

 

 

 만약 그레이들의 버전을 변경해야 한다면 터미널 창에서 다음 명령어를 입력해 준다.

 

    gradlew wrapper --gradle-version [버전명]

 


 

    (2) 깃과 깃허브 연동    

 

 먼저 윈도우에서는 ctrl + shift + A, 맥에서는 command + shift + A를 사용해 Action 검색창을 열어 share project on github를 검색한다. 

 

 

 해당 Action을 선택하면 깃허브 로그인 화면이 나온다. 본인의 깃허브 계정을 로그인한다. 

 

 

 로그인에 성공하면 다음과 같은 화면이 뜨는데 Repository name을 수정하고 share 버튼을 누른다.

 

 

 로그인에 성공하면 다음과 같은 화면이 뜨는데 Repository name을 수정하고 share 버튼을 누른다. 그 후에 .idea 폴더를 제외하고 Commit Message를 작성한 뒤 Add 버튼을 누른다. 

 

 

 로그인에 성공하면 다음과 같은 화면이 뜨는데 Repository name을 수정하고 share 버튼을 누른다. 그 후에 .idea 폴더를 제외하고 Commit Message를 작성한 뒤 Add 버튼을 누른다. 

 

 만약 다음과 같은 화면이 나오는데 여기서는 .gitignore이라는 파일을 추가할 것인지에 대한 것이므로 cancel을 눌러주고 위에서 다시 .gitignore 파일을 체크 해제를 한 뒤 커밋을 하면 된다. 

 

 

 여기까지 진행되었으면 github 레포지토리가 생성된 것을 확인할 수 있다.

 

 


 

    (4) .gitignore 세팅    

 

 깃허브와 동기화는 되었으면 이제 처음 커밋했을 때 제외했던 .idea 폴더를 앞으로의 모든 커밋 대상에서 제외되도록 처리를 해야한다. 만약 .gitignore가 생성되지 않았다면 플러그인에서 .gitignore를 지원 받아야 한다. 

 

 플러그인에 대한 정보는 여기서 참조하면 된다.

 

2020/09/16 - [IDE/IntelliJ] - 2. 플러그인(plugin)

 

2. 플러그인(plugin)

플러그인 인텔리제이에서 특정 기능에 대한 지원이 없으면 플러그인(plugin)에서 지원을 받는다. 예를 들어, .gitignore 파일에 대해 지원받고 싶다면 .ignore 플러그인에서 지원을 받으면 된다.  먼저

almotjalal.tistory.com

 

 

 윈도우는 Ctrl + Shift + A, 맥은  Command + Shift + A를 사용해 [Actions]탭 검색창을 열어 plugins를 검색하고 선택한다.

 

 

 

 [Marketplace]탭은 설치 가능한 플러그인 목록을 보여주며, [Installed] 탭은 이미 설치된 플러그인 목록을 나타낸다. [Marketplace]탭을 선택하고 .ignore을 검색하고 [install]을 클릭하여 설치한다. 

 

 

 

 설치가 완료 되었으면 인텔리제이를 다시 시작해야만 설치한 플러그인이 적용되므로 재시작을 한다. 

 

 

 

 재시작을 하면 프로젝트 왼쪽 위의 프로젝트 이름을 선택한 뒤, 마우스 오른쪽 버튼을 누르거나 단축키를 눌러 생성 목록을 연다. 단축키는 윈도우 Alt + Insert, 맥 Command + N이다. 생성 목록 아래에 [.ignore file], [gitignore file(git)]을 차례로 선택해 .gitignore 파일을 생성한다. 

 

 

    (1)  참고 사항   

 

  인텔리제이(IntelliJ)에는 이클립스(Eclipse)의 워크 스페이스와 같은 개념이 없고 프로젝트와 모듈의 개념만 있다. 즉, 모든 프로젝트를 한 번에 불러올 수 없으므로 한 화면에서는 하나의 프로젝트만 열린다. 

 


 

    (2) 프로젝트 생성    

 

 아래 그림에서 빨간 색으로 표시된 New Project를 선택한다. 

 

 

 왼쪽 목록에서 그레이들(Gradle)을 선택해주고 Next 버튼을 누른다. 

 

 

 

 다음은 프로젝트에 해당하는 이름을 적어준다. Name 항목과 ArtifactId 항목은 같은 값으로 입력이 된다. 다 입력 되었으면 Finish 버튼을 누른다. 

 

 


 

    (3) 그레이들(Gradle) 프로젝트를 스프링 부트 프로젝트로 변경    

 

 인텔리제이에서 build.gradle 파일을 열어보면 다음 화면과 같다.

 

 

 코드를 자세히 살펴보면 아직 자바 개발에 가장 기초적인 설정만 되어 있는 상태이다. 이제 여기에 스프링 부트에 필요한 설정들을 하나씩 추가해야 한다.

 

plugins {
    id 'java'
}

group 'com.daldalhada.book'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

 

 

 코드를 자세히 살펴보면 아직 자바 개발에 가장 기초적인 설정만 되어 있는 상태이다. 이제 여기에 스프링 부트에 필요한 설정들을 하나씩 추가해야 한다.

 

 물론 스프링 이니셜라이저(start.spring.io/)를 통해 진행할 수 있지만 bulid.gradle 파일의 코드들이 각각 무슨 역할을 하는지, 이니셜라이저 외에 추가로 의존성 추가가 필요하면 어떻게 해야 하는지에 대해 알아보기 위해 이니셜라이저를 쓰지 않았다.

 

 먼저 build.gradle 파일 맨 위에 위치할 코드는 다음과 같다.

buildscript {
    ext {
        springBootVersion = '2.1.7.RELEASE'
    }
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

 위의 코드는 이 프로젝트의 플러그인 의존성 관리를 위한 설정이다. ext라는 키워드는 build.gradle 파일에서 사용하는 전역변수를 설정하겠다는 의미이다. 즉, 위에 코드에서는 spring-boot-gradle-plugin이라는 스프링 부트 그레이들 플러그인의 2.1.7.RELEASE를 의존성으로 받겠다는 의미이다. 

 

 다음은 앞서 선언한 플러그인 의존성들을 적용할 것인지를 결정하는 코드이다.

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

 io.spring.dependency-management 플러그인은 스프링 부트의 의존성들을 관리해주는 플러그인이라 꼭 추가해야만 한다. 위의 3개를 포함하여 총 4개의 플러그인은 자바와 스프링 부트를 사용하기 위해 필수이므로 항상 추가해야 한다. 

 

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

 

 repositories는 각종 의존성(라이브러리)들을 어떤 원격 저장소에서 받을지를 정한다. mavenCentral을 많이 사용하지만 최근에는 라이브러리 업로드 난이도 때문에 jcenter도 많이 사용한다고 한다. 

 

 mevenCentral은 이전부터 많이 사용하는 저장소지만, 본인이 만든 라이브러리를 업로드하기 위해서는 정말 많은 과정과 설정이 필요하다. 그러다보니 개발자들이 직접 만든 라이브러리를 업로드 하는 것이 힘들어 점점 공유가 안되는 상황이 발생한다. 

 

 최근에 나온 jcenter는 이런 문제점을 개선하여 라이브러리 업로드를 간단하게 할 수 있다. 또한, jcenter에 라이브러리를 업로드 하면 mavenCentral에도 업로드 될 수 있도록 자동화 할 수 있다. 

 

 여기서는 mavenCentral과 jcenter 둘 다 등록해서 사용할 것이다. 

 

 dependencies는 프로젝트 개발에 필요한 의존성들을 선언하는 곳이다. compile 메소드 안에 라이브러리의 이름의 앞부분만 추가한 뒤 자동완성(ctrl + space)을 사용하면 라이브러리의 목록을 볼 수 있다. 의존성 코드를 작성할 때 주의해야 할 점은 버전을 명시해야 하지 않아야 한다. 그래야만 맨 위에 작성한 classpath 메소드의 ${springBootVersion}의 버전을 따라가며 버전 충돌 문제가 발생하지 않는다. 

 

 지금까지의 최종 작성된 코드는 다음과 같다.

 

buildscript {
    ext {
        springBootVersion = '2.1.7.RELEASE'
    }
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group 'com.daldalhada.book'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

 

 

 맨 오른쪽에서 Gradle 버튼을 클릭해서 의존성을 확인한다.

    (1)  테스트 코드   

 

 TDD(Test Driven Development)는 테스트가 주도하는 개발을 말하며 테스트 코드를 먼저 작성하는 것부터 시작한다. 아래 그림과 같이 RED-GREEN Cycle을 말한다.

 

  1. RED: 항상 실패하는 테스트를 먼저 작성한다.
  2. GREEN: 테스트가 통과하는 프로덕션 코드를 작성한다.
  3. REFACTOR: 테스트가 통과하면 프로덕션 코드를 리팩토링한다. 

 

출처: https://hyem-study.tistory.com/98?category=893981

 

 반면 단위 테스트는 TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 말한다. TDD와 달리 테스트 코드를 꼭 먼저 작성하는 것이 아니고 리팩토링 작업도 포함되지 않는다. 순수하게 테스트 코드만 작성하는 것을 말한다. 

 


 

    (2) 단위테스트 장점    

 

 위키피디아에서의 단위 테스트의 장점은 다음과 같다.

 

  • 개발단계 초기에 문제를 발견하게 도와준다.
  • 개발자가 나중에 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지 확인할 수 있다. 
  • 기능에 대한 불확실성을 감소시킬 수 있다.
  • 시스템에 대한 실제 문서를 제공한다. 즉, 단위 테스트 자체를 문서로 사용할 수 있다. 

 

 이 밖에도 더 많은 장점이 있는데 단위 테스트를 하기 전의 개발 방식은 보통 다음가 같은 것이다. 

 

  1. 테스트 코드를 작성한다.
  2. 프로그램(Tocmat 등)을 실행한다.
  3. Postman과 같은 API 테스트 도구로 HTTP 요청한다.
  4. 요청 결과를 출력(console.log, System.out.println 등)하여 눈으로 검증한다.
  5. 결과가 다르면 다시 프로그램을 중지하고 코드를 수정한다. 

 여기서 2~5번은 매번 코드를 수정할 때마다 반복해야 한다. 

 


 

    (3)  테스트 코드 작성하기    

 

 프로젝트 내 [src] - [main] - [java] 폴더의 마우스 오른쪽 버튼을 클릭하여 [New ▶ Package]를 차례로 선택해서 생성한다. 일반적으로 패키지명은 웹 사이트 주소의 역순으로 한다고 한다. 예를 들어, admin.daldalhade.com이라는 사이트라면 패키지명은 com.daldalhade.admin으로 하면 된다. 

 

 

 패키지가 생성됐으면 패키지를 마우스 오른쪽 버튼으로 클릭하고 [New ▶ Java Class]를 차례로 선택하면 된다. 클래스의 이름은 Application으로 설정한다.

 

 

 

그리고 클래스의 코드를 다음과 같이 작성한다. 

 

package com.daldalhada.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

 방금 생성한 Application 클래스는 프로젝트의 메인 클래스가 된다. @springBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성이 모두 자동으로 설정된다. 특히,  @springBootApplication이 있는 위치부터 설정을 읽기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야 한다. 

 

 main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS를 실행한다. 여기서 내장 WAS는 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 이야기 한다. 이러면 항상 서버에 톰캣을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일(실행 가능한 Java 패키징 파일)로 실행하면 된다. 

 

 스프링 부트는 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있기 때문에 내장 WAS를 사용하는 것을 권장하고 있다. 외장 WAS를 쓰게 되면 모든 서버는 WAS의 종류와 버전, 설정일 일치시켜야 하기에 복잡하다. 1대면 문제가 없지만 30대의 서버가 존재할 때 WAS의 버전을 올린다고 하면 실수할 여지가 많고 시간적인 면에서도 비효율적이 된다. 

 

 

 이번에는 테스트를 위한 Controller를 만들기 위해 패키지 하위에 web이라는 패키지를 만든다.

 

 

 그리고 테스트를 해 볼 Hello Cotroller를 하나 생성한다.

 

 

 

package com.daldalhada.springboot.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

 

 위의 코드 중 @RestController는 JSON을 반환하는 컨트롤러로 만들어 주는 역할을 하고 @GetMapping은 HTTP 메서드인 Get의 요청을 받을 수 있는 API를 만들어 준다. 

 

 작성한 코드가 제대로 작동하는지 테스트를 하기 위해 WAS를 실행하지 않고 테스트 코드로 검증해보기 위해 [src]-[test]-[java] 디렉토리에 앞에 생성했던 패키지를 그대로 다시 생성한다. 그리고 테스트 코드를 작성할 클래스를 생성한다. 일반적으로 테스트 클래스는 대상 클래스 이름에 Test를 붙인다고 한다. 따라서, HelloControllerTest로 생성한다. 

 

 

 생성되면 다음과 위의 사진과 같게 되고 HelloControllerTest에 코드를 추가한다. 

 

package com.daldalhada.springboot;

import com.daldalhada.springboot.web.HelloController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HelloController.class)
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void hello_return() throws Exception {
        String hello = "hello";

        mvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string(hello));
    }
}

 

 위의 코드는 다음과 같다.

 

1. @RunWith(SpringRunner.class)

  • 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시키는데 여기서는 SpringRunners라는 스프링 실행자를 사용한다.
  • 즉, 스프링 부트 테스트와 JUnit 사이를 연결하는 역할을 한다.

2. @WebMvcTest

  • 수 많은 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션이다. 
  • 스프링이 관리하는 모든 빈을 등록시켜 테스트하는 @SpringBootTest와 달리 @WebMvcTest는 web 관련 빈들만 등록하므로 가볍다.

3. @Autowired

  • 스프링이 관리하는 빈을 주입 받는다.

4. private MockMvc mvc

  • 웹 API를 테스트 할 때 사용되고 스프링 MVC 테스트의 시작점이다. 
  • 이 클래스를 통해 HTTP의 메서드인 GET, POST 등에 대한 API 테스트를 할 수 있다. 

5. mvc.perform(get("/hello"))

  • MockMvc를 통해 /hello 주소로 HTTP GET 요청을 한다. 
  • 체이닝이 지원되어 .andExpect에 여러 검증 기능을 이어서 선언할 수 있다.

6. andExpect(status().isOk())

  • mvc.perform의 결과를 검증하며 특히, HTTP Header의 Status를 검증한다. 
  • isOk()는 Status code가 200인지 아닌지 검증한다.

7. andExpect(content().string(hello))

  • 응답 본문의 내용을 검증한다.
  • Controller에서 "hello"를 리턴하기 때문에 값이 맞는지 검증하는 역할을 한다.

 

 HelloControllerTest.java에서 빨간색으로 표시된 화살표 부분을 클릭하고 Run HelloControllerTest.java를 클릭해 테스트 코드를 실행해보면 테스트가 통과되었다면 Tests passed가 뜬다. 

 

 HelloControllerTest.java에서 빨간색으로 표시된 화살표 부분을 클릭하고 Run HelloControllerTest를 클릭해 테스트 코드를 실행해보면 테스트가 통과되었다면 Tests passed가 뜬다. 

 

 이번에는 Application.java로 가서 수동으로 실행해 정상적으로 값이 출력되는지 확인해본다. 빨간색으로 표시된 화살표 버튼을 클릭하고 Run Application을 클릭하면 톰캣 서버가 8080포트로 실행되었다는 것이 콘솔 창에 출력된다. 

 

 

 

 

 

 이제 웹 브라우저에서 localhost:8080/hello로 접속하여 문자열 hello가 잘 나오는지 확인한다. 웹 브라우저를 열어 localhost:8080/hello로 접속한다.

 

 하지만, localhost:8080/login으로 바뀌어 다음과 같은 화면이 뜨는 경우가 발생할 수 있다. 처음에는 포트 충돌 문제인지 알고 포트를 변경하는 방법을 시도했다.

 

 포트를 변경하는 방법은 먼저, [build]-[resources]-[main] 또는 [src]-[main]-[resource]의  application.properties을 찾는다. 

 

 

 

 application.properties을 열어 server.port 부분을 원하는 포트 번호로 수정한다. 

 

 

 하지만, 포트를 변경해도 소용없었고 구글링 하던 중 스택 오버 플로우에서 Username은 user로 Password는 콘솔창에 나와있는 password를 치면 된다고 한다.

 

 

 결과는 정상적으로 출력되었고 테스트 코드의 결과와 같은 것을 알 수 있다.

 

'스프링 부트 프로젝트 > 테스트 코드 작성' 카테고리의 다른 글

4. 롬복 설치하기  (0) 2020.10.05

 

   개요   

 

 


 

   (1) 초창기 컴퓨터(1940년대)  

 

 최초의 컴퓨터인 에니악은 30톤 규모의 거대한 계산기로 미사일 탄도를 계산하기 위해 제작되었다. 에니악은 키보드, 마우스, 모니터와 같은 컴퓨터 자원이 없었고 운영체제 또한 없었다. 진공관이라는 소자를 사용하여 진공관이 켜지면 1, 꺼지면 0이라고 판단했고 이는 컴퓨터가 2진법을 사용하는 계기가 되었다. 에니악은 전선으로 논리회로를 구성하여 원하는 결과만 얻는 방식으로 다른 계산이나 수식을 사용하려면 전선을 다시 연결해야 했다. 

 


 

   (2) 일괄 작업 시스템(1950년대)  

 기술 발전을 거쳐 진공관에서 IC(Integrated Circuit)라는 칩으로 변화했다. 이 칩은 진공관과 전선으로 만들어진 논리회로를 아주 작은 크기로 구현하였다. 미약하지만 CPU와 메인 메모리가 있었으나 키보드, 모니터와 같은 입출력 장치가 없었다. 대신, 천공카드 리더(OMR)와 라인 프티러를 입출력 장치로 사용했다. 

 

 천공카드 리더와 라인 프리터를 사용함으로써 지금의 프로그래밍과 유사한 방식으로 다양한 소프트웨어를 개발할 수 있게 되었다. 하지만, 한 번에 한 가지 작업만 할 수 있었다. 이처럼 모든 작업을 한꺼번에 처리해야 하고 프로그램 실행 중간에 사용자가 데이터를 입력하거나 수정하는 것이 불가능한 시스템을 일괄 작업 시스템(Batch Job System) 또는 일괄 처리 시스템이라고 한다. 일괄 작업 시스템은 운영체제가 사용되었으며 메인 메모리가 운영체제의 상주 영역과 사용자의 사용 영역으로 나뉘었다.

 

 


 

   (3) 대화형 시스템(1960년대 초)    

 

 1960년대 초반에는 모니터와 키보드가 등장했다. 이들은 비효율적인 일괄 작업 시스템을 대화형 시스템(Interactive System)으로 바꿔 놓았다. 일괄 작업 시스템과는 달리 대화형 시스템은 작업 중간에 사용자가 입력을 하고나 사용자에게 중간 결과값을 보여줄 수 있게 되었고 또한 중간 결과값을 출력하여 프로그램에 이상이 있는지 혹은 정상적으로 진행되는지 확인할 수 있게 되었다. 

 

 하지만 대화형 시스템에도 문제점이 있다. 일괄 작업 시스템은 입출력이 거의 없어 작업 시간을 예측할 수 있지만 대화형 시스템의 경우 작업 시간을 예측하기 어려웠다.  

 

 

 



   (4) 시분할 시스템(1960년대 말)   

 

 1960년대 후반에는 컴퓨터의 크기가 작아지고 계산 능력이 향상되었다. 하지만 상당히 비싸서 한 번에 하나의 작업만 수행하는 것은 낭비라 효율적인 방법이 필요했다. 연구가 진행되면서 다중 프로그래밍(multiprogramming) 기술이 개발되었다. 이는 하나의 CPU로 여러 작업을 동시에 실행할 수 있는 기술로 한 번에 하나의 작업만 처리할 수 있는 일괄 작업 시스템에 비해 효율성이 뛰어났다.

 

 하지만 여러 작업이 하나의 CPU를 공동으로 사용하면 작업이 끊겨 보일 텐데 어떻게 이를 구현한 것일까? 방법은 시간을 분할하는 것이였다. 다중 프로그래밍 시스템에서는 CPU 사용 시간을 아주 잘게 쪼개어 여러 작업에 나누어 준다. 

 

 예를 들어, A~C 작업을 동시에 진행한다고 가정한다. CPU가 각 작업에 1초를 할당하면 작업이 끊어지면서 실행되겠지만 0.1초를 할당하면 각각 A~C 작업은 0.1초씩 할당되고 A 작업은 0.3초 뒤에 다시 CPU를 얻어 작업할 수 있다. 이처럼 시간 분배를 최대한 적게하여 반복되면 작업이 동시에 실행되는 것처럼 보인다. 

 

 

 

 이처럼 여러 작업을 조금씩 처리하여 작업이 동시에 이루어지는 것처럼 보이게 하는 것을 시분할 시스템(Time Sharing System) 또는 다중 작업(Multitasking)이라고 한다. CPU 사용 시간을 잘게 쪼개어 나눠줌으로써 모든 작업이 동시에 처리되는 것처럼 보인다. 이 때의 시간 조각들을 타임 슬라이스(Time Slice) 또는 타임 퀀텀(Time Quantum)이라고 한다. 오늘날 대부분 시분할 시스템이 사용된다. 

 

 하지만, 시분할 시스템에도 단점이 존재한다. 시스템 내에 많은 작업이 공존할 경우, 중요한 작업이 일정 시간 안에 끝나는 것을 보장하지 못한다는 것이다. 이는 특정 시스템에서 일정 시간 안에 작업이 처리되도록 보장하는 실시간 시스템(Real-time System)을 사용한다. 

 

시분할 시스템에서 동시에 실행되는 작업의 개수를 멀티 프로그래밍 수준(Level of Multiprogramming) 또는 멀티 프로그래밍 정도라고 한다. 일괄 작업 시스템은 멀티프로그래밍 수준이 1이고 위의 그림에서는 작업이 3개이므로 멀티프로그래밍 수준이 3이 된다. 

 

 시분할 시스템에서 여러 작업이 동시에 실행할 수 있다는 것은 한 사람이 여러 프로그램을 동시에 실행할 수 있다는 의미이기도 하고 여러 사람이 동시에 작업할 수 있다는 의미이기도 하다. 즉, 시분할 시스템은 하나의 컴퓨터에서 여러 사람이 동시에 사용할 수 있는 다중 사용자 시스템(Multi-user System)을 가능하게 했다. 값비싼 컴퓨터를 한 사람 또는 하나의 작업이 독점하는 것이 아닌 여러 사람이 동시에 사용할 수 있는 환경이 만들어졌다.

 

 



   (5) 분산 시스템(1970년대 말)   

 

 1970년대 말에는 인터넷이 등장했으며 서로 호환되지 않는 LAN들을 하나로 묶는 아르파넷(ARPAnet)이 만들어 졌고 컴퓨터 간 네트워킹을 위한 TCP/IP라는 프로토콜이 정의되었다.

 

 또한, 개인용 컴퓨터와 인터넷이 보급되면서 값이 싸고 크기가 작은 컴퓨터들을 묶어 대형 컴퓨터에 버금가는 시스템을 만들었는데 이를 분산 시스템(Distributed system)이라고 부른다. 분산 시스템은 네트워크 상에 분산되어 있는 여러 컴퓨터로 작업을 처리하고 그 결과를 상호 교환하도록 구성한 시스템이다. 

 

 


 

   (6) 클라이언트/서버 시스템(1990년대 ~ 현대)   

 

 분산 시스템은 모든 컴퓨터가 동일한 지위이기 때문에 컴퓨터가 고장 나거나 추가되면 작업을 분배하고 결과를 모으기가 쉽지 않다. 클라리언트/서버 시스템(Client/Server Syetem)은 이러한 문제를 해결한다. 모든 컴퓨터의 지위가 동일한 분산 시스템과 달리 작업을 요청하는 클라이언트와 거기에 응답하여 요청받은 작업을 처리하는 서버의 이중 구조로 나뉜다. 웹 시스템이 전형적인 클라이언트/서버 구조를 이루고 있다. 

 

 클라이언트/서버 시스템도 문제점이 존재한다. 바로 서버 과부하인데 수십만명의 모든 요청이 서버로 집중되면서버가 다운 될 수 있다. 

 

 

 


 

   (7) P2P 시스템(2000년대 ~ 현대)   

 

 1990년대 말에 전 세계의 MP3 음악 파일을 공유하려는 시도가 있었는데 문제는 클라이언트/서버 시스템의 서버 과부하였다. 웹 페이지는 킬로 바이트 단위이지만 MP3 파일은 메가 바이트에 달하기 때문에 서버 과부하로 서비스를 할 수 없다. 

 

 서버의 부하를 줄일 수 있는 새로운 시스템으로 P2P 시스템(Peer-to-Peer System)이 만들어졌다. peer는 말단 노드, 즉 사용자의 컴퓨터를 가르키며, P2P는 서버를 거치지 않고 사용자와 사용자를 직접 연결한다는 의미이다. P2P 시스템에는 서버가 파일 검색만 맡고 사용자 간에 파일 전송이 이루어지기에 서버의 부하가 적다는 장점이 있다. 또 다른 예로는 메신저와 파일 공유를 들 수 있다. 

 

 다음 그림은 대용량 파일 공유 P2P 시스템의 작동 방법이다. 이 시스템에서는 같은 파일을 가진 여러 사람으로부터 데이터를 나누어 받는다. 예를 들어, korea.avi라는 동영상을 받는다면 korea.avi를 가진 여러 사용자로부터 조금씩 나누어 받아 하나의 파일을 완성하는 것이다. 당연히 파일을 가지고 있는 사람이 많을수록 속도가 빨라진다. 

 

 

 

 


 

   (8) 기타 컴퓨팅 환경(2000년대 초반 ~ 현대)   

 

 ■ 그리드 컴퓨팅

       - 정해진 기간에만 컴퓨터를 사용하고 지불할 수 있도록 컴퓨팅 자원을 구매하여 사용하는 컴퓨팅 환경을 그리드 컴퓨팅

       (Grid Computing)이라고 한다. 그리드 컴퓨팅은 분산 시스템 중 한 분야로, 서로 다른 기종의 컴퓨터들을 묶어 대용량

       의 컴퓨터 풀(Computer Pool)을 구성하고 이를 원격지와 연결하여 대용량 연산을 수행하는 컴퓨팅 환경이다. 대용량

       데이터의 연산을 소규모 연산으로 나누어 여러 대의 컴퓨터로 분산 시킨다는 점에서 분산 시스템이라고 볼 수 있다. 

 

      - 그리드 시스템은 일반인을 위한 시스템이라기 보다 연구용이나 상업용으로 이용되는 시스템으로 일반인은 웹 하드 서비

      스에서 경험할 수 있다. 서버에 엄청난 양의 데이터를 보관하고 있는 웹 하드는 과부하를 방지하기 위해 P2P 시스템 기술을

      사용한다. 또한, 웹 하드 서비스는 그리드 딜리버리 기술을 이용하여 사용자 몰래 프로그램을 깔아서 서버의 부하를 방지한

      다.

 

 

 ■ 쿨라우드 컴퓨팅

      - 클라우드 컴퓨팅(Cloud Computing)은 언제 어디서나 응용 프로그램과 데이터를 자유롭게 사용할 수 있는 컴퓨팅 환경

      으로 그리드 컴퓨팅과 SaaS(필요한 기능을 모아 서버에서 실행하고 사용자는 인터넷을 통해 필요한 서비스만 제공받으며

      월별로 혹은 서비스 이용량 만큼 비용을 지불하는 방식)를 합쳐놓은 방식이다. 

 

 

 ■ 사물 인터넷

      - 사물 인터넷(Internet of Things)은 사물에 센서와 통신 기능을 내장하여 인터넷에 연결하는 기술이다. 인터넷으로 연결

      된 사물들이 데이터를 주고 받아 스스로 분석하고 학습한 정보를 사용자에게 제공하거나 서비스를 창출한다. 인공지능, 로

      봇 공학, 무인 운동 수단 등에 쓰인다. 

 

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

6. CPU와 메모리(2)  (0) 2020.10.14
5. CPU와 메모리(1)  (0) 2020.10.06
4. 하드웨어의 기본 구성  (0) 2020.10.05
3. 운영체제의 구조  (0) 2020.10.05
1. 운영체제  (0) 2020.09.28

 

   일상 속 운영체제   

 

 OS(Operating System)라고 일컫는 운영체제는 일반 컴퓨터나 노트북의 전원을 켜면 가장 먼저 만나게 되는 소프트웨어이며 대표적인 예로 윈도우, 유닉스, 리눅스, 안드로이드, iOS 등이 있다.

 

 또한, 컴퓨터와 스마트폰 뿐만 아니라 MP3 플레이어, 내비게이션, PMP, 전자사전 등에도 운영체제가 있다. 이처럼 CPU의 성능이 낮고 메모리 크기도 작은 시스템에 내장하도록 만든 운영체제를 임베디드 운영체제 또는 임베디드 시스템이라고 한다. 

 

 임베디드 운영체제를 사용하는 기계와 그렇지 않은 기계는 차이가 있는데 운영체제가 없는 MP3 플레이어는 처음 구현한 기능 외에 다른 기능을 추가할 수 없다. 하지만, 임베디드 운영체제가 있는 기계는 음질을 향상하거나 다양한 종류의 음악 파일을 지원하도록 기능을 추가할 수 있다. 블루투스(무선 통신)을 이용하여 MP3 파일도 공유할 수 있다. 유선 전화기와 스마트폰도 마찬가지이다. 다시 말해, 임베디드 운영체제가 있는 기계는 기능을 계속 향상시킬 수 있다.  

 


 

   운영체제의 필요성  

 

 초기의 컴퓨터는 정해진 계산만 수행했기 때문에 특별한 사용 규칙이 필요 없었다. 하지만, 시대가 변함에 따라 메모리, CPU 등의 성능이 향상되고, 여러 작업을 동시에 수행할 수 있는 기능이 조성되면서 사용 규칙이 필요해졌다. 복잡한 작업 환경에 사용 규칙이 없으면 기계를 망가뜨릴 수 있기 때문에 등장한 것이 운영체제이다. 

 

 운영체제가 있는 기계는 새로운 기능의 추가나 성능의 변경이 가능하므로 성능 및 효율성을 꾀할 수 있으며, 이러한 기계를 프로그램이 가능한(programmable) 기계라고 부른다.  

 

 그렇다면 운영체제는 성능을 향상하는데에만 필요할까? 운영체제는 성능 향상뿐 아니라 자원 관리라는 중요한 역할도 한다. 우리는 컴퓨터를 사용할 때 웹 브라우저, 채팅, 음악 재생 소프트웨어 등을 동시에 사용한다. 이러한 소프트웨어를 응용 프로그램(application program)이라고 한다. 

 

하지만, 많은 응용 프로그램에 비해 컴퓨터를 구성하는 장치는 매우 제한적이다. 즉, 대부분이 하나의 키보드, CPU, 메모리, 마우스, 모니터, 하드 디스크 등으로 이루어져 있어 각각의 응용 프로그램이 이 장치를 서로 독차지하려 한다. 이러한 문제를 해결하려면 자원을 관리하는 강력한 중재자가 필요하다. 이 중재자는 누구에게 먼저 키보드를 주어야 할지, 모니터를 주어야 할지 등을 결정하고 악의적인 응용 프로그램으로부터 보호하는 역할도 한다. 

 

 

 

   운영체제는 자원을 어떻게 관리할까?  

 

 기술이 발달하면서 메모리 크기가 커지고 하드 디스크, 마우스, 사운드 카드, 그래픽카 드, 네트워크 카드 등 다양한 주변장치가 등장하게 되었다. 이렇게 컴퓨터에 부착된 모든 장치를 컴퓨터 자원(Computer Resource)이라고 한다. 

 

 운영체제는 사용자가 직접 자원에 접근하는 것을 막음으로써 자원을 보호하고 관리한다. 예를 들어, 사용자가 하드 디스크에 데이터를 저장하는 경우라고 가정한다. 사용자는 하드 디스크의 특정 위치에 저장할 수 없으며 운영체제가 알아서 적당한 위치에 저장한다. 만약 사용자가 하드 디스크에 데이터를 저장할 수 있다면 악의적인 사용자가 데이터를 지우거나 덮어쓰는 일이 발생할 수 있다. 이 때문에 운영체제는 응용 프로그램과 사용자에게 모든 자원을 숨긴다. 

 

 대신 운영체제는 사용자가 자원을 이용할 수 있는 여러 방법을 제공한다. 한 예로 사용자가 저장하려는 데이터를 운영체제에게 주면 운영체제는 하드 디스크의 적당한 장소에 저장하고 번호표를 사용자에게 돌려준다. 이와 같이 사용자가 컴퓨터 자원을 사용할 수 있도록 해주고 그 결과를 알려주는 것을 인터페이스(Interface)라고 한다.

 

 좀 더 쉽게 설명하면 인터페이스는 자동차에서 핸들 및 계기판과 같다. 자동차는 엔진과 바퀴로 움직이지만 운전자가 핸들로 조종하고 계기판을 통해 현재 주행 상황을 인지한다. 마찬가지로 컴퓨터는 CPU와 메모리로 명령을 처리하지만 사용자가 키보드나, 마우스로 자료를 입력하고 출력 결과를 얻는다. 즉, 운영체제 인터페이스를 통해서만 컴퓨터 자원을 사용할 수 있다. 

 

 운영체제는 사용자가 컴퓨터를 보다 쉽게 사용할 수 있도록 다양한 인터페이스를 제공함으로써 컴퓨터 자원을 보호함과 동시에 사용자의 편의를 도모한다. 

 

 


 

   운영체제란 무엇인가?    

 

 운영체제는 사용자에게 편리한 인터페이스 환경을 제공하고 컴퓨터 시스템의 자원을 효율적으로 관리하는 소프트웨어이다. 

 

 레스토랑을 예로 들면, 손님이 레스토랑에서 음식을 주문하면 웨이터가 그 음식을 주방에 알려주고 손님은 주문한 음식을 제공받는다. 이 때, 손님은 주방에 들어갈 수 없다. 직접 조리하거나 주방 기구를 만지려 한다면 레스토랑 관리자가 저지할 것이다. 운영체제도 마찬가지이다. 사용자가 하드 디스크의 특정 위치에 저장하려고 하면 기존 데이터와 섞여 엉망이 되므로 운영체제가 중재하여 대신 데이터를 저장하고 꺼내준다. 

 

 다시 말해 운영체제는 여러 응용 프로그램에 컴퓨터 자원을 적절히 나누어주는 역할을 한다. 

 

 

 



   운영체제의 역할   

 

 ■ 자원 관리

       - 운영체제는 키보드, 마우스, 네트워크카드, 사운드카드등의 컴퓨터 자원을 응용 프로그램에 분배하여 사용자가 원할하게

       작업할 수 있도록 돕는다. 자원을 요청한 프로그램이 여러 개라면 적당한 순서로 자원을 배분하고 적절한 시점에 자원을 회

       수하여 다른 응용 프로그램에 나누어 준다.

 

 ■ 자원 보호

      - 컴퓨터 내의 자원을 악의적인 사용자나 미숙한 사용자로부터 보호하는 것도 중요하다. 어떤 응용 프로그램이 남의 영역을

       침범한다면 다른 사람의 작업을 망치거나 데이터를 지울 수 있기 때문에 비정상적인 작업으로부터 컴퓨터 자원을 지키는

       것이 중요하다.

 

 ■ 하드웨어 인터페이스 제공

      - 어떤 제품을 사용할 때마다 별도의 소프트웨어를 설치한다면 불편할 것이다. 하지만, 운영체제는 복잡한 과정 없이 다양한

      장치를 사용할 수 있도록 해주는 하드웨어 인터페이스를 제공한다. 하드웨어 인터페이스 지원은 드라이버를 컴퓨터에 설치

      해야 가능한데 운영체제는 하드웨어 인터페이스가 자동으로 설치되게 함으로써 하드웨어의 종류에 상관없이 사용할 수 있

      다. 

 

 ■ 사용자 인터페이스 제공

    - 과거에 유닉스 MS-DOS와 같은 운영체제로 작업할 때는 마우스 없이 키보드만 가능했다. 여러모로 불편하며 일반인이 사

    용법을 배우기가 쉽지 않았다. 하지만 운영체제가 사용자 인터페이스(Graphical User Interface)를 제공하므로 대부분의

    작업을 마우스로 수핼할 수 있다. 이처럼 운영체제를 편리할 수 있게 사용할 수 있도록 제공되는 기능이 사용자 인터페이스

    이다. 

 

 

 

 

 



   운영체제의 목표   

 

 

 

 ■ 효율성

       - 같은 작업량을 처리하는 데 보다 적은 자원을 효율적으로 사용하는 것이 중요하다. 효율적인 자원 관리는 다양한 방법으

       로 이루어질 수 있는데, 일반적으로 운영체제의 크기를 최소화하고 운영체제가 사용하는 코드를 최적화 하는 것이다.

 

 ■ 안정성

      - 운영체제가 불안하면 그 피해가 고스란히 사용자에게 돌아가기 때문에 운영체제는 안정성이 담보되어야 한다. 흔히 발생

       하는 예로 문서 작업을 하고 있는데 갑자기 컴퓨터가 다운되어버리는 일이 일어나서는 안된다. 이처럼 안정적이려면 사용

       자와 응용 프로그램의 안전 문제와 하드웨어적인 보안 문제를 처리할 수 있어야 하며 문제가 발생했을 때 이전으로 복구하

       는 결팜 포용 기능을 수행해야 한다.

 

 ■ 확장성

      - 운영체제는 다양한 시스템 자원을 추가하거나 제거하기 편리해야 한다. 즉, 확장성이 좋아야 한다. 하드웨어의 종류에 상

       관없이 꽂으면 바로 실행할 수 있는 플러그 앤 플레이 기능을 제공해야 한다. 

 

 ■ 편리성

   - 사용자에게 편리하게 작업할 수 있는 환경을 제공하는 것도 중요하다. 사용자에게 GUI를 제공하지 않고 텍스트만 사용하게

   하면 운영체제의 효율성은 높아질 수 있지만 사용자 입장에서는 엄청 불편할 것이다. 따라서, 응용 프로그램과 사용자에게 다

   양한 편리성을 제공하면서도 자원의 낭비 요소를 막아야 한다.

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

6. CPU와 메모리(2)  (0) 2020.10.14
5. CPU와 메모리(1)  (0) 2020.10.06
4. 하드웨어의 기본 구성  (0) 2020.10.05
3. 운영체제의 구조  (0) 2020.10.05
2. 운영체제의 역사  (0) 2020.09.28

 

   전기신호란 무엇인가?   

 

 데이터는 전기 신호로 변환 되어 네트워크를 통해 전송된다. 네트워크 통신에서는 0과 1만 사용하기 때문에 0과 1로 이루어진 비트열을 전기 신호로 변환하는 작업이 필요하다. 

 

 그렇다면 0과 1로 이루어진 비트열을 전기 신호로 변환하는 작업은 누가 수행할까? OSI 모델에서는 물리 계층이 수행한다. 물리 계층은 컴퓨터와 네트워크 장비를 연결하고 전송되는 데이터를 전기 신호로 변환하는 계층이다. 

 

 

 

 

 그런데 0과 1을 어떻게 전기 신호로 변환할 수 있을까? 각각의 컴퓨터는 네트워크를 통해 데이터를 송, 수신 할 수 있도록 랜 카드가 메인 보드에 포함되어 있다. 이처럼 각각의 컴퓨터는 내장형 랜 카드나 별도의 랜 카드를 가지고 있기 때문에 0과 1의 정보가 컴퓨터 내부에 있는 랜 카드로 전송되고 랜 카드는 0과 1을 전기 신호로 변환한다. 

 

이미지 출처: 위키백과

 


 

   전기 신호의 종류  

 

 전기 신호에는 아날로그와 신호와 디지털 신호가 존재한다. 아날로그 신호는 물결 모양으로 전화 회선이나 라디오 방송에서 사용한다. 디지털 신호는 막대 모양으로 네트워크 데이터 전송에서 사용한다. 

 

 

 

 데이터가 어떻게 전기 신호로 변환되는지 살펴보면 다음 그림과 같이 데이터 송신 측의 0과 1의 비트열 데이터를 전기 신호로 변환하고 이 전기 신호는 네트워크를 통해 수신 측에 도착하고 수신 측은 전기 신호를 0과 1의 비트열 데이터로 복원한다.

 

 

 


 

   네트워크 전송 매체    

 

 전송 매체데이터가 흐르는 물리적인 선로를 말하는데 이는 유선과 무선으로 나뉜다.  그 중 유선에는 트위스트 페어 케이블, 광 케이블 등이 있고 무선에는 라디오파, 마이크로파, 적외선 등이 있다. 

 

 가장 많이 사용되는 트위스트 페어 케이블의 종류에는  UTP 케이블과 STP 케이블이 있다. 또한, 트위스트 페어 케이블은 일반적으로 랜 케이블이라고도 불리는데 랜 케이블의 양쪽 끝에는 RJ-45라고 부르는 커넥터가 붙어 있다. 이 커넥터를 컴퓨터의 랜 포트나 네트워크 기기에 연결할 수 있다.

 

 

 먼저, UTP 케이블은 구리 선 여덟 개를 두 개씩 꼬아 만든 네 쌍의 전선으로 실드가 없는 케이블이다. 실드는 금속 호일이나 금속의 매듭과 같은 것으로 외부에서 발생하는 노이즈를 막는 역할을 한다. UTP 케이블은 실드로 보호되어 있지 않아서 노이즈의 영향을 받기 쉽지만 저렴하기 때문에 일반적으로 많이 사용된다. 

 

 STP 케이블은 두 개씩 꼬아 만든 선을 실드로 보호한 케이블로 UTP 케이블 보단 노이즈의 영향을 매우 적게 받지만 비싸기 때문에 보편적으로 사용하지는 않는다. 

 

 

출처: 링크허브 공식블로그(https://linkhub.tistory.com/85)

 

 그렇다면 구리 선을 두 개를 비틀어 만드는 이유는 무엇일까? 바로 노이즈 때문이다. 노이즈의 영향을 적게 받기 위해 구리 선 두 개를 비틀어 꼬아 케이블을 만든다. 

 



   랜 케이블의 종류   

 

 랜 케이블의 종류에는 다이렉트 케이블과 크로스 케이블이 있다.

 

 다이렉트 케이블은 구리 선 여덟 개를 같은 순서로 커넥터에 연결한 케이블이고 컴퓨터와 스위치를 연결할 때 사용된다. 

 

 크로스 케이블은 구리 선 여덟 개 중 한쪽 커넥터의 1번과 2번에 연결되는 구리 선을 다른 쪽 커넥터의 3번과 6번에 연결한 케이블로 컴퓨터 간 직접 랜 케이블로 연결할 때 사용된다. 

 

 실제로 다이렉트, 크로스 케이블 모두 1, 2, 3, 6번 구리선을 사용하고 나머지 네 개의 선은 사용하지 않는다.

 

 만약 양쪽 컴퓨터에서 1번과 2번으로 데이터를 전송하면 어떻게 될까? 데이터 충돌이 발생한다. 이 때문에 크로스 케이블은 일부로 중간에 전선을 교차시켜 송, 수신 측이 올바르게 연결되도록 돕는다. 

 

 

출처: https://helpdeskgeek.com/networking/connect-two-computers-using-a-crossover-cable/

 

 



   물리 계층의 네트워크 장비   

 

 물리 계층의 네트워크 장비에는 리피터와 허브가 있다. 

 

 리피터전기 신호를 정형(노이즈로부터 복원)하고 증폭하는 기능을 가진 네트워크 중계 장비이다. 이는 멀리 있는 상대방과도 통신할 수 있도록 파형을 정상으로 만드는 기능을 한다. 하지만, 요즘은 네트워크 장비가 리피터 기능을 지원하기 때문에 리피터를 쓸 필요가 없어졌다. 

 

 또한, 허브는 포트를 여러 개 가지고 있기 때문에 랜 케이블을 사용하여 여러 컴퓨터와 허브를 연결하고 통신할 수 있다. 이 때문에 어떤 특정 포트로 부터 데이터를 받는다면 해당 포트뿐만 아니라 나머지 모든 포트에도 받은 데이터를 전송하는 특징이 있다.

 

 예를 들어, 허브에 컴퓨터 A~D가 연결되어 있다고 가정해보자. 컴퓨터 A에서 B로 데이터를 전송하면 허브에 연결되어 있는 컴퓨터 C, D에도 데이터가 전송된다. 하지만 컴퓨터 C와 D에는 불필요한 데이터일 수 있는데 이처럼 허브는 스스로 판단하지 않고, 전기 신호를 모든 포트로 보내서 더미 허브라는 이름으로 불리기도 한다. 이러한 문제점을 해결하기 위해 스위치라고 하는 네트워크 장비가 등장했다. 스위치는 데이터 링크 계층에서 설명하고 여기서는 생략한다. 

 

또한, 허브도 리피터와 마찬가지로 전기 신호를 정형하고 증폭하는 기능을 한다.  

 

    5번 문제   

 

 



    내가 풀이한 답   

 

 4번 문제와 똑같은 방법으로 풀었고 단순히 인접행렬을 인접리스트 방식으로 바꾸었다. 문제에서 입력 예제를 인접리스트로 나타내면 다음과 같다. 각각의 벡터들이 다른 인덱스값을 가지고 있고 인덱스 하나에 두 개의 값이 들어 있다. 하나는 간선으로 연결될 종점과 나머지 하나는 가중치 값이다. 

 

 

//#include <bits/stdc++.h>
#include <iostream>
#include <vector>
using namespace std;

vector<pair<int, int> > map[21];
vector<int> check(21);
int n, minValue = 77777, temp = 0;

int dfs(int v){
	if(v==n) {
		if(minValue > temp) minValue = temp;
	}
	else {
		for(int i=0; i<map[v].size(); i++){
			if(check[map[v][i].first] == 0){
				check[map[v][i].first] = 1;
				temp += map[v][i].second;
				dfs(map[v][i].first);
				check[map[v][i].first] = 0;
				temp -= map[v][i].second;
			}
		}
	}
}

int main() {
	//freopen("input.txt", "rt", stdin);
	ios_base::sync_with_stdio(false);
	
	int m, i, a, b, c; 
	cin >> n >> m;
	
	for(i=0; i<m; i++){
		cin >> a >> b >> c;
		map[a].push_back({b, c});
	}
	
	check[1] = 1;
	dfs(1);

	cout << minValue;
	return 0;
}

 

 

 결과는 통과하였다.

 

 


 

    사이트의 답안   

 

#include<stdio.h>
#include<vector>
#include<algorithm>
#define x first
#define y second
using namespace std;	
int ch[30], cnt=0, n, cost=2147000000;
vector<pair<int, int> > map[30];

void DFS(int v, int sum){
	int i;
	if(v==n){
		if(sum<cost) cost=sum;
	}
	else{
		for(i=0; i<map[v].size(); i++){
			if(ch[map[v][i].x]==0){
				ch[map[v][i].x]=1;
				DFS(map[v][i].x, sum+map[v][i].y);
				ch[map[v][i].x]=0;
			}
		}
	}
}
				
int main(){
	freopen("input.txt", "rt", stdin);
	int m, i, a, b, c;
	scanf("%d %d", &n, &m);
	for(i=1; i<=m; i++){
		scanf("%d %d %d", &a, &b, &c);
		map[a].push_back(make_pair(b, c));
	}
	ch[1]=1;
	DFS(1, 0);
	printf("%d\n", cost);
	return 0;
}