地址格式
一个地址标识一个特定通信域的套接字端点,地址格式与这个特定的通信域相关。为使不同的格式地址能够传入到套接字函数,地址会被强制转化成一个通用的地址结构:
struct sockaddr{
sa_family_t sa_family; //address family
char sa_data[]; //variable-length address
.
.
.
};
在IPv4因特网域(AF_INET)中, 套接字结构地址用以下结构表示:
struct in_addr{
in_addr_t s_addr; //IPv4 address
};
struct sockaddr_in{
sa_family_t sin_family; //address family
in_port_t sin_port; //port number
struct in_addr_t sin_addr; //IPv4 address
};
最终将转化为结构体 sockaddr 输入到套接字例程中。
有两个函数用于将地址格式进行转换:
char *inet_ntop(int domain, void *addr, char *str, socklen_t size)
//用于将网络字节序的二进制地址转化成文本字符串格式
char *inet_pton(int domain, char *str, void *addr)
//用于将文本字符串格式转换成网络字节序的二进制地址。
地址查询
<span style="color:#000000;">struct hostent *gethostent(void);
//可以找到给定计算机系统的主机信息
struct netent *getnetent(void);
//获取网络编号和网络名字</span>
我们可以用以下函数在协议名字和协议编号之间进行映射
struct protoent *getprotobyname(char * name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
各服务和端口号之间的关系:
struct servent *getservbyname(char *name, char *proto)
//将一个服务名映射到一个端口号
struct servent *getserbyport(int pert, char *name)
//将一个端口号映射到一个服务名
struct servent *getservent(void);
//顺序扫描服务数据库
int getaddrinfo(char *host, char *service, struct addrinfo *hint, struct addrinfo **resg)
//用于将一个主机名和一个服务名映射到一个地址
//可以提供一个可选的hint来选择符合特定条件的地址
如果getaddrinfo调用失败,不能使用perror或strerror来生成错误信息,而是调用
函数 char *gai_strerror(int error)
如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以导致返回多个addrinfo结构的情形有以下2个:
1. 如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
2. 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。
函数 getnameinfo将一个地址转换成一个主机名和一个服务名
以下两个程序说明 getaddrinfo 的使用方法:
注:总是显示bind错误,尚未搜出个明白来。
例一:
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: %s hostname\n",
argv[1]);
exit(1);
}
struct addrinfo *answer, hint, *curr;
char ipstr[16];
bzero(&hint, sizeof(hint));
hint.ai_family = AF_INET;
hint.ai_socktype = SOCK_STREAM;
int ret = getaddrinfo(argv[1], NULL, &hint, &answer);
if (ret != 0) {
fprintf(stderr,"getaddrinfo: &s\n",
gai_strerror(ret));
exit(1);
}
for (curr = answer; curr != NULL; curr = curr->ai_next) {
inet_ntop(AF_INET,
&(((struct sockaddr_in *)(curr->ai_addr))->sin_addr),
ipstr, 16);
printf("%s\n", ipstr);
}
freeaddrinfo(answer);
exit(0);
}
例二:
void print_family(struct addrinfo *aip);
void print_type(struct addrinfo *aip);
void print_flags(struct addrinfo *aip);
void print_protocol(struct addrinfo *aip);
int main(int ac, char *av[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
if(ac != 3)
fprintf(stderr,"Usage : %s nodename service", av[0]);
hint.ai_family = 0; //初始化hint结构体用于过滤
hint.ai_socktype = 0;
hint.ai_protocol = 0;
hint.ai_addrlen = 0;
hint.ai_flags = AI_CANONNAME; //需要一个规范名
hint.ai_addr = NULL;
hint.ai_next = NULL;
hint.ai_canonname = NULL;
if(getaddrinfo(av[1].av[2],&hint,&ailist) != 0)
printf("%s \n",gai_strerror(error));
for(aip=ailist; aip ; aip=aip->ai_next) //打印得到的结果
{
print_family(aip);
print_type(aip);
print_protocol(aip);
print_flags(aip);
printf("\n\thost %s", aip->ai_canonname ?aip->ai_canonname:"-");
if(aip->ai_family == AF_INET)
{
/* 获取IP地址,并把网络字节序的二进制地址转换为文本字符串地址 */
sinp = (struct sockaddr_in *)aip->ai_addr;
addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
printf(" address %s", addr ? addr:"unknown");
printf(" port %d", ntohs(sinp->sin_port));
}
printf("\n");
}
return 0;
}
void print_family(struct addrinfo *aip)
{
printf(" family-- ");
switch(aip->ai_family)
{
case AF_INET://IPv4
printf("inet");
break;
case AF_INET6://IPv6
printf("inet6");
break;
case AF_UNIX://UNIX域
printf("Unix");
break;
case AF_UNSPEC://未指定
printf("unspecified");
break;
default:
printf("unknown");
}
}
void print_type(struct addrinfo *aip)
{
printf(" type.. ");
switch(aip->ai_socktype)
{
case SOCK_STREAM:
printf("stream");
break;
case SOCK_DGRAM:
printf("datagram");
break;
case SOCK_RAW:
printf("raw");
break;
case SOCK_SEQPACKET:
printf("seqpacket");
break;
default:
printf("unknown (%d)", aip->ai_socktype);
}
}
void print_protocol(struct addrinfo *aip)
{
printf(" protocol++ ");
switch(aip->ai_protocol)
{
case IPPROTO_TCP:
printf("TCP");
break;
case IPPROTO_UDP:
printf("UDP");
break;
case IPPROTO_SCTP:
printf("SCTP");
break;
case 0:
printf("default");
break;
default:
printf("unknown (%d)", aip->ai_protocol);
}
}
void print_flags(struct addrinfo *aip)
{
printf(" flags ");
if(aip->ai_flags == 0)
printf("0");
else
{
if(aip->ai_flags & AI_PASSIVE)
printf(" passive ");
if(aip->ai_flags & AI_CANONNAME)
printf(" canon ");
if(aip->ai_flags & AI_NUMERICHOST)
printf(" numhost ");
}
}
将套接字与地址关联
int bind(int sockfd, struct sockaddr *addr, socklen_t len)
//用来关联地址和套接字
/*对于使用的地址有以下一些限制:
1、在进程正在运行的计算机上,指定的地址必须有效; 不能指定一个其他机器的地址
2、地址必须和创建套接字时的地址族所支持的格式相匹配
3、地址中的端口号必须不小于1024,除非该进程具有相应的特权(root身份)
4、一般只能将一个套接字端点绑定到一个给定地址上,尽管有些协议允许多重绑定
*/
getsockname(int sockfd, struct sockaddr *addr, socklen_t alenp)
//用来发现绑定到套接字上的地址
/*在调用 getsockname之前, 将 alenp 设置为一个指向整数的指针。该整数指定缓冲区 sockaddr 的长度。 返回时, 该整数会被设置返回地址的大小。但是,如果地址和提供的缓冲区长度不匹配,地址会被自动截断而不报错。如果当前没有地址绑定到该套接字,结果是未定的。
*/
getpeername(int sockfd, struct sockaddr *addr, socklen_t alenp)
//如果套接字已经和对等方连接, 可以用来找到对方的地址。
建立连接
int connect(int sockfd, struct sockaddr *addr, socklen_t len)
//在请求服务的进程套接字(客户端)和提供服务的进程套接字之间建立一个连接。
//所填入的地址是我们想要与之通信的服务器地址。
监听套接字
int listen(int sockfd, int backlog)
//用于宣告服务器愿意接受连接请求
//一旦系统满,就会拒绝多余的连接请求,所以backlog的值应该基于服务器期望负载和处理量来选择,其中处理量是指接受连接请求与启动服务的数量。
获取套接字信息
int accept(int sockfd, struct sockaddr *addr, socklen_t len)
/* 一旦服务器调用了listen, 所用的套接字就接收连接请求。使用 accept 函数获得连接请求并建立连接
返回的文件描述符是连接到调用connect的客户端的套接字描述符
如果服务器调用accept,并且当前没有连接请求,服务器会阻塞知道一个链接请求到来。
另外,服务器可以调用 poll 或 select 来等待一个请求的到来*/