컴퓨터(Computer Science)/네트워크, Network

소켓 프로그래밍, Socket Programming

게임이 더 좋아 2022. 9. 3. 20:36
반응형
728x170

 

소켓.. 전구 소켓이 아니라 다른 소켓이다.

의미는 같지만 가리키는 것이 다르다.

socket은 콘센트와 같은 것을 말하는데 연결되면 전기가 공급되듯이

네트워크에서는 수많은 bit가 흘러들어온다.

알아보자

 


 

소켓은 크게 2종류가 있다.

종류를 정하는 것은 Protocol인데

여기선 L4이다.

TCP와 UDP이다.

TCP는 TCP/IP 라고도 부르는데 하나로 묶여서 쓰이기 때문이다.

 

즉, 소켓을 구성하는데는 수많은 규약들이 정해져있다.

OS의 이해도 필요하고 Protocol의 이해도 필요하고 데이터를 주고 받는 방식, 바이트를 읽는 방식 등 모든 규약을 정해야 정상적인 통신이 가능해진다.

또한 네트워크인만큼 예외상황도 많이 발생한다. 정말 힘들다.

 


 

우선 네트워크는 서버와 클라이언트로 나누어진다.

소켓도 클라이언트와 서버로 나누어진다.

 

즉, 서버와 클라이언트가 통신하는 것이 바로 소켓의 역할이다.

**참고로 역할만 다르지.. 소켓의 종류는 같다. 서버나 클라이언트나 같은 소켓을 쓴다.

 

소켓이 통신하는 프로세스를 보자

 

**여기서 read, write는 send recv랑 같다.

 

 

간단하게 클라이언트부터 살펴보자.

Client Socket을 1)create하고 난 뒤서버 측에 2)connect을 요청한다.

그리고 서버 소켓에서 연결이 받아들여지면 데이터를 3)send/recv 하고, 모든 처리가 완료되면 Socket 4)close 한다.

 

서버는 조금 더 과정이 있다.

Socket을 1)create한다.

 그리고 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 2)bind 시킨다.

 그런 다음 클라이언트로부터 연결 요청이 수신되는지 3)listen한다.

요청이 수신되면 요청을 4)accept 데이터 통신을 위한 소켓을 만든다.

만들어진 새로운 소켓을 통해 연결이 ESTABLISHED되면, 클라이언트와 마찬가지로 데이터를 5)send/recv할 수 있다.

마지막으로 데이터 송수신이 완료되면, Socket을 6)close 한다.

 

이것만 보면 간단해 보인다.

하지만 그 작업 하나하나가 필요한 것들이 꽤나 많다.

 


 

클라이언트의 소켓 프로세스를 더 자세히 알아보자

 

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

먼저 소켓을 생성한다.

소켓을 만들 때 종류를 지정해야 한다.

1. TCP (Stream)

2. UDP (Datagram)

3. 그 외.. 나머지

여기서는 어떤 Protocol을 사용할 지 정한다. 

어떠한 연결 대상과 통신할 지는 정하지 않는다.

 

2 연결 요청, connect()

이제야 어떠한 연결 대상과 통신할 지 정하는 작업이다.

connect는 xxx.xxx.xxx.xxx/3456 과 같이 IP와 포트 번호로 연결 요청을 보낸다.

connect의 작동 방식은 Blocking으로 연결 요청에 대한 처리가 끝날 때까지 다른 작업을 하지 못한다.

 즉, 연결 요청에 대한 결과(성공, 거절, 시간 초과 등)가 있어야 다음 작업을 할 수 있다.

대부분 통신은 비동기로 하는데 비동기와 동기 그리고 block과 non-block은 조금 다르다.

** thread를 생성하곤 한다.

만약 성공하면 이제 데이터를 주고 받을 준비가 된 것이다.

 

3. 데이터 송수신 send() , recv()

데이터를 보낼 때는 send()

데이터를 받을 때는 recv

하지만 connect와 마찬가지로 send()와 recv() API가 모두 block 방식으로 동작한다.

즉, 보내는 것이 완료되고, 받는 것이 완료되어야 다른 작업이 진행 가능하다는 이야기다.

 

하지만 send랑 recv는 조금 다른 이야기인데

send는 내가 보내니까 얼만큼 보내고, 언제 보내는지 알 수 있다.

다만 상대방이 언제 보내는지, 얼만큼 보내는지는 클라이언트가 알 수 없다.

 

그게 뭐 상관있냐고 물을지 모르지만... recv가 block인 만큼..

실행하면 완료될 때까지 작업을 할 수 없다.

 

???? 그럼 어떻게 받아..?

 

그래서 recv는 원래 다른 스레드에서 실행된다.

소켓의 생성과 연결이 완료되면 새로운 스레드가 생겨서 그곳에서 recv를 관리한다.

