Unix getaddrinfo C函数启动服务器的用法 [英] usage of Unix getaddrinfo C function to start set the server
问题描述
我正在使用C语言构建一个客户端-服务器应用程序,其源代码取自《 Unix环境高级编程》一书.
I am building a client-server application in C with the source code taken from the book Advanced Programming in Unix Environment.
在服务器中,它正在执行以下操作:
In the server it is doing the following:
struct addrinfo hint;
memset(&hint, 0, sizeof(hint));
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_addr = NULL;
hint.ai_next = NULL;
....
if ((n = sysconf(_SC_HOST_NAME_MAX))<0)
{
n = HOST_NAME_MAX;
}
if((host = malloc(n)) == NULL)
{
printf("malloc error\n");
exit(1);
}
if (gethostname(host, n)<0)
{
printf("gethostname error\n");
exit(1);
}
...
if((err = getaddrinfo(host, "ruptime", &hint, &ailist))!=0)
{
syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
exit(1);
}
for (aip = ailist; aip!=NULL; aip = aip->ai_next)
{
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN))>=0)
{
//printf("starting to serve\n");
serve(sockfd);
exit(0);
}
}
据我了解,函数getaddrinfo
用于在主机上查找运行名为ruptime
且类型为SOCK_STREAM
的服务的套接字地址结构.
As far as I understood the function getaddrinfo
is used to look on the host the socket address structures running the service named ruptime
and of type SOCK_STREAM
.
尽管在书中未指定,但要工作,我必须在文件/etc/services/
中运行一个新条目,并使用一个未使用的端口并指定名称ruptime
:
Although it was not specified in the book, to work I had to run a new entry in the file /etc/services/
with an unused port and the specified name ruptime
:
ruptime 49152/tcp #ruptime Unix System Programming
ruptime 49152/udp #ruptime Unix System Programming
尽管未使用,但建议也添加UDP部分.
where although unused it was suggested to add also the UDP part.
但是它说的文档
如果在
hints.ai_flags
中指定了AI_PASSIVE
标志,并且节点为NULL
,则返回的套接字地址将适用于 绑定(2)一个将接受(2)连接的套接字.返回的 套接字地址将包含通配符地址"(INADDR_ANY
IPv4地址,IN6ADDR_ANY_INIT
为IPv6地址).通配符 该地址由打算用于以下用途的应用程序(通常是服务器)使用: 接受主机的任何网络地址上的连接.
If the
AI_PASSIVE
flag is specified inhints.ai_flags
, and node isNULL
, then the returned socket addresses will be suitable for bind(2)ing a socket that will accept(2) connections. The returned socket address will contain the "wildcard address" (INADDR_ANY
for IPv4 addresses,IN6ADDR_ANY_INIT
for IPv6 address). The wildcard address is used by applications (typically servers) that intend to accept connections on any of the host's network addresses.
因此,从这里开始,以及其他有关SO的讨论,例如:
So from here and from other discussions on SO something like:
hint.ai_flags |= AI_PASSIVE
...
getaddrinfo(NULL, myserviceport, &hint, &aihint)
似乎更合适.
这两种方法到底有什么区别?第二个也在寻找SOCK_DGM
吗?有什么理由在书中选择第一种方法?由于第二种方式是在代码中指定端口,因此可以避免在/etc/services/
中添加新条目吗?
Exactly what is the difference between these two methods? Is the second looking also for the SOCK_DGM
? Is there any reason why in the book the first method was chosen? In the second way since I am specifying the port in the code, does it allow to avoid adding a new entry in the /etc/services/
?
另一个问题.
我必须向客户端传递主机名.我认为环回(客户端和服务器在同一台计算机上运行)地址可以.相反,主机名类似于./client MBPdiPippo.lan
.是什么定义了可以使用主机名而不使用环回地址创建连接的事实?是我将host
作为第一个参数传递给服务器中的getaddrinfo
吗?
Another question.
To the client I had to pass the host name. I thought the loopback (client and server are running on the same machine) address would be ok. Instead the hostname is something like ./client MBPdiPippo.lan
. What defines the fact that the connection can be created with the hostname but not with the loopback address? Is it that I am passing host
as first parameter to the getaddrinfo
in the server?
完整代码
server.c
server.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> //_SC_HOST_NAME_MAX
#include<string.h>
#include<netdb.h> //Here are defined AF_INET and the others of the family
#include<syslog.h> //LOG_ERR
#include<errno.h> //errno
#include <sys/types.h>
#include"utilities.h"
#include "error.h"
#define BUFLEN 128
#define QLEN 10
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 156
#endif
int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen);
void serve(int sockfd);
int main(int argc, char* argv[])
{
printf("entered main\n");
struct addrinfo *ailist, *aip, hint;
int sockfd, err, n;
char *host;
if (argc != 1)
{
printf("usage: ruptimed\n");
exit(1);
}
if ((n=sysconf(_SC_HOST_NAME_MAX))<0)
{
n = HOST_NAME_MAX;
}
if((host = malloc(n)) == NULL)
{
printf("malloc error\n");
exit(1);
}
if (gethostname(host, n)<0)
{
printf("gethostname error\n");
exit(1);
}
printf("host: %s\n", host);
printf("Daemonizing\n");
int res = daemonize("ruptimed");
printf("%d\n", res);
printf("Daemonized\n");
memset(&hint, 0, sizeof(hint)); //set to 0 all bytes
printf("hint initialized\n");
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
printf("getting addresses\n");
if((err = getaddrinfo(host, "ruptime", &hint, &ailist))!=0)
{
printf("error %s\n", gai_strerror(err));
syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
exit(1);
}
printf("Got addresses\n");
for (aip = ailist; aip!=NULL; aip = aip->ai_next)
{
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN))>=0)
{
printf("starting to serve\n");
serve(sockfd);
exit(0);
}
}
exit(1);
}
void serve(int sockfd)
{
int clfd;
FILE *fp;
char buf[BUFLEN];
set_cloexec(sockfd);
for(;;)
{
/*After listen, the socket can receive connect requests. accept
retrieves a connect request and converts it into a connection.
The file returned by accept is a socket descriptor connected to the client that
called connect, haing the same coket type and family type. The original
soket remains available to receive otherconneion requests. If we don't care
about client's identity we can set the second (struct sockaddr *addr)
and third parameter (socklen_t *len) to NULL*/
if((clfd = accept(sockfd, NULL, NULL))<0)
{
/*This generates a log mesage.
syslog(int priority, const char *fformat,...)
priority is a combination of facility and level. Levels are ordered from highest to lowest:
LOG_EMERG: emergency system unusable
LOG_ALERT: condiotin that must be fied immediately
LOG_CRIT: critical condition
LOG_ERR: error condition
LOG_WARNING
LOG_NOTICE
LOG_INFO
LOG_DEBUG
format and other arguements are passed to vsprintf function forf formatting.*/
syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
exit(1);
}
/* set the FD_CLOEXEC file descriptor flag */
/*it causes the file descriptor to be automatically and atomically closed
when any of the exec family function is called*/
set_cloexec(clfd);
/**pg. 542 Since a common operation is to create a pipe to another process
to either read its output or write its input Stdio has provided popen and
pclose: popen creates pipe, close the unused ends of the pipe,
forks a child and call exec to execute cmdstr and
returns a file pointer (connected to std output if "r", to stdin if "w").
pclose closes the stream, waits for the command to terminate*/
if ((fp = popen("/usr/bin/uptime", "r")) == NULL)
{
/*sprintf copy the string passed as second parameter inside buf*/
sprintf(buf, "error: %s\n", strerror(errno));
/*pag 610. send is similar to write. send(int sockfd, const void *buf, size_t nbytes, it flags)*/
send(clfd, buf, strlen(buf),0);
}
else
{
/*get data from the pipe that reads created to exec /usr/bin/uptime */
while(fgets(buf, BUFLEN, fp)!=NULL)
{
/* clfd is returned by accept and it is a socket descriptor
connected to the client that called connect*/
send(clfd, buf, strlen(buf), 0);
}
/*see popen pag. 542*/
pclose(fp);
}
close(clfd);
}
}
int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
int fd, err;
int reuse = 1;
if ((fd = socket(addr->sa_family, type, 0))<0)
{
return (-1);
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))<0)
{
goto errout;
}
if(bind(fd, addr, alen)<0)
{
goto errout;
}
if (type == SOCK_STREAM || type == SOCK_SEQPACKET)
{
if(listen(fd, qlen)<0)
{
goto errout;
}
}
return fd;
errout:
err = errno;
close (fd);
errno = err;
return(-1);
}
utilities.c
:包含demonize
和setcloexec
函数.在daemonize
函数中,我没有关闭文件描述符以进行调试.
utilities.c
: containing the demonize
and setcloexec
functions. In daemonize
function I did not close file descriptors for debugging.
#include "utilities.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/time.h>//getrlimit
#include <sys/resource.h>//getrlimit
#include <signal.h> //sigempyset , asigcation (umask?)
#include <sys/resource.h>
#include <fcntl.h> //O_RDWR
#include <stdarg.h>
#include "error.h"
int daemonize(const char *cmd)
{
int fd0, fd1, fd2;
unsigned int i;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
/* *Clear file creation mask.*/
umask(0);
/* *Get maximum number of file descriptors. */
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{
err_quit("%s: can’t get file limit", cmd);
}
/* *Become a session leader to lose controlling TTY. */
if ((pid = fork()) < 0)
{
err_quit("%s: can’t fork", cmd);
}
else if (pid != 0) /* parent */
{
exit(0); //the parent will exit
}
setsid();
/* *Ensure future opens won’t allocate controlling TTYs. */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
err_quit("%s: can’t ignore SIGHUP", cmd);
}
if ((pid = fork()) < 0)
{
err_quit("%s: can’t fork", cmd);
}
else if (pid != 0) /* parent */
{
exit(0);
}
/*
*Change the current working directory to the root so
* we won’t prevent file systems from being unmounted.
*/
if (chdir("/") < 0)
{
err_quit("%s: can’t change directory to /", cmd);
}
/* Close all open file descriptors. */
if (rl.rlim_max == RLIM_INFINITY)
{
rl.rlim_max = 1024;
}
printf("closing file descriptors\n");
/*for (i = 0; i < rl.rlim_max; i++)
{
close(i);
}*/
/* *Attach file descriptors 0, 1, and 2 to /dev/null.*/
//printf not working
/*printf("closed all file descriptors for daemonizing\n");*/
/*fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);*/
/* *Initialize the log file. Daemons do not have a controlling terminal so
they can't write to stderror. We don't want them to write to the console device
because on many workstations the control device runs a windowing system. They can't
write on separate files either. A central daemon error-logging facility is required.
This is the BSD. 3 ways to generate log messages:
1) kernel routines call the log function. These messages can be read from /dev/klog
2) Most user processes (daemons) call syslog to generate log messages. This causes
messages to be sent to the UNIX domain datagram socket /dev/log
3) A user process on this host or on other host connected to this with TCP/ID
can send log messages to UDP port 514. Explicit network programmin is required
(it is not managed by syslog.
The syslogd daemon reads al three of log messages.
openlog is optional since if not called, syslog calls it. Also closelog is optional
openlog(const char *ident, int option, int facility)
It lets us specify ident that is added to each logmessage. option is a bitmask:
LOG_CONS tells that if the log message can't be sent to syslogd via UNIX
domain datagram, the message is written to the console instead.
facility lets the configuration file specify that messages from different
facilities are to be handled differently. It can be specified also in the 'priority'
argument of syslog. LOG_DAEMON is for system deamons
*/
/*
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2)
{*/
/*This generates a log mesage.
syslog(int priority, const char *fformat,...)
priority is a combination of facility and level. Levels are ordered from highest to lowest:
LOG_EMERG: emergency system unusable
LOG_ALERT: condiotin that must be fied immediately
LOG_CRIT: critical condition
LOG_ERR: error condition
LOG_WARNING
LOG_NOTICE
LOG_INFO
LOG_DEBUG
format and other arguements are passed to vsprintf function forf formatting.*/
/*syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
exit(1);
}*/
return 0;
}
/*The function set the FD_CLOEXEC flag of the file descriptor already open that
is passed to as parameter. FD_CLOEXEC causes the file descriptor to be
automatically and atomically closed when any of the exec family function is
called*/
int set_cloexec(int fd)
{
int val;
/* retrieve the flags of the file descriptor */
if((val = fcntl(fd, F_GETFD, 0))<0)
{
return -1;
}
/* set the FD_CLOEXEC file descriptor flag */
/*it causes the file descriptor to be automatically and atomically closed
when any of the exec family function is called*/
val |= FD_CLOEXEC;
return (fcntl(fd, F_SETFD, val));
}
错误功能我使用的
/* Fatal error unrelated to a system call.
* Print a message and terminate*/
void err_quit (const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
err_doit (0, 0, fmt, ap);
va_end (ap);
exit(1);
}
/*Print a message and return to caller.
*Caller specifies "errnoflag"*/
static void err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf [MAXLINE];
vsnprintf (buf, MAXLINE-1, fmt, ap);
if (errnoflag)
{
snprintf (buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
strerror (error));
}
strcat(buf, "\n");
fflush(stdout); /*in case stdout and stderr are the same*/
fputs (buf, stderr);
fflush(NULL); /* flushes all stdio output streams*/
}
推荐答案
首先,一个nitpick. getaddrinfo()
代码应合并到initserver()
函数中,并在循环后释放套接字结构的链接列表(使用freeaddrinfo()
).这使代码更具可维护性.您希望将紧密耦合的实现紧密联系在一起.
First, a nitpick. The getaddrinfo()
code should be incorporated into the initserver()
function, and the linked list of socket structures freed (using freeaddrinfo()
) after the loop. This makes the code much more maintainable; you want to keep tightly coupled implementations close together.
这两种方法到底有什么区别?
Exactly what is the difference between these two methods?
绑定到通配符地址(即,使用getaddrinfo()
获得适当的套接字描述时,使用NULL
节点和AI_PASSIVE
标志)意味着该套接字作为一组绑定到所有网络接口,而不是特定的网络接口. .当您绑定到特定的节点名称时,您将绑定到特定的网络接口.
Binding to the wildcard address (i.e., using NULL
node and AI_PASSIVE
flag when obtaining suitable socket descriptions using getaddrinfo()
) means the socket is bound to all network interfaces as a set, not to a specific network interface. When you bind to a specific node name, you bind to a specific network interface.
实际上,这意味着,如果在运行时可以使用其他网络接口,则内核在将数据包路由到绑定到通配符地址的套接字或从该套接字路由出数据包时将考虑它们.
In practice, it means that if additional network interfaces become available at run time, the kernel will consider them when routing packets to/from sockets bound to the wildcard address.
这实际上应该是每个系统管理员的选择,因为在某些用例中,服务(您的应用程序)应侦听所有网络接口上的传入连接,但在其他用例中,服务应侦听传入的连接仅在特定或某些特定接口上.一个典型的情况是一台机器连接到多个网络.对于服务器,这是令人惊讶的普遍现象.对于实际情况,请参见如何配置 Apache Web服务器.
It really should be a choice made by each system administrator, as there are use cases where the service (your application) should listen for incoming connections on all network interfaces, but also other use cases where the service should listen for incoming connections on a specific or some specific interfaces only. A typical case is when a machine is connected to multiple networks. It is surprisingly common for servers. For practical cases, see e.g. how the Apache web server can be configured.
我个人将重写OP的initServer()
函数,使其类似于以下内容:
Personally, I would rewrite OP's initServer()
function to look something like the following:
enum {
/* TCP=1, UDP=2, IPv4=4, IPv6=8 */
SERVER_TCPv4 = 5, /* IPv4 | TCP */
SERVER_UDPv4 = 6, /* IPv4 | UDP */
SERVER_TCPv6 = 9, /* IPv6 | TCP */
SERVER_UDPv6 = 10, /* IPv6 | UDP */
SERVER_TCP = 13, /* Any | TCP */
SERVER_UDP = 14 /* Any | UDP */
};
int initServer(const char *host, const char *port,
const int type, const int backlog)
{
struct addrinfo hints, *list, *curr;
const char *node;
int family, socktype, result, fd;
if (!host || !*host || !strcmp(host, "*"))
node = NULL;
else
node = host;
switch (type) {
case SERVER_TCPv4: family = AF_INET; socktype = SOCK_STREAM; break;
case SERVER_TCPv6: family = AF_INET6; socktype = SOCK_STREAM; break;
case SERVER_TCP: family = AF_UNSPEC; socktype = SOCK_STREAM; break;
case SERVER_UDPv4: family = AF_INET; socktype = SOCK_DGRAM; break;
case SERVER_UDPv6: family = AF_INET6; socktype = SOCK_DGRAM; break;
case SERVER_UDP: family = AF_UNSPEC; socktype = SOCK_DGRAM; break;
default:
fprintf(stderr, "initServer(): Invalid server type.\n");
return -1;
}
memset(&hints, 0, sizeof hints);
hints.ai_flags = AI_PASSIVE;
hints.ai_family = family;
hints.ai_socktype = socktype;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
result = getaddrinfo(node, port, &hints, &list);
if (result) {
/* Fail. Output error message to standard error. */
fprintf(stderr, "initServer(): %s.\n", gai_strerror(result));
return -1;
}
fd = -1;
for (curr = list; curr != NULL; curr = curr->ai_next) {
int reuse = 1;
fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (fd == -1)
continue;
if (bind(fd, curr->ai_addr, curr->ai_addrlen) == -1) {
close(fd);
fd = -1;
continue;
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
&reuse, sizeof (int)) == -1) {
close(fd);
fd = -1;
continue;
}
if (listen(fd, backlog) == -1) {
close(fd);
fd = -1;
continue;
}
break;
}
freeaddrinfo(list);
if (fd == -1) {
fprintf(stderr, "initServer(): Cannot bind to a valid socket.\n");
return -1;
}
return fd;
}
(注意:代码未经测试,甚至没有经过编译;但是基本逻辑是正确的.如果发现任何问题或错误,请在评论中告知我,以便在必要时进行检查,检查和修复.)
(Note: code is untested, not even compiled; but the underlying logic is sound. If you find any issues or errors, let me know in a comment, so I can review, check, and fix if necessary.)
这样,您可以从配置文件中读取host
和port
.如果host
为"*"
,为空或为NULL
,则该函数将尝试绑定到通配符地址. (顺便说一下,这应该是默认值;如果服务器管理员希望限制为特定接口,则可以提供该接口对应的IP地址或主机名.)
This way, you can read the host
and port
from a configuration file. If host
is "*"
, empty, or NULL
, the function will attempt to bind to the wildcard address. (This should be the default, by the way; if the server administrator wants to limit to a specific interface, they can supply either the IP address, or the host name corresponding to that interface.)
同样,系统管理员可以使用配置文件将port
指定为services
数据库(getent services
)中定义的任何字符串,或者指定为十进制数字字符串.在OP的情况下,"49152"
和"ruptime"
都可以工作.
Similarly, the system admin can use the configuration file to specify port
as any string defined in the services
database (getent services
), or as a decimal number string; in OP's case, both "49152"
and "ruptime"
would both work.
由于我在代码中指定了端口,因此是否可以避免在/etc/services/中添加新条目?
Since I am specifying the port in the code, does it allow to avoid adding a new entry in the /etc/services/?
服务数据库(运行getent services
到在您的计算机上看到它)仅包含TCP(SOCK_STREAM
)和/或UDP(SOCK_DGRAM
)协议的服务名称和端口号之间的映射.
The services database (run getent services
to see it on your machine) contains only the mapping between service names and port numbers for TCP (SOCK_STREAM
) and/or UDP (SOCK_DGRAM
) protocols.
唯一避免将ruptime 49152/tcp
条目添加到服务数据库的方法是将端口指定为十进制数字字符串"49152"
而不是名称"ruptime"
.这会影响服务器和客户端. (也就是说,即使您的服务器知道ruptime是TCP套接字的端口49152,客户端也不会知道,除非它们在自己的服务数据库中拥有.)
The only way you can avoid having to add the ruptime 49152/tcp
entry to your services database, is to specify the port as a decimal number string, "49152"
instead of name "ruptime"
. This affects both servers and clients. (That is, even if your server knows ruptime is port 49152 for TCP sockets, the clients won't know that unless they have it in their own services database.)
通常,大多数管理员不会费心编辑服务数据库,而改用显式端口号.如果您安装了防火墙(以及相关的实用程序,例如fail2ban,即使在工作站和便携式计算机上,我也建议这样做),但如果端口号清楚地显示在服务配置文件中,则可以更轻松地维护规则.
Usually, most admins do not bother editing the services database, and use the explicit port numbers instead. When you have a firewall installed (and related utilities like fail2ban, which I recommend even on workstations and laptops), it is easier to maintain the rules if the port numbers are clearly shown in the service configuration files.
我要使用端口号,
对于在同一台计算机上运行的客户端,我必须传递主机名.我认为环回地址会起作用.是什么定义了可以使用主机名而不使用环回地址创建连接的事实?是我将主机作为第一个参数传递给服务器中的getaddrinfo吗?
To the client running on the same machine I had to pass the host name. I thought the loopback address would work. What defines the fact that the connection can be created with the hostname but not with the loopback address? Is it that I am passing host as first parameter to the getaddrinfo in the server?
是的.如果将服务绑定到通配符地址,它将响应所有网络接口上的请求,包括回送地址.
Yes. If you bind the service to the wildcard address, it will respond to requests on all network interfaces, including the loopback address.
如果绑定到特定的主机名,它将仅响应对该特定网络接口的请求.
If you bind to a specific host name, it will only respond to requests to that specific network interface.
(这是由OS内核完成的,并且是将网络数据包路由到用户空间应用程序的一部分.)
(This is done by the OS kernel, and is part of how network packets are routed to userspace applications.)
这还意味着绑定到特定主机名(而不是通配符地址)的,启用了Internet的适当"服务应该真正能够侦听多个套接字(而不是仅一个套接字)上的传入连接.它不一定是绝对必要的,甚至在大多数用例中也不是必须的,但是我可以告诉您,当该服务在跨多个不同网络的计算机上运行时,它肯定会派上用场仅对其中一些服务.幸运的是,您可以使监听套接字成为非阻塞的(使用fcntl(fd, F_SETFL, O_NONBLOCK)
-我还建议在定义O_CLOEXEC
的系统上使用fcntl(fd, F_SETFD, O_CLOEXEC)
,以使监听套接字不会意外地传递给执行外部二进制文件的子进程),并且然后使用 select()
或 accept()
可用的连接;连接到达时,每个套接字都变得可读.
This also means that a "proper" internet-enabled service that binds to specific host names (rather than the wildcard address) should really be able to listen for incoming connections on several sockets, rather than only one. It may not be absolutely necessary, or even needed in most use cases, but I can tell you it sure comes in handy when the service is run on a machine straddling several different networks, and you want to provide the service to only some of them. Fortunately, you can make listening socket nonblocking (using fcntl(fd, F_SETFL, O_NONBLOCK)
-- I also recommend using fcntl(fd, F_SETFD, O_CLOEXEC)
on systems that define O_CLOEXEC
, so that the listening sockets are not accidentally passed on to child processes that execute external binaries), and then use select()
or poll()
to wait for accept()
able connections; each socket becomes readable when a connection arrives.
这篇关于Unix getaddrinfo C函数启动服务器的用法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!