linxu套接字头文件:
#include <sys/socket.h>
创建套接字:
int socket(int domain,int type,int protocol);
domain:
AF_INET IPv4
AF_INET6 IPv6
AF_UNIX Unix域
AF_UNSPEC 未指定
type:
SOCK_DGRAM 长度固定,无连接,不可靠传递 默认协议是UDP
SOCK_RAW ip协议的数据报接口
SOCK_SEQPACKET 长度固定,有连接,可靠的传递
SOCK_STREAM 有序,可靠,双向的连接字节流 默认协议是TCP
protocol:
一般为0,表示按给定的域和套接字类型选择默认协议
调用socket和open类似,均可以使用输入/输出文件描述符(但并非所有,例如lseek),不再使用时候用close关闭
可以使用shutdown禁止套接字上的输入输出
int shutdown(int sockfd,int how);
how:
SHUT_RD 关闭读端,无法从套接字上读取数据
SHUT_WR 关闭写端,无法从套接字上发送数据
SHUT_RDWR 无法读和写
关闭套接字:
close(int sockfd);
字节排序:
字节排序有两种,大端和小端,在32位机器上,大端排序为:n,n+1,n+2,n+3,小端排序为:n+3,n+2,n+1,n,但是不管字节排序如何,数字最高位总是在左边,最小位总是在右边,比如0x04030301,数字最高位必定是4,最低位必定是1
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32); 返回值:网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); 返回值:网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32); 返回值:主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16); 返回值:主机字节序表示的16位整数
记忆方法:
h=host,n=network,l=long(表示4个字节),s=short(表示两个字节);
注意:
有些系统将其定义在<netinet/in.h>
地址格式:
为使不同格式地址能够被传入套接字函数,地址被强制转换成sockaddr表示:
struct sockaddr
{
sa_family_t sa_family;
char sa_data[];
......
};
套接字可以自由添加额外成员并自定义sa_data的大小,在linxu中,被定义为:
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
FreeBSD:
struct sockaddr
{
unsigned char sa_len;
sa_family_t sa_family;
char sa_data[14];
};
#include <netinet/in.h>
IPv4中(AF_INET)中,套接字地址结构为:
struct in_addr
{
in_addr_t s_addr; in_addr_t为uint32_t
};
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port; in_port_t为uint16_t
struct in_addr sin_addr;
};
uint32_t和uint16_t定义在<stdint.h>
IPv6(AF_INET6):
struct in6_addr
{
uint8_t s6_addr[16];
};
struct sockaddr_in6
{
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
以上为必须定义,可以自由添加额外的字段:
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8]; //扩充字段,必须全部置为0
};
地址格式化:
#include <arpa/inet.h>
const char *inet_ntop(int doman,const void *restrict addr,char *restrict str,socklen_t size); 如果成功,返回地址字符串,否则返回NULL;
将网络字节序的二进制地址转换成文本字符串
size指定网络文本字符串缓冲区大小,INET4_ADDRSTRLEN和INET6定义足够大用来存放文本字符串
int inet_pton(int domain,const char *restrict str,void *rstrict addr); 如果成功返回1,无效返回0,出错返回-1
将文本字符串转换成网络字节序的二进制
如果domain是(AF_INET)IPv4,缓冲区addr有足够大存放32位地址,对于IPv6(AF_INET6),则需要足够大的空间存放128地址
以上两个函数均支持IPv4和IPv6,inet_addr和inet_ntoa仅支持IPv4
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
地址查询:
#include <netdb.h>
struct hostent *gethostent(void); 成功返回指针,失败NULL
打开数据文件,如果没有打开,会打开它
void sethostent(int stayopen);
如果没有打开,回绕
void endhostend(void);
关闭数据文件
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_lenght;
char **h_addr_list;
};
注意:返回的地址采用网络字节顺序
还有两个函数gethostbyname,gethostbyaddr,不过被认为是过时的,将会有替代函数
struct netent *getnetbyaddr(uint32_t net,int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
三个函数若成功返回指针,失败NULL
void setnetent(int stayopen);
void entnetent(void);
struct netent
{
char *n_name;
char **n_aliases;
int n_addrtype;
uint32_t n_net;
.....
};
按照网络字节返回,n_addrtype是一个地址足常量(例如AF_INET);
可以将协议号采用以下函数映射:
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
void setprotoent(int stayopen);
void endprotoent(void);
struct protoent
{
char *p_name;
char **p_aliases;
int p_proto;
.....
};
getservebyname可以将一个服务名字映射到一个端口号,getservbyport反之,也可以采用getservent顺序扫描服务数据库;
struct servent *getservbyname(const char *name,const char *proto);
struct servent *getservbyport(int port,const char *proto);
struct servent *getservent(void);
void setservent(int stayopen);
void endservent(void);
struct servent
{
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
.....
};
这几个函数代替gethostbyname和gethostbyaddr
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host,const char *restrict service,const struct addrinfo *restrict hint,struct addrinfo **restrict res);
成功返回0,出错返回非0
需要提供主机名字,服务名字,或者两者都提供,如果仅仅提供一个名字,另外一个必须使空指针,主机名字可以使一个节点名或者十进制计法表示的主机地址.
void freeaddrinfo(struct addrinfo *ai);
getaddrinfo返回一个addrinfo的链表,用freeaddrinfo释放.
struct addrinfo
{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
.....
};
ai_flags:
AI_ADDRCONFIG 查询配置的地址类型(IPv4 or IPv6)
AI_ALL 查找IPv4 and IPv6 地址(仅用于AI_V4MAPPED)
AI_CANONNAME 需要一个规范名
AI_NUMERICHOST 数字格式返回主机地址
AI_NUMERICSERV 以端口号返回服务
AI_PASSIVE 套接字地址用于监听绑定
AI_V4MAPPED 如果没有IPv6,返回映射到IPv4的地址
PS:
如果getaddrinfo失败,不能用perror or strerror生成错误消息,替代调用gai_strerror将错误码转换错误消息
const char *gai_strerror(int error);
int getnameinfo(const struct sockaddr *restrict addr,socklen_t alen,char *restrict host,socklen_t hostlen,char *restrict service,socklen_t servlen,unsigned servlen,unsigned int flags);
将地址转换成主机名或者服务名
flags:
NI_DGRAM 服务基于数字报而非流
NI_NAMEREQD 如果找不到主机,作为一个错误对待
NI_NOFQDN 对于本地主机,仅返回完全限定域名的节点名字部分
NI_NUMERICHOST 数字形式返回主机地址
NI_NUMERICSERV 数字形式返回服务地址
// 示例代码:
#include < netdb.h >
#include < arpa / inet.h >
#include < sys / socket.h >
#include < netinet / in .h >
#include < stdlib.h >
#include < stdio.h >
#include < string .h >
void print_family( struct addrinfo * aip)
{
printf( " family " );
switch (aip -> ai_family)
{
case AF_INET:
printf( " inet " );
break ;
case AF_INET6:
printf( " inet6 " );
break ;
case AF_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_SEQPACKET:
printf( " seqpacket " );
break ;
case SOCK_RAW:
printf( " raw " );
break ;
default :
printf( " unknown %d " ,aip -> ai_socktype);
}
}
void print_protocol( struct addrinfo * aip)
{
printf( " protocol " );
switch (aip -> ai_protocol)
{
case 0 :
printf( " default " );
break ;
case IPPROTO_TCP:
printf( " TCP " );
break ;
case IPPROTO_UDP:
printf( " UDP " );
break ;
case IPPROTO_RAW:
printf( " raw " );
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 " );
if (aip -> ai_flags & AI_NUMERICSERV)
printf( " numserv " );
}
}
int main( int argc, char * argv[])
{
struct addrinfo * ailist, * aip;
struct addrinfo hint;
struct sockaddr_in * sinp;
const char * addr;
int err;
char abuf[INET_ADDRSTRLEN];
if (argc != 3 )
{
printf( " %s nodename service " ,argv[ 0 ]);
return 1 ;
}
hint.ai_flags = AI_CANONNAME;
hint.ai_family = 0 ;
hint.ai_socktype = 0 ;
hint.ai_protocol = 0 ;
hint.ai_addrlen = 0 ;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[ 1 ],argv[ 2 ], & hint, & ailist)) != 0 )
{
printf( " error:%s\n " ,gai_strerror(err));
return 1 ;
}
for (aip = ailist;aip != NULL;aip = aip -> ai_next)
{
print_flags(aip);
print_family(aip);
print_type(aip);
print_protocol(aip);
printf( " \n\thost %s\n " ,aip -> ai_canonname ? aip -> ai_canonname: " - " );
if (aip -> ai_family == AF_INET)
{
sinp = ( struct sockaddr_in * )aip -> ai_addr;
addr = inet_ntop(AF_INET, & sinp -> sin_addr,abuf,INET_ADDRSTRLEN);
printf( " address %s\n " ,addr ? addr: " unknown " );
printf( " port %d " ,ntohs(sinp -> sin_port));
}
printf( " \n " );
}
return 0 ;
}