2011/06/22

Apple Push Notification Service - 推播服務

最近在研究怎麼收送push notification,以及怎麼架設notification server。


推播服務的介紹及運作方式可以參考:
簡單來說就是每個iOS的app第一次開啓時會向apple註冊推播服務,然後得到專屬的device token,可以想像成擁有專屬的tunnel,再來只要向apple的server傳送message(JSON格式),並帶上target device token,server就會自動幫你轉傳給對方。



寫一個小小的測試程式
網站說明非常清楚,大致上的流程如下:
  • 登入 iOS dev center 然後創建一個新的 App ID
  • 開啓 push notification service
  • 下載 "Development Push SSL Certificate" 及 "Provisioning Profile"
Receiver (iOS app):
  • 安裝 provisioning profile
  • 設定 identifier
  • Write the code to register from remote notification server and get "device token"
範例:
要求使用者開啓 Push notification 服務
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[UIApplication sharedApplication]registerForRemoteNotificationTypes:
  (UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}

Implement 3個 delegate
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{ 
    /* Get device token */ 
    NSString *strDevToken = [NSString stringWithFormat:@"%@", deviceToken];

    /* Replace '<', '>' and ' ' */
    NSCharacterSet *charDummy = [NSCharacterSet characterSetWithCharactersInString:@"<> "];
    strDevToken = [[strDevToken componentsSeparatedByCharactersInSet: charDummy] componentsJoinedByString: @""];
    NSLog(@"Device token=[%@]", strDevToken);

    /* 可以把token傳到server,之後server就可以靠它送推播給使用者了 */
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
{
    NSLog(@"Error=[%@]", err);
    // TODO: when user do not allow push notification service, pop the warning message.
}

// This function called when receive notification and app is in the foreground.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
{
    /* 把收到的推播列舉出來 */
    for (id key in userInfo) {
        NSLog(@"Key=[%@], Value=[%@]", key, [userInfo objectForKey:key]); 
    }

    /* 印出 Badge number */
    NSLog(@"Badge: %@", [[userInfo objectForKey:@"aps"] objectForKey:@"badge"];

}

Sender (MacOS app):
  • Base on sample code "PushMeBaby"
  • Install "Development Push SSL Certificate"
  • Set target device token then send

參考資料


至於要如何架設APNS,可以參考下一篇:架設 Apple Push Notification Provider Server

2011/06/20

HTTP server over libevent - sample code (2)

加入一些parse的function,可以得到request的method、URI、header、body、path

#include 
#include 
#include 
#include 
#include 

#define SVR_IP                         "127.0.0.1"
#define SVR_PORT                       8080

void dump_cb (struct evhttp_request *req, void *arg) {
    const char        *uri;
    struct evkeyvalq  *headers;
    struct evkeyval   *header;
    struct evbuffer   *evbuf;
    struct evhttp_uri *ev_uri;
    
    /* Get method */
    switch (evhttp_request_get_command(req)) {
        case EVHTTP_REQ_GET: 
            printf("Request method=[GET]\n");
            break;
        case EVHTTP_REQ_POST:
            printf("Request method=[POST]\n");
            break;
        default:
            printf("Request method=[others]\n");
            break;
    }
    
    /* Get path */
    uri = evhttp_request_get_uri(req);
    printf("Request uri=[%s]\n",  uri);
    
    /* Get header */
    printf("Headers:\n");
    headers = evhttp_request_get_input_headers(req);
    for (header = headers->tqh_first; header;
        header = header->next.tqe_next) {
        printf("  %s: %s\n", header->key, header->value);
    }
        
    /* Get body */
    evbuf = evhttp_request_get_input_buffer(req);
    printf("Body:\n");
    while (evbuffer_get_length(evbuf)) {
        int  size;
        char buf[128];
        
        memset(buf, 0, sizeof(buf));
        size = evbuffer_remove(evbuf, buf, sizeof(buf) - 1);
        printf("  %s\n", buf);
    }
    
    /* */
    ev_uri = evhttp_uri_parse(uri);
    if (ev_uri) {
        const char *path;
        char       *decoded_path;
        
        path = evhttp_uri_get_path(ev_uri);
        decoded_path = evhttp_uridecode(path, 0, NULL);
        printf("Request path=[%s]\n", decoded_path);
        
        /* Free resource */
        free(decoded_path);
        evhttp_uri_free(ev_uri);
    }
    
    /* Response */
    evhttp_send_reply(req, HTTP_OK, "OK", NULL);
}

int main (int argc, char **argv) {
    struct event_base *evbase;
    struct evhttp     *evhttp;

    /* Init event base */
    if ((evbase = event_base_new()) == NULL) {
        printf("event_base_new() failed\n");
        return -1;
    }

    /* Init evhttp */
    if ((evhttp = evhttp_new(evbase)) == NULL) {
        printf("evhttp_new() failed\n");
        return -1;
    }

    /* Set server IP, port */
    if (evhttp_bind_socket(evhttp, SVR_IP, SVR_PORT) == -1) {
        printf("evhttp_bind_socket() failed\n");
        return -1;
    } else {
        printf("Listening on [%s:%d]\n", SVR_IP, SVR_PORT);
    }
    
    /* Set timeout */
    evhttp_set_timeout(evhttp, 10);
    
    /* Set a callback for default path */
    evhttp_set_gencb(evhttp, dump_cb, NULL);

    /* Enter event loop */
    event_base_dispatch(evbase);

    /* Free resource */
    evhttp_free(evhttp);
    event_base_free(evbase);
    
    return 0;
}



Compile and run
$ gcc -g -Wall -o httpd httpd.c -levent -I/opt/libevent-2.0.12-stable/include/ -L/opt/libevent-2.0.12-stable/lib/
$ LD_LIBRARY_PATH=/opt/libevent-2.0.12-stable/lib ./httpd
Listening on [127.0.0.1:8080]

Request method=[GET]
Request uri=[/test.php?name=kevin&sex=male]
Headers:
  User-Agent: Wget/1.11.4 Red Hat modified
  Accept: */*
  Host: 127.0.0.1:8080
  Connection: Keep-Alive
Body:
Request path=[/test.php]

Request method=[POST]
Request uri=[/test.php]
Headers:
  User-Agent: Wget/1.11.4 Red Hat modified
  Accept: */*
  Host: 127.0.0.1:8080
  Connection: Keep-Alive
  Content-Type: application/x-www-form-urlencoded
  Content-Length: 19
Body:
  name=sylar&sex=male
Request path=[/test.php]


try to send a request to server...
# Get
$wget -q -O - 'http://127.0.0.1:8080/test.php?name=sylar&sex=male'

# Post
$wget -q -O - --post-data='name=sylar&sex=male' http://127.0.0.1:8080/test.php

2011/06/17

HTTP server over libevent - sample code (1)

使用libevent HTTP API寫一個簡易的server。

#include 
#include 
#include 

#define SVR_IP                         "127.0.0.1"
#define SVR_PORT                       8080

void specific_cb (struct evhttp_request *req, void *arg) {
    struct evbuffer *evbuf;
    
    if ((evbuf = evbuffer_new()) == NULL) {
        printf("evbuffer_new() failed");
        evhttp_send_reply(req, HTTP_INTERNAL, "Internal error", NULL);
        return;
    }

    /* Body */
    evbuffer_add_printf(evbuf, "It's work!");
    
    /* Response */
    evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
    
    /* Free resource */
    evbuffer_free(evbuf);
}

void generic_cb (struct evhttp_request *req, void *arg) {

    /* Response */
    evhttp_send_reply(req, HTTP_NOTFOUND, "Not found", NULL);
}

int main (int argc, char **argv) {
    struct event_base *evbase;
    struct evhttp     *evhttp;

    /* Init event base */
    if ((evbase = event_base_new()) == NULL) {
        printf("event_base_new() failed\n");
        return -1;
    }

    /* Init evhttp */
    if ((evhttp = evhttp_new(evbase)) == NULL) {
        printf("evhttp_new() failed\n");
        return -1;
    }

    /* Set server IP, port */
    if (evhttp_bind_socket(evhttp, SVR_IP, SVR_PORT) == -1) {
        printf("evhttp_bind_socket() failed\n");
        return -1;
    } else {
        printf("Listening on [%s:%d]\n", SVR_IP, SVR_PORT);
    }
    
    /* Set a callback for specific path */
    if (evhttp_set_cb(evhttp, "/test", specific_cb, NULL) < 0) {
        printf("evhttp_set_cb() failed\n");
        return -1;
    }
    
    /* Set a callback for default path */
    evhttp_set_gencb(evhttp, generic_cb, NULL);

    /* Enter event loop */
    event_base_dispatch(evbase);

    /* Free resource */
    evhttp_free(evhttp);
    event_base_free(evbase);
    
    return 0;
}
Compile and run
$ gcc -g -Wall -o httpd httpd.c -levent -I/opt/libevent-2.0.12-stable/include/ -L/opt/libevent-2.0.12-stable/lib/
$ LD_LIBRARY_PATH=/opt/libevent-2.0.12-stable/lib ./httpd
Listening on [127.0.0.1:8080]
try to send a request to server...
$ wget -q -O - http://127.0.0.1:8080/test
$ wget -S -O - http://127.0.0.1:8080/xxx
不知道為什麼我用2.0.10-stable版本跑會有下列warning,用2.0.12-stable就沒問題了。
[warn] evhttp_connection_base_new: bufferevent_new failed: No such file or directory
[warn] evhttp_get_request: cannot get connection on 9: No such file or directory

2011/06/16

Echo server over libevent - sample code

一個簡易的echo server,使用了bufferevent。

Bufferevent
Bufferevents are higher level than evbuffers: each has an underlying evbuffer for reading and one for writing, and callbacks that are invoked under certain circumstances.

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event.h>
#include <event2/listener.h>

#define SVR_IP                         "127.0.0.1"
#define SVR_PORT                       10000
#define BUFF_SIZE                      1024

static void read_cb(struct bufferevent *bev, void *ctx) {
    char buff[BUFF_SIZE];
    int len;

    memset(buff, 0, sizeof(buff));

    /* Read data */
    len = bufferevent_read(bev, buff, sizeof(buff));
    printf("read: len=[%d] data=[%s]\n", len, buff);

    /* Write data */
    bufferevent_write(bev, buff, len);
}

static void write_cb(struct bufferevent *bev, void *ctx) {
    printf("write finished\n");
}

static void event_cb(struct bufferevent *bev, short events, void *ctx) {
    if (events & BEV_EVENT_EOF) {
        printf("client disconnect\n");
        bufferevent_free(bev);
    } else if (events & BEV_EVENT_TIMEOUT) {
        printf("client timeout\n");
        bufferevent_free(bev);
    } else {
        /* Other case, maybe error occur */
        bufferevent_free(bev);
    }
}

static void accept_cb(struct evconnlistener *lev, evutil_socket_t fd,
        struct sockaddr *sa, int socklen, void *ctx) {
    struct event_base *evbase = ctx;
    struct bufferevent *bev;
    struct timeval tv;

    printf("client connect from [%s:%u] over fd [%d]\n", 
            inet_ntoa(((struct sockaddr_in *) sa)->sin_addr), 
            (unsigned short) ntohs(((struct sockaddr_in *) sa)->sin_port), fd);

    /* Create socket-based buffer event */
    bev = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE);
    if (bev == NULL) {
        printf("bufferevent_socket_new() failed\n");
        evutil_closesocket(fd);
        return;
    }

    /* Set up callback function */
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);

    /* Set up timeout for data read */
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    bufferevent_set_timeouts(bev, &tv, NULL);

    /* Enable read event */
    bufferevent_enable(bev, EV_READ);
}

