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; } |
UNIX网络编程总结三的更多相关文章
- 【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数
本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O ...
- 【unix网络编程第三版】阅读笔记(三):基本套接字编程
unp第三章主要介绍了基本套接字编程函数.主要有:socket(),bind(),connect(),accept(),listen()等. 本博文也直接进入正题,对这几个函数进行剖析和讲解. 1. ...
- 【UNIX网络编程第三版】阅读笔记(一):代码环境搭建
粗略的阅读过<TCP/IP详解>和<计算机网络(第五版)>后,开始啃这本<UNIX网络编程卷一:套接字联网API>,目前linux下的编程不算太了解,在阅读的过程中 ...
- 【unix网络编程第三版】ubuntu端口占用问题
<unix网络编程>一书中的代码并不是能直接运行,有时候需要结合各方面的知识来解决,大家在这本书的时候,一定要把代码都跑通,不难你会错过很多学习的机会! 1.问题描述 本人在阅读<U ...
- 【unix网络编程第三版】阅读笔记(二):套接字编程简介
unp第二章主要将了TCP和UDP的简介,这些在<TCP/IP详解>和<计算机网络>等书中有很多细致的讲解,可以参考本人的这篇博客[计算机网络 第五版]阅读笔记之五:运输层,这 ...
- 【UNIX网络编程(三)】TCP客户/server程序演示样例
上一节给出了TCP网络编程的函数.这一节使用那些基本函数编写一个完毕的TCP客户/server程序演示样例. 该样例运行的过程例如以下: 1.客户从标准输入读入一行文本,并写给server. 2.se ...
- 【unix网络编程第三版】阅读笔记(四):TCP客户/服务器实例
本篇博客主要记录一个完整的TCP客户/服务器实例的编写,以及从这个实例中引发的对僵死进程的处理等问题. 1. TCP客户/服务器功能需求 本实例完成以下功能: (1) 客户从标准输入读入一行文本,并写 ...
- unix网络编程第三版源代码ubuntu下配置的问题解决
第一步:首先下载本书配套的源码unpv13e.tar.gz 第二步:解压后进入根文件夹有一个README 4 Execute the following from the src/ directory ...
- Unix网络编程第三版源码编译
配置: $ cd Unix-Network-Programming/ $ chmod 755 configure $ ./configure 主要的工作是检查系统是否有源码编译所依赖的各种资源(系统版 ...
随机推荐
- Vue中的MVVM框架
ViewModel:数据双向绑定 场景: 针对具有复杂交互逻辑的前段应用 提供基础的架构抽象 通过Ajax数据持久化,保证前端用户体验 什么是vue.js? 是一个轻量级的mvvm框架 数据驱动+组 ...
- Linux驱动开发4——并发和竞态
Linux系统处于一个高并发的运行环境,不管是系统调用还是中断都要求可重入,但是有一些系统资源处于临界区,因此,必须保证临界区资源访问的原子性. 对于临界区资源被占用时,发起访问的进程,有三种处理方法 ...
- 源码搭建mysql5.7.20
转载过来的文章,只是借用原文样式与框架,根据自己实验环境进行全面改动,仅供参考! 使用yum安装的MySQL一般版本比较旧,但是运行稳定.如果想要尝试最新的功能或者需要指定特殊的功能的话,就需要手工进 ...
- 架构-数据库访问-SQL语言进行连接数据库服务器-DB-Library:DB-Library
ylbtech-数据库访问-SQL语言进行连接数据库服务器-DB-Library:DB-Library 1.返回顶部 1. 在基于三层构架的信息系统开发中,应用服务器要利用SQL语言进行连接数据库服务 ...
- lazyload懒加载插件
在main.js中引入vue-lazyload插件 并使用 注册插件: import VueLazyLoad from 'vue-lazyload' Vue.use(VueLazyLoad,{ lo ...
- CentOS 6 安装Syslog-ng
entOS 6 安装 Syslog-ng 一. yum 安装 syslog-ng3.7.1 是专门用于RHEL/CentOS version 6 ,不要安装成其他版本.亲身经历,安装成syslog-n ...
- 树莓派3b折腾指南
最近入手了树梅派3b,搭建了宿舍共享的热点和NAS,搭建透明代理科学上网的计划还没实现. 先报个价,一套折腾下来花了500大洋,树梅派3加外壳200,电源加内存卡100,显示器淘宝二手150,有线键鼠 ...
- redis集群主从中断,报io过高 不错
问题原因:1.由于这个集群redis操作非常频繁,1分钟操作数据达到1-2G,所有自动aof非常频繁,主从复制打包rdb也非常频繁,之前配置已经无法满足要求报异常如下6943:M 19 Jul 20: ...
- MySQL使用Navicat远程连接时报错1251
1.报错信息 client does not support authentication protocol requested by server:consider upgrading MySQL ...
- YOLOV3中Darknet中cfg文件说明和理解
今天将要说明的是Darknet中的cfg文件,废话少说,直接干!(以cfg/yolov3.cfg为例,其它类似) [net] ★ [xxx]开始的行表示网 ...