소켓 프로그래밍. (Socket Programming)

2019. 3. 5. 11:04


1. 소켓(Socket)

만약 네트워크와 관련된 프로젝트를 진행하면서, 사용자(User)의 관점이 아닌, 개발자(Developer)의 관점에서 네트워크를 다뤄본 경험이 있다면, "소켓(Socket)"이라는 용어가 아주 낯설게만 느껴지는 단어는 아닐 것입니다. 하지만 이제 막 정보통신학과 전공을 배우는 학생이거나 TCP/IP 통신 프로그래밍을 한번도 접해보지 못한 개발자라면, 소켓(Socket)이란 그저 벽에 뚫린 전원 케이블 연결 구멍 정도로 생각될지도 모르겠네요. (물론, 전혀 생뚱맞은 개념 이해는 아니지만...)


"소켓(Socket)"은 사전적으로 "구멍", "연결", "콘센트" 등의 의미를 가집니다. 주로 전기 부품을 규격에 따라 연결할 수 있게 만들어진 "구멍 형태의 연결부"를 일컫는 단어인데, 가정에서 흔히 볼 수 있는 콘센트 구멍을 떠올리면 쉽게 이해할 수 있을 것입니다. 다시 한번 풀어서 쓰자면, 전기를 필요로하는 디바이스 또는 부품들이 전기를 공급받을 수 있도록, 전기 공급 인프라 환경에 연결할 수 있게 만들어진 연결부가 "소켓(Socket)"인 것이죠.

소켓의 사전적 의미


네트워크 프로그래밍에서의 소켓(Socket)에 대한 의미도, 사전적 의미를 크게 벗어나지 않습니다. 프로그램이 네트워크에서 데이터를 송수신할 수 있도록, "네트워크 환경에 연결할 수 있게 만들어진 연결부"가 바로 "네트워크 소켓(Socket)"입니다.

네트워크 소켓(Socket)


하지만 엄밀히 따지자면, "네트워크 소켓"이라는 용어가 정확한 표현은 아닙니다. 전기 소켓이 전기를 공급받기 위해 정해진 규격(110V, 220V 등)에 맞게 만들어져야 하듯, 네트워크에 연결하기 위한 소켓 또한 정해진 규약, 즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들어져야 합니다. 보통 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, 이를 "TCP 소켓" 또는 "TCP/IP 소켓"이라고 부릅니다. (UDP에서 동작하는 소켓은 "UDP 소켓"이라고 합니다.)

2. TCP/IP 소켓 프로그래밍(Socket Programming)

앞에서 비교적 단순한 문장으로 TCP/IP 소켓(Socket)에 대한 의미를 설명했지만, 소켓(Socket)을 사용하여 네트워크 통신 기능을 구현하는 과정, 즉, 소켓 프로그래밍(Socket Programming)은 그 개념만큼 아주 단순하지만은 않습니다. 그 이유는 바로, 소켓(Socket)으로 네트워크 통신 기능을 구현하기 위해서는, 소켓을 만드는 것과, 만들어진 소켓을 통해 데이터를 주고 받는 절차에 대한 이해가 필요하고, 운영체제 및 프로그래밍 언어에 종속적으로 제공되는 소켓 API 사용법을 숙지해야 하기 때문입니다.


그리고 덤으로, 케이블 분리로 인한 네트워크 단절, 트래픽 증가에 따른 데이터 전송 지연, 시스템 리소스 관리 문제로 인한 에러 등, 네트워크 환경에서 발생할 수 있는 다양한 예외사항에 대해서도 처리가 필요하기 때문에 소켓 프로그래밍(Socket Programming)이 초보 개발자에게는 더욱 어렵게 느껴질 수 밖에 없죠.


하지만 다행히도, 소켓 프로그래밍(Socket Programming)은 그 역사가 꽤 긴 편입니다. 거의 모든 운영체제에서 지원되고, 대부분의 프로그래밍 언어와 개발 플랫폼에서 소켓(Socket) 관련 API가 제공되고 있죠. 그리고 많은 양의 문서와 예제를, 책과 인터넷을 통해 참고할 수 있습니다.


여기서는 일단, 소켓 프로그래밍 방법을 설명하기 위해, 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)의 역할에 대한 내용부터 살펴볼까 합니다. 데이터를 주고받기 위해서는 먼저 소켓의 연결 과정이 선행되어야 하고, 그 과정에서의 연결 요청과 수신이 각각 클라이언트 소켓과 서버 소켓의 역할이기 때문입니다.

2.1 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)

