UNIX网络编程总结三

套接口结构

IPv4套接口地址结构:

1

2

3

4

5

6

7

struct sockaddr_in{/*16字节*/

uint8_t sin_len;  /*结构体长度,8位*/

sa_family_t sin_family;/*一般来说为AF_INET或PF_INET,8位*/

ln_port_t sin_port;/*必须要采用网络数据格式,16位*/

struct in_addr sin_addr;/*网络字节序,32位*/

unsigned char sin_zero[8];/*8字节为了跟SOCKADDR结构在内存中对齐*/ 

};

1

2

3

4

struct in_addr

{

   in_addr_t s_addr;

}

 

进程到内核传递套接口地址结构的4个套接口函数:bind,connect,sendto,sendmsg,源自Berkeley的sockargs函数从这四个函数获取地址,并以传入的长度设置sin_len成员。

内核到进程传递套接口地址的5个套接口函数:accetp,recvfrom,recvmsg,getpeername和getsockname,在返回前设置sin_len。

几乎所有实现都增加sin_zero成员,,sin_zero成员暂时不使用,但总是将它置为0。

IPv4地址和TCP、UDP端口号,在套接口地址结构中以网络字节序来存储。

 

通用套接字地址结构

1

2

3

4

5

6

struct sockaddr

{

   uint8_t sin_len;

   sa_family_t sa_famliy;

   char sa_data[14];   /*14字的协议地址*/

}

套接口函数被定义为指向通用套接口地址的指针,对于这些函数的调用,必须将特定协议的套接口地址结构的指针类型转换为指向通用套接口地址的指针,例如:

1

2

struct sockaddr_in serv;

bind(fd, (struct sockaddr *)&serv, sizeof(serv)

 

IPv6套接口地址结构:

1

2

3

4

5

6

7

struct sockaddr_in6{/*24字节*/

uint8_t sin6_len;  /*结构体长度,8位*/

sa_family_t sin6_family;/*一般来说为AF_INET或PF_INET,8位*/

in_port_t sin6_port;/*必须要采用网络数据格式,16位*/

uint32_t sin6_flowinfo;/*流标,32位,24位流量标号+4位优先级+4位保留*/

struct in6_addr sin6_addr;/*网络字节序,128位*/

};

1

2

3

4

struct in6_addr

{

   in_addr_t s6_addr[16]; /*128位*/

}

从进程到内核的函数我们仍然用bind举例,bind将指向套接口地址结构的指针和结构长度传给内核,这样内核就知道从进程拷贝多少数据,但是这只是一个最大值,实例可能并没有拷贝那么多数据,具体拷贝了多少存档在len的指针里,而从内核到进程,传递的是套接口地址结构的指针和表示地址结构大小的指针,该指针指向的长度就是实际写入的长度。因为当函数被调用时结构大小是一个值,当函数返回时,结构大小是一个结果。当使用值-结果参数作为套接口地址结构的长度时,若套接口地址结构是定长的,则从内核返回也是定长的,否则,返回值可能比结构的最大长度小。

大端-小端

将低序字节存在起始地址为小端,将高序字节存在起始地址为大端。比如:数字16的16进制表示为0x0010,数字4096的16进制表示为0x1000。由于Intel机器是小尾端,存储数字16时实际顺序为1000,存储4096时实际顺序为0010。我们可以通过一小段程序来判断大端还是小端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#include <stdio.h>

# 通过存储位置来确定字节序

int main(int argc, char **argv){

    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");

        else if(un.c[0] == 2 && un.c[1] == 1)

            printf("little-endian\n");

        else

            printf("UNKNOW\n");

    }else

        printf("sizeof(short) = %d", sizeof(short));

    return 0;

}

  而网际协议在处理多节整数时使用大端字节序,因为存在不一致,我们就需要考虑主机字节序和网络字节序的转换转换用如下四个函数,h代表host,n代表net,s代表short,l代表long:

1

2

3

4

5

6

7

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

 

协议无关的地址转换函数

以下两个函数协议无关,IPv4和IPv6均可处理,字母p和n分别代表presentation和numeric。

1

2

3

4

5

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

 

下面是一个只支持IPv4的简单inet_pton:

1

2

3

4

5

6

7

8

9

10

11

1213

14

15

16

17

18

19

20

21

22

23

24

25

26

27

#include <arpa/inet.h>

#include <sys/socket.h>

#include <string.h>

#include <errno.h>

#include <stdio.h>

 

int main(int argc, char **argv){

    struct sockaddr_in  servaddr;

    int inets_pton_t(int, const char *, void *);

    servaddr.sin_family = AF_INET;

    servaddr.sin_port   = htons(2329);

    inet_pton_t(AF_INET, argv[1], &servaddr.sin_addr);

    printf("addr is %d\r\n", servaddr.sin_addr);

    return 0;

}

int inet_pton_t(int family, const char *strptr, void *addrptr){

    if(family == AF_INET){

        struct in_addr in_val;

        if(inet_aton(strptr, &in_val)){

            memcpy(addrptr, &in_val, sizeof(struct in_addr));

            return 1;

        }

        return 0;

    }

    errno = EAFNOSUPPORT;

    return -1;

}

