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 함수들을 통해 직접 데이터를 읽어야 합니다.