두 개의 시스템(또는 프로세스)이 소켓을 통해 네트워크 연결(Connection)을 만들기 위해서는, 최초 어느 한 곳에서 그 대상이 되는 곳으로 연결을 요청해야 합니다. IP 주소와 포트 번호로 식별되는 대상에게, 자신이 데이터 송수신을 위한 네트워크 연결을 수립할 의사가 있음을 알리는 것이죠.

소켓(Socket) 연결 요청


그런데, 최초 한 곳에서 무작정 연결을 시도한다고 해서, 그 요청이 무조건 받아들여지고 연결이 만들어져 데이터를 주고 받을 수 있게 될까요? 아닙니다. 한 곳에서 연결 요청을 보낸다고 하더라도 그 대상 시스템이 그 요청을 받아들일 준비가 되어 있지 않다면, 해당 요청은 무시되고 연결은 만들어지지 않습니다.

소켓(Socket) 연결 요청 거부


그러므로 요청을 받아들이는 곳에서는 어떤 연결 요청(일반적으로 포트 번호로 식별)을 받아들일 것인지를 미리 시스템에 등록하여, 요청이 수신되었을 때 해당 요청을 처리할 수 있도록 준비해야 합니다.


이렇듯 두 개의 시스템(또는 프로세스)이 소켓을 통해 데이터 통신을 위한 연결(Connection)을 만들기 위해서는, 연결 요청을 보내는지 또는 요청을 받아들이는지에 따라 소켓의 역할이 나뉘게 되는데, 전자에 사용되는 소켓을 클라이언트 소켓(Client Socket), 후자에 사용되는 소켓을 서버 소켓(Server Socket)이라고 합니다.

클라이언트 소켓과 서버 소켓의 연결


그런데 여기서, 오해하기 쉬운 내용이 있습니다. 앞서 설명한 내용을 보면 마치 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)이 태생적으로 구조가 다른, 전혀 별개의 소켓(Socket)인 것처럼 여겨질 수 있다는 것이죠. 하지만 두 소켓(Socket)은 동일합니다. 소켓의 역할과 구현 절차 구분을 위해 다르게 부르는 것일 뿐, 전혀 다른 형태의 소켓이 아니라는 것이죠. 단지 역할에 따라 처리되는 흐름, 즉, 호출되는 API 함수의 종류와 순서들이 다를 뿐입니다.


또한 위의 그림을 보고, 소켓 연결이 완료된 다음 클라이언트 소켓과 서버 소켓이 직접 데이터를 주고 받는다고 생각하지 마시기 바랍니다. 서버 소켓은 클라이언트 소켓의 연결 요청을 받아들이는 역할만 수행할 뿐, 직접적인 데이터 송수신은 서버 소켓의 연결 요청 수락의 결과로 만들어지는 새로운 소켓을 통해 처리됩니다. 이와 관련된 내용은 지금 당장 이해되지 않더라도 너무 걱정하지 마세요. 뒤에서 좀 더 자세하게 설명할 것입니다.

2.2 소켓 API(Socket API) 실행 흐름.

자 그럼 이제 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)이 어떻게 다루어지는지, API 호출 흐름을 통해 조금 더 자세하게 살펴보도록 하겠습니다. 일단, 각 소켓(Socket)이 처리되는 흐름을 간단한 문장으로 표현해 볼까요?


클라이언트 소켓(Client Socket)은 처음 소켓(Socket)을 [1]생성(create)한 다음, 서버 측에 [2]연결(connect)을 요청합니다. 그리고 서버 소켓에서 연결이 받아들여지면 데이터를 [3]송수신(send/recv)하고, 모든 처리가 완료되면 소켓(Socket)을 [4]닫습니다(close).


서버 소켓(Server Socket)은 처리 과정이 조금 복잡합니다. 일단 클라이언트와 마찬가지로, 첫 번째 단계는 소켓(Socket)을 [1]생성(create)하는 것입니다. 그리고 서버 소켓이 해야 할 두 번째 작업은, 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 [2]결합(bind)시키는 것입니다. 그런 다음 클라이언트로부터 연결 요청이 수신되는지 [3]주시(listen)하고, 요청이 수신되면 요청을 [4]받아들여(accept) 데이터 통신을 위한 소켓을 생성합니다. 일단 새로운 소켓을 통해 연결이 수립(ESTABLISHED)되면, 클라이언트와 마찬가지로 데이터를 [5]송수신(send/recv)할 수 있습니다. 마지막으로 데이터 송수신이 완료되면, 소켓(Socket)을 [6]닫습니다(close).


간략한 문장들로 각 소켓의 처리 흐름을 정리해봤는데요, 그 흐름이 머리 속에 잘 그려지나요? 이해가 잘 가지 않는다면 아래 그림을 통해 다시 한번 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)의 실행 흐름을 확인하시기 바랍니다.

