소켓 통신/Boost

Boost.asio 서버 개요

마달랭 2024. 11. 18. 15:10

개요

서버는 분산 어플리케이션의 반대쪽 부분인 클라이언트에게 받을 서비스를 제공하는 역할을 맡는다.

그리고 클라이언트는 서버가 제공하는 서비스를 받기 위해 서버와 통신한다.

 

일반적으로 서버는 클라이언트-서버 통신 과정에서 수동적인 역할을 한다.

서버는 호스트의 포트에 붙고, 그 포트로 클라이언트가 요청을 보내지 않는지 기다린다.

요청이 도착하면 서버는 자신이 제공하는 서비스에 맞춰 동작한다.

 

서버가 제공하는 서비스에 따라 요청 처리 과정도 매우 다르다.

예를 들어, HTTP 서버라면 대체로 요청 메시지에서 원한 파일 내용을 읽어 클라이언트로 보낸다.

프록시 서버라면 클라이언트의 요청을 실제로 처리할 다른 서버로 전달한다.

좀 더 구체적인 서버들이라면 클라이언트가 제공한 데이터에 대해 복잡한 계산을 거쳐 그 결과를 클라이언트로 되돌려 보낸다.

 

모든 서버가 수동적인 역할만 하는 것은 아니다.

일부 서버는 클라이언트가 요청을 보내기를 기다리지 않고 바로 메시지를 보낼 수도 있다.

대체로 그런 서버들은 알리미처럼 동작하면서 유용한 이벤트가 발생했을 때 클라이언트에게 알려준다.

이 경우, 클라이언트는 서버에게 데이터를 전송할 필요가 전혀 없다.

 

그 대신 클라이언트 측이 수동적으로 서버가 알림을 보내기를 기다리며 데이터가 오면 그에 맞춰 동작한다.

이러한 통신 모델은 푸시 스타일 통신이라고 한다.

좀 더 유연하게 동작할 수 있기 때문에 현대 앱에서는 이러한 모델이 점점 인기를 얻고 있다.

 

 

서버 프로토콜

TCP 프로토콜은 매우 유명하며, 많은 일반 목적 서버 어플리케이션에서 사용하고 있다.

이 밖에도 좀 더 구체적인 목표가 있는 서버들은 UDP 프로토콜을 쓸 수도 있다.

한 번에 TCP와 UDP 프로토콜을 함께 사용하고, 서비스를 제공하는 다중 프로토콜 서버도 있다.

 

 

반복적 서버

반복적 서버의 경우 클라이언트를 한 번에 하나씩 서비스 한다, 은행 창구를 떠올리면 이해하기 쉽다.

다시 말해 현재 서비스하고 있는 클라이언트를 처리하기 전에는 다음 클라이언트에 대한 서비스를 시작하지 않는다.

반복적 서버는 상대적으로 구현하기 쉽다.

요청이 자주 들어오지 않아 다음 요청이 들어오기 전에 서버가 충분히 처리할 수 있다면 써볼 만 하다.

하지만 반복적 서버는 확장성이 떨어지며, 프로세서를 더 추가하더라도 서버의 처리량을 늘릴 수 없다.

 

 

병렬 서버

반대로 병렬 서버는 병렬적으로 여러 클라이언트를 서비스한다.

예를 들어, 연결을 맺은 클라이언트로부터 들어오는 요청 메시지를 기다리는 동안, 서버는 다음 클라이언트와 연결을 맺으려고 할 수도 있고, 세 번째 클라이언트에서 보낸 요청을 읽을 수도 있다.

그런 다음, 첫 번째 클라이언트로 되돌아가 계속 서비스를 할 수 있다.

이러한 종류의 병렬 처리를 의사 병렬이라고 부르며, 프로세서는 여러 클라이언트 사이를 계속해서 돌아다니지만, 진짜로 한꺼번에 서비스하지는 못한다, 프로세서가 하나 뿐이라면 이런 일은 불가능하다.

 

다중 프로세서가 있는 컴퓨터라면 진짜로 병렬 처리가 가능하다.

서버가 한 번에 하나 이상의 클라이언트를 여러개의 하드웨어 스레드를 사용해 서비스하는 것이다.

적절히 구현만 한다면 확장성이 뛰어나며 단일 프로세서 컴퓨터에서 실행시키는 것보다 자주 요청이 들어와도 처리할 수 있다.

 

 

