Unix网络编程学习笔记之第11章 名字与地址转换

一、 域名系统(DNS)

1. 简介

DNS主要用于主机名和IP地址之间的映射。

主机名可以是简单的名字ljm,也可以是全限定域名ljm.localdomainbaidu.com等。

2.资源记录

DNS中的条目称为资源记录(RR)。我们感兴趣的RR类型只有几个:

A             A记录把一个主机名映射为一个32位的IPv4地址。

AAAA    4A记录把一个主机名映射为一个128位的IPv6地址。

例如:

ljm               IN      A    127.0.0.1

IN      AAAA3ffe:1f8d:9bc3:1234:ef93:ac89

PTR        称为指针记录。把IP地址映射为主机名。对IPv4地址,32位的4个字节先反转顺序,再添加in-addr.arpa。对于IPv6地址,128位地址每四位组先反转,再添加ip6.arpa。

例如上面主机ljm的两个PTR记录为:

1.0.0.127.in-addr.arpa和8.9.c.a.3.9.f.e….ip6.arpa

3. 解析器和名字服务器

1> 名字服务器。

每个组织机构都会有一个名字服务器(有时也叫DNS服务器)。它们保存着主机名和IP地址之间的映射的资源记录。

2> 解析器

客户端和服务器端等应用程序通过解析器的函数来对主机名或IP地址进行解析。

典型的解析器函数为gethostbyname和gethostbyaddr。

3> 具体过程

当我们需要解析某个主机名或IP地址时,我们在代码中调用解析器函数,则解析器函数就会根据解析器配置文件(/etc/resolv.conf)中的本地名字服务器的IP地址,去发出UDP查询,如果找不到,则会在整个因特网上查询其他名字服务器。如果答案太长,则本地解析器会自动切换到TCP。

二、gethostbyname和gethostbyaddr函数

1. gethostbyname函数

从主机名到IPv4地址的映射。

该函数执行的只是查询A记录,所以即使主机有IPv6地址,也不会返回。

#include     <netdb.h>
struct hostent* gethostbyname(const char* hostname);
//返回:若成功返回非NULL指针,若出错返回NULL并设置h_errno

1> 我们来看看结构体hostent:

struct hostent{
       char* h_name;      //规范名字
       char** h_aliases;  //主机别名
       int h_addrtype;    //地址族:AF_INET
       int h_length;      //地址长度:4
       char** h_addr_list;//IPv4地址
};

规范名字,也可以理解为全限定域名。

主机别名,一个主机可能有多个主机别名,所以这里是二维char数组。

地址族和地址长度,由于这里只能是IPv4的地址映射,所以这两个值不会改变,既然不会改变,为何需要这两个参数?感觉有点多余。

h_addr_list,主机可能会有多个IP地址,所以这里是二维数组。这里的IP地址显然是二进制型的,如要输出也需转换表达式型。注意这里是char型的二维数组。为何不是in_addr结构的一维数组?不得而知。

2> 当gethostbyname返回错误时:

这里当函数发送错误时,不是设置errno,而是设置h_errno。一般我们使用函数hstrerror来解析这个h_errno错误值。

const char* hstrerror(int h_errno);

3> 我们来写一个例子程序,输入任意多个主机名,输出每个主机名对应的规范名字,别名,IP地址。

#include "unp.h"
int main(int argc, char** argv)
{
       char* ptr, ** pptr;
       char buff[4];
       struct hostent * hptr;
       while(--argc>0)
       {
             ptr=*++argv;
             if((hptr=gethostbyname(ptr))==NULL)
             {
                    printf("error for host : %s : %s\n", ptr,hstrerror(h_errno));
                    continue;
             }
             printf("host name : %s\n",ptr);
             for(pptr=hptr->h_aliases;*pptr!=NULL;pptr++)
                    printf("aliases: %s\n",*pptr);
             switch(hptr->h_addrtype)
             {
             case AF_INET:
                    for(pptr=hptr->h_addr_list;*pptr!=NULL;pptr++)
                           printf("aliases: %s\n",Inet_ntop(hptr->h_addrtype, *pptr, buff, sizeof(buff)));
                    break;
             default:
                    printf("unknown address type\n");
                    break;
             }
       }
}

2. gethostbyaddr函数。

与上面的函数作用相反,从一个二进制的IPv4地址转换到相应的主机名。

#include     <netdb.h>
struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);// len为4,family为AF_INET
//返回:若成功返回非NULL指针,若出错返回NULL并设置h_errno

