套接口结构

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;

}

UNIX网络编程总结三的更多相关文章

  1. 【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数

    本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O ...

  2. 【unix网络编程第三版】阅读笔记(三):基本套接字编程

    unp第三章主要介绍了基本套接字编程函数.主要有:socket(),bind(),connect(),accept(),listen()等. 本博文也直接进入正题,对这几个函数进行剖析和讲解. 1. ...

  3. 【UNIX网络编程第三版】阅读笔记(一):代码环境搭建

    粗略的阅读过<TCP/IP详解>和<计算机网络(第五版)>后,开始啃这本<UNIX网络编程卷一:套接字联网API>,目前linux下的编程不算太了解,在阅读的过程中 ...

  4. 【unix网络编程第三版】ubuntu端口占用问题

    <unix网络编程>一书中的代码并不是能直接运行,有时候需要结合各方面的知识来解决,大家在这本书的时候,一定要把代码都跑通,不难你会错过很多学习的机会! 1.问题描述 本人在阅读<U ...

  5. 【unix网络编程第三版】阅读笔记(二):套接字编程简介

    unp第二章主要将了TCP和UDP的简介,这些在<TCP/IP详解>和<计算机网络>等书中有很多细致的讲解,可以参考本人的这篇博客[计算机网络 第五版]阅读笔记之五:运输层,这 ...

  6. 【UNIX网络编程(三)】TCP客户/server程序演示样例

    上一节给出了TCP网络编程的函数.这一节使用那些基本函数编写一个完毕的TCP客户/server程序演示样例. 该样例运行的过程例如以下: 1.客户从标准输入读入一行文本,并写给server. 2.se ...

  7. 【unix网络编程第三版】阅读笔记(四):TCP客户/服务器实例

    本篇博客主要记录一个完整的TCP客户/服务器实例的编写,以及从这个实例中引发的对僵死进程的处理等问题. 1. TCP客户/服务器功能需求 本实例完成以下功能: (1) 客户从标准输入读入一行文本,并写 ...

  8. unix网络编程第三版源代码ubuntu下配置的问题解决

    第一步:首先下载本书配套的源码unpv13e.tar.gz 第二步:解压后进入根文件夹有一个README 4 Execute the following from the src/ directory ...

  9. Unix网络编程第三版源码编译

    配置: $ cd Unix-Network-Programming/ $ chmod 755 configure $ ./configure 主要的工作是检查系统是否有源码编译所依赖的各种资源(系统版 ...

随机推荐

  1. 浅谈IPv4至IPv6演进的实施路径

    作者:个推运维平台网络工程师 宗堂   1 业务背景 在互联网呈现爆炸式发展的今天, IPv4网络地址数量匮乏等问题将会影响到我国的互联网发展与应用,制约物联网.5G等新业务开展.今年4月国家工信部发 ...

  2. SQL报错:ORA-00911:无效的字符错误

    转载自:https://blog.csdn.net/huangyanlong/article/details/38096469 *)ORA-00911:无效的字符错误——由编译环境下一个小错误引起.S ...

  3. 关于Java协变性的思考

    简而言之,如果A IS-A B,那么A[] IS-A B[]. 举例:现在有类型Person.Employee和Student.Employee 是一个(IS-A) Person,Student是一个 ...

  4. C#和.NET获取绝对路径

    c#获取绝对路径:System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt"); .net获取绝 ...

  5. Eclipse的switch workspace 选项中删除多余的workspace

    方法1 Eclipse图形化工具: 打开Eclipse后,选择功能菜单里的Windows->Preferences->,弹出对话框后,选择General->Startup and S ...

  6. 树的基本概念以及java实现二叉树

    树具有的特点有: (1)每个结点有零个或多个子结点 (2)没有父节点的结点称为根节点 (3)每一个非根结点有且只有一个父节点 (4)除了根结点外,每个子结点可以分为多个不相交的子树.   树的基本术语 ...

  7. VUE 全局监听sessionStorage变化

    在做项目的时候,可能需要在其他模块获取推送的信息或者变量,但是数据量或者说数目少,而且项目中也没有引用VUEX,那么可以下手的方法之一也就是sessionStorage类的浏览器存储了. 首先在全局的 ...

  8. MIUI8改MAC

    1.手机Root 2.RE文件管理器复制:/data/nvram/APCFG/APRDEB/WIFI 到电脑上 3.如下图用UltraEdit打开WIFI文件修改MAC 4.拷贝修改后的WIFI文件到 ...

  9. Notepad++的tab设置为四个空格

    参考:https://www.cnblogs.com/jyfootprint/p/9409934.html 1.Python使用缩进来组织代码块,坚持使用4个空格的缩进. 在文本编辑器中,需要设置把T ...

  10. sql exist 和not exist(转载)

    exists : 强调的是是否返回结果集,不要求知道返回什么, 比如:  select name from student where sex = 'm' and mark exists(select ...