套接口学习(一)实现

套接口这个概念最先由4.2BSD(1983)引入,现在已经成为一个通用的网络应用程序编程接口,受到所有操作系统的支持。套接口层位于应用程序和 协议栈之间,对应用程序屏蔽了与协议相关实现的具体细节。

通常,应用程序中调用库函数,而库函数通过系统调用进入套接口层,Linux的套接口层实现提供了一组专门的套接口系统调用,分别在对应的库函数名之上加上"sys_"前缀。此外为了体现一切皆文件的理念,Linux也允许标准I/O系统调用通过一个套接口文件描述符来读写其对应套接口上的网络连接,就像通过文件描述符访问一个已经打开的普通文件一样。套接口在创建时,即与一个文件及一个文件描述符绑定,此后所有对该套接口的操作都是通过与其绑定的文件描述符进行的,包括专门的套接口系统调用,页包括标准的I/O系统调用对套接口的操作,与套接口绑定的文件类型为套接口文件。

套接口层的调用涉及以下文件:

include/linux/net.h  定义套接口层相关的结构、宏和函数原型

include/net/sock.h   定义基本的传输控制块结构、宏和函数原型

net/socket.c  实现套接口层的系统调用

net/ipv4/af_inet.c 网络层和传输层接口

socket结构

struct socket {
	socket_state		state;

	kmemcheck_bitfield_begin(type);
	short			type;
	kmemcheck_bitfield_end(type);

	unsigned long		flags;
	/*
	 * Please keep fasync_list & wait fields in the same cache line
	 */
	struct fasync_struct	*fasync_list;
	wait_queue_head_t	wait;

	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;
};

state

用于表示所在套接口所处的状态标志。该标志有些状态只对TCP套接口有意义,因为只有TCP是面向连接的协议

typedef enum {

SS_FREE = 0,
/* not allocated */

SS_UNCONNECTED,
/* unconnected to any socket */

SS_CONNECTING,
/* in process of connecting */

SS_CONNECTED,
/* connected to socket */

SS_DISCONNECTING
/* in process of disconnecting */

} socket_state;

type

套接口类型

enum sock_type {

SOCK_STREAM
= 1, 基于连接的套接口

SOCK_DGRAM
= 2, 基于数据报的套接口

SOCK_RAW
= 3, 原始套接口

SOCK_RDM
= 4, 可靠传送报文套接口

SOCK_SEQPACKET
= 5, 顺序分组套接口

SOCK_DCCP
= 6, 数据报拥塞控制协议套接口

SOCK_PACKET
= 10,混杂模式套接口

};

flags

一组标志位

#define SOCK_ASYNC_NOSPACE
0  该套接口发送队列是否已满

#define SOCK_ASYNC_WAITDATA 1  应用程序通过recv调用时,是否在等待数据的接收

#define SOCK_NOSPACE 2  非异步情况下该套接口的发送队列是否已满

#define SOCK_PASSCRED 3  是否设置了SOCK_PASSCRED选项

#define SOCK_PASSSEC 4  是否设置了SOCK_PASSSEC

struct fasync_struct *fasync_list

存在了异步通知的队列

wait_queue_head_t wait

等待该套接口的进程队列

struct file *file

指向了与该套接口绑定的file结构的指针

struct sock *sk;

与该套接口关联的传输控制块

const struct proto_ops
*ops;

用来将套接口层的系统调用映射到相应传输层协议实现

PF_INET协议族定义了三种prote_ops结构实例

TCP      inet_stream_ops

UDP      inet_dgram_ops

RAW      inet_sockraw_ops

proto_ops结构

整个proto_ops结构可以看作是一张套接口系统调用到传输层函数的跳转表,其中的某些操作会继续通过proto结构跳转表,进入具体的传输层或网络层的处理。

既然proto_ops结构完成的是从与协议无关的套接口层到协议相关的传输层的转接,而proto结构又将传输层映射到网络层,那么可想而知每个传输层的协议都需要定义一个特定的proto_ops实例和proto实例。在ipv4协议族中,一个传输层协议对应一个inet_protosw结构,inet_protosw结构包含了proto_ops结构和proto结构。

套接口文件系统

每一种文件都有各自的文件类型,如设备文件包括字符设备和块设备文件等,而与套接口关联的文件类型为套接口文件。

套接口文件系统类型

为了能使套接口与文件描述符关联,并支持特殊套接口层的i节点的分配和释放,系统中增加了sockfs文件系统sock_fs_type,通过sockfs文件系统的getsb接口和超级块操作集合中的alloc_inode和destroy_inode,可以分配和释放与套接口文件相关的i节点。可以通过/proc/filesystems文件查看操作系统支持的文件系统。

static const struct super_operations sockfs_ops = {
	.alloc_inode =	sock_alloc_inode,
	.destroy_inode =sock_destroy_inode,
	.statfs =	simple_statfs,
};

