C++ Socket 프로로그래밍 [POSIX]
💡 Quotation
네트워크 소켓(network socket)은 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점이다. 오늘날 컴퓨터 간 통신의 대부분은 인터넷 프로토콜을 기반으로 하고 있으므로, 대부분의 네트워크 소켓은 인터넷 소켓이다. 네트워크 통신을 위한 프로그램들은 소켓을 생성하고, 이 소켓을 통해서 서로 데이터를 교환한다. 소켓은 RFC 147에 기술사항이 정의되어 있다. wikipedia
소켓은 운영체제에 종속되어 있어 플랫폼마다 사용하는 방법이 아주 조금 다릅니다. 이 글에서는 유닉스 또는 리눅스에 사용되는 POSIX1) Socket 을 간단하게 소개하고 프로그래밍 방법을 소개합니다.
POSIX Socket 🌏
BSD Socket 또는 Burkeley Socket 이라고 불리는 이 소켓의 가장 큰 특징은 파일처럼 핸들링한다는 점입니다! C 언어에서 파일을 열고 쓰기 위해 FILE
구조체를 open()
함수로 연 후 fputs()
와 같은 함수에 FILE
구조체를 인자로 전달하여 파일을 썼습니다.
마찬가지로 상대방 호스트의 특정 포트에 소켓을 열어 recv()
또는 send()
함수를 사용하여 파일을 읽고 쓰면서 통신을 합니다.
💡 Tips
파일과 관련된 함수를 사용해도 작동합니다!
이제 소켓으로 연결하는 과정을 살펴보도록 하겠습니다.
Procedure
Server 📡
서버는 클라이언트의 접속을 기다렸다가 연결 신호가 오면 연결을 해야 합니다. 따라서 기본적인 소켓 서버의 설정과 백그라운드에서 대기하며 클라이언트의 연결 수락과 데이터 수신을 할 수 있는 스레드 기술이 필요합니다!
Index | Function | Description |
---|---|---|
0 | socket() | 주소체계, 종류, 프로토콜(TCP, UDP 등)을 설정하고 소켓을 반환합니다. |
1 | bind() | 소켓에 주소를 설정(바인딩)합니다. |
2 | listen() | 서버를 열어 클라이언트의 연결을 기다립니다. |
3 | accept() | 클라이언트의 연결을 받아들이고 클라이언트와 통신할 소켓을 반환합니다. |
4 | recv() / send() | 반환된 클라이언트 소켓을 이용하여 읽고/쓰면서 통신을 합니다. |
5 | shutdown() | 소켓의 연결을 종료합니다. 이때 한 방향만 종료할 수도 있습니다. |
6 | close() | 생성된 소켓의 File descriptor1)를 닫습니다. |
Client 🔌
클라이언트는 서버에 비해 간단합니다. 서버처럼 연결된 클라이언트들의 소켓을 관리해줄 필요도 없으며, 클라이언트 본인이 생성한 소켓으로만 통신을 하니 구현도 간단합니다.
Index | Function | Description |
---|---|---|
0 | socket() | 주소체계, 종류, 프로토콜(TCP, UDP 등)을 설정하고 소켓을 반환합니다. |
1 | connect() | 설정된 주소로 서버에 접속합니다. |
2 | recv() / send() | 반환된 클라이언트 소켓을 이용하여 읽고/쓰면서 통신을 합니다. |
3 | shutdown() | 소켓의 연결을 종료합니다. 이때 한 방향만 종료할 수도 있습니다. |
4 | close() | 생성된 소켓의 File descriptor 를 닫습니다. |
Design 🛠️
- Blocking I/O 처리
accept()
함수와recv()
함수는 클라이언트의 연결 또는 수신된 데이터가 있을 때까지 함수가 종료되지 않는 Blocking function입니다. 따라서 메인 스레드에서 위와 같은 함수를 사용한다면 프로그램 전체가 멈출테니 스레드를 생성하여 처리해야 합니다.
- Callback 처리
- 메인 스레드에서 소켓을 통해 들어온 데이터를 수시로 확인하면서 처리한다면busy wating 쓸데없는 자원 낭비와 좋은 성능을 얻지 못합니다. 따라서 메인 스레드로부터 데이터를 수신하면 실행할 함수를 미리 받아 처리합니다.
- 확실한 자원 해제
- 소켓뿐만 아니라 C/C++ 프로그래밍에 있어 자원의 해제는 엄청 중요합니다. close() 함수를 통하여 반드시 File descriptor 를 해제해야 합니다.
⚠️ Warning
Non-Blocking I/O를 구현하기 위해
select()
함수를 사용할텐데, 이 때 File descriptor 가 1000개가 넘어가면 비정상적인 작동을 일으킵니다!
- 가변 길이 전송
- 소켓을 이용하여 데이터를 전송할 때 데이터의 크기는 정해져 있지 않으며 어떤 형태던지 전송할 수 있어야 합니다.
💡 Tips
가변 길이 구조체를 사용하면 훨씬 쉽게 구현할 수 있습니다!
자세한 내용은 이 곳을 참조해주세요. 😎
Implement ⌨️
구현 코드는 Github 에 있으며 샘플은 main.cpp를 참고하시기 바랍니다.
각주
- POSIX(포직스, /ˈpɒzɪks/)는 이식 가능 운영 체제 인터페이스(移植可能運營體制 interface, portable operating system interface)의 약자로, 서로 다른 UNIX OS의 공통 API를 정리하여 이식성이 높은 유닉스 응용 프로그램을 개발하기 위한 목적으로 IEEE가 책정한 애플리케이션 인터페이스 규격이다. wikipedia ↩
- 컴퓨터 프로그래밍 분야에서 파일 서술자(file descriptor) 또는 파일 기술자는 특정한 파일에 접근하기 위한 추상적인 키이다. 이 용어는 일반적으로 POSIX 운영 체제에 쓰인다. wikipedia ↩