函数查询的是in_addr.arpa消息记录。

函数返回的是和gethostbyname返回一样的结构体,只是这里我们只关心结构体中的h_name。

三、getservbyname和getservbyport函数

上面提到我们可以用主机名来代替IP地址,下面我们可以使用服务名来代替端口号。

0. 像主机一样:可以使用主机名和IP地址来标识一台主机。

服务:可以使用服务名和端口号来标识一个服务。

一个服务可以支持多个协议(TCP,UDP,SCTP),就像一个进程可以同时监听TCP套接字和UDP套接字一样。一般一个服务只有一个端口号如http的80

服务名和端口号的映射保存在一个文件(/etc/services)中。

这样即使端口号发生变动,我们只需要修改文件(/etc/services)即可。而不需要重新编译程序。

看一下/etc/services的内容:

# service-name port/protocol  [aliases ...]   [# comment]

tcpmux         1/tcp                           #TCP port service multiplexer

tcpmux         1/udp                           # TCPport service multiplexer

http           80/tcp          www www-http    # WorldWideWeb HTTP

http           80/udp          www www-http    # HyperText Transfer Protocol

http           80/sctp                         #HyperText Transfer Protocol

https          443/tcp                         #http protocol over TLS/SSL

https          443/udp                         #http protocol over TLS/SSL

https           443/sctp                        # http protocol overTLS/SSL

1. getservbyname函数

从服务名映射到端口号。

struct servent* getservbyname(const char* servname, const char* protoname);
//返回:如果成功返回非空指针,如果失败返回NULL

此函数就是查找本地的/etc/services里面的内容,上面可知,/etc/services每一行有三个东西,service-name,port,protocol。所以本函数提供了两个参数来唯一标识端口号。

函数返回的结构体servent:

struct servent{
       char* s_name;
       char** s_aliases;
       int port;
       char* s_proto;
};

这里我们只关系的是port,注意返回的是网络字节序的,所以给sockaddr_in赋值时,无需再去转换字节序。

这里函数的第二个参数可以是NULL,此时返回哪个端口号取决于实现,但一般无所谓,因为一个服务通常不同的协议,通常对应一个端口号。

如果指定protoname,则该协议必须支持。

ser=getservbyname("tcpmux","tcp");//ok
ser=getservbyname("tcpmux","sctp");//error

2. getservbyport函数

从端口号到服务名的映射

struct servent* getservbyport(int port, const char* protoname);
//返回:如果成功返回非空指针,如果失败返回NULL

注意这里的port参数必须是网络字节序的。

struct servent* ser;
ser=getservbyport(htons(1),"tcp");//ok
ser=getservbyport(htons(1),NULL);//ok
ser=getservbyport(htons(1),"sctp");//error

之前的程序我们是用IP地址和端口号来标识一个目标主机上的进程的。到目前为止,我们就可以使用主机名和服务名来标识一个目标主机上的进程。

四、 我们把我们之前的获取时间客户端改为使用主机名和服务名来标识。

#include     "unp.h"
#define MAXLINE 1024
#define PORT 13
void err_sys(const char* s)
{
    fprintf(stderr, "%s\n", s);
    exit(1);
}
int main(int argc, char** argv)
{
    int sockfd,nbytes;
    struct sockaddr_in servaddr;
    char buff[MAXLINE+1];
       char str[128];
       struct hostent* hp;
       struct in_addr** pptr;//注意这里是一维数组,每个元素指向一个结构体。
       struct servent* ser;
       if(argc!=3)//输入主机名和服务名
        err_sys("input error");
       hp=gethostbyname(argv[1]);
       if(hp==NULL)
       {
             err_sys("wrong hostname");
       }
       pptr=(struct in_addr**)hp->h_addr_list;
       if((ser=getservbyname(argv[2],"tcp")==NULL))
             err_sys("wrong server name");
       for(;*pptr!=NULL;pptr++)
       {
             if((sockfd=socket(AF_INET,SOCK_STREAM, 0))<0)
                    continue;
             bzero(&servaddr,sizeof(servaddr));
             servaddr.sin_family=AF_INET;
             servaddr.sin_port=ser->s_port;
             memcpy(&servaddr.sin_addr,*pptr,sizeof(struct in_addr));
             printf("trying %s\n",inet_ntop(AF_INET,(struct sockaddr*)&servaddr,str,sizeof(str)));
             if(connect(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr))==0)
                    break;//success
             close(sockfd);
       }
       if(*pptr==NULL)
             err_sys("unable to connect");
       while(nbytes=read(sockfd,buff,MAXLINE)>0)
       {
             buff[nbytes]=0;
             fputs(buff,stdout);
       }
    exit(0);
}