int main(void) {
    struct event_base *evbase;
    struct sockaddr_in sin;
    struct evconnlistener *lev;

    /* Initialize event base */
    if ((evbase = event_base_new()) == NULL) {
        printf("event_base_new() failed\n");
        return -1;
    }

    /* Set up server address */
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr(SVR_IP);
    sin.sin_port = htons(SVR_PORT);

    /* Bind socket */
    lev = evconnlistener_new_bind(evbase, accept_cb, evbase, 
            LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, 
            (struct sockaddr *) &sin, sizeof(sin));

    if (lev == NULL) {
        printf("bind() failed\n");
        return -1;
    } else {
        printf("bind to [%s:%u] successfully\n", SVR_IP, SVR_PORT);
    }

    /* Enter event loop */
    event_base_dispatch(evbase);

    return 0;
}


編譯 (指定library path)
gcc -g -Wall -o server server.c -levent -I/opt/libevent-2.0.10-stable/include/ -L/opt/libevent-2.0.10-stable/lib/

C socket client in Linux - sample code

Sample code

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LOCAL_IP                        "127.0.0.1"
#define LOCAL_PORT                      5678
#define SVR_IP                          "127.0.0.1"
#define SVR_PORT                        8080
#define BUF_SIZE                        1024
#define MESSAGE                         "hello"

