博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【unix网络编程第三版】阅读笔记(二):套接字编程简介
阅读量:4197 次
发布时间:2019-05-26

本文共 8842 字,大约阅读时间需要 29 分钟。

unp第二章主要将了TCP和UDP的简介,这些在《TCP/IP详解》和《计算机网络》等书中有很多细致的讲解,可以参考本人的这篇博客,这篇博客就不再赘述。

本篇博客主要记录套接字编程API,从一些基本的API来一步一步了解套接字网络编程。

1.套接字地址结构

大多数的套接字函数都以一个指向套接字地址结构的指针作为参数。每个协议簇都定义了自己的套接字地址结构。

套接字地址结构均以sockaddr_开头,并以对应每个协议簇的唯一后缀结尾。

1.1 ipv4套接字地址结构

//ipv4的套接字地址结构typedef uint32_t in_addr_t;struct in_addr{    in_addr_t s_addr;//32位的ipv4地址};//网络套接字地址结构struct sockaddr_in{    //查看in.h源码发现此处为__SOCKADDR_COMMON (sin_)    uint8_t sin_len  //套接字地址结构的长度    sa_family_t sin_family //   AF_INET    in_port_t sin_port; //端口号    struct in_addr sin_addr;    //网络地址    unsigned char sin_zero[8];//不使用,该字段必须为0};

通常我们在使用套接字的时候,只会用到三个字段:sin_family,sin_addr和sin_port,如下:

struct sockaddr_in  servaddr;//声明一个套接字地址bzero(&servaddr, sizeof(servaddr));//套接字结构体清0servaddr.sin_family = AF_INET;//使用ipv4的协议簇servaddr.sin_port   = htons(13);//端口号,13为获取日期和时间的端口

1.2 通用套接字结构

当作为一个参数传递进任何套接字函数时,套接字地址总是以引用形式来传递。

类似于void*代表通用指针类型,通用套接字地址结构可以便于参数传递,使得套接字函数能够处理来自所支持的任何协议簇的套接字地址结构。

struct sockaddr{    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */    char sa_data[14];       /* Address data.  */};

以bind函数为例,说明它的用法:

struct sockaddr_in  servaddr;//SA为struct scokaddr的简写Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//必须对servaddr进行强制转换//附:Bind函数的原型int bind(int , struct sockaddr* , socklen_t);

1.3 IPv6的套接字地址结构