这里我们从命令行输入参数:服务器主机名和服务名

我们调用gethostbyname来获得服务器IP地址列表,一个一个尝试连接。如果连接失败,要关闭套接字,然后重新socket,connect。不能直接重connect。

然后我们调用getservbyname来获得端口号,这里的端口号是众所周知的端口号,因为该函数是查看本机的/etc/services,来获知端口号的。而服务器主机也是利用这个众所周知的端口号服务名进行bind的。

五、getaddrinfo函数

因为gethostbyname和gethostbyaddr这两个函数只适用于IPv4地址。所以有了既支持IPv4和IPv6的函数getaddrinfo。

getaddrinfo函数能够处理IP地址和主机名的转换,而且还能同时处理端口号和服务名的映射。

#include     <netdb.h>
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo **result);
//返回:成功返回0,失败返回非0.

1. hostname为输入的主机名,service为输入服务名,hints为需要返回结果的提示信息,该信息会影响返回结果,可以是NULL。Result为函数返回的结果。

2. 说一下为何返回结果result前有两个*,该函数返回一个链表,*result指向链表的头结构,注意此时我们是让函数内部开辟内存空间,调用函数者只需提供一个指针,在函数内部修改这个指针本身,而不是要修改这个指针指向的东西。例如:

int main()
{
       char * s1, * s2;
       getaddrinfo1(&s1);
       getaddrinfo2(s2);
}
void getaddrinfo1(char** s)
{
       *s=new char[];
}
void getaddrinfo2(char* s)
{
       s=new char[];
}

显然s1指向了一个内部的合理空间,而s2经过函数调用后,仍然属于野指针。

3. 这里看一下结构体addrinfo:

struct addrinfo{
       int ai_flags;//AI_PASSIVE, AI_CANONNAME
       int ai_family;//AF_XXX
       int ai_socktype;//SOCK_XXX
       int ai_protocol;//0 or IPPROTO_XXX
       socklen_tai_addrlen;//length of ai_addr
       char* ai_canonname;//ptr to canonical name for host
       struct sockaddr* ai_addr;//ptr to socket address
       struct addrinfo* ai_next;//ptr to next struct in linked list
};

这里前4个成员是给hints参数设置的。后4个成员是result返回的结果。因为前4个成员的不同值会影响后4个成员的值。

1> ai_flags    一般的标识值为AI_PASSIVE和AI_CANONNAME

AI_PASSIVE:套接字将用于被动打开。

AI_CANONNAME:告知函数返回主机的规范名字。即ai_canonname

2> ai_family    即返回的是主机名对应的IPv4地址还是IPv6地址。

3> ai_socktype     即因为服务名可能对应多个协议,所以这里指定返回TCP还是UDP

4> ai_protocol     指定具体的协议,当ai_socktype不能唯一标识特定的协议时,就要用到此参数。即因为SCTP也属于流协议,其socktype也是SOCK_STREAM,所以如果某个服务支持TCP和SCTP,则我们就必须指明协议名。即:

IPPROTO_TCP或IPPROTO_UDP。

下面4个成员就不说了,很清楚。

4. 如果hints为NULL,则ai_flags,ai_socktype,ai_protocol的值均为0,ai_family的值为AF_UNSPEC。

5. 该函数返回是一个链表,为何?

当提供的主机名有多个IP地址时,每个IP都会返回一个对应的结构。

当未指明ai_socktype时,提供的服务名支持多个协议,则每个协议返回一个结构。

所以当主机名有2个IP地址,服务名支持tcp和udp,且未指明ai_socktype,则就会返回2*2=4个结构。

返回的链表的结构体的顺序是不固定。

6. 返回的结构体中的ai_addr可直接用于socket,connect函数调用。因为其IP地址都是二进制型的,且IP地址的类型为sockaddr,和协议无关的。且IP地址和端口号都是网络字节序的,所以无需任何转换函数。

7. 函数getaddrinfo的一些常见的输入

函数有六个可输入的参数值:hostname,,service,以及hints的前4个成员。

(1) 客户端