따라서 recv는 항상 할 수 있다는 말과 같다.

 

이제 통신이 끝났다면??

 

4 소켓 닫기, close()

더 이상 데이터 통신이 필요없게되면 소켓을 닫기 위해 close()를 호출한다.

닫은 다음 아차.. 하고 해봤자 다시 통신하지 못한다.

앞선 과정을 다시 거쳐야 통신이 가능하다.

 


 

서버의 소켓 프로세스도 알아보자

 

 1. 서버 소켓 생성, socket()

클라이언트 소켓과 마찬가지다.

소켓을 생성한다.

클라이언트 소켓과 같은 과정이다.

 

2. 바인딩, bind()

바인드는 뭘 바인딩하는 것일까?

bind()  사용되는 인자는 Socket과 IP Address+Port Number다.

IP address만 있으면 될 것 같지만 포트 번호도 중요하다.

 

이 유튜브에 잘 나온다.

https://www.youtube.com/watch?v=QLvDf3o7BpE

 

포트 번호는 프로세스를 결정할 수 있는 중요한 요소라고 볼 수 있다.

 

IP만 있다면 서버의 수많은 프로세스 중 어느 프로세스에 데이터를 전달해야 할 것인지 모를것이다.

때문에 OS에서는  중복된 포트 번호를 사용하지 않도록, 내부적으로 포트 번호와 소켓 연결 정보를 관리한다.

bind가 되지 않는다면 중복된 포트 번호를 써서 제대로 통신할 수 없다고 봐도 된다.

일반적으로 서버 소켓은 고정된 포트 번호를 사용한다.

그리고 그 포트 번호로 클라이언트의 연결 요청을 받아들인다.

 

3 클라이언트 연결 요청 대기, listen()

 Socket에 IP 주소+포트 번호 를 bind하고 나면, Socket을 통해 클라이언트의 연결 요청을 받는다.

다시 말해서 클라이언트에 의한 연결 요청이 수신될 때까지 기다린다.

 

listen()  Socket에 바인딩된 포트 번호로 클라이언트의 연결 요청이 있는지 확인하며 대기 상태로 있는다. 

요청이 수신되면, 그 때 대기상태를 멈추게 된다.

 

하지만 요청이 없더라도 대기 상태를 멈추는 경우가 있는데

먼저 클라이언트 요청이 수신되는 경우와, 에러가 발생하면 그렇다.

만약 listen API가 성공해도 리턴 값에 클라이언트의 요청에 대한 정보는 들어있지 않다.

왜냐하면 아직 연결된 것이 아니기 때문에 굳이 데이터를 담지 않은 것이다. 

listen은 그저 클라이언트 연결 요청이 수신되었는지(SUCCESS), 그렇지 않고 에러가 발생했는지(FAIL) 만 판단한다.

하지만 클라이언트가 연결되었을 경우 정보를 써야 하므로 연결 요청에 대한 정보는 시스템 내부적으로 관리되는 Queue로 관리한다.

 

 

4 클라이언트 연결 수립, accept()

실질적인 Connection을 수립하려면 accept가 되어야 한다.

연결 요청을 accept 하면 소켓 간 연결을 establish 한다.

하지만 여기서 연결되는 소켓은 앞서 만든 소켓과 다른 소켓이다.

??? 엥 그럼 그 소켓은 언제 만든거야????

recv에서 스레드로 소켓을 하나 더 만든 것 기억하나??

이것도 마찬가지다.

충분한 통신이 되려면 소켓이 하나 더 있어야 한다.

바로 accept 내부에서 새로 만들어지는 Socket이다.

 

서버 소켓은 그저 연결 요청용인 것이다.

Server Socket의 핵심 역할은 클라이언트의 연결 요청을 수신하는 것이다.

accept() 에서, 데이터 송수신을 위한 새로운 Socket을 만들고 서버 소켓의 대기 Queue 의 연결 요청을 매핑시킨다.

그렇게 되면 이제 1개의 연결 요청을 처리하기 위한 소켓의 역할이 끝난 것이다.

=> 서버 입장에서는 여러 개의 클라이언트가 있기 때문이다.

=> 그래서 accept할 때 소켓을 새로 만드는 것이다.

그 소켓은 이제 다시 재활용해서 또 다른 연결 요청을 처리하기 위해 다시 listen)하거나 Socket을 close 하면 된다.

 

5. 데이터 송수신, send()  recv()

6. 소켓 연결 종료, close()

앞서 클라이언트에서의 소켓 프로세스와 같다.

 


 

이로써 소켓의 단순한 설명은 끝났다.

이것을 어떻게 구현하냐가 이제 프로그래머에게 남겨진 과제다.

아래 링크에 C++ 소켓프로그래밍이 있다. 시간이 나면 보자

https://www.geeksforgeeks.org/socket-programming-cc/

 

728x90
반응형
그리드형