static int sockfs_get_sb(struct file_system_type *fs_type,
			 int flags, const char *dev_name, void *data,
			 struct vfsmount *mnt)
{
	return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC,
			     mnt);
}

static struct vfsmount *sock_mnt __read_mostly;

static struct file_system_type sock_fs_type = {
	.name =		"sockfs",
	.get_sb =	sockfs_get_sb,
	.kill_sb =	kill_anon_super,
};

套接口文件的inode

套接口文件系统的i节点和套接口是一一对应的,因此套接口文件系统的i节点的分配时比较特殊的,分配的并不是一个单纯的i节点,而是i节点和socket结构的组合体,即socket_alloc结构,这样可以使套接口的分配及与之绑定的套接口文件i节点的分配同时进行。在应用层访问套接口要通过文件描述符,这样可以快速通过文件描述符定位与之绑定的套接口。

struct socket_alloc {
	struct socket socket;
	struct inode vfs_inode;
};
static struct inode *sock_alloc_inode(struct super_block *sb)
{
	struct socket_alloc *ei;

	ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
	if (!ei)
		return NULL;
	init_waitqueue_head(&ei->socket.wait);

	ei->socket.fasync_list = NULL;
	ei->socket.state = SS_UNCONNECTED;
	ei->socket.flags = 0;
	ei->socket.ops = NULL;
	ei->socket.sk = NULL;
	ei->socket.file = NULL;

	return &ei->vfs_inode;
}

static void sock_destroy_inode(struct inode *inode)
{
	kmem_cache_free(sock_inode_cachep,
			container_of(inode, struct socket_alloc, vfs_inode));
}

套接口文件

套接口有一套独立的系统调用,包括建立套接口、连接和IO操作等,由于在建立套接口后返回的是文件描述符,因此也可以通过标准的文件IO操作进行对套接口的读写,例如用send()进行数据的发送,而事实上也可以通过write系统调用而达到同样的效果。这是由于在创建套接口文件时,使file结构中的f_op指向了socket_file_ops。通过socket_file_ops,可以看到套接口文件支持那些系统调用

static const struct file_operations socket_file_ops = {
	.owner =	THIS_MODULE,
	.llseek =	no_llseek,
	.aio_read =	sock_aio_read,
	.aio_write =	sock_aio_write,
	.poll =		sock_poll,
	.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = compat_sock_ioctl,
#endif
	.mmap =		sock_mmap,
	.open =		sock_no_open,	/* special open code to disallow open via /proc */
	.release =	sock_close,
	.fasync =	sock_fasync,
	.sendpage =	sock_sendpage,
	.splice_write = generic_splice_sendpage,
	.splice_read =	sock_splice_read,
};

套接口文件与套接口的绑定

应用层是通过文件描述符来访问套接口的,因此在调用socket系统调用创建套接口时,在创建后会调用sock_map_fd()绑定套接口和文件描述符

static int sock_attach_fd(struct socket *sock, struct file *file, int flags)
{
	struct dentry *dentry;
	struct qstr name = { .name = "" };

	dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
	if (unlikely(!dentry))
		return -ENOMEM;

	dentry->d_op = &sockfs_dentry_operations;
	/*
	 * We dont want to push this dentry into global dentry hash table.
	 * We pretend dentry is already hashed, by unsetting DCACHE_UNHASHED
	 * This permits a working /proc/$pid/fd/XXX on sockets
	 */
	dentry->d_flags &= ~DCACHE_UNHASHED;
	d_instantiate(dentry, SOCK_INODE(sock));

	sock->file = file;
	init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE,
		  &socket_file_ops);
	SOCK_INODE(sock)->i_fop = &socket_file_ops;
	file->f_flags = O_RDWR | (flags & O_NONBLOCK);
	file->f_pos = 0;
	file->private_data = sock;

	return 0;
}

int sock_map_fd(struct socket *sock, int flags)
{
	struct file *newfile;
	int fd = sock_alloc_fd(&newfile, flags);

	if (likely(fd >= 0)) {
		int err = sock_attach_fd(sock, newfile, flags);

		if (unlikely(err < 0)) {
			put_filp(newfile);
			put_unused_fd(fd);
			return err;
		}
		fd_install(fd, newfile);
	}
	return fd;
}

进程、文件描述符和套接口

在task_struct结构中,files指向file_struct结构,该结构的主要功能是管理fd_array指针数组指向的描述符,每一个file结构描述一个打开的文件。

套接口层的初始化

static int __init sock_init(void)
{
	/*
	 *      Initialize sock SLAB cache.
	 */

	sk_init();

	/*
	 *      Initialize skbuff SLAB cache
	 */
	skb_init();

	/*
	 *      Initialize the protocols module.
	 */

	init_inodecache();
	register_filesystem(&sock_fs_type);
	sock_mnt = kern_mount(&sock_fs_type);

	/* The real protocol initialization is performed in later initcalls.
	 */

#ifdef CONFIG_NETFILTER
	netfilter_init();
#endif

	return 0;
}
时间: 2024-10-12 08:26:23