int main (int argc, char **argv) {
    struct sockaddr_in  local_addr, server_addr;
    socklen_t           len;
    int                 sock_fd;
    char                buff[BUF_SIZE];
    int                 recv_len;
    
    len = sizeof(struct sockaddr_in);
    
    /* Set local address (In general, we don't care local address) */
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = inet_addr(LOCAL_IP);
    local_addr.sin_port = htons(LOCAL_PORT);
    
    /* Set server address */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SVR_IP);
    server_addr.sin_port = htons(SVR_PORT);

    /* Create endpoint */
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket()");
        return -1;
    } else {
        printf("sock_fd=[%d]\n", sock_fd);
    }

    /* Bind */
    if (bind(sock_fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) == -1) {
        perror("bind()");
        return -1;
    } else {
        printf("bind [%s:%u] success\n",
            inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port));
    }

    /* Connect */
    if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect()");
        return -1;
    } else {
        printf("connect to [%s:%u] success\n",
            inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));
    }
    
    /* Send */
    send(sock_fd, MESSAGE, strlen(MESSAGE), 0);

    /* Receive */
    memset(buff, 0, sizeof(buff));
    recv_len = recv(sock_fd, buff, sizeof(buff), 0);
    if (recv_len == -1) {
        perror("recv()");
        return -1;
    } else if (recv_len == 0) {
        printf("Client disconnect\n");
    } else {
        printf("Receive: len=[%d] msg=[%s]\n", recv_len, buff);
    }
    
    return 0;
}

