套接字编程的常用函数

2014-06-23 14:29:27   最后更新: 2014-07-02 13:12:56   访问数量:885




本章节主要介绍在套接字编程中常常需要使用的几个函数,关于TCP套接字编程中连接的建立和终止等需要用到的函数,可以参见:TCP连接的建立和终止和基本TCP套接字函数

 

对于一个16位整数,由2个字节组成,内存中存储这两个字节有两种方法:

  • 小端字节序:将低序字节存储在起始地址
  • 大端字节序:将高序字节存储在起始地址

如图所示:

 

遗憾的是,现今并没有统一的标准来规定系统如何存储数据,因此,这两种字节序都有系统使用,因此我们把某个给定系统所使用的字节序称为“主机字节序”

下面的程序可以用来确定主机的字节序

/* * file: main.c * author: 龙泉居士 * date: 2013-05-12 18:22 */ #include <stdio.h> int main () { union { short s; char c[sizeof(short)]; } un; un.s = 0x0102; if (sizeof (short) == 2) { if (un.c[0] == 1 && un.c[1] == 2) printf ("big-endian\n"); if (un.c[0] == 2 && un.c[1] == 1) printf ("little-endian\n"); else printf ("unknown\n"); } return 0; }

 

当然,同样的问题也存在于32位数的存储

对于通信的每个分节来说,发送协议栈和接收协议栈必须就多字节字段的各个字节的传输顺序达成一致,因此,网络协议必须指定一个网络字节序,网际协议使用大端字节序来传送多字节整数

从理论上说,具体实现可以按照主机字节序存储套接字地址结构中的各个字段,知道需要这些字段和协议首部响应字段之间移动时,再在主机字节序和网络字节序之间进行互转,这样我们可以免于操心转换细节,然而,由于历史原因和POSIX规范的规定,套接字地址结构中的某些字段必须按照网络字节序进行维护,因此我们需要用到 <netinet/in.h> 提供的四个函数进行两种字节序之间的转换:

uint16_t htons (uint16_t host16bitvalue); uint32_t htonl (uint32_t host32bitvalue); uint16_t ntohs (uint16_t net16bitvalue); uint32_t ntohl (uint32_t net32bitvalue);

 

上面的例子中,前两个是由参数的主机字节序转换为网络字节序并返回,后两个由网络字节序转换为主机字节序并返回,分别用于 short 类型转换和 long 类型的转换(h代表host,n代表net,s代表short,l代表long)

 

在数据传输过程中的各种数据一般都被简单的视为 char * 类型,但是他们并不能使用 <string.h> 中提供的字符串操作函数进行操作

 

下面的三个函数起源于 4.2BSD,因此函数都以b开头,他们都定义于 <strings.h> 中(在POSIX.1-2008标准里已经没有 strings.h 中定义的这些函数了,因此推荐使用 ANSI C 中定义的相应函数):

void bzero (void *dest, size_t nbytes); void bcopy (const void *src, void *dest, size_t nbytes); void bcmp (const void *ptr1, const void *ptr2, size_t nbytes);

 

  • bzero 函数将目标字串中的nbytes个字节置为0
  • bcopy 函数将指定数目的字节从源字串复制到目标字串中
  • bcmp 函数比较两个任意字串,相同则返回0,否则返回非0值

 

下面的三个函数是上面三个函数在 ANSI C 中的对应版本,定义与 <string.h> 中:

void *memset (void *dest, int c, size_t len); void *memcpy (void *dest, const void *src, size_t nbytes); int memcmp (const void *ptr1, const void *ptr2, size_t nbytes);

 

  • memset 函数将目标字串中的指定数目字节置为c
  • memcpy 函数将源字串中的 nbytes 个字节复制到目标字串
  • memcmp 函数将两个字串进行比较,如果 ptr1>ptr2,则返回值大于0,否则小于0,相等返回0

 

地址转换函数在 ASCII 字符串与网络字节序的二进制值(存放在套接字地址结构中的值)之间转换网际地址

 

1、传统地址转换函数

下面的三个函数定义于 <arpa/inet.h> 中,用来在点分十进制数串与对应的网络字节序二进制值间转换IPv4地址

int inet_aton (const char *strptr, struct in_addr *addrptr); in_addr_t inet_addr (const char *strptr); char *inet_ntoa (struct in_addr inaddr);

 

  • inet_aton 函数将 strptr 所指向的 C 字符串转换成一个32位的网络字节序二进制值,并通过指针 addrptr 指向的缓冲区来存储,调用成功返回1,否则返回0
  • inet_addr 进行相同的转换,返回值为32位的网络字节序二进制值,该函数存在一个问题:不能处理255.255.255.255这个IP地址,会返回 INADDR_NONE,因为他的二进制值被用来只是该函数的失败(-1),如今,这个函数已经被废弃,应该使用 inet_aton 函数或者使用接下来将介绍的新的函数
  • inet_ntoa 函数将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串并返回

 

2、兼容 IPv4 与 IPv6 的地址转换函数

下面的两个函数是随着 IPv6 出现的新函数,这两个函数同样兼容 IPv4 地址,因此更加推荐使用下面的两个函数,函数名中的 p 和 n 分别代表表达和数值,地址的表达格式是 ASCII 字符串,数值格式则是存放到套接字地址结构中的二进制值,同样定义于 <arpa/inet.h> 中

int inet_pton (int family, const char *strptr, void *addrptr); const char *inet_ntop (int family, const void *addrptr, char *strptr, size_t len);

 

  • 如果第一个函数调用成功会返回1,如果输入不是有效表达格式则返回0,否则返回-1
  • 如果第二个函数调用成功则返回指向结果的指针,否则返回NULL

通过这两个函数的 family 参数确定是 IPv4 地址还是 IPv6 地址,对应的取值分别为 AF_INET、AF_INET6,如果使用不支持的地址族作为 family 参数,这两个函数会将 errno 置为 EAFNOSUPPORT

len 参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区,为了有助于指定这个大小,在 <netinet/in.h> 头文件中有如下定义:

#define INET_ADDRSTRLEN 16 #define INET6_ADDRSTRLEN 46

 

如果len太小,不足以容纳表达式结果,那么返回一个空指针,并且置 errno 为 ENOSPC

 

下面总结了上面的五个函数的用法:

 

字节流套接字(例如TCP套接字)上的 read、write 函数所表现的行为不同于通常的文件IO,字节流套接字上调用 read 或 write 函数输入或输出的字节数可能比请求的数量少,然而这并不是出错的状态

这个现象的原因在于内核中用于套接字的缓冲区可能已经达到了极限,这时需要再次调用 read 或 write 函数,以输入或输出剩余的字节

 






读书笔记      技术帖      c++      cpp      c语言      unp      socket      龙潭书斋      套接字     


京ICP备15018585号