上一篇分析了main函数的流程,startup也出现在其中,也说了startup的作用。这篇分析startup的源码。
int startup(u_short *port)
{
int httpd = 0;
struct sockaddr_in name;
//申请一个socket
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
//配置socket
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定socket
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
if (*port == 0) /* if dynamically allocating a port */
{
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
//开启监听
if (listen(httpd, 5) < 0)
error_die("listen");
//返回服务端的套接字描述符
return(httpd);
}
首先申请了一个套接字,这个套接字是服务端的,其次初始化了套接字地址结构的内存,之后每个成员依次赋值,继而确定了协议族以及IP和端口,接下来绑定套接字和这个地址,这里有个有意思的地方,就是从主函数传来的port的值是0,所以我们绑定的端口是端口0,当绑定的端口是0时,内核会为我们提供一个可用的端口,所以说这里传入0,我们是想让内核反馈给我们一个可用的端口,这样我们不用去实验那个端口是可用的了。下面的判断port是否等于0,是想获得内核给我提供的那个端口,因为内核为我们分配了一个端口,而我们却不知道端口是哪个,所以判断一下,如果内核为我们提供了,我们就通过getsocketname去获得这个端口,然后把我们使用的真实端口给port,最后开启监听并返回了服务端的套接字描述符。
返回的套接字可以让我们在main函数中去和客户端通信,而被改变的port的值,可以让我们在主函数中通过一个打印来提示我们当前我们正在监听的端口,从而让客户端知道去连接那个端口。
这个函数比较有意思的地方就是随机端口的使用了。利用了内核的特性来达到这样一个目的。帮助我们省了一些功夫。