logo

English

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

iOS,OSX - CFSocket 사용법

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
CFSocket 은 BSD Socket 과 유사합니다. BSD Socket 에서 사용하는 sockaddr_in 을 사용하는 점도 동일합니다. BSD Socket 보다 편리한 점은 Socket 으로 데이터가 들어올 경우를 별도로 프로그래밍해주지 않아도 이것을 Callback 으로 처리해 줄 수 있도록 설계되어 있다는 점입니다.
 
먼저 아래 4개의 헤더를 포함시킵니다.
#import <sys/types.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <netdb.h>
 
Socket 의 생성은 다음과 같습니다.
// Network connection
ref = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, 0, 
                     kCFSocketReadCallBack|kCFSocketDataCallBack|kCFSocketConnectCallBack|kCFSocketWriteCallBack,
                     CFSockCallBack, NULL);
 
첫번째 파라미터는 오브젝트 생성을 위해 메모리할당을 해줄 할당자를 지정하는 곳입니다. kCFAllocatorDefault 혹은 0 을 설정하면 디폴트 할당자가 그 일을 맡습니다. PF_INET 은 인터넷 프로토콜, SOCK_STREAM 은 스트리밍 소켓타입을 의미합니다. 4 번째 항목은 프로토콜인데 0 을 설정할 경우 3번째 항목이 SOCK_STREAM 이라면 IPROTO_TCP, SOCK_DGRAM 이라면 IPROTO_UDP 로 자동설정이 됩니다. 물론 IPROTO_TCP 라고 명시적으로 써주셔도 됩니다. 5번째 항목이 Callback 조건을 지정해주는 것인데 콜백 타입은 아래와 같이 정의되어 있습니다.
 
enum CFSocketCallBackType {
   kCFSocketNoCallBack = 0,
   kCFSocketReadCallBack = 1,
   kCFSocketAcceptCallBack = 2,
   kCFSocketDataCallBack = 3,
   kCFSocketConnectCallBack = 4,
   kCFSocketWriteCallBack = 8
};
 
클라이언트 소켓의 경우는 Read, Data, Connect, Write 정도의 콜백을 설정해주면 되겠습니다. Accept 는 서버소켓인 경우 설정합니다.
6번째 항목은 Callback 함수를 지정해주는 곳입니다. CFSockCallBack 이라고 이름지어봤습니다. 마지막 항목은 Socket 컨텍스트 정보를 받기 위한 곳입니다. 안 받으려면 NULL 로 지정합니다.
 
더 공부하실 분들을 위해, 소켓생성 함수의 원형은 아래와 같습니다.
 
CFSocketRef CFSocketCreate (
   CFAllocatorRef allocator,
   SInt32 protocolFamily,
   SInt32 socketType,
   SInt32 protocol,
   CFOptionFlags callBackTypes,
   CFSocketCallBack callout,
   const CFSocketContext *context
);
 
 
소켓이 생성된 후에는 BSD Socket 과 동일하게 remote address 를 정해주고 연결해야 합니다.
 
struct sockaddr_in theName;
struct hostent *hp;
    
theName.sin_port = htons(80);
theName.sin_family = AF_INET;
    
hp = gethostbyname("naver.com");
if( hp == NULL ) {
    return;
}
memcpy( &theName.sin_addr.s_addr, hp->h_addr_list[0], hp->h_length );
 
이렇게 만들어진 sockaddr_in 구조체를 이용해서 접속에 사용될 CFData 레퍼런스를 만듭니다.
 
CFDataRef addressData = CFDataCreate( NULL, &theName, sizeof( struct sockaddr_in ) );
 
소켓과 어드레스데이터가 만들어졌으므로 이제 connect 하면 되겠죠?
 
CFSocketConnectToAddress(ref, addressData, 30);
 
30은 timeout 이며 초단위지정입니다. 만약 음수값이 주어지면 CFSocketConnectToAddress 는 즉각 반환되며, 백그라운드에서 접속을 시도합니다. 접속이 이루어지면 Socket 생성때 사용했던 CallBack 함수가 호출됩니다.
 
 
CFRunLoopSourceRef FrameRunLoopSource = CFSocketCreateRunLoopSource(NULL, ref , 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), FrameRunLoopSource, kCFRunLoopCommonModes); 
 
이 두줄은 소켓에 대한 LoopSource 를 만들고 이 LoopSource 를 실행하는 명령입니다. 이 두줄이 아주 편리한 부분인데, 이로서 특별히 Callback 에 대한 프로그래밍(Thread 나 기타 프로세스 제어)이 없이도 Callback 을 사용할 수 있도록 해줍니다.
 
 
이제 Callback 부분을 보겠습니다.
 
void CFSockCallBack (
                     CFSocketRef s,
                     CFSocketCallBackType callbackType,
                     CFDataRef address,
                     const void *data,
                     void *info
) {
 
    NSLog(@"callback!");
    if(callbackType == kCFSocketDataCallBack) {
        NSLog(@"has data");
        // 데이터 수신시 여기에 프로그래밍하세요
        UInt8 * d = CFDataGetBytePtr((CFDataRef)data);
        int len = CFDataGetLength((CFDataRef)data);
        for(int i=0; i < len; i++) {
            NSLog(@"%c",*(d+i));
        }
    }
    if(callbackType == kCFSocketReadCallBack) {
        NSLog(@"to read");
        // 소켓에서 읽을 수 있습니다.
        char buf[100] = {0};
        int sock = CFSocketGetNative(s);
        NSLog(@"to read");
        NSLog(@"read:%d",recv(sock, &buf, 100, 0));
        NSLog(@"%s",buf);
    }
    if(callbackType == kCFSocketWriteCallBack) {
        NSLog(@"to write");
        // 데이터 송신이 가능해졌습니다.
        char sendbuf[100]={0};
        strcpy(sendbuf,"GET / HTTP/1.0\r\n\r\n");
        CFDataRef dt = CFDataCreate(NULL, sendbuf, 100);
        CFSocketSendData(s, NULL, dt, strlen(sendbuf));
    }
    if(callbackType == kCFSocketConnectCallBack) {
        NSLog(@"connected");
        // 연결이 이루어졌습니다.
    }
}
 
대략 이런 형태를 가집니다. 물론 각각의 callbackType 에 대해 프로그래밍을 해주어야겠죠.
주의 사항은, CallbackType 에 kCFSocketDataCallBack 을 포함시켰을 경우 kCFSocketReadCallBack 은 호출되지 않습니다. kCFSocketDataCallBack 을 설정하면 Socket 으로 들어오는 데이터는 자동으로 읽혀지며 읽혀진 데이터는 CFData 형식으로 data 포인터로 전달되어집니다. kCFSocketDataCallBack 이 Callback type 에 지정되지 않았을 경우는 kCFSocketReadCallBack 이 수행되어야 하며 이때는 소켓으로부터 직접 데이터를 읽어주어야 합니다. 읽을 데이터가 남아있을 때까지 콜백은 계속 호출됩니다.
위의 경우는 Socket Creation 시 kCFSocketDataCallBack 을 지정해주었기 때문에 데이터가 자동으로 읽혀졌습니다. 만약 소켓생성시에 kCFSocketDataCallBack 을 콜백타입으로 지정해주지 않았다면 kCFSocketReadCallBack 을 처리해주어야 하며 예제에서처럼 recv 함수들을 통해 직접 데이터를 읽어야 합니다.