对于TCP和UDP客户端而言,我们使用该函数,用来创建连接,连接服务器的。所以我们需要指定hostname和service的值,而至于hints的4个成员,如果确认知道自己处理的是哪一类型套接字,应该指定ai_socktype或和ai_protocol。一般ai_flags为AI_PASSIVE。而一般不指定ai_family,因为主机有可能用于IPv4和IPv6地址,我们需要一个一个尝试socket->connect。

(2) 服务器端

对于服务器,我们使用该函数,只是用来指定端口号的。所以我们一般不指定主机名,只指定service。而主机名为空,则返回的套接字地址中的IP地址为通配地址,也正是我们想要的。

注意如果不指定ai_family或指定为AF_UNSPEC,这时至少返回两个结构,一个包含Ipv4的通配地址INADDR_ANY,另一个包含IPv6的通配地址IN6ADDR_ANY_INIT。这时我们可以使用select来监听这两个套接字。

对于hints的前4个成员,我们指定ai_flags为AI_PASSIVE。且应该指定套接字的类型,以防止返回多个结构。

注意:因为我们后续需要调用accept函数来获得套接字,我们就需要先new一个套接字结构,这个套接字结构的大小如何确定?

函数返回的链表中,addrinfo的每个结构体中都会有其套接字结构的大小ai_addrlen。所以我们根据这个值可以确认套接字的大小。

8. 可以说,getaddrinfo函数功能很强大,但使用起来也很复杂。

六、gai_strerror函数

上文提到,getaddrinfo函数,成功返回0,失败返回非0的整数.

而gai_strerror函数就是用来解释失败的错误整数的。

const char* gai_strerror(int error);
典型的使用方法:
int ret=getaddrinfo(...);
if(ret!=0)
{
       err_sys("%s\n",gai_strerror(ret));
}
 

七、freeaddrinfo函数

函数getaddrinfo函数返回一个结构体链表,其所有的存储空间都是动态获取的,如new或malloc。所以我们用完之后要释放这些内存。就是调用freeaddrinfo来释放的。

void freeaddrinfo(struct addrinfo* ai);

//典型的用法:
struct addrinfo* result;
intret=getaddrinfo(...,&result);
freeaddrinfo(result);

这样就可以释放返回的整个链表。

但是这有个问题,比如我们通过遍历找到了我们所需的结构,然后把这个结构体复制出来,然后调用该函数释放所有内存。

但是:如果只复制这个结构体本身的话,是有问题的,因为这个结构体内有指针(套接字结构指针和规范名字指针),复制的时候切记把这些指针指向的空间也要复制。不然只复制指针的话,则freeaddrinfo会释放掉所有内存,这样我们复制出来的这个结构体的指针指向的是已释放的内存,这样很危险。

所以复制结构体时,切记需要深度复制。

八、 一些实际的例子

1. 对于TCP客户端:

提供确认的主机名和服务名,然后对于服务器的每个IP地址,进行:

while(){
socket->connect
}

2. 对于TCP服务器端:

一般不提供主机名,而是提供服务名,然后对于本地的每个类型的通配地址以及服务名,进行:

while(){
socket->bind
}
accept()
…

如果bind成功,则跳出循环。有点问题,这样只能绑定一个IP地址:或为IPv4的通配地址,或为IPv6的通配地址。

九、getnameinfo函数

该函数为getaddrinfo的互补函数,即提供套接字地址,返回主机名和服务名。

<pre name="code" class="cpp">int getnameinfo(const structsockaddr* addr, socklen_t addrlen, char*hostname, socklen_t hostlen, char* serv,socklen_t servlen, int flags);
//返回:成功返回0,失败返回非0

各个参数都很明显,只有最后一个flag,其用于指示一些东西。比如:

NI_DGRAM:指示返回的服务是基于UDP的,因为有可能端口号相同的不同协议对应的是不同的服务。

其他的一些标志值:

注意:这里可以把标记值进行或,然后这样就可以同时设置两个标志值。

注意:getnameinfo和getaddrinfo都是设计DNS的,而一般服务器是不使用getnameinfo函数的,直接用IP地址来标识就可以了,因为getnameinfo设计DNS,其是很耗时的。

十、 可重入函数

1. 我们先来看看gethostbyname和gethostbyaddr的代码:

static structhostent host;
struct hostent* gethostbyname(const char* hostname)
{
       /*.....*/
       return (&host);
}
struct hostent* gethostbyaddr(const char* addr,socklen_t len, int family)
{
       /*.....*/
       return (&host);
}

