最近学习tinyhttpd,在服务器端的startup函数中看到了如下声明以及调用:
struct sockaddr_in name;
bind(httpd, (struct sockaddr *)&name, sizeof(name);
getsockname(httpd, (struct sockaddr *)&name, &namelen;
以下是tinyhttpd中服务器端startup函数的代码:
/**********************************************************************/
/* This function starts the process of listening for web connections
* on a specified port. If the port is 0, then dynamically allocate a
* port and modify the original port variable to reflect the actual
* port.
* Parameters: pointer to variable containing the port to connect on
* Returns: the socket */
/**********************************************************************/
/*服务器端套接字初始化设置*/
int startup(u_short *port)
{
int httpd = 0;
struct sockaddr_in name;
httpd = socket(PF_INET, SOCK_STREAM, 0);//创建服务器端套接字
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;//地址簇
name.sin_port = htons(*port);//指定端口
name.sin_addr.s_addr = htonl(INADDR_ANY);//通配地址
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)//绑定到指定地址和端口,传入的sin_port为0,bind函数会随机给sin_port分配一个端口号,但是*port还是0
error_die("bind");
if (*port == 0)
{
int namelen = sizeof(name);
/*在以端口号0调用bind后,getsockname用于返回由内核赋予的本地端口号*/
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);//网络字节顺序转换为主机字节顺序,返回主机字节顺序表达的数
}
if (listen(httpd, 5) < 0)//服务器监听客户端请求。套接字排队的最大连接个数5
error_die("listen");
return(httpd);
}
很奇怪为什么声明name的时候用的是sockadd_in结构体,而函数调用的时候传入的是sockaddr结构的name。于是查看sockadd_in和sockaddr的定义:
include <netinet/in.h>
struct sockaddr {
unsigned short sa_family; // 2 bytes address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
// IPv4 AF_INET sockets:
//sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)
struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6, 地址族
unsigned short sin_port; // 2 bytes e.g. htons(3490) TCP/UDP端口号
struct in_addr sin_addr; // 4 bytes see struct in_addr, below 32位IP地址
char sin_zero[8]; // 8 bytes zero this if you want to
};
// 该结构体中提到的另一个结构体in_addr定义如下,它用来存放32位IP地址
struct in_addr {
unsigned long s_addr; // 4 bytes load with inet_pton()
};
struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。在各种系统调用或者函数中,只要和网络地址打交道,就得用到这两个结构体。
网络中的地址包含3个方面的属性:1 地址类型: ipv4还是ipv6
2 ip地址
3 端口
那为什么声明name的时候用的是sockadd_in结构体,而函数调用的时候传入的是sockaddr结构的name?
sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了。sockaddr_in解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中。
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
通常把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数。
sockaddr_in用于socket定义和赋值
sockaddr用于函数参数
sockaddr_in是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。
参考链接:
[gcc编程] socket编程——sockaddr_in结构体操作