struct in6_addr{    uint8_t __u6_addr8[16];//128位IPv6地址,网络字节序}struct sockaddr_in6{    uint8_t sin_len  //套接字地址结构的长度,128    sa_family_t sin_family //   AF_INET6    in_port_t sin6_port;    //传输端口    uint32_t sin6_flowinfo; //IPv6 流标字段    struct in6_addr sin6_addr;  //IPv6 地址    uint32_t sin6_scope_id; //对于具备范围的地址,此字段标志其范围};

1.4 新的通用套接字地址结构

为IPV6套接字API定义的新的通用套接字地址结构,其足以容纳所支持的任何套接字地址结构

书中说到在in.h文件中,可是我怎么也找不到。后来在usr/include/X86_64-linux-gnu/bits的socket.h中找到

struct sockaddr_storage{    uint8_t ss_len;//length of this struct    sa_family_t ss_family; // address family:AF_XXXX value    __ss_aligntype __ss_align;  /* Force desired alignment.  */    char __ss_padding[_SS_PADSIZE];//enough storage to hold any type of socket address that the system supports};

2. 值-结果参数

当一个套接字函数传递一个套接字地址结构时,往往是采用引用的方式传递。为了读取正确的字节数和写入时不会越界,该套接字的长度也需要作为参数传递给套接字函数。

不过,其传递方式取决于该结构的传递方向:是从进程到内核,或者内核到进程

2.1 从进程到内核

这类函数有三个:bind,connect和sendto,其传递的时该结构的整数大小,这样内核就知道从进程中复制多少数据,例如:

struct sockaddr_in serv; connect( sockfd, ( SA * )&serv, sizeof( serv ) );//第三个参数为传递的该结构的整数大小

2.2 从内核到进程

这类函数有4个,accept,recvfrom,getsockname和getpeername,其传递的是指向某个套接字结构的指针和指向表示该结构大小的整数变量和指针:

struct sockaddr_un cli;socklen_t len;len = sizeof(cli);//len is valuegetpeername(unixfd,(SA*)&cli,&len);//len既作为值被传递进去,又作为结果返回出来

3. 字节排序函数

3.1 大端和小端字节序

字节序分为大端字节序和小端字节序,以下面的代码来判断系统到底时何种字节序:

#include "unp.h"int main(int argc, char const *argv[]){    union {        short s;        char c[sizeof(short)];    }un;    un.s = 0x0102;    printf("%s: ", CPU_VENDOR_OS);    if (sizeof(short) == 2)    {        if (un.c[0]==0x01 && un.c[1]==0x02)        {            printf("big-endian\n");        }        else if(un.c[0]==0x02 && un.c[1]==0x01)        {            printf("little-endian\n");        }        else             printf("unknown\n");    }    else         printf("sizeof(short)=%d\n", (int)sizeof(short));    exit(0);    return 0;}

运行此代码后输出:

$ gcc byteorder.c -o byteorder -lunp$ ./byteorder x86_64-unknown-linux-gnu: little-endian

代表本机时小端字节序

3.2 转换函数

网络字节序使用大端字节序来传送,那么本机和网络之间要正确传递数据,就需要一个转换函数。

这两种字节序之间的转换使用以下4种函数:

uint16_t htons( uint16_t host16bitvalue );uint32_t htonl( uint32_t host32bitvalue );----均返回:网络字节序的值uint16_t ntohs( uint16_t net16bitvalue );uint32_t ntohl( uint32_t net32bitvalue );----均返回:主机字节序的值

h代表host主机,n代表net网络,s代表short,l代表long

测试用例:将0x1234(4660)从主机字节序转换成网络字节序,转换后应该为0x3412(13330)

#include "unp.h"int main(int argc, char const *argv[]){    uint16_t portAddr;    portAddr = htons(0x1234);//十进制值为4660    printf("the port 0x1234 netword port is:%d\n", portAddr);    printf("the port is:%d\n", ntohs(portAddr));    return 0;}//输出结果the port 0x1234 netword port is:13330the port is:4660//答案正确

4. 字节操纵函数

操作多字节字段的函数有两组,他们既不对数据作解释,也不假设数据是以空字符结束的C字符串。

#include 
//b开头(表示字节)的一组函数void bzero(void* dest,size_t nbytes);//将指针dest以后的nbytes位置0void bcopy(const void* src,void *dest , size_t nbytes);//将指针src后的nbytes位复制到指针destint bcmp(const void *ptr1, const void* ptr2, size_t nbytes);//比较ptr1和ptr2后的nbytes位的大小//mem开头(表示内存)的一组函数void *memset(void *dest , int c , size_t len);//将dest开始的一段长度为len的内存的值设为cvoid *memcpy(void *dest , const void* src , size_t nbytes);//同bcopyint memcmp(const void *ptr1 , const void *ptr2, size_t nbytes);//同bcmp

5.地址转换函数

5.1 inet_aton,inet_addr和inet_ntoa函数

在点分十进制数串和与它长度为32位的网络字节序二进制值间转换ipv4地址

//将__cp所指的字符串转换成一个32位的网络字节序,保存在__inp结构体中int inet_aton (const char *__cp, struct in_addr *__inp) ;//返回:若字符串有效则为1,否则为0//同上,只不过转换后的值作为返回值直接返回in_addr_t inet_addr (const char *__cp);//如字符串有效则返回32位字节序,否则返回INADDR_NONE//将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串char *inet_ntoa (struct in_addr __in);//返回一个点分十进制数串的指针

测试用例:输入一个点分十进制网络地址,测试输出

#include "unp.h"int main(int argc, char const *argv[]){    struct in_addr addr;    char *pAddr;    inet_aton(argv[1],&addr);    printf("%d\n",addr.s_addr);    pAddr = inet_ntoa(addr);    printf("%s\n",pAddr);    return 0;}//输出结果:$ ./Test_inet_aton 127.0.0.1  //0x7F00000116777343//小端字节序,0100007F127.0.0.1//转换为点分十进制数串

5.2 inet_pton和inet_ntop函数

随着IPv6出现的新函数,p代表表达式,即ASCII字符串;n代表数值,即存放在套接字地址结构中的二进制值。

这两个函数对于ipv4和ipv6都适用。

//int __af为AF_INET或者AF_INET6//将__cp存储的字符串转换成二进制,结果存放在__bufint inet_pton (int __af, const char *__cp, void * __buf)//将__cp所指的二进制转换成字符串,__len指定二进制大小,const char *inet_ntop (int __af, const void *__cp, char *__buf, socklen_t __len)

5.3 sock_ntop和相关函数(作者自定义的函数)

inet_ntop函数的基本问题时要求调用者传递一个指向某个二进制地址的指针,而该地址通常包含在一个套接字地址中

因此调用者事先需要知道这个结构的格式。

struct sockaddr_in addr;//需要事先定义,如果为ipv6则为sockaddr_in6inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));

为了解决这个问题,作者自己写了一个函数:

#include "unp.h"char *sock_ntop(const struct sockaddr *sa, socklen_t salen);//具体实现如下:char *sock_ntop(const struct sockaddr *sa, socklen_t salen){    char        portstr[8];    static char str[128];       /* Unix domain is largest */    switch (sa->sa_family) {    case AF_INET: {        struct sockaddr_in  *sin = (struct sockaddr_in *) sa;        if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)            return(NULL);        if (ntohs(sin->sin_port) != 0) {            snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));            strcat(str, portstr);        }        return(str);    }/* end sock_ntop */#ifdef  IPV6    case AF_INET6: {        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;        str[0] = '[';        if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL)            return(NULL);        if (ntohs(sin6->sin6_port) != 0) {            snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port));            strcat(str, portstr);            return(str);        }        return (str + 1);    }#endif//后续省略了一些case,如case AF_UNIX:}

6. readn,writen和readline

字节流套接字上调用read和write输入和输出的字节数可能比请求的数量要少,所以作者自己写了readn,writen和readline三个函数。

readn的实现如下:

#include    "unp.h"ssize_t                     /* Read "n" bytes from a descriptor. */readn(int fd, void *vptr, size_t n){    size_t  nleft;    ssize_t nread;    char    *ptr;    ptr = vptr;    nleft = n;    while (nleft > 0) {        if ( (nread = read(fd, ptr, nleft)) < 0) {
//循环读取 if (errno == EINTR) nread = 0; /* and call read() again */ else return(-1); } else if (nread == 0) break; /* EOF */ nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */}/* end readn */

writen的实现如下:

#include    "unp.h"ssize_t                     /* Write "n" bytes to a descriptor. */writen(int fd, const void *vptr, size_t n){    size_t      nleft;    ssize_t     nwritten;    const char  *ptr;    ptr = vptr;    nleft = n;    while (nleft > 0) {        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {            if (nwritten < 0 && errno == EINTR)                nwritten = 0;       /* and call write() again */            else                return(-1);         /* error */        }        nleft -= nwritten;        ptr   += nwritten;    }    return(n);}

readline的实现如下:

#include    "unp.h"//较慢的一个版本,需要每次都调用系统的read函数//作者还写了一个较快的版本,这里就不贴出代码了,主要思想是定义一个my_read一次读取进来,但每次都只返回给readline一个字符ssize_treadline(int fd, void *vptr, size_t maxlen){    ssize_t n, rc;    char    c, *ptr;    ptr = vptr;    for (n = 1; n < maxlen; n++) {        if ( (rc = read(fd, &c, 1)) == 1) {            *ptr++ = c;            if (c == '\n')                break;        } else if (rc == 0) {            if (n == 1)                return(0);  /* EOF, no data read */            else                break;      /* EOF, some data was read */        } else            return(-1); /* error */    }    *ptr = 0;    return(n);}/* end readline */

转载地址:http://vmyli.baihongyu.com/

你可能感兴趣的文章
SQL语言的组成部分 ddl dcl dml
查看>>
mysql数据库从库同步延迟的问题
查看>>
1.mysql数据库主从复制部署笔记
查看>>
mysql数据库主从同步的问题解决方法
查看>>
mysql 配置 - on xFanxcy.com
查看>>
MySQL数据库高并发优化配置
查看>>
mysql一: 索引优化
查看>>
测试人员,今天再不懂BDD就晚了!
查看>>
35岁后还被职场青睐的人,都做了这几件事
查看>>
全链路压测那点事(一)
查看>>
阿里巴巴开源性能监控神器Arthas初体验
查看>>
使用猴子测试工具(7)
查看>>
使用猴子测试工具(8)
查看>>
一个简单的猴子测试小工具
查看>>
是QA还是AQ?
查看>>
害怕自动化(1)
查看>>
Script and Test Data
查看>>
在ITPub上发表文章《如何进行测试自动化的成本估算》
查看>>
深圳市软件质量提升工程系列活动——安全测试百人大课堂
查看>>
做培训讲师就像做一名导演
查看>>