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 主要的工作是检查系统是否有源码编译所依赖的各种资源(系统版 ...
随机推荐
- Socket网络通信编程(一)
1.学习基本概念.传统的同步阻塞式I/O编程.伪异步IO实现 2.学习基于NIO的同步非阻塞式编程 3.了解基于NIO2.0的异步非阻塞(AIO)编程 1.1 基本概念 Socket又称“套接字”,应 ...
- Twice Equation
题目链接:https://nanti.jisuanke.com/t/A1541 题意:给你一个L,要你求一个不小于L的最小数字n,对于一个整数m,满足2*(m+1)*m=n*(n+1). 思路:打表找 ...
- Java基础之方法详解
方法的所属性 在Java的语言中,方法相当于C语言中的函数,但是它与传统的函数也有着明确的不同:在结构化的语言中,函数是一等公民,整个程序是由一个个函数组成的:但是在面向对象的语言里,类是一等公民,整 ...
- 关于openGL、GPUImage、ios直播相关不错的博客
http://www.jianshu.com/users/815d10a4bdce/latest_articles
- element-ui的rules全局验证
原文:https://www.jianshu.com/p/6a29e9e51b61 rules.js var QQV = (rule, value, callback) => { debugge ...
- 《JavaScript 高级程序设计》
第 3 章 基本概念 3.5.2 位操作符 ECMAScript 中所有数值都是以 IEEE-754 64 位格式存储,但位操作符并不直接操作 64 位的值.而是先将 64 位的值转换成 32 位的整 ...
- 008-elasticsearch5.4.3【二】ES使用、ES客户端、索引操作【增加、删除】、文档操作【crud】
一.ES使用,以及客户端 1.pom引用 <dependency> <groupId>org.elasticsearch.client</groupId> < ...
- 解决Nginx反向代理不会自动对特殊字符进行编码的问题 如gitblit中的~波浪线
问题起因是利用Nginx做反向代理的时候,需要访问如下链接http://192.168.14.141/iserver/services/3D-0524hd/rest/realspace/datas/0 ...
- UI自动化之异常与截图处理
对操作不成功时,希望能够继续执行其他操作,或者是,希望操作不成功时,能够写日志记录 目录 1.常见异常 2.截图处理 1.常见异常 1.NoSuchElementException:没有找到元素 2. ...
- Pycharm中使用virtualenv创建虚拟环境
虚拟环境是Python解释器的一个私有副本,在这个环境中你可以安装私有包,而且不会影响系统中安装的全局Python解释器. 虚拟环境非常有用,可以在系统的Python解释器中避免包的混乱和版本的冲突. ...