Unix网络编程 之 socket基础
基本结构
(这部分的地址均为网络地址<网络字节序>)
1、struct sockaddr:通用套接字地址结构
此结构用于存储通用套接字地址。
数据结构定义:
typedef unsigned short sa_family_t;
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sa_family:实际使用地址——依据不同的协议族,採用不同的地址类,最经常使用的三种:
1)本地地址族:AF_UNIX或AF_LOCAL;
#include <sys/un.h> struct sockaddr_un {
sa_family_t sun_family; /*AF_UNIX*/
char sun_path[108]; /*地址数据*/
};
2)网络地址族:AF_INET;
#include <netinet/in.h> struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
uint16_t sin_port; /*端口号*/
struct in_addr sin_addr; /*Internet地址*/
unsigned char sin_zero[8] /*占位字节*/
};
3)红外地址类:AF_IRDA;
#include <sys/types.h> struct sockaddr_irda {
sa_family_t sir_family; /* AF_IRDA */
u_int8_t sir_lsap_sel; /* LSAP selector */
u_int32_t sir_addr; /* Device address */
char sir_name[25]; /*:IrDA:TinyTP ,OBEX,etc.*/
};
sa_data[]:包括远程主机的地址、port号和套接字的数目,这些信息包括于字符串中。
而在实际应用中,我们常常使用AF_INET。为了处理struct sockaddr,Unix建立了另外一个相似结构struct sockaddr_in。
其结构例如以下所看到的。
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
uint16_t sin_port; /*端口号*/
struct in_addr sin_addr; /*Internet地址*/
unsigned char sin_zero[8] /*占位字节*/
};
此结构提供了简洁的方法用于訪问socket address(struct sockaddr)结构中的每个元素。
sin_zero[8]是为了使两个结构在内存中具有同样的尺寸,使用sockaddr_in时要把sin_zero[]所有设置为零值(使用bzero()函数或memset()函数)。
此结构能够觉得是IPv4套接字地址结构。
2、struct in_addr:因特网地址结构
该结构用于表示Internet Address(因特网地址)。
/*Internet address*/
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
在这里。我们由s_addr的类型定义可知,此处的因特网地址长度为32bit。也就是说此处的地址为IPv4地址。同一时候,当使用struct sockaddr_in类型的ina变量时,ina.sin_addr.s_addr即为32bit的IP地址。
当然,此处,我们应该注意这里的IP地址是网络字节序。
3、IPv6套接字地址结构
在上文。我们介绍了IPv4套接字地址结构,那么。对于IPv6套接字,其地址结构又怎样呢?
IPv6套接字地址结构在<netinet/in.h>头文件里定义:
struct in6_addr{
uint8_t s6_addr[16]; /*128bits IPv6 address network byte ordered*/
}; struct sockaddr_in6{
sa_family_t sin6_family; /*AF_INET6 */
in_port_t sin6_port; /*transport layer port network byte ordered */
uint32_t sin6_flowinfo; /*flow information, undefined*/
struct in6_addr sin6_addr; /*IPv6 address network byte ordered */
uint32_t sin6_scope_id; /*set of interfaces for a scope*/
};
对于IPv6套接字地址结构,我们要注意这样几点:
*IPv6的地址族(sa_family)是AF_INET6,而IPv4的地址族是AF_INET;
*结构中字段的先后顺序做过编排,使得假设sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的。
*sin6_flowinfo字段分为两个字段:低序20位流标(flow table) + 高序12位保留。
*对于具备范围的地址(scoped address),sin6_scope_id字段标识其范围(scope),最常见的是链路局部地址(link-local address)的接口索引(interface index)。
4、struct sockaddr_storage:新的通用套接字地址结构
struct sockaddr_storage{
uint8_t ss_len; //length of the struct
sa_family_t ss_family; //address family: AF_xxx value
};
在上文。我们能够知道struct sockaddr为通用套接字地址结构,可是因为因特网地址结构的限制,该结构仅支持IPv4地址。
而新的通用套接字地址结构struct sockaddr_storage克服了现有struct sockaddr的一些缺点。足以容纳系统所支持的不论什么不论什么套接字地址结构。
sockaddr_storage类型提供的通用套接字地址结构相比sockaddr存在下面两点区别:
(1)、假设系统支持的不论什么套接字地址结构有对齐须要。那么sockaddr_storage可以满足最苛刻的对齐要求。
(2)、sockaddr_storage足够大,可以容纳系统支持的不论什么套接字地址结构。
5、套接字地址结构的比較:
基本转换函数
1、值-结果參数
当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递。也就是说传递的是指向该结构的一个指针。该结构的长度也作为一个參数来传递,只是其传递方式取决于该结构的传递方向:是从进程到内核。还是从内核到进程。
(1)、从进程到内核传递套接字地址结构的函数有3个:bind、connect和sendto。这些函数一个參数是指向某个套接字地址结构的指针。还有一个參数是该结构的整数大小。比如:
struct sockaddr_in serv; connect(sockfd, (struct sockaddr *) &serv, sizeof(serv));
既然指针和指针所指内容的大小都传递给了内核,内核就知道究竟须要从进程复制多少数据。
下图展示了这个情形。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhb2JyeWFudA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
(2)、从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername。这四个函数的当中两个參数是指向某个套接字地址结构的指针和指向表示该结构大小的整数变量的指针。比如:
struct sockaddr_un cli; /*Unix domain*/ socklen_t len;
len = sizeof(cli);
getpeername(unixfd, (struct sockaddr *)&cli, &len);
把套接字地址结构大小这一參数从一个整数改为指向某个整数变量的指针,其原因在于:
当函数被调用时,结构大小是一个值(value)。它告诉内核该结构的大小,这样内核在写该结构时不至于越界。当函数返回时,结构大小又是一个结果(result)。它告诉进程内核在该结构中到底存储了多少信息。这样的类型的參数称为“值-结果(value-result)”參数。下图展示了这个情形。
关于值-结果參数。在后面介绍套接字调用函数时会进一步解析。
2、字节排序函数
IP地址的三种表示格式:
1)ASCII(点分十进制字符串)
2)网络地址(网络字节序,Network Byte Order)
3)主机地址(主机字节序。Host Byte Order)
那么。网络字节序和主机字节序有何不同?
内存在存储数据时有两种处理方法:一种是将低序字节存储在起始地址,此称为小端(little-endian)字节序。还有一种是将高序字节存储在起始地址,此称为大端(big-endian)字节序。
採用大端方式进行数据存放符合人类的正常思维。而採用小端方式进行数据存放利于计算机处理。
对于2个字节的数据存储。大端存储与小端存储的差别例如以下图:
在套接字地址表示方面。网络地址(网络字节序)一般採用大端字节序。而主机地址(主机字节序)主要取决于系统。
那么主机地址和网络地址(或者说主机字节序和网络字节序)各自主要应用于什么场合?
主机地址主要用于主机处理时,由于计算机更加擅好处理小端字节序(对採用小端存储的主机)。而在网络协议中,发送协议栈和接受协议栈必须就多字节字段的各个字节的传送顺序达成一致,为了便于人类思维过程,一般採用大端字节序,即网络字节序。
主机字节序和网络字节序之间相互转化的过程即为字节排序的过程。这两种字节序之间的转换使用下面4个函数:
#include <netinet/in.h> /*均返回网络字节序的值*/
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue); /*均返回主机字节序的值*/
uint16_t ntonhs(uint16_t net16bitvalue);
uint32_t ntonhl(uint32_t net32bitvalue);
在这些函数的名字中,h代表host,n代表network。s代表short,l代表long。
现在,我们把s视为16位的值(比如TCP/UDPport号),把l视为32位的值(比如IPv4地址)。
这些函数在处理时。会依据主机系统究竟是小端存储还是大端存储来相应地调整函数的处理过程。假设主机系统支持小端存储。则相应实现字节反转过程,反之,则这些函数为空宏。
在什么时候应该使用这些函数呢?当我们存在内核与进程之间的套接字地址结构訪问时就必须使用对应的字节转换函数来实现地址的正常传递。
3、字节操纵函数
操纵多字节字段的函数有两组(Berkeley&ANSIC)。它们既不正确数据作解释。也不如果数据是以空字符结束的C字符串。
名字以b(表示字节)开头的第一组函数起源于4.2BSD,现今支持套接字函数的系统仍然提供它们。名字以mem(表示内存)开头的第二组函数起源于ANSIC标准,支持ANSIC函数库的全部系统都提供它们。
Berkeley:
#include <string.h> void bero(void *dest, size_t, nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/*相等返回0,否则返回非0值*/
/*此处使用const限定词,表示所限制的指针所指的内容不会被函数更改。换句话说。函数仅仅是读而不改动由const指针所指的内存单元。*/
ANSIC:
#include <string.h> void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/*相等返回0。否则返回非0值,或大于0。或小于0*/
4、地址转换函数
地址转换函数实现ASCII字符串(点分十进制)与网络字节序的二进制(存放在套接字地址结构中的值)之间的网际地址的转换。
地址转换函数有两组,各自是:
(1)、inet_aton、inet_addr和inet_ntoa函数
完毕点分十进制(如”192.168.1.1”)与它长度为32位的网络字节序二进制间转换IPv4地址。
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr);
/*
此函数将strptr所指的C字符串转换成一个32位的网络字节序二进制,并通过指针addrptr来存储。 若成功。返回1,否则返回0。
*/ in_addr_t inet_addr(const char *strptr);
/*
此函数与inet_aton函数运行同样的操作,返回32位的网络字节序二进制值,否则返回INADDR_NONE。注意,此函数不能处理255.255.255.255点分十进制数串。
*/ char *inet_ntoa(struct in_addr inaddr);
/*
此函数将一个32位的网络字节序二进制IPv4地址转换成对应的点分十进制数串。
由该函数的返回值所指向的字符串驻留在静态内存中。 另外。该函数是以一个结构而不是以指向该结构的一个指针作为參数。
*/
注:inet_addr()函数已被废弃,建议採用inet_aton()函数。
(2)、inet_pton和inet_ntop函数
这两个函数式随IPv6出现的新函数,对于IPv4和IPv6地址均适用。
函数名中的p代表表达(presentation)和数值(numeric)。地址的表达格式一般是ASCII字符串。数值格式则是存放到套接字地址结构中的二进制值。
#include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr);
/*
此函数转换由strptr指针所指的字符串。并通过addrptr指针存放二进制结果。 若成功,则返回值为1,否则返回0。 */ const char *inet_ntop(int family, const void *addrptr, size_t len);
/*
此函数运行从数值格式(addrptr)到表达格式(strptr)的转换。
strptr參数不能是空指针,调用者必须为目标存储单元分配内存并指定大小。 调用成功时,返回此指针。
*/
对于这两个函数,family字段能够是AF_INET或AF_INET6。
假设以不被支持的地址族作为family參数,这两个函数会返回错误,并将errno置为EAFNOSUPPORT。
另外,在inet_ntop()函数中,len參数是目标存储单元的大小。以免该函数溢出其调用者的缓冲区。
为了有助于指定此大小,在<netinet/in.h>头文件里有例如以下定义:
#define INET_ADDRSTRLEN 16 /*IPv4 dotted-decimal*/
#define INET6_ADDRSTRLEN 46 /*IPv6 hex-string*/
假设len太小。不足以容纳表达格式结果,那么返回空指针。并置errno为ENOSPC。
Unix网络编程 之 socket基础的更多相关文章
- Unix网络编程(1)——socket一窥
套接口地址结构 IPv4的套接口地址结构为: struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; struct in_addr ...
- UNIX网络编程——关于socket阻塞与非阻塞情况下的recv、send、read、write返回值
1.阻塞模式与非阻塞模式下recv的返回值各代表什么意思?有没有 区别?(就我目前了解阻塞与非阻塞recv返回值没有区分,都是 <0:出错,=0:连接关闭,>0接收到数据大小,特别:返回 ...
- 【Linux/unix网络编程】之使用socket进行TCP编程
实验一 TCP数据发送与接收 [实验目的] 1.熟练掌握套接字函数的使用方法. 2.应用套接字函数完成基本TCP通讯,实现服务器与客户端的信息交互. [实验学时] 4学时 [实验内容] 实现一个服务器 ...
- java基础-网络编程(Socket)技术选型入门之NIO技术
java基础-网络编程(Socket)技术选型入门之NIO技术 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.传统的网络编程 1>.编写socket通信的MyServer ...
- 记录一次配置unix网络编程环境的过程和遇到的问题
记录一次搭建unix网络编程环境过程中遇到的问题和总结 计算机环境虚拟机 linuxmint-18-xfce-64bit 1.打开unix网络编程.iso 把目录下的文件复制到某一目录,修改权限,可命 ...
- iOS 网络编程:socket
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- UNIX网络编程——select函数的并发限制和 poll 函数应用举例
一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置, ...
- unix网络编程 str_cli epoll 非阻塞版本
unix网络编程 str_cli epoll 非阻塞版本 unix网络编程str_cli使用epoll实现讲了使用epoll配合阻塞io来实现str_cli,这个版本是配合非阻塞io. 可以看到采用非 ...
- 《Unix 网络编程》05:TCP C/S 程序示例
TCP客户/服务器程序示例 系列文章导航:<Unix 网络编程>笔记 目标 ECHO-Application 结构如下: graph LR; A[标准输入/输出] --fgets--> ...
随机推荐
- iOS开发 数据缓存-数据库
iOS中数据存储方式 Plist(NSArray\NSDictionary) Preference(偏好设置\NSUserDefaults) NSCoding (NSKeyedArchiver\NSk ...
- 一起来学SpringBoot(十七)优雅的参数校验
参数校验在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有两个麻烦: 验证代码繁琐,重复劳动方法内代码显得冗长每次要看哪些参数 ...
- 为什么java String是固定的 为什么字符串是不可变的
String类不可变的好处 String是所有语言中最常用的一个类.我们知道在Java中,String是不可变的.final的.Java在运行时也保存了一个字符串池(String pool),这使得S ...
- ssh架包下载地址
1.连接MySQL数据库所需架包点击进入官网下载 2.连接Oracle数据库所需架包点击进入官网下载 3.JUnit测试所需架包点击进入官网下载或者点击进入官网下载 4.Struts所需架包点击进入官 ...
- 怎样从SpringMVC返回json数据
Srping3中配置 maven依赖pom.xml 需要jackson库的依赖 <dependency> <groupId>org.codehaus.jackson</g ...
- IDEA修改背景图片和主题代码风格
使用IDEA有一段时间了,之前为了写代码没时间折腾这玩意,以前使用Eclipse的时候一次性折腾好了,现在把之前Eclipse的配置再配到IDEA里面来,下面是效果图.Eclipse的设置见:点击这里 ...
- C语言的移位操作符及位运算
C语言的移位操作符 位移位运算符是将数据看成二进制数,对其进行向左或向右移动若干位的运算.位移位运算符分为左移和右移两种,均为双目运算符.第一运算对象是移位对象,第二个运算对象是所移的二进制位数. 位 ...
- noi.ac NOIP2018 全国热身赛 第二场 T1 ball
[题解] 可以发现每次推的操作就是把序列中每个数变为下一个数,再打一个减一标记:而每次加球的操作就是把球的位置加上标记,再插入到合适的位置. 用set维护即可. #include<cstdio& ...
- 初学数位DP
所谓数位dp,字面意思就是在数位上进行dp,数位的含义:一个数有个位.十位.百位.千位.等等,数的每一位就是数位. 数位DP一般应用于: 求出给定区间[A,B]内,符合条件P[i]的数 i 的个数. ...
- 集训第六周 数学概念与方法 J题 数论,质因数分解
Description Tomorrow is contest day, Are you all ready? We have been training for 45 days, and all g ...