目前来说,多数的app都是具有推送功能。目前也有很多第三方的推送服务平台提供sdk集成的方案。
但实际运用的时候发现,如果自身的业务比较复杂,第三方平台提供的api也很难进行集成。也许自己开发一个推送服务器是一种重复造轮子的行为,
但轮子总归是有尺寸、材料等各方面的差异,自己造一个轮子是为了更好的跑,跑的更远。
目前来说,实现推送功能有几种方法。
第一种是后台轮询,轮询的频率越高,设备电池消耗的越快,而且也无法保证消息能够及时到达。
第二种方式是简历一个长连接,手机与服务器之间保持连接,通过心跳机制来维系连接。
第三种方式是通过udp方式来进行推送。
第一种方式弊端明显,第二种方式相较于第三种方式,好处在于更容易实现,udp需要考虑穿透、自行实现可靠数据传输等机制,在开发工作量远远大于第二种方式。
折衷的方案采取socket长连接。我们采用的是linux服务器,linux实际是限制了进程能够打开的最大连接数,通过如下命令可以查看最大连接数:
ulimit -n
可以看到,centos这个数字是1024,也就是说,进程最大能够打开1024个文件,也就是,套接字程序单进程最多只能允许开启1024个连接。
linux系统下,可以自行修改这个值。此处不介绍如何修改这个参数。
为何要使用libevent?
libevent是一个事件库。
在使用libevent之前,我们编写网络应用程序,通常都是阻塞式的
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int c, char **v)
{
const char query[] =
"GET / HTTP/1.0\r\n"
"Host: www.baidu.com\r\n"
"\r\n";
const char hostname[] = "www.baidu.com";
struct sockaddr_in sin;
struct hostent *h;
const char *cp;
int fd;
ssize_t n_written, remaining;
char buf[1024];
/* Look up the IP address for the hostname. Watch out; this isn't
threadsafe on most platforms. */
h = gethostbyname(hostname);
if (!h) {
fprintf(stderr, "Couldn't lookup %s: %s", hostname, hstrerror(h_errno));
return 1;
}
if (h->h_addrtype != AF_INET) {
fprintf(stderr, "No ipv6 support, sorry.");
return 1;
}
/* Allocate a new socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
/* Connect to the remote host. */
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr = *(struct in_addr*)h->h_addr;
if (connect(fd, (struct sockaddr*) &sin, sizeof(sin))) {
perror("connect");
close(fd);
return 1;
}
/* Write the query. */
/* XXX Can send succeed partially? */
cp = query;
remaining = strlen(query);
while (remaining) {
n_written = send(fd, cp, remaining, 0);
if (n_written <= 0) {
perror("send");
return 1;
}
remaining -= n_written;
cp += n_written;
}
/* Get an answer back. */
while (1) {
ssize_t result = recv(fd, buf, sizeof(buf), 0);
if (result == 0) {
break;
} else if (result < 0) {
perror("recv");
close(fd);
return 1;
}
fwrite(buf, 1, result, stdout);
}
close(fd);
return 0;
}
上面代码中,关于网络请求的代码均采用block io的模式,gethostbyname直到正确的解析了域名才有返回,connect需要一直等到真正连接了才能返回,recv一直到有数据传输才会接收数据。
send也需要一直等待,可见,阻塞IO是一种很简单同样也是很低效的方案。
libevent是一个非常有用的事件库,我们的推送服务也采用这个库来解决io和提升性能,目标是达到单台服务器百万级别的连接。