소켓 API 실행 흐름


3. 클라이언트 소켓 프로그래밍. (Client Socket Programming)

위의 그림으로 볼 수 있듯이 소켓(Socket) API의 실행 흐름은 꽤 직관적입니다. 특히 클라이언트 소켓(Client Socket)을 다루는 과정은 이해하기가 그리 어렵지 않죠.

3.1 클라이언트 소켓 생성. (socket())

소켓 통신을 위해 가장 먼저 해야 할 일은 소켓을 생성하는 것입니다. 이 때 소켓의 종류를 지정할 수 있는데, TCP 소켓을 위해서는 스트림(Stream) 타입, UDP 소켓을 위해서는 데이터그램(Datagram) 타입을 지정할 수 있습니다. (물론, 좀 더 다양한 소켓 옵션(family, raw)들이 존재하지만, 여기서는 따로 언급하지 않습니다.)


최초 소켓이 만들어지는 시점에는 어떠한 "연결 대상"에 대한 정보도 들어 있지 않습니다. 껍데기 뿐인 소켓이 하나 만들어진 것 뿐이죠. 연결 대상(IP:PORT)을 지정하고 연결 요청을 전달하기 위해서는, 여기서 생성한 소켓을 사용하여 connect() API를 호출해야 합니다.

3.2 연결 요청. (connect())

connect() API는 "IP주소"와 "포트 번호"로 식별되는 대상(Target)으로 연결 요청을 보냅니다.


connect() API는 블럭(Block) 방식으로 동작합니다. 즉, 연결 요청에 대한 결과(성공, 거절, 시간 초과 등)가 결정되기 전에는 connect()의 실행이 끝나지 않는다는 것이죠. 그러므로 connect() API가 실행되지마자 실행 결과와 관계없이 무조건 바로 리턴될 것이라 가정해선 안됩니다.


connect() API 호출이 성공하면, 이제 send() / recv API를 통해 데이터를 주고 받을 수 있습니다.

3.3 데이터 송수신. (send()/recv())

연결된 소켓을 통해 데이터를 보낼 때는 send(), 데이터를 받을 때는 recv API를 사용합니다. 언뜻보면 API를 호출하는 방법이 매우 단순할 것 같지만, 한 가지 중요한 사실이 있습니다. send()recv() API가 모두 블럭(Block) 방식으로 동작한다는 것이죠. 즉, 두 API 모두 실행 결과(성공, 실패, 종료)가 결정되기 전까지는 API가 리턴되지 않습니다. 특히 recv()는 데이터가 수신되거나, 에러가 발생하기 전에는 실행이 종료되지 않기 때문에, 데이터 수신 작업을 생각만큼 단순하게 처리하기 쉽지 않습니다.


send()의 경우 데이터를 보내는 주체가 자기 자신이기 때문에, 얼마만큼의 데이터를 보낼 것인지, 언제 보낼 것인지를 알 수 있습니다. 하지만 데이터를 수신하는 경우, 통신 대상이 언제, 어떤 데이터를 보낼 것인지를 특정할 수 없기 때문에 recv() API가 한번 실행되면 언제 끝날지 모르는 상태가 되는 것입니다.


그래서 데이터 수신을 위한 recv() API는 별도의 스레드에서 실행합니다. 소켓의 생성과 연결이 완료된 후, 새로운 스레드를 하나 만든 다음 그곳에서 recv()를 실행하고 데이터가 수신되길 기다리는 것이죠.


음, 관련 내용을 모두 설명하려면 내용이 너무 길어지니, 나중에 예제를 통해 좀 더 자세히 설명하도록 하죠.


send() / recv API를 통해 데이터 송수신 과정이 완료되면, close() API를 사용하여 소켓을 닫습니다.

3.4 소켓 닫기. (close())

더 이상 데이터 송수신이 필요없게되면, 소켓을 닫기 위해 close() API를 호출합니다. close()에 의해 닫힌 소켓은 더 이상 유효한 소켓이 아니기 때문에, 해당 소켓을 사용하여 데이터를 송수신할 수 없습니다.


그리고 만약 소켓 연결이 종료된 후 또 다시 데이터를 주고 받고자 한다면, 또 한번의 소켓 생성(socket())과 연결(connect()) 과정을 통해, 소켓이 데이터를 송수신할 수 있는 상태가 되어야 합니다.

4. 서버 소켓 프로그래밍. (Server Socket Programmng)

클라이언트 소켓을 처리하는 과정에서 사용하는 API들은 비교적 그 의미가 쉽게 와닿는 편입니다. "만들고, 연결하고, 주고받고, 닫는다."...간단하죠?