下面是一个只支持IPv4的简单的inet_ntop:

1

2

3

4

5

6

7

8

9

10

11

1213

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

#include <stdio.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <string.h>

#include <errno.h>

 

#ifndef INET_ADDRSTRLEN

#define INET_ADDRSTRLEN 16

#endif

 

int main(int argc, char **argv){

    const char *inet_ntop_t(int, const void *, char *, size_t);

    struct sockaddr_in  servaddr;

    servaddr.sin_family = AF_INET;

    servaddr.sin_port = htons(2329);

    servaddr.sin_addr.s_addr = 606284554;

    char c[INET_ADDRSTRLEN];

    const char *str_ptr;

 

    str_ptr = inet_ntop_t(AF_INET, &servaddr.sin_addr, c, sizeof(c));

    printf("str_ptr is %s\r\n", str_ptr);

    printf("addr is %s\r\n", c);

    return 0;

}

 

const char *inet_ntop_t(int family, const void *addrptr, char *strptr, size_t len){

    const u_char *p = (const u_char *)addrptr;

    if(family == AF_INET){

        char temp[INET_ADDRSTRLEN];

        snprintf(temp, sizeof(temp), "%d, %d, %d, %d", p[0], p[1], p[2], p[3]);

        if(strlen(temp) >= len){

            errno = ENOSPC;

            return NULL;

        }

    strcpy(strptr, temp);

    return (strptr);

    }

    errno = EAFNOSUPPORT;

    return NULL;

}

 

readn_t, writen_t, readline_t

这里定义了几个函数,是对read,write的封装,是为了预防返回不足的字节计数值,比如write的时候内核套接口缓存区已经满了,只写入部分,那我们仍然要继续写入,直到写完,读也是一样。

下面给出以上三个函数代码,这些代码都是跑过的,可直接拷贝使用:

readn_t:

1

2

3

4

5

6

7

8

9

10

11

1213

14

15

16

17

18

19

20

21

22

#include "lib_sock.h"

 

ssize_t readn_t(int fd, void *vptr, size_t n){

    ssize_t nleft;

    ssize_t nread;

    char *ptr;

    

    ptr = vptr;

    nleft = n;

    while(nleft > 0){

        if((nread = read(fd, ptr, nleft)) < 0){ # 读取n个数据

            if(errno == EINTR) 

                nread = 0; # 信号中断,nleft不变,重新读取

            else

                return -1; # 否则报异常

        }else if(nread == 0) # 为0则已无数据,直接结束

            break;

        nleft -= nread;

        ptr += nread;

    } # 循环结束,则数据读取完成

    return (n - nleft); # 返回实际读取的size。

}

writen_t:

1

2

3

4

5

6

7

8

9

10

11

1213

14

15

16

17

18

19

20

21

22

#include "lib_sock.h"

 

ssize_t writen_t(int fd, const void *vptr, size_t n)

{

    size_t n_left;

    ssize_t nwriten;

    const char *ptr;

 

    ptr = vptr;

    n_left = n;

    while(n_left > 0){ # 一直写,直到所有数据全部写入

        if((nwriten = write(fd, ptr, n_left)) <= 0){

            if(errno == EINTR)

                nwriten = 0;

            else

                return -1;

        }

        n_left -= nwriten;

        ptr += nwriten;

    }

    return n; 

}

 

readline_t:

1

2

3

4

5

6

7

8

9

10

11

1213

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#include "lib_sock.h"

 

static ssize_t my_read(int, char *);

 

ssize_t readline_t(int fd, void *vptr, size_t maxlen){

    ssize_t n, rc;

    char c, *ptr;

 

    ptr = vptr;

    for(n=1; n<maxlen; n++){

      again:

        if((rc = my_read(fd, &c)) == 1){ # 每次读取一个数值

            *ptr++ = c;

            if(c == '\n') # 遇到换行符,则一行读取完成,break

                break;

        }else if(rc == 0){# 未读取到信息

            if(n == 1) # 数据为空

                return 0;

            else    # 数据读取完成

                break;

        }else{ 

            if(errno == EINTR)

                goto again;

            return -1;

        }

    }

    *ptr = 0;

    return n;

}

 

static ssize_t my_read(int fd, char *ptr){

    static int read_cnt = 0;

    static char *read_ptr;

    static char read_buf[MAXLINE];

 

    if(read_cnt <= 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)

            return 0;

        read_ptr = read_buf;

    }

    read_cnt--;

    *ptr = *read_ptr++; # 每次取一个数据

    return 1;

}

测试套接字是否为套接口描述字的函数isfdtype

 1

 2

 3

 4

 5

 6

 7

 8

 9

10

11

#include "../lib/lib_sock.h"

 

int isdftype(int fd, int fdtype){

    struct stat buf;

    if(fstat(fd, &buf) < 0)

        return -1;

    if((buf.st_mode & S_IFMT) == fdtype)

        return 1;

    else

        return 0;

}

posted @ 2018-07-19 17:49  Small_office  阅读(164)  评论(0编辑  收藏  举报