深入理解TCP/IP协议的实现之socket(基于linux1.2.13)

socket大家都知道是用于网络通信的,也知道他是ip和端口的组合。但是很多同学可能不是很清楚socket的原理和实现。下面我们深入理解一下socket到底是什么。
    我们回忆一下socket编程的步骤,不管是客户端还是服务端,第一个调的函数都是socket。我们就从这个函数的实现开始,看看一个socket到底是什么。

// 新建一个socket结构体,并且创建一个下层的sock结构体,互相关联
static int sock_socket(int family, int type, int protocol)
{
	int i, fd;
	struct socket *sock;
	struct proto_ops *ops;

	// 找到对应的协议族,比如unix域、ipv4
	for (i = 0; i < NPROTO; ++i) 
	{	// 从props数组中找到family协议对应的操作函数集,props由系统初始化时sock_register进行操作
		if (pops[i] == NULL) continue;
		if (pops[i]->family == family) 
			break;
	}

	if (i == NPROTO) 
	{
  		return -EINVAL;
	}
	// 函数集
	ops = pops[i];

	// 检查一下类型
	if ((type != SOCK_STREAM && type != SOCK_DGRAM &&
		type != SOCK_SEQPACKET && type != SOCK_RAW &&
		type != SOCK_PACKET) || protocol < 0)
			return(-EINVAL);

	// 分配一个新的socket结构体
	if (!(sock = sock_alloc())) 
	{
		...
	}
	// 设置类型和操作函数集
	sock->type = type;
	sock->ops = ops;
	if ((i = sock->ops->create(sock, protocol)) < 0) 
	{
		sock_release(sock);
		return(i);
	}
	// 返回一个新的文件描述符
	if ((fd = get_fd(SOCK_INODE(sock))) < 0) 
	{
		sock_release(sock);
		return(-EINVAL);
	}

	return(fd);
}

我们从上到下,逐步分析这个过程。
1 根据传的协议类型,找到对应的函数集,因为不同的协议族他的底层操作是不一样的。
2 分配一个socket结构体。定义如下。我们大概了解一下字段就行。

struct socket {
  short			type;		/* SOCK_STREAM, ...		*/
  socket_state		state;
  long			flags;
  struct proto_ops	*ops;	
  // 这个字段要记一下	
  void			*data;		
  struct socket		*conn;		
  struct socket		*iconn;		
  struct socket		*next;
  struct wait_queue	**wait;		
  struct inode		*inode;
  struct fasync_struct  *fasync_list;	
};

struct socket *sock_alloc(void)
{
	struct inode * inode;
	struct socket * sock;
	// 获取一个可用的inode节点
	inode = get_empty_inode();
	if (!inode)
		return NULL;
	// 初始化某些字段
	inode->i_mode = S_IFSOCK;
	inode->i_sock = 1;// socket文件
	inode->i_uid = current->uid;
	inode->i_gid = current->gid;
	// 指向inode的socket结构体,初始化inode结构体的socket结构体
	sock = &inode->u.socket_i;
	sock->state = SS_UNCONNECTED;
	sock->flags = 0;
	sock->ops = NULL;
	sock->data = NULL;
	sock->conn = NULL;
	sock->iconn = NULL;
	sock->next = NULL;
	sock->wait = &inode->i_wait;
	// 互相引用
	sock->inode = inode;		/* "backlink": we could use pointer arithmetic instead */
	sock->fasync_list = NULL;
	// socket数加一
	sockets_in_use++;
	// 返回新的socket结构体,他挂载在inode中
	return sock;
}

sock_alloc首先分配了一个inode,inode节点里有一个socket结构体,然后初始化socket结构体的一些字段,并把他的地址返回。

3 这时候我们拿到一个socket结构体。接着调create函数(省略了部分代码)。

// 创建一个sock结构体,和socket结构体互相关联
static int inet_create(struct socket *sock, int protocol)
{
	struct sock *sk;
	struct proto *prot;
	int err;
	// 分配一个sock结构体
	sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);
	switch(sock->type) 
	{
		case SOCK_STREAM:
			protocol = IPPROTO_TCP;
			// 函数集
			prot = &tcp_prot;
			break;

		case SOCK_DGRAM:
			protocol = IPPROTO_UDP;
			prot=&udp_prot;
			break;
		
	}
	// sock结构体的socket字段指向上层的socket结构体
	sk->socket = sock;
	// 省略一堆对sock结构体的初始化代码
}

