socket原理详解
1、什么是socket
我们知道进程通信的方法有管道、命名管道、信号、消息队列、共享内存、信号量,这些方法都要求通信的两个进程位于同一个主机。但是如果通信双方不在同一个主机又该如何进行通信呢?在计算机网络中我们就学过了tcp/ip协议族,其实使用tcp/ip协议族就能达到我们想要的效果,如下图(图片来源于《tcp/ip协议详解卷一》第一章1.3)
         
                           图一 各协议所处层次
当然,这样做固然是可以的,但是,当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展。于是UNIX BSD就发明了socket这种东西,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。百说不如一图,看下面这个图就能明白了。
         
                               图二 socket所处层次
那么,在BSD UNIX又是如何实现这层抽象的呢?我们知道unix中万物皆文件,没错,bsd在实现上把socket设计成一种文件,然后通过虚拟文件系统的操作接口就可以访问socket,而访问socket时会调用相应的驱动程序,从而也就是使用底层协议进行通信。(vsf也就是unix提供给我们的面向对象编程,如果底层设备是磁盘,就对磁盘读写,如果底层设备是socket就使用底层协议在网中进行通信,而对外的接口都是一致的)。下面再看一下socket的结构是怎样的(图片来源于《tcp/ip协议详解卷二》章节一,1.8描述符),注意:这里的socket是一个实例化之后的socket,也就是说是一个具体的通信过程中的socket,不是指抽象的socket结构,下文还会进行解释。
         
                          图三 udp socket实例的结构
很明显,unix把socket设计成文件,通过描述符我们可以定位到具体的file结构体,file结构体中有个f_type属性,标识了文件的类型,如图,DTYPE_VNODE表示普通的文件DTYPE_SOCKET表示socket,当然还有其他的类型,比如管道、设备等,这里我们只关心socket类型。如果是socket类型,那么f_ops域指向的就是相应的socket类型的驱动,而f_data域指向了具体的socket结构体,socket结构体关键域有so_type,so_pcb。so_type常见的值有:
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流服务,当使用Internet地址族时使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,当使用Internet地址族使用UDP。
SOCK_RAW 原始套接字,允许对底层协议如IP或ICMP进行直接访问,可以用于自定义协议的开发。
so_pcb表示socket控制块,其又指向一个结构体,该结构体包含了当前主机的ip地址(inp_laddr),当前主机进程的端口号(inp_lport),发送端主机的ip地址(inp_faddr),发送端主体进程的端口号(inp_fport)。so_pcb是socket类型的关键结构,不亚于进程控制块之于进程,在进程中,一个pcb可以表示一个进程,描述了进程的所有信息,每个进程有唯一的进程编号,该编号就对应pcb;socket也同时是这样,每个socket有一个so_pcb,描述了该socket的所有信息,而每个socket有一个编号,这个编号就是socket描述符。说到这里,我们发现,socket确实和进程很像,就像我们把具体的进程看成是程序的一个实例,同样我们也可以把具体的socket看成是网络通信的一个实例。
2、具体socket实例如何标识
我们知道具体的一个文件可以用一个路径来表示,比如/home/zzy/src_code/client.c,那么具体的socket实例我们该如何表示呢,其实就是使用上面提到的so_pcb的那几个关键属性,也就是使用so_type+ip地址+端口号。如果我们使用so_type+ip地址+端口号实例一个socket,那么互联网上的其他主机就可以与该socket实例进行通信了。所以下面我们看一下socket如何进行实例化,看看socket给我们提供了哪些接口,而我们又该如何组织这些接口
3、socket编程接口
3.1、socket接口
int socket(int protofamily, int so_type, int protocol);
protofamily 指协议族,常见的值有:
AF_INET,指定so_pcb中的地址要采用ipv4地址类型
AF_INET6,指定so_pcb中的地址要采用ipv6的地址类型
AF_LOCAL/AF_UNIX,指定so_pcb中的地址要使用绝对路径名
当然也还有其他的协议族,用到再学习了
so_type 指定socket的类型,也就是上面讲到的so_type字段,比较常用的类型有:
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
protocol 指定具体的协议,也就是指定本次通信能接受的数据包的类型和发送数据包的类型,
常见的值有:
IPPROTO_TCP,TCP协议
IPPROTO_UDP,UPD协议
0,如果指定为0,表示由内核根据so_type指定默认的通信协议
这里解释一下图三,图三其实是使用AF_INET,SOCK_DGRAM,IPPRTO_UDP实例化之后的一个具体的socket。
那为什么要通过这三个参数来生成一个socket描述符?
答案就是通过这三个参数来确定一组固定的操作。我们说过抽象的socket对外提供了一个统一、方便的接口来进行网络通信,但对内核来说,每一个接口背后都是及其复杂的,同一个接口对应了不同协议,而内核有不同的实现,幸运的是,如果确定了这三个参数,那么相应的接口的映射也就确定了。在实现上,BSD就把socket分类描述,每一个类别都有进行通信的详细操作,分类见下图。而对socket的分类,就好比对unix设备的分类,我们对设备write和read时,底层的驱动是有各个设备自己提供的,而socket也一样,当我们指定不同的so_type时,底层提供的通信细节也由相应的类别提供。
                 
                                  图4 socket层次图
更详细的socket()函数参数描述请移步:
blog.csdn/liuxingen/article/details/44995467
blog.csdn/qiuchangyong/article/details/50099927
3.2、bind接口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数就是给图三种so_pcb结构中的地址赋值的接口
sockfd   是调用socket()函数创建的socket描述符socket通信为什么要指定端口
addr     是具体的地址
addrlen  表示addr的长度
struct sockaddr其实是void的typedef,其常见的结构如下图(图片来源传智播客邢文鹏linux系统编程的笔记),这也是为什么需要addrlen参数的原因,不同的地址类型,其地址长度不一样:
                 
                              图5 地址结构图
AF_INET:
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;  /* port in network byte order */
    struct in_addr sin_addr;  /* internet address */
};
struct in_addr {
    uint32_t      s_addr;    /* address in network byte order */
};
AF_INET6:
struct sockaddr_in6 {
    sa_family_t    sin6_family;  /* AF_INET6 */
    in_port_t      sin6_port;    /* port number */
    uint32_t        sin6_flowinfo; /* IPv6 flow information */
    struct in6_addr sin6_addr;    /* IPv6 address */
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
    unsigned char  s6_addr[16];  /* IPv6 address */
};
AF_UNIX:
#define UNIX_PATH_MAX    108
struct sockaddr_un {
    sa_family_t sun_family;              /* AF_UNIX */
    char        sun_path[UNIX_PATH_MAX];  /* pathname */
};
3.3、connect接口
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
这三个参数和bind的三个参数类型一直,只不过此处strcut sockaddr表示对端公开的地址。三个参数都是传入参数。connect顾名思义就是拿来建立连接的函数,只有像tcp这样面向连接、提供可靠服务的协议才需要建立连接
 
3.4、listen接口
int listen(int sockfd, int backlog)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。