[소켓의 종료]
소켓의 종료는 close함수와 shutdown함수가 있다.
close함수의 인자로 생성된 소켓의 번호(socket descriptor)를 전달한다.
close로는 소켓을 생성하면 생기는 송신채널과 HostA와 HostB모두에서 수신채널이 동시에 끊어지게 된다.
그런데 shutdown함수는 송신과 수신채널중 하나만 골라서 선택적으로 끊을 수 있다.
그래서 인자로 소켓에 대한 고유 번호와 어떤 채널을 닫을것인지도 보낸다. SHUT_RD, SHUTWR, SHUT_RDWR중 고르면 된다.
[소켓을 이용한 자료의 송수신]
지난 시간에 소켓을 이용해 송수신을 위해 read와 write을 썼다.
자료를 수신하는 read함수는 파일을 읽는데도 쓰이지만 소켓을 통해 자료를 읽기 위해서도 동일한 방법으로 쓰인다.
인자는 세개이며 반환값으로 int값을 반환한다. 첫번째 인자인 소켓에서 buf로 최대 count개수만큼의 자료를 읽어오게되고 실제 읽어온 값의 크기를 반환할 것이다. count크기일 수도 있고 그보다도 작을수도 있고.
우선 socket으로 소켓을 생성하면 비어있는 Descriptor Table을 통해서 소켓 채널(전화국)이 하나 생성된다. 그럼 실제로 커널 내에 각각 송신과 수신이 가능한 Send Buffer와 Receive Buffer가 생성된다. 버퍼는 우체통으로 비유할 수 있다.
read하면 우체통에서 우편물을 꺼내오는 것이다. 우편물은 TCP나 UDP같은 우편 배달부들이 원격지에서 편지를 가져다가 우체통에 넣는 것이다. 그럼 read로 우체통에서 우편물을 가져온다.
①read(s,buffer,2); 라 하면 원격지에서 가져오는것이 아니라 이미 소켓 s를 통해 들어온 원격지에서 가져온 자료가 있는 Receive Buffer(OS-커널에서 넣어준 버퍼)에서 인자이자 사용자 공간에 있는 buffer에 앞의 2byte를 복사해 읽어오는 것이다.
②그리고 만약에 수신버퍼에 원격지에서 도착한 데이터가 없다면 자료가 올 때까지 기다린다. 커널내에서 블로킹되어 있다가 자료가 오면 데이터를 가져오는 것이다.
③실제 리턴값은 수신버퍼에서 응용프로그램의 버퍼에 복사한 자료의 수를 반환하는 것이다.
예를 들어 2byte를 읽어오도록 요청했는데 1byte만 읽어온다면 1을 반환한다.
👉개념적으로 원격지에 있는 데이터가 사용자 공간에 있는 버퍼에 복사되어 오는 것이지만 실제 과정을 보면 TCP나 UDP가 원격지로부터 가져다가 수신버퍼에 넣어두면 read를 통해 수신버퍼에서 가져오는 것이다.
write함수 또한 read함수의 과정과 비슷하게 설명할 수 있다.
사용자 공간(buf)에 있는 내가 쓴 편지를 소켓으로(fd) 최대 count만큼 보낸다. 그러면 실제로 보낸만큼의 크기가 반환된다. read와 마찬가지로 편지를 보낼 때 그림처럼 두 개의 △△를 보내는데 바로 원격지까지 날아가는 것이 아니라 커널에서 만든 송신버퍼를 통해 이동하여 날아간다.
① write(s,buffer,2); 라 하면 응용프로그램의 buffer에 있는 것을 읽어다가 소켓 s로 만든 송신버퍼에다가 2byte를 복사한다. 그러면 송신버퍼에서 TCP나 UDP로 원격지에 있는 우편함에 보내지게 된다.
② 만약에 송신버퍼가 꽉 차있어서 더이상 응용프로그램의 buffer에서 복사해 저장할 공간이 없으면 공간이 생길 때까지 기다리게 된다. (블로킹) 그리고 공간이 생기면 복사하고 복사한 크기를 반환하게된다.
③ 반환값은 커널의 송신버퍼로 복사한 자료의 바이트 수이다.
[응용]
소켓의 생성과 종료, 송신과 수신을 확인하기 위해 간단한 응용프로그램을 만들어보자.
그리고 그 전에 앞서 socketpair함수를 살펴보자.
socketpair는 두 개의 소켓을 동시에 만들어 주는 함수이다.
1,2장의 프로그램에서 봤듯이 실제론 두 개의 호스트가 필요하다. 왼쪽을 Host A, 오른쪽을 Host B라고 하자.
Host A에서 소켓을 만들면 디스크립터 테이블은 커널에 생성된 소켓에 연결되어있다. Host A입장에서 원격지인 Host B도 소켓을 만들고 디스크립터 테이블이 연결되어 있으며 커널에 송수신 버퍼가 있을 것이다. 이 소켓들은 각 컴퓨터 내에서 포트를 통해 인터넷에 연결되어 있고 인터넷망으로 두 호스트가 연결되어 있다.
따라서 자료를 주는 write함수를 통해 SendBuffer에 데이터가 들어가고 TCP나 UDP라는 우편배달부가 연결되어 있는 네트워크를 통해서 원격의 ReceiveBuffer에 집어 넣고 read함수를 통해서 이를 가져가게 된다.
이런 복잡한 과정을 생략하면서 socketpair로 소켓을 생성하고 자료를 주고받는 기능에 집중해보자.
socketpair함수는 두개의 소켓을 동시에 생성하는 함수이다.
socketpair(PF_LOCAL, SOCK_STREAM, 0, sd);
를 보면 인자에 순서대로 도메인, 타입, ?, int 타입의 크기가 2인 배열이 네 번째 인자로 들어간다.
두 개의 소켓이 생성되면 들어갈 두 개의 디스크립터 테이블의 공간을 전달하는 것이다.
socketpair함수로 두 개의 소켓이 생성되고 별도의 처리 없이 두 소켓이 소켓디스크립터에 자동으로 연결된다.
두 개의 소켓에 각각 SendBuffer와 ReceiveBuffer가 생긴다. 따라서 이 응용프로그램에서 데이터를 보내기 위해 sd[0]에 쓰면 sd[0]의 SendBuffer에 복사가 되고 TCP나 UDP를 통해 원격지까지 알아서 보내게 된다. sd[1]에 하면 마찬가지로 sd[1]의 소켓으로 쓰면 이에 생성된 SendBuffer에 적게된다.
그런데 첫번째 인자로 로컬로 하게되면 자신한테 온다. 이전의 프로그램들과 다르게 두개의 응용프로그램으로 로컬내에서 각각 HostA, HostB인척 해야했던 반면 이번엔 하나의 응용프로그램만으로 안에서
[socketpair를 이용한 프로그램 작성하기]
응용프로그램에서 수신용으로 쓸 버퍼 buf와 보낼 데이터인 data[]를 둔 다음에 socketpair를 쓰고 있다. socketpair는 전화기 두 개를 만드는 것과 마찬가지로 두개를 만든다음 각각의 전화기는 연결되며 sd[0]와 sd[1]에 저장된다.
이 프로그램은 sd[0]에서 sd[1]로 데이터를 보내는 프로그램이다. line14에서 sd[0]에 data에 써있던 "This is from sd[0]" 를 쓰게 된다. (=우편함에 넣는다. sd[0]는 소켓이므로 소켓의 송신 버퍼에 넣는 것이다.) 그럼 sd[0]에서 sd[1]의 수신버퍼에 전달이 되고 line17에서 sd[1]의 수신버퍼에서 read로 buf에 읽어간다.
처음에 설정하지 않았어도 socketpair로 소켓을 생성했으므로 두 소켓은 연결 되어 있는 것이다.
프로그램이 동작하는 방법, 소켓을 통해 데이터를 주고받는 절차는 아래와 같다.
socketpair로 두 개의 소켓을 만들고 두 개의 우편함(커널의 송신,수신버퍼들)이 생기고 descriptor table에 우편함이 가리키도록 되어 있다. 이 상태에서 ①우선 write로 sd[0]에 data를 보내는데 sd[0]이 가리키고 있는 송신버퍼에 data의 내용을 복사하는 것이다. ②복사만 해놓으면 TCP나 UDP는 sd[1]의 수신버퍼에 데이터를 전달한다. ③read로 sd[1]의 수신버퍼에 있는 내용을 응용프로그램의 buf로 읽어오고 출력한다.
socketpair는 하나의 컴퓨터 안에서만 동작하는 한계가 있다. 그러나 read와 write의 개념을 잘 보여준다.
이렇게 하나의 터미널 내에서 테스트 해본 결과.
'[TCP,IP]' 카테고리의 다른 글
TCP/IP 소켓 프로그래밍 실습환경 설정 (0) | 2022.03.28 |
---|