반면, 서버 소켓(Server Socket)을 처리하는 과정은 조금 복잡하게 느껴질 수 있습니다. 그 이유는 아마도, 소켓(Socket)에 IP 주소와 포트 번호를 결합하는 bind() API와 클라이언트 요청이 있는지 확인하는 listen() API의 존재 때문일거라 생각합니다. "bind(결합하다, 묶다)"와 "listen(듣다, 경청하다)"이라는 단어의 사전적 의미가, 서버 소켓의 처리 과정에서 어떤 역할을 의미하는지 쉽게 이해가 되지 않죠.


하지만 너무 복잡하게 생각할 필요는 없습니다. 본문의 장황한 설명과는 달리 서버 소켓을 사용하는 소스 코드 자체는 간단한 편이고, 서버 소켓의 처리 로직이 어플리케이션의 성격에 큰 영향을 받지 않는 경우가 대부분이기 때문입니다.


그럼 이제, 서버 소켓을 다루는 과정을 살펴볼까요?

4.1 서버 소켓 생성. (socket())

클라이언트 소켓과 마찬가지로, 서버 소켓을 사용하려면 최초에 소켓을 생성해야 합니다. [3.1 클라이언트 소켓 생성] 단계에서 설명한 내용과 크게 다르지 않습니다.

4.2 서버 소켓 바인딩. (bind())

"bind"는 "결합하다", "구속하다", "묶다" 등의 사전적 의미를 가지고 있습니다.

bind의 사전적 의미


bind() API에 사용되는 인자는 소켓(Socket)과 포트 번호(또는 IP 주소+포트 번호)입니다. 위의 사전적 의미대로라면, "소켓(Socket)과 포트 번호를 결합(bind)한다"는 것인데요. 이 "결합(bind)"이라는 동작이 구체적으로 무엇을 의미하는 걸까요?


보통 시스템에는 많은 수의 프로세스가 동작합니다. 그 중에는 네트워크 관련 기능을 수행하는 프로세스도 다수 포함되어 있죠. 만약 프로세스가 TCP 또는 UDP 프로토콜을 사용한다면, TCP(RFC-793) 또는 UDP(RFC-768) 표준에 따라, 각 소켓은 시스템이 관리하는 포트(0~65535) 중 하나의 포트 번호를 사용하게 됩니다. 그런데 만약 소켓이 사용하는 포트 번호가 다른 소켓의 포트 번호와 중복된다면 어떤 상황이 될까요? 모든 소켓이 10000 이라는 동일한 포트 번호를 사용하게 된다면, 네트워크를 통해 10000 포트로 데이터가 수신될 때 어떤 소켓이 처리해야 하는지 결정할 수 없는 문제가 발생할 것입니다.

소켓 bind() 존재 이유


이런 이유로, 운영체제에서는 소켓들이 중복된 포트 번호를 사용하지 않도록, 내부적으로 포트 번호와 소켓 연결 정보를 관리합니다. 그리고 bind() API는 해당 소켓이 지정된 포트 번호를 사용할 것이라는 것을 운영체제에 요청하는 API인 것이죠. 만약 지정된 포트 번호를 다른 소켓이 사용하고 있다면, bind() API는 에러를 리턴합니다.


정리하자면, 일반적으로 서버 소켓은 고정된 포트 번호를 사용합니다. 그리고 그 포트 번호로 클라이언트의 연결 요청을 받아들이죠. 그래서 운영체제가 특정 포트 번호를 서버 소켓이 사용하도록 만들기 위해 소켓과 포트 번호를 결합(bind)해야 하는데, 이 때 사용하는 API가 바로 bind인 것입니다.


참고로 여기서는 bind의 뜻을 설명하기 위해 "결합"이라는 용어를 사용하였지만, 보통 소켓에 bind() API를 호출하는 것을 "소켓 바인드" 또는 "소켓 바인딩"이라고 부릅니다.

4.3 클라이언트 연결 요청 대기. (listen())

서버 소켓(Server Socket)에 포트 번호(또는 IP 주소+포트 번호)를 결합(bind)하고 나면, 서버 소켓(Server Socket)을 통해 클라이언트의 연결 요청을 받아들일 준비가 되었습니다. 이제 할 일은, 클라이언트에 의한 연결 요청이 수신될 때까지 기다리는 것인데요, listen API 가 그 역할을 수행합니다.

listen의 사전적 의미


"듣다" 또는 "귀를 기울이다"라는 뜻을 가진 listen() API는 서버 소켓(Server Socket)에 바인딩된 포트 번호로 클라이언트의 연결 요청이 있는지 확인하며 대기 상태에 머무릅니다. 클라이언트에서 호출된 connect() API에 의해 연결 요청이 수신되는지 귀 기울이고(?) 있다가, 요청이 수신되면, 그 때 대기 상태를 종료하고 리턴하는 것이죠.