동기 서버

요청한 연산이 끝나거나 오류가 발생할 때까지 실행 중이던 스레드를 멈추는 방식의 API를 사용한다.

일반적인 TCP 서버는 accept()와 같은 메서드로 클라이언트 연결 요청을 받아들이고, read()를 사용해 클라이언트가 보낸 요청 메시지를 받는다.

이후 요청에 맞는 응답 메시지를 write()을 통해 클라이언트에게 보낸다.

해당 메서드는 모두 관련 연산이 끝나거나 오류가 발생할 때까지 실행 중이던 스레드를 기다려 서버를 동기화 한다.

 

동기 서버의 가장 큰 장점은 간단하는 것이다. 개발 및 디버깅하기 쉽고, 비동기 방식에 비해 같은 기능을 지원하기도 쉽다.

하지만 동기 방식은 기능상 한계가 있다, 일단 동기 연산은 시작하면 취소할 수 없거나 원하는 시간보다 오래 실행 중이면 중단하도록 타이머를 설정할 수 없다.

동기 연산은 취소할 수 없기 때문에 동기 서버를 적용할 수 있는 연역은 매우 좁다.

일반에게 공개되어 있는 서버가 동기 연산을 사용한다면 범죄자들의 공격에 매우 취약하다.

그런 서버가 단일 스레드를 사용한다면, 악의적인 클라이언트 혼자서도 서버를 멈추게 만들고 다른 클라이언트와는 통신하지 못하게 만들 수 있다.

악의적인 클라이언트가 서버에 연결한 후 데이터를 보내지 않는다면 서버의 동기 읽기 함수나 메서드는 그 상태에서 그대로 멈추며, 다른 클라이언트는 전혀 서비스를 할수가 없다.

 

동기 서버는 안전하게 보호받을 수 있는 내부 네트워크나 프로세스 간의 통신에 사용되는 서버에서 주로 사용된다.

또는 시험 삼아 프로토타입을 만들어 볼 때에도 동기 서버를 서볼 수 있다.

 

 

비동기 서버

동기 방식과 달리 비동기 소켓 API를 사용한다.

비동기 TCP 서버는 비동기적 클라이언트 연결 요청을 처리하기 위해 async_accept() 메서드를 사용한다.

또한 읽기는 async_read(), 쓰기는 async_write() 메서드를 사용해 비동기 연산 작업을 한다.

 

비동기 연산은 연산을 시작한 후부터 끝나기 전까지는 언제라도 취소할 수 있는 장점이 있다.                                      

또한 매우 많은 요청이 들어올때의 처리 효율성 면에서도 비동기 방식이 동기 방식보다 효율적이다.

비동기 연산을 사용하는 서버가 동기 서버보다 효율적이고 확장성도 좋지만, 특히 운영체제가 비동기 네트워크 I/O를 지원하는 다중 프로세서 컴퓨터라면 더욱 효과가 좋다.

 

비동기 연산은 연산을 시작한 곳과는 완전히 다른 부분에서 완료된다는 점 때문에 좀 더 복잡하다.

대체로 요청과 콜백 함수의 문맥을 유지하기 위해 자유 메모리상에 데이터 구조체를 추가로 할당해야 하고, 스레드를 동기화 해야하며, 프로그램 구조를 복잡하게 만들고 오류도 나기 쉽게하는 여러가지를 생각해야 한다.

이 밖에도 비동기 방식은 계산상으로나 메모리상으로 부하가 좀 더 있어 일부 상황에서는 동기 방식보다 효율적이지 못하다.

 

서버 설계

서버를 설계할 때는 사용할 프로토콜, 사용가능한 HW에 의해 반복 혹은 병렬적으로 처리할지 여부를 결정하고

클라이언트의 요청을 동기 혹은 비동기 적으로 처리할지에 대한 내용을 프로그램의 초기 설계 단계에서 요구사항에 맞게 세밀하게 분석하여 결정해야 한다.

프로그램이 어떻게 발전해 나갈 것인지, 앞으로 어떤 새로운 요구사항이 들어올 수 있는지를 모두 고려해야한다.

 

언제나 그렇듯이 각각의 방식에는 장점도 있고, 단점도 있다.

 

728x90