zoukankan      html  css  js  c++  java
  • 1.unix网络编程基础知识

      接触网络编程一年多了,最近在系统的学习vnp两本书,对基础知识做一些总结,希望理解的更透彻清晰,希望能有更多的沉淀。

    1.套接口地址

      针对IPv4和IPv6地址族,分别定义了两种类型的套接口地址:sockaddr_in和sockaddr_in6,两种套接口地址结构如下所示: 

    /* IPv4地址族套接口地址结构 */ 
    struct in_addr {
        in_addr_t s_addr;             /* IPv4地址,网络序存储 */       
    } 
    struct sockaddr_in {
        uint8_t sin_len;              /* 结构体大小 */
        sa_family_t sin_family;       /* 地址族:AF_INET */ 
        in_port_t sin_port;           /* 16位端口号,网络序存储 */
        struct in_addr sin_addr;      /* IPv4地址,网络序存储 */
        char sin_zero[8]              /* 保留字段,未使用 */ 
    };
    
    /* IPv6地址族套接口地址结构 */ 
    struct in6_addr {
        uint8_t s6_addr[16];             /* IPv6地址,网络序存储 */       
    } 
    struct sockaddr_in6 {
        uint8_t sin6_len;              /* 结构体大小 */
        sa_family_t sin6_family;       /* 地址族:AF_INET */ 
        in_port_t sin6_port;           /* 16位端口号,网络序存储 */
        uint32_t sin6_flowinfo         /* 流标记或优先级,网络序存储 */
        struct in6_addr sin6_addr;      /* IPv4地址,网络序存储 */ 
    };

    这两个套接口地址结构在/netinet/in.h头文件中定义,上面结构体的描述和头文件中的定义有一些差别,比如在头文件中sa_family_t成员通过宏定义在了公共部分,另外表示结构体大小的sin_len和sin6_len成员,在某些实现中没有定义,两种套接口地址结构都定义了地址族类型成员,是自描述的结构。

      另外为了使套接口函数能够统一处理所有协议族的套接口地址结构,定义了通用套接口地址:

    /* 通用套接口地址结构 */
    struct sockaddr {
        uint8_t sa_len;                 /* 结构体大小 */
        sa_family_t fa_family;          /* 地址族 */ 
        char sa_data[14];               /* 协议地址 */ 
    } 

    通用套接口地址结构的用处只有两个:1、用于套接口函数的声明,任何使用套接口地址指针为参数的函数,其指针全部声明为指向通用套接口地址类型。2、类型转换,用于将特定于协议族的地址指针转换为通用地址指针。

    2.套接口函数

      socket创建套接口描述符:

    #include <sys/socket.h>
    /* 功能:创建套接口描述符-sockfd。 
       参数:1、domain-协议族,常用AF_INET、AF_INET6、AF_UNIX或AF_LOCAL等。 
             2、type-套接口类型,常用SOCK_STREAM、SOCK_DGRAM 等。 
             3、protocol-协议,一般设置为0,协议有内核根据前两个参数选择。 
       返回值:成功则返回非负描述符,失败返回-1。 
     */
    int socket(int domain, int type, int protocol) 

      bind将套接口描述符绑定到特定的套接口地址:

    #include <sys/socket.h>
    #include <sys/socket.h>
    /* 功能:将套接口描述符-sockfd绑定到特定的套接口地址。 
       参数:1、sockfd-套接口描述符。 
             2、addr-指向通用套接口地址的指针。 
             3、addrlen-套接口地址结构的大小。 
       返回值:成功则返回0,失败返回-1,并修改errno为相应的值。 
     */
    int bind(int sockfd, const struct sockaddr *addr,
             socklen_t addrlen);

      connect将本地描述符连接到套接口地址addr指定的服务端:

    #include <sys/socket.h>
    /* 功能:将本地描述符连接到套接口地址addr指定的服务端。 
       参数:1、sockfd-套接口描述符。 
             2、addr-指向通用套接口地址的指针。 
             3、addrlen-套接口地址结构的大小。 
       返回值:成功则返回0,失败返回-1,并修改errno为相应的值。 
     */
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

      listen将套接口描述符设置为被动连接状态,用于在指定端点进行监听:

    #include <sys/socket.h>
    /* 功能:将套接口描述符设置为被动等待连接状态,用于在指定端点进行监听。 
       参数:1、sockfd-套接口描述符。 
             2、backlog-未完成连接对了和已完成连接队列的最大值。  
       返回值:成功则返回0,失败返回-1,并修改errno为相应的值。 
     */
    int listen(int sockfd, int backlog);

      accept用于服务器端接受客户端的一个连接:

    #include <sys/socket.h>
    /* 功能:用于服务器端接受客户端的一个连接。 
       参数:1、sockfd-套接口描述符。 
             2、addr-指向通用套接口地址的指针,函数返回时保存了客户端套接口地址信息。 
             3、addrlen-套接口地址结构的大小。 
       返回值:成功则返回一个已连接到客户端的套接口描述符,失败返回-1,并修改errno为相应的值。 
     */
    int accept(int sockfd, struct sockaddr *addr, 
               socklen_t *addrlen);

     3.字节序转换函数和地址转换函数

    #include <arpa/inet.h>
    /* 功能:主机序转换成网络序。 
       参数:1、hostlong/hostshort-待转换32位长整形或者16位短整形。  
       返回值:转换成网络序的32位长整形或16为短整形。 
     */
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    /* 功能:网络序转换成主机序。 参数:1、netlong/netshort-待转换32位长整形或者16位短整形。 返回值:转换成主机序的32位长整形或16为短整形。 */ uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);

        备注:多字节类型数据的表示/存储方式分为小端序和大端序。小端序,高位字节存储在高地址内存空间,低位字节存储在低位内存空间,大端序恰恰相反。对于不同架构的主机序不一样,有的架构主机序采用小端字节序,有的架构主机序采用大端架构,另外网络序采用大端字节序当在网络上传输字节流时,不需要考虑字节序的问题,当在网络上传输多字节类型数据时需要考虑字节序问题

        验证本地host字节序的方法:

    #include <stdlib.h>
    #include <stdio.h>
    
    int main(void) {
        union {
            short a;
            char c[sizeof(short)];
        } t;
        t.a = 0x0102;
        /* 高位字节存存储于低地址内存,
         * 低位字节序存储于高地址内存  */
        if (1 == t.c[0] && 2 == t.c[1])
            printf("big-endian
    ");
        else
            printf("little-endian
    ");
        return 0;
    }

    4.多字节操纵函数

      string.h中定义了两组多字节类型操作函数,这两组函数不对待处理的多字节类型数据做任何假设。第一组函数由b开头,起源于4.2BSD,当前几乎所有支持套接口的系统都提供这一组函数。

    /* 功能:将s指向大小为n的内存空间初始化为全0。 
       参数:1、s-内存地址。
             2、n-内存大小  
       返回值:无。 
     */
    void bzero(void *s, size_t n);
    /* 功能:内存拷贝。 
       参数:1、src-源内存地址。
             2、dest-目标内存地址。
             3、n-拷贝内存大小。  
       返回值:无。 
     */
    void bcopy(const void *src, void *dest, size_t n);
    /* 功能:内存比较。 
       参数:1、s1-内存地址1。
             2、s2-内存地址2。
             3、n-比较的内存大小。  
       返回值:0-表示两块内存数据相同,非0-表示两块内存数据不同。 
     */
    int bcmp(const void *s1, const void *s2, size_t n);

      第二组函数由mem开头,起源于ansi c,由任何支持ansi c标准的系统提供。

    #include <strings.h>
    /* 功能:内存初始化。 
       参数:1、str-内存地址。
             2、c-初始化值。
             3、n-内存大小。  
       返回值:0-表示两块内存数据相同,非0-表示两块内存数据不同。 
     */
    void *memset(void *str, int c, size_t n)
    /* 功能:内存拷贝。 
       参数:1、src-源内存地址。
             2、dest-目标内存地址。
             3、n-拷贝内存大小。  
       返回值:无。 
     */
    void *memcpy(void *dest, const void *src, size_t n);
    /* 功能:内存比较。 
       参数:1、s1-内存地址1。
             2、s2-内存地址2。
             3、n-比较的内存大小。  
       返回值:0-表示两块内存数据相同,非0-表示两块内存数据不同。 
     */
    int memcmp(const void *s1, const void *s2, size_t n);

      除了上面的两组函数以为,string.h头文件定义了专门处理字符串的函数,这些函数以str开头比如strcpy/strcmp等,这些函数假设处理的字符串都以0结尾。

    5.套接口ip地址到字符串转换函数

    #include <arpa/inet.h>
    /* 功能:将字符串IP地址转换成机器IP地址,。 
       参数:1、af-地址族:AF_INET, AF_INET6。
             2、strptr-字符串ip。
             3、addrptr-地址结构。  
       返回值:1-成功,0-针对af, strptr不是有效的ip地址格式,。 
     */
    int inet_pton(int af, const char *strptr, void *addrptr);
    /* 功能:将机器IP地址转换成字符串IP地址,。 
       参数:1、af-地址族:AF_INET, AF_INET6。
             2、addrptr-机器地址结构。
             3、strptr-字符串ip。
             4、size-strptr的长度,为了防止内核写溢出。  
       返回值:成功则返回指向strptr的指针,失败-返回null,并设置errno为相应值。。 
     */
    const char *inet_ntop(int af, const void *addrptr, char *strptr, socklen_t size);

    6. 套接口读写函数

      readn从一个socket中读取n个字节(引用unp示例代码):

    size_t Readn(int fd, void *vptr, size_t n)
    {
        size_t    nleft;
        size_t    nread;
        char    *ptr;
    
        ptr = vptr;
        nleft = n;
        while (nleft > 0) {
            if ( (nread = read(fd, ptr, nleft)) < 0) {
                if (errno == EINTR)
                    nread = 0;        /* 系统调用被信号打断,重新调用 */
                else
                    return(-1);     /* 发生异常 */ 
            } else if (nread == 0)
                break;                /* 读到结束符,读到本地tcp收到的fin时 */
    
            nleft -= nread;
            ptr   += nread;
        }
        return(n - nleft);        /* return >= 0 */
    }

      Writen向套接口描述符中写入n个字节(引用unp示例代码):

    /* 功能:从套接口中读取n个字节 
       返回值:n-成功;-1:失败*/
    size_t Writen(int fd, const void *vptr, size_t n)
    {
        size_t        nleft;
        size_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;        /* 被信号打断,再次调用 */
                else
                    return(-1);            /* 写异常:write返回0-写一个tcp连接关闭的sock */
            }
    
            nleft -= nwritten;
            ptr   += nwritten;
        }
        return(n);
    }                  

      Readline从描述符fd中读取一行(引用unp示例代码):

    static int    read_cnt;/* 缓冲区中可读的字节数 */
    static char    *read_ptr;/* 当前缓冲区中可读字节的指针 */
    static char    read_buf[MAXLINE];/* 读缓冲区 */
    
    /* 功能:读去一个字节, 
       返回值:1-成功;0-读到文件结束符;-1-系统调用错误*/
    static ssize_t
    my_read(int fd, char *ptr)
    {
        if (read_cnt <= 0) { /* 当缓冲区中可读字节数为0时,从文件中读数据到缓冲区 */
    again:
            if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
                if (errno == EINTR) /* 系统调用被信号打断 */
                    goto again;
                return(-1);
            } else if (read_cnt == 0) /* 读到文件结束符,正常返回0 */
                return(0);
            read_ptr = read_buf;
        }
    
        read_cnt--;
        *ptr = *read_ptr++; /* 从缓冲区中读一个字节给用户 */
        return(1);
    }
    
    /* 功能:从fd中读取一行到vptr中,行的最大长度maxlen 
       返回值:n-成功,实际读取的字节数;-1:读失败*/
    ssize_t
    readline(int fd, void *vptr, size_t maxlen)
    {
        ssize_t    n, rc;
        char    c, *ptr;
    
        ptr = vptr;
        for (n = 1; n < maxlen; n++) {
            if ( (rc = my_read(fd, &c)) == 1) {
                *ptr++ = c;
                if (c == '
    ')
                    break;    /* newline is stored, like fgets() */
            } else if (rc == 0) {
                *ptr = 0;
                return(n - 1);    /* EOF, n - 1 bytes were read */
            } else
                return(-1);        /* error, errno set by read() */
        }
    
        *ptr = 0;    /* null terminate like fgets() */
        return(n);
    }
    
    
    /* 功能:从fd中读取一行到vptr中,行的最大长度maxlen 
       返回值:n-成功;-1-失败,并打印错误日志*/
    ssize_t
    Readline(int fd, void *ptr, size_t maxlen)
    {
        ssize_t        n;
    
        if ( (n = readline(fd, ptr, maxlen)) < 0)
            err_sys("readline error");
        return(n);
    }

      Readline存在的问题:因为间接使用了静态全局变量,因此Readline是不可重入的,不是线程安全的。

  • 相关阅读:
    【javascript】手写call,apply,bind函数
    http压缩 Content-Encoding: gzip
    【javascript】强大的CSS3/JS:帧动画的多种实现方式与性能对比
    【canvas】html5 canvas常用api总结(二)--图像变换API
    【canvas】html5 canvas常用api总结(一)--绘图API
    python的列表试用3-6
    UIImagePickerController获取照片的实现,添加overlay方法 (相机取景框)
    调试JDK1.8源码的方法
    多线程-Executor,Executors,ExecutorService,ScheduledExecutorService,AbstractExecutorService
    多线程-Fork/Join
  • 原文地址:https://www.cnblogs.com/VincentXu/p/3203911.html
Copyright © 2011-2022 走看看