listen() API가 대기 상태에서 빠져나오는 경우는 크게 두 가지입니다. 클라이언트 요청이 수신되는 경우와, 에러가 발생(소켓 close() 포함)하는 경우죠. 그런데 listen() API가 성공한 경우라도, 리턴 값에 클라이언트의 요청에 대한 정보는 들어 있지 않습니다. listen()의 리턴 값으로 판단할 수 있는 것은 클라이언트 연결 요청이 수신되었는지(SUCCESS), 그렇지 않고 에러가 발생했는지(FAIL) 뿐이죠.


대신 클라이언트 연결 요청에 대한 정보는 시스템 내부적으로 관리되는 큐(Queue)에서 쌓이게 되는데, 이 시점에서 클라이언트와의 연결은 아직 완전히 연결되지 않은(not ESTABLISHED state) 대기 상태입니다.


대기 중인 연결 요청을 큐(Queue)로부터 꺼내와서, 연결을 완료하기 위해서는 accept() API를 호출해야 합니다.

4.4 클라이언트 연결 수립. (accept())

다시 한번, listen() API가 클라이언트의 연결 요청을 확인하고 문제없이 리턴한다고 해서, 클라이언트와의 연결 과정이 모두 완료되는 것은 아닙니다. 아직 실질적인 소켓 연결(Connection)을 수립하는 절차가 남아 있죠. 최종적으로 연결 요청을 받아들이는 역할을 수행하는 것은 accept() API 입니다.


accept() API는 그 사전적 의미만큼 직관적인 역할을 수행합니다. 연결 요청을 받아들여(accept) 소켓 간 연결을 수립하는 것이죠. 그런데 주의할 점은 최종적으로 데이터 통신을 위해 연결되는 소켓이, 앞서 bind() 또는 listen() API에서 사용한 서버 소켓(Server Socket)이 아니라는 것입니다.


"응? 여태까지 서버 소켓과의 연결 요청이 어쩌구저쩌구 해놓고, 이제 와서 연결된 소켓이 서버 소켓이 아니라니.. 무슨 말이지?" 라고 당혹감을 느낄 수도 있을텐데요. 결론부터 말하자면, 최종적으로 클라이언트 소켓(Client Socket)과 연결(Connection)이 만들어지는 소켓(Socket)은 앞서 사용한 서버 소켓(Server Socket)이 아니라, accept API 내부에서 새로 만들어지는 소켓(Socket)입니다.


앞에서도 언급했지만, 서버 소켓(Server Socket)의 핵심 역할은 클라이언트의 연결 요청을 수신하는 것입니다. 이를 위해 bind()listen()을 통해 소켓에 포트 번호를 바인딩하고 요청 대기 큐를 생성하여 클라이언트의 요청을 대기하였죠. 그리고 accept() API에서, 데이터 송수신을 위한 새로운 소켓(Socket)을 만들고 서버 소켓의 대기 큐에 쌓여있는 첫 번째 연결 요청을 매핑시킵니다. 여기까지, 하나의 연결 요청을 처리하기 위한 서버 소켓의 역할은 끝났습니다. 서버 소켓의 입장에서 남은 일은, 또 다른 연결 요청을 처리하기 위해 다시 대기(listen)하거나, 서버 소켓(Socket)을 닫는(close) 것 뿐이죠.


실질적인 데이터 송수신은 accept API에서 생성된, 연결(Connection)이 수립(Established)된 소켓(Socket)을 통해 처리됩니다.

4.5 데이터 송수신. (send()/recv())

데이터를 송수신하는 과정은 클라이언트 소켓 처리 과정에서 설명했던 [3.3 데이터 송수신]의 내용과 동일합니다.

4.6 소켓 연결 종료. (close())

클라이언트 소켓 처리 과정과 마찬가지로 소켓을 닫기 위해서는 close() API를 호출하면 됩니다.


그런데 서버 소켓에서는 close()의 대상이 하나만 있는 것이 아니라는 것에 주의해야 합니다. 무슨 말이냐구요? 최초 socket() API를 통해 생성한 서버 소켓에 더해, accpet() API 호출에 의해 생성된 소켓도 관리해야한다는 의미입니다.

5. 참고.

.END.