可以看到函数返回的都是一个static的对象。问题就在这上面。

假如我们在一个主程序中调用gethostbyname函数,且在信号处理函数中也调用gethostbyname,看看会发生什么:

main()
{
       struct hostent* hp;
       ...
       signal(SIGALRAM,sig_alrm);
       ...
       hp=gethostbyname(...);
}
void sig_alrm(intsigno)
{
       struct hostent* hp1;
       hp1=gethostbyname(...);
}

假如此时主程序中执行到函数gethostbyname期间,而且函数已经处理好static的host对象了,准备返回了,此时来了个信号,则主程序中断,去处理这个信号,而在信号处理函数中重新调用了gethostbyname,那么该host对象将被重用,因为此时只有一个进程,只保留一个副本,这么一来,原先由主程序计算出的值被重写成了信号处理函数调用计算出的值。则这样就会产生错误。

这样就是不可重入函数。

2. 看看以前的函数可重入性:

1> gethostbyname、gethostbyaddr、getservbyname、getservbyport都是不可重入函数

2> inet_pton、inet_ntop都是可重入的。

3> getaddrinfo可重入的前提是调用的函数都是可重入的,比如函数内调用的是gethostbyname可重入版本,getservbyname的可重入版本。

4> getnameinfo可重入的前提是调用的函数都是可重入的,比如函数内调用的是gethostbyaddr可重入版本,getservbyport的可重入版本。

3. errno变量也有类似的问题。

首先,每个进程有一个errno的副本。而在同一个进程中,例如如下代码:

if(close(fd)<0)
{
       fprintf(stderr,"close error, errno= %s\n", errno);
}

如果此时close函数产生错误,则内核设置errno,当程序调用结束close时,还未来得及执行输出时,此时信号来了,信号处理函数中也产生了错误,则errno被重置,则返回到主程序时,就有问题了。

4. 解决可重入性问题的一个方法:就是在信号处理函数中永远不要调用不可重入函数,且可把errno先进行保存,然后在函数最后还原回来。如:

void sig_handler(int signo)
{
       int errno_save=errno;
       /*...other code*/
       errno=errno_save;
}

还有在信号处理函数中,不要调用标准I/O函数,如fprintf等。因为很多版本实现的标准I/O函数都是不可重入的。

5. gethostbyname_r和gethostbyaddr_r函数

首先,把不可重入函数改为可重入函数有两种方法:

1> 不可重入函数的问题在于返回一个全局static对象。而我们可以由调用者动态开辟一个对象空间,然后交由函数进行修改。如gethostbyname,我们可以让调用者先动态开辟一个hostent对象,然后让该函数来进行处理。

gethostbyname_r和gethostbyaddr_r函数就是这么做的。

但引入的问题:

调用者不仅需要new一个hostent对象,还要提供hostent对象中的指针指向的空间。

struct hostent* gethostbyname_r(const char* hostname,struct hosten* result, char*buf, int buflen, int*h_errnop);

其中result就是交由函数修改的对象。而buf就是这个对象中的指针指向的一块内存空间。错误码为h_errnop,而不是全局变量h_errno

这里很难确认这个buf的大小。不好用。

2> 我们可以让不可重入函数本身new一个对象,最后返回。而不是返回一个全局static对象。getaddrinfo函数就是这么做的。

引入的问题:该函数内部分配的空间,必须需要调用者显示释放掉,即调用函数freeaddrinfo。否则造成内存泄漏。

十一、 其他网络信息

我们上面提到了gethostby…、getservby…。

网络信息包含:主机,服务,网络,协议。因为我们有:

其中主机和网络是通过DNS来获取的。

协议和服务是通过查询本地主机的文件来获取的。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-01 22:47:17

Unix网络编程学习笔记之第11章 名字与地址转换的相关文章

Unix网络编程学习笔记之第2章 TCP和UDP

TCP 1. TCP面向连接的协议,是一个字节流协议,没有任何记录边界.发送的是数据分组. 2. TCP提供了可靠性:确认重传和重组 (1) TCP每发送一份数据都会要求对端进行确认.如果超时,就会重传.TCP会估计往返时间RTT,以确定等待多长时间重传. (2) 如果多次发送数据分组,TCP可以保证分组的按序达到.即会根据序列号进行重组. 3. TCP提供流量控制 TCP在任何时刻通知对端,它此时一次能够接受多少字节的数据,即通告窗口.该窗口指出接受缓冲区当前可用的空间. 4. 为何说TCP是

