Socket에 관하여

2020. 8. 5. 00:52

네트워크 프로그래밍 분야에서 소켓은 네트워크의 양 끝단을 추상화 시킨 개념이고, 컴퓨터 관점에서는 컴퓨터 외부와 컴퓨터 내부의 프로그램을 이어주는 인터페이스이다. 단순히 프로그램의 내부와 외부를 잇는 표준 입출력과는 달리 소켓은 네트워크의 반대편이 어디인지에 대한 정보를 가지고 있다.

 

PYTHON의 Socket 모듈은 소켓 프로그래밍에 필요한 시스템 콜을 래핑하는 API를 제공하는 모듈이다. 소켓 통신을 위해서 소켓을 생성해 사용하지만, 서버일 때와 클라이언트일 때가 약간 다르다.

 

소켓 생성하기

socket.socket() 함수를 이용해 소켓 객체를 생성할 수 있다. 이 함수는 두 가지 인자를 받는데, 하나는 family이고 다른 하나는 type이다.

 

- 첫 번째 인자는 family이다. Socket에서 family란 네트워크 반대편으로의 주소체계가 어떻게 되어있는지에 관한 것으로 흔히 AF_INET(IPv4에 사용)이나 AF_INET6(IPv6에 사용)를 많이 쓴다. 각각 socket.AF_INET, socket.AF_INET6로 정의되어있다.

- 두 번째 인자는 type이다. raw, stream, datagram Socket 등이 있는데, 보통 많이 쓰이는 것은 socket.SOCK_STREAM 또는 socket.SOCK_DGRAM이다.

 

보통의 조합으로는 socket.AF_INET, socket.SOCK_STREAM이 사용되며, 이는 socket.socket() 함수의 family, type의 디폴트 값이다. 따라서 socket.socket(socket.AF_INET, socket.SOCK_STREAM) == socket.socket()이 성립한다고 볼 수 있다.

 

이렇게 생성된 Socket을 사용하기 위해서는 Socket을 포트에 맵핑하고 상대측 포트에 연결하는 과정이 필요하다. 이 과정에 대해 알아보자.

 

바인드와 리스닝

Socket 통신에서 서버는 보통 최초의 수신자가 되는 노드이다. 따라서 서버는 Socket을 만들고 포트에 맵핑한 뒤 클리이언트가 접속하길 기다린다. 서버가 Socket을 포트에 맵핑하는 것을 바인딩이라하며 이는 생성된 Socket 객체에 대해 sock.bind() 함수를 호출한다. bind() 호출 시에는 호스트이름과 포트번호를 튜플로 감싸 전달한다.

 

바인드가 완료된 후 수행하는 것은 리스닝이다. 이는 Socket에 대해 listen() 함수를 호출해 수행한다. 이 함수는 클라이언트가 바인드 된 포트로 연결할 때까지 기다리는 blocking 함수이다. 클라이언트로부터 연결 요청이 들어오게되면 리턴하게 된다. 따라서 이 연결을 받아들이기 위한 accept() 함수를 호출하는 부분이 다음 행에 주로 온다.

 

accept()는 (Socket, addr)로 구성되는 튜플을 리턴한다. 이때의 소켓은 처음 생성한 Socket과는 별개의 객체로 클라이언트와 연결이 구성되어 실제로 데이터를 주고 받을 수 있는 창구가 된다. 이 Socket은 연결이 들어와 listen(), accept()가 호출될 때마다 생성될 수 있어 연결이 구성된 Socket을 멀티스레드로 처리한다면 1:N의 연결도 처리 가능하다.

 

데이터 주고 받기

Socket으로 부터 데이터를 읽을 때는 sock.recv()를, 데이터를 보낼 때는 sock.sendall()을 사용한다. 데이터를 읽어들일 때는 버퍼의 크기를 전달해야한다. sock.recv(bufsize)는 최대 bufsize만큼의 데이터를 읽어온다. 만약 데이터가 없다면 상대방이 보내줄 때까지 기다린다.

 

읽어들인 데이터는 bytes 타입의 바이트 시퀀스이며, 데이터를 보낼 때에도 바이트 시퀀스를 보내야한다. 만약 문자열을 주고 받고 싶다면 해당 문자열은 encoding/decoding해서 전달해야한다.

 

Socket 닫기

Java에서 Scanner를 열고 닫는 것처럼, C, Python, Java 등등에서 파일을 열었다면 다시 닫는 것처럼, SSS에서 db에 연결한 뒤 다시 연결을 끝는 것처럼 Socket도 열었다면 안전하게 닫아야한다.

 

서버와 클라이언트 모두 Socket을 닫아야하며, 이미 닫혀있는 Socket에서 데이터를 받으려하거나 보내려고 한다면 에러가 뜬다. Socket을 닫을 때는 sock.close() 함수를 사용한다.

Socket 객체는 컨텍스트 메니저 프로토콜을 지원하므로 with 구문과 함께 사용하면 안전하게 닫히는 것을 보장할 수 있다.

 

클라이언트가 서버에 연결하기

이때는 바인드나 리스닝의 과정이 필요없다. 클라이언트가 능동적으로 서버에 연결하며, 연결된 소켓으로 항상 1:1 통신을 하기 때문이다.

 

연결은 sock.connect() 함수를 사용하며, 이 때의 인자는 bind()와 동일하다.

이 함수는 연결되면 리턴하며, 리턴 값은 없다. 따라서 클라이언트는 최초 생성한 Socket을 통신에 사용하면 된다.

 

Socket에 관한 간단한 예제들은 아래의 출처 사이트를 확인하면 된다.

 

출처 : https://soooprmx.com/archives/8737