2011/06/15

C socket server in Linux - sample code

長期用libevent來implement socket program,都快忘了一般select的用法了,寫了一個echo server
供各位參考。

不過有一點我還是不太懂,為何fd_set要copy一份起來,是因為fd_set 經過select function後就無效了嗎?我試過只用一個fd_set,果真只能接收到第一個request,除非每次select前重新把所有的fd做FD_SET()。

Select的用法可以參考:石頭閒語:select() - I/O Multiplexer

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SVR_IP                          "127.0.0.1"
#define SVR_PORT                        8080
#define BUF_SIZE                        1024

int main (int argc, char **argv) {
    struct sockaddr_in  server_addr;
    socklen_t           len;
    fd_set              active_fd_set;
    int                 sock_fd;
    int                 max_fd;
    int                 flag = 1;
    char                buff[BUF_SIZE];
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SVR_IP);
    server_addr.sin_port = htons(SVR_PORT);
    len = sizeof(struct sockaddr_in);

    /* Create endpoint */
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket()");
        return -1;
    } else {
        printf("sock_fd=[%d]\n", sock_fd);
    }

    /* Set socket option */
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) < 0) {
        perror("setsockopt()");
        return -1;
    }

    /* Bind */
    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind()");
        return -1;
    } else {
        printf("bind [%s:%u] success\n",
            inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port));
    }

    /* Listen */
    if (listen(sock_fd, 128) == -1) {
        perror("listen()");
        return -1;
    }

    FD_ZERO(&active_fd_set);
    FD_SET(sock_fd, &active_fd_set);
    max_fd = sock_fd;

    while (1) {
        int             ret;
        struct timeval  tv;
        fd_set          read_fds;

        /* Set timeout */
        tv.tv_sec = 2;
        tv.tv_usec = 0;

        /* Copy fd set */
        read_fds = active_fd_set;
        ret = select(max_fd + 1, &read_fds, NULL, NULL, &tv);
        if (ret == -1) {
            perror("select()");
            return -1;
        } else if (ret == 0) {
            printf("select timeout\n");
            continue;
        } else {
            int i;

            /* Service all sockets */
            for (i = 0; i < FD_SETSIZE; i++) {
                if (FD_ISSET(i, &read_fds)) {
                    if (i == sock_fd) {
                        /* Connection request on original socket. */
                        struct sockaddr_in  client_addr;
                        int                 new_fd;

                        /* Accept */
                        new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len);
                        if (new_fd == -1) {
                            perror("accept()");
                            return -1;
                        } else {
                            printf("Accpet client come from [%s:%u] by fd [%d]\n",
                                inet_ntoa(client_addr.sin_addr),
                                ntohs(client_addr.sin_port), new_fd);

                            /* Add to fd set */
                            FD_SET(new_fd, &active_fd_set);
                            if (new_fd > max_fd)
                                max_fd = new_fd;
                        }
                    } else {
                        /* Data arriving on an already-connected socket */
                        int recv_len;

                        /* Receive */
                        memset(buff, 0, sizeof(buff));
                        recv_len = recv(i, buff, sizeof(buff), 0);
                        if (recv_len == -1) {
                            perror("recv()");
                            return -1;
                        } else if (recv_len == 0) {
                            printf("Client disconnect\n");
                        } else {
                            printf("Receive: len=[%d] msg=[%s]\n", recv_len, buff);

                            /* Send (In fact we should determine when it can be written)*/
                            send(i, buff, recv_len, 0);
                        }

                        /* Clean up */
                        close(i);
                        FD_CLR(i, &active_fd_set);
                    }

                } // end of if
            } //end of for
        } // end of if
    } // end of while
    
    return 0;
}

2011/06/08

Get IPv4 address from sockaddr or socket

(sock_fd is a socket)
sockaddr_in  addr_in;
int          len = sizeof(sockaddr_in);

/* Get sockaddr_in structure information from socket */
getsockname(sock_fd, (sockaddr *)&addr_in, (socklen_t *)&len);

/* Get address from sockaddr_in */
printf("IP=[%s] port=[%d]\n", inet_ntoa(addr_in.sin_addr), addr_in.sin_port);

2011/06/02

netcat

Top 100 Network Security Tools 排行第四名的軟體,也是我在寫socket program時最常用到的工具,不管是當server/client,tcp/udp都沒問題!


wiki中已經講述了大部份的使用方法:

  • raw connection
  • http server
  • file server
  • proxy server
  • port scanning
  • port forwarding

下載:
The GNU Netcat -- Official homepage -- Downloads

Support SSL的版本
netcat SSL

這一套感覺更強大,可以support UDP和SSL!
SSL Capable NetCat