logo

English

이곳의 프로그래밍관련 정보와 소스는 마음대로 활용하셔도 좋습니다. 다만 쓰시기 전에 통보 정도는 해주시는 것이 예의 일것 같습니다. 질문이나 오류 수정은 siseong@gmail.com 으로 주세요. 감사합니다.

iOS - BSD Socket 네트워크 프로그래밍

by digipine posted Nov 01, 2017
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print

iOS의 네트워킹은 여러단계의 레이어를 가지고 있습니다. 가장 하위에는 BSD Socket 이 자리잡고 있으면 이 위로 레이어들이 있는데 물론 레이어가 올라갈 수록 추상화의 정도도 높아집니다.

BSD Socket 의 바로 위에는 CFNetwork 계층이 있습니다. 지난번에 올렸던 NSStream 이나 NSURL 은 이보다 더 상위계층이며 최상위 계층에는 Web Kit이 자리잡고 있습니다.

지난번에는 NSStream 을 이용한 네트워킹 예제를 보여드렸는데, 이번에는 최하위 계층인 BSD Socket 에 대해 잠깐 아는데까지만 언급해보려 합니다.

BSD Socket 은 워낙 자료들도 풍부하고 해서 굳이 제가 더 정리할 필요가 없을 수도 있지만, 네트워크 부분은 소소한 것일지라도 자료로 남겨두는 게 좋다는 생각이 들어서 올려보려고 합니다. 틀린 부분이 있으면 지적해주시기 바랍니다.



BSD Socket 은 먼저 소켓을 생성하는 것으로 시작합니다. bsd 소켓을 사용하려면 다음의 헤더를 포함해야 합니다.


#import <sys/types.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <netdb.h>


int sockfd;