ANDROID 프로그래밍/NETWORK , , , , , , , , , , , , ,

  1. 이전 댓글 더보기
  2. 포스팅 잘 보고 갑니다.

  3. 방문해 주셔서 감사합니다.

  4. Blog Icon
    먐먐

    소켓 API에 대한 내용을 잊어버려서 다시 찾아보는데, 오랜만에 봤는데도 이해가 너무 잘되게 써주셨네요...!
    정리가 됐습니다!!감사합니다.

  5. 도움이 되셨다니 다행이네요.
    방문해 주셔서 감사합니다.

  6. Blog Icon

    비밀댓글입니다

  7. 흠.. 댓글만으로 답을 드릴 수 있는 내용이 아닌 것 같습니다. 정보도 너무 없고요.
    일단, 주변의 선배, 경력자 분들의 도움을 받으시는 게 좋을 것 같습니다.

    1. 수신데이터를 어떤 방식으로 받을 것인가..
    => 어떤 데이터를 주고 받을 것인가.
    => 얼마나 많은 데이터를 주고 받을 것인가.
    => 얼마나 자주 데이터를 주고 받을 것인가.
    => 어떤 통신 프로토콜을 사용할 것인가.
    => 데이터 프로토콜은 어떻게 정의할 것인가.

    이것만 봐도 먼저 고민하고 정리해야 할 내용이 많죠?

    먼저, 어떤 데이터를 주고받을 것인지에 대해서 정리해보세요. 그것부터 차근차근 정리해서 주변의 도움을 받으면 만들어가실 수 있을 것 같습니다.

    감사합니다.

  8. Blog Icon
    Seol7523

    안그래도 요즘 파이썬에 재미를 느낀 시기인데 이런 글을 찾아 정말 좋네요
    그럼 이 소켓을 사용하면 다른 친구와 멀티플레이 게임을 할수도 있겠네요!!!(물론 코딩이 힘들겠지만요)
    암튼 감사합니다~~

  9. 방문해 주셔서 감사합니다.

  10. Blog Icon

    비밀댓글입니다

  11. 일단 bind, listen 까지 while 안에 넣을 필요는 없습니다.

    소켓을 만들고 bind, listen 까지 수행하고 나면, 그 이후에는 클라이언트 연결 요청에 대해 accept를 while 내에서 실행하여 데이터를 주고받으면 되고요.

    질문 내용만으로만 봤을 때는 client 연결 종료 시 처리 코드의 문제로 server가 종료되는 것으로 보여집니다.

    음, 제가 소스 내용을 확인하지 못해서 구체적인 조언을 드리기는 힘들 것 같고요.

    일단, 어디서 문제가 발생하는지 원인을 파악하는 게 우선인 듯 하네요.

    조그 번거롭더라도 최대한 자세하게 로그 메시지를 출력하여 문제가 되는 곳이 어딘지 먼저 파악해보시기 바랍니다.

    감사합니다.

  12. Blog Icon
    letsdoit

    네트워크 개론 수업을 듣다가 ㄴㅔ트워크 분야에 대해 관심이 생겨서 이것저것 찾아서 읽고 있습니다! 알아듣기 쉽게 설명해주셔서 감사합니다!

  13. 정리이 내용이 공부에 도움되셨으면 좋겠습니다.
    방문해 주셔서 감사합니다.

  14. Blog Icon
    zz

    현재 네트워크 프로그래밍 수강하고 있는 컴공생입니다. 글 정말 많이 도움되었습니다!! 감사합니다.

  15. 도움되셨다니 다행이네요.
    방문해 주셔서 감사합니다.

  16. Blog Icon
    J

    좋은 글 아주 잘 읽었습니다. 답글을 잘 적어주시는 것 같아 저도 한가지 질문을 드리려고 합니다! 클라이언트에서 소켓 요청은 한번에 여러번 가능한가요? 가능하다면 한번에 하나씩 처리가 되는건가요 아니면 한번에 같이 처리가 되는건가요? 예를 들어 포트 124번이랑 245번을 요청하게 되면 어떻게 되나요?! 감사합니다.

  17. 한번에 같이 처리되지는 않고요.
    시스템에 요청이 쌓인 순서대로 처리됩니다.
    기본적으로 소켓 요청은 queue에 쌓이기 때문에, FIFO(First-In-First-Out) 방식으로 처리됩니다.

    감사합니다.

  18. Blog Icon
    sein

    2.2의 그림에도 있드시 저는 서버와 클라이언트 둘다 소켓에 read와 write를 한다고 이해했습니다.
    근데 제가 지금 공부하는 daytime c코드를 보면 서버에는 write코드만 있고 read코드는 없고
    클라이언트에는 read코드만 있고 write코드는 없던데 이유가 궁금합니다 ㅜㅜ

  19. 공부하고 계시는 daytime c 코드에서 왜 그렇게 만들었는지는 제가 정확히 알지는 못하지만...

    아마도 확인하신 예제에서는,
    단순히 서버에서는 데이터 송신만 하고, 클라이언트에서는 수신만 하도록 코딩되어 있지 않나... 생각이 드네요.

    뭐.. 어쨌든 다른 사람이 만들어놓은 예제를... 왜 그렇게 만들었는지 그 이유를 제가 알 수는 없죠. ^^;;;

    공부 열심히 하세요~

    감사합니다.

  20. Blog Icon
    이준형

    한가지 여쭤 볼게 있습니다
    네트워크를 처음 접하는 사람들을 위한 책이 있을까요?

  21. "네트워크 관련 책"을 본 적이 없어서, 추천드릴 수가 없네요.

    저의 경우에는, 네트워크 장비를 만들면서 경험했던 것들이 지식 축적에 많이 도움되었습니다.

    현장 경험 또는 실무를 통해 쌓은 내용들은 RFC 문서를 통해 이론적 배경을 마련했고요.

    처음 접근이 낯설지도 모르지만, RFC 문서를 보는 것도 괜찮은 것 같습니다.

    감사합니다.

  22. Blog Icon
    나그네

    지나가다 본 글인데 정말 가독성 좋게 잘 쓰여있네요 잘 보고갑니다.

  23. 칭찬 댓글 남겨주셔서 감사합니다.
    더 좋은 내용들 담을 수 있도록 노력하겠습니다.

    감사합니다.

  24. 방문해 주셔서 감사합니다.

  25. Blog Icon
    창맨

    안녕하세요, 글너무 잘봤습니다 ㅠㅠ!
    질문이 있습니다!!

    ---

    3.2 연결 요청. (connect())
    connect() API는 "IP주소"와 "포트 번호"로 식별되는 대상(Target)으로 연결 요청을 보냅니다.



    connect() API는 블럭(Block) 방식으로 동작합니다. 즉, 연결 요청에 대한 결과(성공, 거절, 시간 초과 등)가 결정되기 전에는 connect()의 실행이 끝나지 않는다는 것이죠. 그러므로 connect() API가 실행되지마자 실행 결과와 관계없이 무조건 바로 리턴될 것이라 가정해선 안됩니다.

    ---

    connect() 으로 클라이언트가 서버로 연결요청을 보낼때 어떻게 서버소켓 IP와 Port를 인식하는건가요? 코드레벨에서 지정을해주는지 아니면 동적으로 connect()이 서버 소켓을 찾는방법이 있는지 궁금합니다 ㅠㅠ

  26. 클라이언트에서 서버로 연결 요청을 보내기 위해 사용하는 connect()함수에는 대상 서버의 주소 정보를 지정하게 되어 있습니다.

    서버 소켓을 찾는다... 라는 개념은 아니고요.
    클라이언트가 연결하고자 하는 서버의 주소(IP, PORT)를 connect() 함수 호출 시 지정해줘야 합니다.

    감사합니다.

  27. Blog Icon
    123

    이번에 채팅 프로그램을 구현하기 위해 웹소캣에 관해 알아보던중 .. 소캣통신까지 올라오게 됬는데용

    와 설명과 그림 모두 너무 좋네요 ~ 정말 가독성도 좋고 쉽게 잘 이해할수 있었습니다~

    감사합니당.복받으세용

  28. 도움이 되셨다니, 다행이네요.
    격려의 댓글 남겨주셔서 감사합니다.

  29. Blog Icon
    빠끄

    ip는 cmd에서 ipconfig로 알았는데 port 번호는 어떻게 알아야하나요? 그냥 임의로 지정해주면되나요?

  30. port는 소켓이 만들어지고나서 특정 port를 bind하거나, 시스템에 의해 할당됩니다.

    통상적으로 디버그 메시지를 출력하여 port 번호를 확인하는데요. 어떤 이유로 port 번호를 확인하고자 하는지 적어주시면 도움드리기가 더 쉬울 것 같습니다.

    감사합니다.

  31. 회사에서 소스만 보고 이해도 못하고 멍 때리고 있었는데 작성해 주신 글보고 소스를 다시 보니 안보이는게 보이네요. 너무 감사합니다~~~ :)

  32. 도움이 되신 것 같아 다행이네요.
    방문해 주셔서 감사합니다.

  33. Blog Icon
    fluit

    안녕하세요. 포스팅 쉽게 설명해주셔서 너무 잘 읽었습니다.
    저도 질문이 있어서 글을 남겨보아요.
    본문에서
    "connect() API는 블럭(Block) 방식으로 동작합니다. 즉, 연결 요청에 대한 결과(성공, 거절, 시간 초과 등)가 결정되기 전에는 connect()의 실행이 끝나지 않는다는 것이죠."
    이 부분이 있는데, 제가 가지고 있는 소스코드에서는 connect()를 호출하자마자 바로 반환하더군요.
    서버측에서 연결 요청을 받는 곳에 중단점을 걸어놔도요. 본문 말씀이 맞다면, 중단점을 걸어놓으면 영원히 connect가 반환되지 말아야 하지 않나요.?

    혹시, connect()를 블럭킹 방식으로 호출하는 방법, 논블럭킹 방식으로 호출하는 방법이 따로 구분되어 있는 건가요?

  34. connect() 실행이 리턴되는 시점은 서버 응답(또는 네트워크 상태)에 의해 연결 요청 결과가 결정되는 시점입니다.

    connect()를 호출하자마자 바로 반환되었다면, 연결 요청 결과가 바로 결정되었다는 것을 의미하죠. 즉, 서버에 의해 연결이 수락(established)되었을 수도 있고, 연결이 거부(refused)되었을 수도 있습니다. 그 결정이 이미 이루어졌다는 것이죠.

    서버에서 breakpoint를 어디에 잡아서 확인하셨는지 모르겠지만, breakpoint를 잡았다고 하더라도 클라이언트는 이미 connect() 연결에 대한 응답을 받은 후가 될 수 있습니다.

    TCP 연결 과정은 실질적으로 OS(네트워크 모듈)에서 처리되기 때문에, listen() 함수로 OS에 port를 binding하고 대기하고 있다면, 클라이언트 연결 요청에 바로 응답을 보내게 되어 있습니다.
    즉, 문의 하신 내용이 잘못된 내용이 아니며, connect()를 호출하자마자 바로 반환되는 것이 맞습니다.

    본문의 내용이 클라이언트와 서버 연결 과정에서 API 호출 위주의 실행 관점으로 설명이 되어 있다보니, 문의하신 내용 처럼 API 내부의 상세 동작까지는 설명이 부족한 점이 있네요.
    소켓에 대한 조금 더 깊이 있는 이해를 원하시는 분들께는, 본문의 내용이 오히려 잘못된 정보를 전달할 수 있을 것 같습니다.
    본문의 내용 수정이 필요할 것 같습니다.

    Blocking과 Non-blocking은 일단 API 사용 순서에 있어서는 크게 다를 것은 없습니다.
    하지만 Non-blocking의 경우 소켓을 non-blocking으로 만들고, 해당 소켓에서 API를 호출하면 함수가 블럭되지 않기 때문에 현재 소켓의 상태 또는 수신되는 데이터가 있는지를 계속적으로 polling해서 확인해야 합니다.
    여기서 더 설명하기는 좀 힘들 것 같고요.
    non-blocking 소켓 관련 자료를 찾아보시면, 예제 코드는 어렵지 않게 찾으실 수 있습니다.

    답변이 도움되셨으면 좋겠습니다.

    감사합니다.

  35. 서버쪽에서 listen()함수로 대기하고 있다면 클라이언트 쪽의 connect()함수는 바로 리턴하는게 맞군요. 저는 서버측에서 accept()하는 시점이 connect()함수가 리턴하는 시점인줄 알았습니다. 그래서 break point도 accept() 함수 바로 이전코드에 걸어 놨었습니다. 정성스런 답변 감사드립니다.

  36. 도움되셨으면 좋겠습니다.
    좀 더 정확하고 알찬 내용 담을 수 있도록 노력하겠습니다.

    감사합니다.

  37. 정말 너무너무 유익한 정보 감사드립니다 :) 좋은 공부가 되었어요! 저도 소켓에 대해 글을 쓰려하는데 레퍼런스로 넣어도 괜찮을까요?

  38. 네. 링크를 남기는 것은 얼마나 환영합니다.
    감사합니다.

  39. Blog Icon
    mizzicco

    좋은 정보 감사합니다.

  40. 방문해 주셔서 감사합니다.

  41. Blog Icon
    지니

    유익한 글 너무 감사합니다 :-)
    궁금한 점이 있는데, 서버 소캣은 자신의 로컬 IP에 포트번호를 bind해서 연결을 기다리는데, 이때 서버소캣이 AP에 접속할때마다 IP가 바뀐다고 하면, 클라이언트는 매번 새로운 소캣의 IP주소 + 포트번호를 찾아내서 connect를 시도해야 하는거죠??

  42. 답글이 늦어서 죄송합니다.
    건강 문제로 인해 블로그에 신경을 못 썼네요.

    질문해주신 내용대로,
    클라이언트는 변경된 서버 IP를 알아내어서 connect를 시도해야 합니다.
    정확히 이해하고 계신 것으로 보이네요.

    또 궁금한 내용 있으면 질문글 남겨주세요.

    감사합니다.