在redis源码的server.c文件中的initServer()方法中有这样一段代码:
/* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) {
unlink(server.unixsocket); /* don't care if this fails */
server.sofd = anetUnixServer(server.neterr,server.unixsocket,
server.unixsocketperm, server.tcp_backlog);
if (server.sofd == ANET_ERR) {
serverLog(LL_WARNING, "Opening Unix socket: %s", server.neterr);
exit(1);
}
anetNonBlock(NULL,server.sofd);
}
根据注释可以知道这段代码是要打开unix域套接字进行监听,但是打开之前做了一个判断,即server.unixsocket是否为null。这个值是什么意思呢?又是从哪来的呢?看配置文件redis.conf:
# TCP listen() backlog.
#
# In high requests-per-second environments you need an high backlog in order
# to avoid slow clients connections issues. Note that the Linux kernel
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
# in order to get the desired effect.
tcp-backlog 511
# Unix socket.
#
# Specify the path for the Unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
unixsocket /tmp/redis.sock
unixsocketperm 700
可以看到redis中的unix域套接字默认是不打开的,这里是取消注释将其打开了,并定义了参数unixsocket和unixsocketperm。并截取了上面一部分配置tcp-backlog,这个参数之前在文章:套接字api值listen函数中着重提到过,这里后面也会用到。可见方法中的值是来自配置文件。如果配置文件中没打开,那么server.unixsocket值就是为null,也就不必打开unix域套接字进行监听了。
继续看if()里面,调用了一个anetUnixServer()方法,三个参数都来自配置文件。
/**
* path: socket文件配置路径,默认/tmp/redis.sock
* perm: 文件访问权限,默认700
* backlog: listen方法参数,默认511
**/
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
{
int s;
struct sockaddr_un sa;
if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR)
return ANET_ERR;
memset(&sa,0,sizeof(sa));
sa.sun_family = AF_LOCAL;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog) == ANET_ERR)
return ANET_ERR;
if (perm)
chmod(sa.sun_path, perm);
return s;
}
static int anetCreateSocket(char *err, int domain)
{
int s;
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s", strerror(errno));
return ANET_ERR;
}
/* Make sure connection-intensive things like the redis benchmark
* will be able to close/open sockets a zillion of times */
if (anetSetReuseAddr(err,s) == ANET_ERR) {
close(s);
return ANET_ERR;
}
return s;
}
看方法体发现这里并没有调用socketpair()方法创建一对unix域套接字,而是调用的anetCreateSocket()方法创建的,实际也就是socket()方法,只不过第一个参数是AF_LOCAL(AF_UNIX的别名),返回unix域套接字描述符并赋值给s。然而这时的unix域套接字是没有名字的,无关进程还不能使用。
anetUnixServer()方法中还定义了一个struct sockaddr_un结构体,定义如下:
struct sockaddr_un {
sa_family_t sun_family; //AF_UNIX
char sun_path[108]; //pathname
};
该结构的sun_path成员保存一个地址,redis就将该结构体和之前生成的套接字描述符s一起传给anetListen()方法。
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog)
{
if (bind(s,sa,len) == -1) {
anetSetError(err, "bind: %s", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, backlog) == -1) {
anetSetError(err, "listen: %s", strerror(errno));
close(s);
return ANET_ERR;
}
return ANET_OK;
}
该方法中主要就是干了两件事,先调用bind()方法将创建的unix域套接字s绑到结构体包含的地址上,然后调用listen()方法开始监听。当启动redis服务时,就会用该路径名创建一个socket文件,也就是这里的/tmp/redis.sock文件。注意该文件无法打开,也不能由应用程序用于通信。
至此,redis中的unix域套接字部分就介绍到这里,欢迎交流。