套接字编程简介

套接字地址结构

IPv4套接字地址结构

通常也称为“网际套接字地址结构”,以sockaddr_in命名,定义在netinet/in.h头文件中:

struct in_addr {
    in_addr_t s_addr; /*32位的IPv4地址,网络字节序*/
};

/*POSIX规范只需要这个结构中的3个字段:sin_family,sin_addr,sin_port*/
struct sockaddr_in {
    uint8_t        sin_len;     /*套接字地址结构长度(16)*/
    sa_family_t    sin_family;  /*AF_INET*/
    in_port_t      sin_port;    /*16位TCP或UDP端口号,网络字节序*/
    struct in_addr sin_addr;    /*32位IPv4地址,网络字节序*/
    char           sin_zero[8]; /*unused*/
};

套接字地址结构可以在两个方向传递:

  • 从进程到内核
    • int bind(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    • int connect(int sockfd, struct sockaddr *sver_addr, socklen_t *addrlen)
    • ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags)
    • ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
      const struct sockadr *to, socklen_t addrlen)
  • 从内核到进程
    • int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
    • ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
    • ssize_t recvfrom(int sockfd, const void *buff, size_t nbytes, int flags,
      struct sockaddr *from, socklen_t *addrlen)
    • int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen)
    • int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen)

套接字地址结构仅在给定的主机上使用:虽然结构中的某些字段(例如IP地址和端口号)用在不同主机之间的通信中,但是结构本身并不在主机之间传递。

通用套接字

为了使套接字函数能够处理来自所支持的任何协议族的套接字地址结构,在<sys/socket.h>头文件中定义了一个通用的套接字地址结构,并将指向某个套接字地址结构的一个指针作为其参数之一:

struct sockaddr {
    uint8_t        sa_len;      /*套接字地址结构长度(>=16)*/
    sa_family_t    sa_family;   /*AF_xxx*/
    char           sa_data[14]; /*protocol-specific address*/
};

IPv6套接字地址结构

IPv6套接字地址结构在netinet/in.h头文件中定义:

struct in6_addr {
    uint8_t s6_addr[16]; /*128位IPv6地址,网络字节序*/
};

#define SIN6_LEN   /* required for compile-time tests */

struct sockaddr_in6 {
    uint8_t         sin6_len;      /* 套接字地址结构大小(28字节) */
    sa_family_t     sin6_familay;  /* AF_INET6 */
    in_port_t       sin6_port;     /* 传输层端口,网络字节序 */
    uint32_t        sin6_flowinfo; /* flow information */
    struct in6_addr sin6_addr;     /* IPv6地址,网络字节序 */
    uint32_t        sin6_scope_id; /* set of interface for a scope */
};

说明:1)sin6_flowinfo字段分为两部分,低20位是流标(flowlabel),高12位保留;2)对于具备范围的地址(scoped address),sin6_scope_id字段标识其范围,最常见的是链路局部地址的接口索引。

新的通用套接字地址结构

作为IPv6套接字API的一部分而定义的新的通用套接字地址结构struct sockaddr_storage足以容纳系统支持的任何套接字地址结构,定义在头文件<netinet/in.h>中:

struct sockaddr_storage {
    uint8_t     ss_len;    /* length of this struct (implementation dependent) */
    sa_family_t ss_family; /* address family: AF_xxx value */
    /* implementation-dependent elements to provide:
     * a) alignment sufficient to fulfill the alignment requirements of all socket
     *    address types that the system supports.
     * b) enough storage to hold any type of socket address that the system
     *    supports.
     */
};

套接字地址结构比较

不同套接字地址结构的比较

值-结果(value-result)参数

在网络编程中,值-结果参数最常见的例子是所返回套接字地址结构的长度,但还会碰到其他一些值-结果参数,如:

  • select函数中间的3个参数
  • getsockopt函数的长度字段
  • 使用recvmsg函数时,msghdr结构中的msg_namelenmsg_controllen字段
  • ifconf结构中的ifc_len字段
  • sysctl函数两个长度参数中的第一个

字节排序函数

小端(litle-endian)字节序大端(big-endian)字节序;主机字节序、网络字节序。

术语“小端”和“大端”表示多个字节值的哪一端存储在该值的起始地址。

由于历史原因和POSIX规范的规定,套接字地址结构中的某些字段(IP地址、端口号)必须按照网络字节序进行维护。主机字节序和网络字节序之间的转换使用如下4个函数:

#include <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);

字节操纵函数

名字以b(表示字节)开头的源自Berkeley的函数定义在头文件<strings.h>中:

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

名字以mem(表示内存)的源自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);

注意:当源字节串与目标字节串重叠时,bcopy能够正确处理,但是memcpy的操作结果却不可知。这时必须改用ANSI C的memmove函数。

IP地址转换函数

地址转换函数定义在头文件<arpa/inet.h>中,它们在ASCII字符串(如“206.168.112.96”)与网络字节序的二进制值(存放在套接字地址结构中的值)之间转换IP地址:

inet_aton、inet_addr和inet_ntoa函数

  • 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_addr函数在出错时返回INADDR_NONE常值,通常是一个32位均为1的值。这意味着点分十进制数串“255.255.255.255”(IPv4的有限广播地址)不能由该函数处理。

inet_ntoa函数返回值指向的字符串驻留在静态内存中,因此该函数是不可重入的。

inet_pton和inet_ntop函数

这两个函数是随IPv6出现的新函数,对于IPv4地址和IPv6地址都适用。

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

在头文件<netinet/in.h>中定义了两个常量,便于指定参数len

#define INET_ADDRSTRLEN  16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */

地址转换函数小节

其它

  1. 通配地址
  2. 字节流套接字、非阻塞套接字
  3. readwrite函数和errno变量
  4. 防御性编程(defensive programming)

当需要从某个套接字读入文本行时,为了提升性能可以使用标准I/O函数库(stdio)。不过需要指出这是种危险的做法,因为stdio缓冲区的状态是不可见的,可能导致应用程序中存在相当隐蔽的缺陷。

基于文本行的网络协议有很多,如SMTP、HTTP、FTP的控制连接协议以及finger等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值