Unix网络编程学习笔记之第7章 套接字选项

一.获取/设置套接字选项的方法 一个套接字描述符相关联的套接字选项很多.获取/设置套接字选项的方法: 1.  getsockopt和setsockopt函数 2. fcntl函数 3. ioctl函数 二. getsockopt和setsockopt函数 int getsockopt(int sockfd, int level, int optname, void* optval, socklen_t* optlen); int setsockopt(int sockfd, int level,

Unix网络编程学习笔记之第6章 I/O复用:select和poll函数

一.I/O复用应用场合 1. 当客户处理多个描述符(既有标准输入,又有网络套接字)时,必须使用IO复用. 2. 一个客户同时处理多个套接字是可能的. 3. 如果一个服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用I/O复用. 4. 如果一个服务器既要处理TCP,又要处理UDP,一般就要I/O复用. 5. 如果一个服务器要处理多个服务或协议,就要用到I/O复用. 其实IO复用就是一个进程/线程处理多个套接字描述符. 二. I/O模型 Unix提供了5种I/O模型: 1. 阻塞式I/O模

Unix网络编程学习笔记之第8章 基于UDP套接字编程

一. UDP C/S的典型函数调用 UDP没有像TCP那样的连接,客户端直接sendto向某服务器发送数据,服务器端一直recvfrom阻塞,以接收任何客户端发送的数据. 二. sendto和recvfrom函数 int sendto(int sockfd, const void* buff, size_t nbytes, int flag, const struct sockaddr* to, socklen_taddrlen); int recvfrom(int sockfd, void*

Unix网络编程学习笔记之第1章 简介

一.一个简单的时间获取客户端 #include <sys/socket.h> #define MAXCON 50 #define MAXLINE 1024 #define PORT 13 void err_sys(const char* s) { fprintf(stderr, "%s\n",s); exit(1); } int main(int argc, char** argv) { int sockfd; structsockaddr_in servaddr; cha

Unix网络编程学习笔记之第5章 TCP客户端/服务器程序示例

一. 一个简单TCP回射服务端程序 #include "unp.h" #define MAXLINE 1024 #define PORT 13 #define CONMAX 5 void err_sys(const char* s) { fprintf(stderr, "%s\n",s); exit(1); } void str_echo(int connfd) { int nbyte; char buff[MAXLINE+1]; again: while(nbyt

Unix网络编程学习笔记之第12章 IPv4与IPv6的互操作性

一. 简介 假设我们本章讨论的主机都是支持双栈的,即支持IPv4地址,也支持Ipv6地址. 我们本次讨论的点:客户端与服务器端使用的是不同类型的地址.因为相同类型的地址没什么可讲的. 二. IPv4客户端与IPv6服务器 即,客户端使用IPv4地址套接字来通信,服务器端使用IPv6地址套接字通信. 原理: 0. 首先IPv6服务器主机保证既有IPv4地址,又有IPv6地址. 1. IPv4客户端通过getaddrinfo函数,找到服务器端的IPv4地址,然后进行连接. 2. 来自客户端的IPv4

Unix网络编程学习笔记之第4章 基于TCP套接字编程

1. socket函数 int socket(int family, int type,int protocol) 成返回一个套接字描述符.错误返回-1 其中family指定协议族,一般IPv4为AF_INET, IPv6为AF_INET6. 其中type指定套接字类型,字节流:SOCK_STREAM.   数据报:SOCK_DGRAM. 一般情况下通过family和type的组合都可以唯一确定一个套接字类型.所以一般我们就把protocol设为0就可以了. 有时在某些特殊情况下,family和

UNIX网络编程学习笔记2 需要用到的一些字节操纵和格式转换函数

当然这些东西是炒鸡无聊的,但是真当自己开始撸代码时才发现熟悉这些枯燥的函数能够节约大量的时间.于是总结一下: 字节序:低序字节存储在起始地址,这称为小端(little-endian),高序字节存储在起始地址,这称为大端(big-endian) 例:存放0x0A0B0C0D LE: 0D 0C 0B 0A BE: 0A 0B 0C 0D 小端的存放方式更加接近于人类思维 网际协议使用大端字节序来传送多字节整数(为何要规定一个字节序来传输ip和port? 呸 这样子协议才能正确“看懂”(解释)这些地