我们发现创建一个socket的时候,申请了一个socket结构体,同时也申请了一个sock结构体。为什么需要两个结构体,并且这两个结构体关联在一起呢?这要说到网络协议的复杂性,而这个设计就是linux对这个复杂性的解决方案。我们回头看看socket函数的参数。

socket(int family, int type, int protocol)

family是协议簇,比如unix域、ipv4、ipv6,type是在第一个参数的基础上的子分类。比如ipv4下有tcp、udp、raw、packet。protocol对tcp、udp没用,对raw、packet的话是标记上层协议类型。这好比一棵树一样,从根节点开始,有很多分支。socket结构体是整个网络协议实现的最上层结构,是第一层抽象。根据协议簇的不同,有不同的实现函数,在同一协议簇下,也有不同的子分类,比如ipv4下有tcp、udp等。不同子类具体的逻辑也不一样。即数据结构和算法都不一样。所以socket结构体有一个data字段,他是自定义的,对于ipv4的实现,他是指向一个sock结构体,对于unix域的实现,unix_proto_data结构体。这就解决了不同协议簇(family)不同实现的问题。那对于同一协议簇下的不同子类型,又如何实现呢?比如ipv4下的tcp、udp。linux给出的方案是在sock结构体中定义一个字段,根据子类型type的值,指向不同的底层协议函数集。
在这里插入图片描述
在申请完sock结构体并且和socket结构体互相关联后。这时候我们拿到了一个inode,一个socket结构体,一个sock结构体。然后根据inode拿到一个file和fd文件描述符。最后返回fd给用户。内容结构图如下。
在这里插入图片描述
这就是socket函数返回后的内存结构体。后续我们调用bind,listen等等函数,传入fd,系统就会根据上面图的指向,一直找到tcp函数集,执行对应的函数,对于udp也是一样,不同是tcp函数集变成udp函数集。这一篇我们先介绍socket函数的逻辑,下面继续分析socket编程系列函数的实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IP协议TCP/IP协议族中的一员,它主要负责实现数据包的路由和转发功能。在IP协议中,数据包的大小是有限制的,而当数据包的大小超过了这个限制时,就需要进行分片处理。本文将介绍如何在Linux 1.2.13内核中实现IP分片功能。 1. IP数据包的分片 在IP协议中,每个数据包都有一个最大传输单元(MTU)的限制,也就是说,当数据包的大小超过了这个限制时,就需要进行分片处理。IP协议中规定,每个分片的大小必须是8字节的倍数,同时每个分片都有一个标识符和偏移量,以便在接收端将分片组合成完整的数据包。 2. IP分片的实现Linux 1.2.13内核中,IP分片的实现是通过ip_fragment函数来完成的。这个函数的主要作用是将大的IP数据包分成多个小的数据包,并设置每个小的IP数据包的标识符和偏移量。ip_fragment函数的参数如下: - skb:需要分片的数据包 - mtu:分片后每个数据包的最大长度 - want:表示是否强制进行分片 在ip_fragment函数中,首先会检查skb数据包的长度是否超过了mtu,如果没有超过,则直接返回。否则,就需要进行分片处理。具体的分片过程如下: - 通过skb_copy_bits函数将原始数据包的IP头部复制到每个新的数据包中 - 根据mtu和原始数据包的长度计算出需要分成多少个分片 - 遍历所有分片,设置每个分片的标识符和偏移量 - 将每个分片添加到skb数据包的队列中,并设置IP头部的总长度和MF标志位 当所有的分片都添加到队列中后,ip_fragment函数就会返回。此时,发送端就可以将每个分片发送到网络中,接收端则可以根据标识符和偏移量将所有分片组合成完整的数据包。 3. 总结 IP分片是TCP/IP协议中非常重要的一部分,它可以让数据包在网络中进行传输。在Linux 1.2.13内核中,IP分片的实现是通过ip_fragment函数来完成的,这个函数可以将大的IP数据包分成多个小的IP数据包,并设置每个小的IP数据包的标识符和偏移量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值