套接口学习(一)实现的相关文章

值得收藏的TCP套接口编程文章

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由jackieluo发表于云+社区专栏 TCP客户端-服务器典型事件 下图是TCP客户端与服务器之间交互的一系列典型事件时间表: 首先启动服务器,等待客户端连接 启动客户端,连接到服务器 客户端发送一个请求给服务器,服务器处理请求,响应客户端 循环步骤3 客户端给服务器发一个文件结束符,关闭客户端连接 服务器也关闭连接 基本TCP客户-服务器程序的套接口函数 套接口编程基本函数 socket 函数 为了执行网络I/O,一个进程(无论

OpenCV(C++接口)学习笔记4-Mat::operator = 的陷阱

当我们想要将一个Mat对象的数据复制给另一个Mat对象时,应该怎么做呢? 我们发现,OpenCV提供了重载运算符Mat::operator = ,那么,是否按照下列语句就可以轻松完成对象的赋值呢? Mat a; Mat b = a; 答案是否定的! 我们可以从reference manual 中看到: Mat::operator = Provides matrix assignment operators. C++: Mat& Mat::operator=(const Mat& m) Pa

OpenCV(C++接口)学习笔记2-像素级的图像操作

1.通过成员函数at(int y, int x)访问 这种方法需要知道像素保存的格式. (1) 这是为模板类型的函数,因为一个函数的返回类型只有在运行时才会知道. (2)这个函数返回的是一个向量即Vector,故有下标的操作. image.at<uchar>(j,i)= 255; 在单通道图像中,采用以上语句可以获取图像(i,j)处的灰度值(注:先行后列,一般用j表示行(rows),i表示列(cols)).如果是灰度图像的话,只需要更改一个数据就可以了.如果是rgb图像的话,就要用"

套接口编程简介

1.套接口地址结构 POSIX规范只需要结构中的三个成员:sin_family.sin_addr.sin_port.其中sin_addr又是一个结构 2.通用套接口地址结构 套接口函数被定义为采用指向通用套接口地址结构的指针.由于套接口函数的定义并没有使用void*指针类型, 所以调用套接口函数时必须进行强制类型转换. 使用通用套接口地址结构的原因:内核必须依据通用套接口地址结,检查sin_family的值来确定结构的类型 3.字节排序函数 多字节数据在内存中存储有两种方法.小端字节序把低字节存

OpenCV(C++接口)学习笔记1-图像的读取、显示、保存

OpenCV在2.0版本之后添加了C++接口函数,之前学习的都是C语言的接口函数,现在OpenCV已经发展到2.4.9版本了,所以决定学习C++接口函数,跟上节奏. 1.创建图像 cv::Mat image; 采用类cv::Mat来定义图像变量或矩阵变量. 当然你也可以指定图像的大小: cv::Mat img(240,320,CV_8U,cv::Scalar(100)); 参数CV_8U中的U代表unsigned,而S代表signed.对于三通道彩色图像可以用CV_8UC3.你也可以声明16或3

高级套接口-(sendmsg和recvmsg)

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类). 那么,如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象,我们应该怎么做呢? 一. 生成对象的原始模式 假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性. var C

C网络编程-TCP套接口编程

只有几个基本的示例如下 建立服务端的绑定监听等过程的函数 1 #include<sys/types.h> 2 #include<sys/socket.h> 3 4 #include<netinet/in.h> 5 #include<arpa/inet.h> 6 #include<unistd.h> 7 8 #include<stdio.h> 9 #include<stdlib.h> 10 11 #include<st

C# 常用接口学习 IEnumerable&lt;T&gt;

C# 常用接口学习 IEnumerable<T> 作者:乌龙哈里 时间:2015-10-24平台:Window7 64bit,Visual Studio Community 2015 本文参考: MSDN IEnumerable<T> Interface MS DotNet 源代码 你曾实现过二叉树吗--匠心十年 你可能不知道的陷阱, IEnumerable接口--沙漠之鹰 本文章节: 正文: 本文是作者摸索学习.Net的过程,逐步进行,比较繁琐,是作者本人用来帮助记忆的博文. 我

套接口选项

设置和影响套接口选项的方法有 1.getsockopt和setsockopt 2.fcntl 3.ioctl getsockopt和setsockopt 这两个方法仅适用于套接字 有两种基本类型的套接口选项:打开或关闭某个特性的二进制标志,取得并返回我们可以设置或检验的特定值的选项,标有标志的列指明是否为标志选项,对于这些项,0表示关闭标志,非0表示打开标志. 并不是所有的系统的套接字都支持所有的选项,必要时候自行验证一番. 套接字的不支持分为两种 1.未实现相关的定义,比如SO_REUSEPO