if ( (sockfd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
    NSLog(@"Error creaing socket");
}


sockfd 는 파일디스크립터입니다. 파일 뿐만 아니라 네트웍 I/O 에서도 read, write 등을 동일하게 사용할 수 있습니다.
socket() 명령을 통해 소켓을 생성하는데 AF_INET 은 TCP, UDP 등을 통한 인터넷 접속을 의미합니다. SOCK_STREAM 은 socket stream 을 뜻하는데, 이는 연결지향 접속을 만들겠다는 의미입니다. 연결지향이란 말은, 마지 두 대의 전화기가 서로 연결되서 음성을 주고 받을 수 있는 상태가 되는 것을 의미합니다. 이 상태에서는 서로 대화를 이해하고 주고 받을 수 있으며 대화의 순서도 정확하고 내용도 누락되지 않습니다. 이와 다른 개념으로 datagram 이란 것이 있습니다. datagram 은 마치 편지와 같습니다. 보내는 사람이 발신처와 수신처를 적어서 발송만 합니다. 먼저 보낸 편지가 먼저 간다는 보장도 없고 중간에 편지가 배달사고로 누락되지 않는다는 법도 없습니다. stream 에 비해 이런 단점이 있지만, 그만큼 처리 속도는 빠릅니다 (수신확인을 안하니깐). datagram 기반의 소켓을 만들려면 SOCK_STREAM 대신 SOCK_DGRAM 을 설정해주면 됩니다. 보통 stream 은 TCP 네트워킹에, datagram 은 UDP 네트워킹에 사용됩니다. 여기서는 stream 만을 예제로 하겠습니다.

소켓이 생성되었으면 연결할 곳의 ip address 와 port 를 정해주어야 합니다.


struct sockaddr_in serverAddress;
bzero( &serverAddress, sizeof(serverAddress) );
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons( 1234 );


sockaddr_in 구조체는 연결에 필요한 정보를 담을 수 있는 구조로 되어 있습니다. 먼저 bzero 함수를 이용해서 구조체변수를 0으로 초기화 시킨 다음 구조체 내의 sin_family 를 AF_INET 으로 지정합니다. 이로서 인터넷 어드레스를 사용하겠다는 설정이 됩니다. 구조체 내의 sin_port 에는 접속하고자 하는 포트를 적는데, 주의해야 할 것은 htons 를 통해서 호스트 정수를 네트워크 정수로 변환해줘야 합니다. 이 과정이 필요한 이유는 기계마다 메모리에서 정수를 다루는 규칙이 다르기 때문입니다. intel 계열의 cpu 는 short 정수를 다룰때 2바이트(16비트)의 메모리를 사용하며 가장 우선적으로 오는 비트가 가장 낮은 수 부분으로 계산됩니다. 즉 1은 2진수로 풀이하면 10000000 00000000 로 표현되고 2는 01000000 00000000 , 3은 11000000 00000000 으로 표현이 됩니다. 이러한 수 체계를 리틀엔디언이라고 합니다. 반면 어떤 기계는 빅 엔디안이라고 해서 1은 00000000 00000001, 2는 0000000 00000010, 3은 00000000 00000011 이런 식으로 표현이 됩니다. 둘다 1이지만 빅엔디언이냐 리틀 엔디언이냐에 따라서 메모리의 내용은 달라집니다. 이러한 숫자들을 컴퓨터간에 통일시키지 않고 전송하면 대혼란이 올 것이 자명하므로 네트워크에서의 숫자는 한가지 방식으로 통일됩니다. 기계의 숫자를 다른 기계로 보내기 위해서는 통일된 네트워크 숫자로 변환할 필요가 있는데 이를 수행하는 함수가 htons (host to network short), htonl (host to network long) 등입니다. 반면 네트워크에서 날아온 숫자를 내 컴퓨터에서 사용하기 위해서는 ntohs , ntohl 등의 변환을 해줘야 합니다.

struct hostent *hp;
hp = gethostbyname("naver.com");
if(hp == 0) {
    NSLog(@"error getting host address");
}
memcpy(&serverAddress.sin_addr, hp->h_addr, hp->h_length);


gethostbyname 은 url 에서 address 를 얻기 위한 함수라고 보시면 됩니다. 여기서 얻어진 결과를 sockaddr_in 구조체 속의 sin_addr 에 복사를 해줍니다. 여기까지 하고 나면 연결할 준비는 완료입니다.

if ( connect( sockfd, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0 )

    NSLog(@"connect error");
}


이렇게 해서 서버에 접속하게 됩니다.
그 이후로는 sockfd 를 이용해서 read, write 혹은 recv, send 를 이용해서 데이터를 주고 받을 수 있습니다.


char buffer[100];
sprintf(buffer,"GET / HTTP/1.0\r\n\r\n");
write(sockfd, buffer, strlen(buffer));
read(sockfd, buffer, 100);
NSLog(@"%s",buffer);




대략적인 접속 시나리오를 정리하면 아래와 같습니다.
socket() 을 통해 소켓 생성
connect() 를 통해 접속
read() 혹은 recv() 를 이용한 데이터 읽기
write() 혹은 send() 를 이용한 데이터 보내기

(참고로 서버쪽 시나리오는 대략 아래와 같습니다.)
socket() 을 통해 소켓 생성
bind() 를 통해 port 와 주소를 설정하고 리스닝 준비
listen() 를 통해 접속 기다림
accept() 를 통해 client socket 획득
write, read, send, recv 등을 이용해서 client socket 으로 클라이언트와 통신


소 켓은 명쾌하고 단순한 구조를 가진 대신 개발자가 신경써야 할 부분이 많습니다. 제일 대표적인 부분으로는 read 나 recv 가 블록킹 상태가 되기 때문에 더이상 프로그램 진행이 되지 않습니다. 이 부분을 해결하기 위해서는 read 부분을 별도의 쓰레드로 만들거나 read 를 논블록킹으로 만들거나 select 등을 이용해서 논블록 멀티 I/O 를 수행해야 합니다. 이런 것들을 일일이 프로그래머가 신경써야 한다는 것도 쉽지 않은 일이죠. 그래서 Cocoa 는 이 BSD Socket 을 한단계 추상화하고 편의성을 추가해서 Framework 속에 집어넣습니다. 이 계층이 CFNetwork 입니다.

TAG •