趣谈网络协议栈(三),Socket编程常用函数的原理及代码实现

趣谈网络协议栈(三),Socket编程常用函数的原理及代码实现往片回顾在上一篇中,主要学习了arp协议,作为网络层协议,其主要用于以太网地址解析;在内核中会维护一个arp缓存表,用于记录目前活跃的网络设备每

大家好,欢迎来到IT知识分享网。

往片回顾

在上一篇中,主要学习了arp协议,作为网络层协议,其主要用于以太网地址解析;在内核中会维护一个arp缓存表,用于记录目前活跃的网络设备

  • 每个arp表项都会注册一个timer,用于检测表项对应的网络设备是否还存活
  • arp整体会注册一个timer,用于定期更新arp缓存表,保证表项内容有效

socket陷入内核

  • start_kernel()中会调用trap_init()注册系统中断及不同中断对应的handler
  • 在触发0x80中断陷入内核后,根据trap_init()中定义,会触发system_call()汇编函数
  • system_call中根据中断号,在_sys_call_table中寻找对应的handle,socket相关的中断号为101,对应sys_socketcall函数

收包数据处理

  1. 全局backlog队列:top half后,驱动会调用netif_rx将接收到的数据包缓存于该队列中
  2. struck sock->back_log队列;网络层在tcp_rcv函数中将接收到的数据包缓存与该队列
  3. struct sock->receive_queue队列:在该队列中的数据包方可被正式处理,可作为tcp_read函数原材料
  • 1–>2:网络设备软中断触发net_bh函数,转移数据包
  • 2–>3:release_sock->tcp_rcv, 先通过skb_dequeue从back_log中取一个包,再通过tcp_rcv将包挂到receive_queue队列;且在tcp_rcv中sk_buff被转换成sock

socket函数

  • sys_socketcall->sock_socket(int family, int type, int protocol),对应用户态socket(AF_INET, SOCK_STREAM, 0)函数,参数刚好对应sock_socket
  • socket创建一般只执行到会话层,调用传输层注册的init函数(tcp,udp都为NULL)后,完成套接字资源创建

sock_socket

/* * 创建一个socket套接字: * 1. 找到域对应的操作接口结构体 struct propto_ops * 2. sock_alloc:申请socket管理结构体;实际是一种特殊的inode * 3. 调用 inet_proto_ops->create 进入会话层 */ static int sock_socket(int family, int type, int protocol) { int i, fd; struct socket *sock; struct proto_ops *ops; /* Locate the correct protocol family. * 通过域协议号,找到对于的表示层->会话层的操作接口结构体 * AF_INET->inet_proto_ops */ for (i = 0; i < NPROTO; ++i) { if (pops[i] == NULL) continue; if (pops[i]->family == family) break; } if (i == NPROTO) { return -EINVAL; } ops = pops[i]; /* 判断要创建的socket类型是否正确 */ if ((type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET && type != SOCK_RAW && type != SOCK_PACKET) || protocol < 0) return(-EINVAL); /* * 申请socket管理结构体;实际是通过申请 struct inode 管理结构体; * inode中包含了struct socket; 所以socket是一种特殊的inode */ if (!(sock = sock_alloc())) { printk("NET: sock_socket: no more sockets\n"); return(-ENOSR); /* Was: EAGAIN, but we are out of system resources! */ } sock->type = type; sock->ops = ops; /* 调用会话层创建接口函数 */ if ((i = sock->ops->create(sock, protocol)) < 0) { sock_release(sock); return(i); } /* 创建文件包裹inode,并最终将文件描述符返回给用户态 * 且会设置inode->f_op = &socket_file_ops,为socket操作函数 */ if ((fd = get_fd(SOCK_INODE(sock))) < 0) { sock_release(sock); return(-EINVAL); } return(fd); } 

大家如果还想了解更多Linux内核开发相关的更多知识点,请后台私信我【内核】免费领取,里面记录了许多的Linux内核知识点。

趣谈网络协议栈(三),Socket编程常用函数的原理及代码实现

内核学习网站:

Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂

inet_create

/* * 完成socket资源创建 * 1. 申请sock管理结构体 * 2. 根据socket类型,找到对应的会话层-->传输层操作接口结构体 struct proto */ 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); if (sk == NULL) return(-ENOBUFS); sk->num = 0; sk->reuse = 0; switch(sock->type) { /* 根据socket类型,找到对应的会话层-->传输层操作接口结构体 struct proto */ case SOCK_STREAM: case SOCK_SEQPACKET: if (protocol && protocol != IPPROTO_TCP) { kfree_s((void *)sk, sizeof(*sk)); return(-EPROTONOSUPPORT); } protocol = IPPROTO_TCP; sk->no_check = TCP_NO_CHECK; prot = &tcp_prot; break; ....... } sk->socket = sock; #ifdef CONFIG_TCP_NAGLE_OFF sk->nonagle = 1; #else sk->nonagle = 0; #endif sk->type = sock->type; ...... sk->state_change = def_callback1; sk->data_ready = def_callback2; sk->write_space = def_callback3; sk->error_report = def_callback1; if (sk->num) { /* 将具有确定端口号一个新sock 结构加入到sock_array 数组表示的sock 结构链表群中 */ put_sock(sk->num, sk); sk->dummy_th.source = ntohs(sk->num);/* 用该端口号初始化TCP头 */ } /* 调用传输层初始化函数 */ if (sk->prot->init) { err = sk->prot->init(sk); if (err != 0) { destroy_sock(sk); return(err); } } return(0); } 

get_fd

/* * 创建一个struct file结构体wrap inode,并将该结构体填入current->files->fd中 * file, inode结构体都是可以在用户态使用,必须使用get_empty_filp()函数来获取内存 */ static int get_fd(struct inode *inode) { int fd; struct file *file; /* * Find a file descriptor suitable for return to the user. * 由于file管理结构体用户态也要使用,因此必须从伙伴系统中分配内存 */ file = get_empty_filp(); if (!file) return(-1); /* 找到本进程下file结构体数组的空位置 */ for (fd = 0; fd < NR_OPEN; ++fd) if (!current->files->fd[fd]) break; if (fd == NR_OPEN) { file->f_count = 0; return(-1); } /* 用来将一个给定的文件描述符加入集合之中 */ FD_CLR(fd, ¤t->files->close_on_exec); current->files->fd[fd] = file;/* 将file结构体填入 */ file->f_op = &socket_file_ops;/* 将socket的操作函数赋给file->f_op */ file->f_mode = 3; file->f_flags = O_RDWR; file->f_count = 1; file->f_inode = inode; if (inode) inode->i_count++; file->f_pos = 0; return(fd); } 

bind函数

  • 用户态函数bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)对应sock_bind

sock_bind

/* * 1. 找到sockfd对应的socket结构体 * 2. 将用户态的地址信息结构体cp到内核态===》表示层完成 * 3. 调用表示层-->会话层接口函数,进入会话层 */ static int sock_bind(int fd, struct sockaddr *umyaddr, int addrlen) { struct socket *sock; int i; char address[MAX_SOCK_ADDR]; int err; if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL) return(-EBADF); /* 找到fd对应的socket */ if (!(sock = sockfd_lookup(fd, NULL))) return(-ENOTSOCK); /* 将用户态的地址信息结构体copy到内核态 */ if((err=move_addr_to_kernel(umyaddr,addrlen,address))<0) return err; /* 调用表示层-->会话层接口函数,进入会话层 inet_bind */ if ((i = sock->ops->bind(sock, (struct sockaddr *)address, addrlen)) < 0) { return(i); } return(0); } 

inet_bind

/* 将sock绑定到对应的传输层协议sock_array中 * 1. 判断sock状态,在没有bind之前,一定是close的 * 2. 如果端口号为0,则分配一个 * 3. 检查source addr * 4. 检查source addr & port num是否符合复用条件限制;如果不允许复用,又再次申请,则报错 * 5. put_sock:将sock注册到sock_array中 */ static int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { struct sockaddr_in *addr=(struct sockaddr_in *)uaddr; struct sock *sk=(struct sock *)sock->data, *sk2; unsigned short snum = 0 /* Stoopid compiler.. this IS ok */; int chk_addr_ret; /* 在进行地址绑定时,该套接字应该处理关闭状态 */ if (sk->state != TCP_CLOSE) return(-EIO); if(addr_len<sizeof(struct sockaddr_in)) return -EINVAL; if(sock->type != SOCK_RAW) { if (sk->num != 0) return(-EINVAL); snum = ntohs(addr->sin_port); /* 用户没有传port下来,则内核自己找一个空闲的port使用 */ if (snum == 0) { snum = get_new_socknum(sk->prot, 0); } /* 0-1023端口号仅限super user使用 */ if (snum < PROT_SOCK && !suser()) return(-EACCES); } chk_addr_ret = ip_chk_addr(addr->sin_addr.s_addr); /* 如果指定的地址不是本地地址,并且也不是一个多播地址,则错误返回:地址不可用 */ if (addr->sin_addr.s_addr != 0 && chk_addr_ret != IS_MYADDR && chk_addr_ret != IS_MULTICAST) return(-EADDRNOTAVAIL); /* 没有指定地址,则系统自动分配一个本地地址 */ if (chk_addr_ret || addr->sin_addr.s_addr == 0) sk->saddr = addr->sin_addr.s_addr; if(sock->type != SOCK_RAW) { /* 对于非SOCK_RAW,需要将分配了端口号的sock插入到对应传输层协议的sock_array hash 链表中. */ cli(); /* sock_array也是一个hash链表,先找到对应的socket hash桶;再在bulk中寻找空闲的sock. */ for(sk2 = sk->prot->sock_array[snum & (SOCK_ARRAY_SIZE -1)]; sk2 != NULL; sk2 = sk2->next) { /* 检查新分配的snum端口号是否有冲突;如果有冲突则要进一步检查 */ if (sk2->num != snum) continue; if (!sk->reuse)/* 如果二者端口号相同,检查sock是否允许地址复用;没有则返回错误 */ { sti(); return(-EADDRINUSE); } if (sk2->num != snum) continue; /* more than one */ if (sk2->saddr != sk->saddr) /* 端口号可以复用的情况下检查source addr是否相同 */ continue; /* socket per slot ! -FB */ /* 如果sock结构为TCP_LISTEN,表示该sock为服务端,服务端不可使用地址复用选项 */ if (!sk2->reuse || sk2->state==TCP_LISTEN) { sti(); return(-EADDRINUSE); } } sti(); /* 保证sk不在sock_array队列中 */ remove_sock(sk); /* 根据新分配的snum将sock插入到sock_array合适位置,相当于正式在传输层注册了sock */ put_sock(snum, sk); /* 初始化tcp header->source_addr */ sk->dummy_th.source = ntohs(sk->num); sk->daddr = 0; sk->dummy_th.dest = 0; } return(0); } 

listen函数

  • 用户态函数listen(int sockfd, int backlog)对应sock_listen

sock_listen

/* * listen函数BSD层对应的函数,为server端接受client端连接做准备 */ static int sock_listen(int fd, int backlog) { struct socket *sock; if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL) return(-EBADF); /* 找到fd对应的socket */ if (!(sock = sockfd_lookup(fd, NULL))) return(-ENOTSOCK); /* */ if (sock->state != SS_UNCONNECTED) { return(-EINVAL); } /* 调用会话层接口,进入会话层 */ if (sock->ops && sock->ops->listen) sock->ops->listen(sock, backlog); sock->flags |= SO_ACCEPTCON;/* 调用完成后,设置标志位 */ return(0); } 

inet_listen

/* * listen函数会话层对应的函数:将sock转为监听状态 * 1. 检查backlog的长度大小 * 2. 检查状态并重置sk的状态 */ static int inet_listen(struct socket *sock, int backlog) { struct sock *sk = (struct sock *) sock->data; if(inet_autobind(sk)!=0) return -EAGAIN; /* We might as well re use these. */ /* * note that the backlog is "unsigned char", so truncate it * somewhere. We might as well truncate it to what everybody * else does.. */ /* 如果设置的队列长度大于128则设置为128 */ if ((unsigned) backlog > 128) backlog = 128; sk->max_ack_backlog = backlog; if (sk->state != TCP_LISTEN) { /* 设置sock为Listen状态 */ sk->ack_backlog = 0; sk->state = TCP_LISTEN; } return(0); } 

accept函数

  • 用户态函数accept(int sockfd, const struct sockaddr *addr, socklen_t addrlen)对应sock_accept

sock_accept

/* * accept函数的BSD层对应函数,用于server端接受一个client的连接 */ static int sock_accept(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen) { struct file *file; struct socket *sock, *newsock; int i; char address[MAX_SOCK_ADDR]; int len; if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL)) return(-EBADF); /* 找到fd对应的socket */ if (!(sock = sockfd_lookup(fd, &file))) return(-ENOTSOCK); /* 检查socket状态 */ if (sock->state != SS_UNCONNECTED) { return(-EINVAL); } /* 检查sock状态 */ if (!(sock->flags & SO_ACCEPTCON)) { return(-EINVAL); } /* 新申请一个socket,同时也申请一个新inode */ if (!(newsock = sock_alloc())) { printk("NET: sock_accept: no more sockets\n"); return(-ENOSR); /* Was: EAGAIN, but we are out of system resources! */ } newsock->type = sock->type; newsock->ops = sock->ops; /* 调用inet_create,创建一个新的socket */ if ((i = sock->ops->dup(newsock, sock)) < 0) { sock_release(newsock); return(i); } /* 使用新的socket,调用会话层accpet函数 */ i = newsock->ops->accept(sock, newsock, file->f_flags); if ( i < 0) { sock_release(newsock); return(i); } /* 将新的socket封装在struct file中 */ if ((fd = get_fd(SOCK_INODE(newsock))) < 0) { sock_release(newsock); return(-EINVAL); } if (upeer_sockaddr) { newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 1); move_addr_to_user(address,len, upeer_sockaddr, upeer_addrlen); } return(fd); } 

inet_accept

/* * 会话层accept: * 1. 创建一个本地新套接字用于通信,原监听套接字仍然用于监听 * 2. 调用传输层accept,发送应答报文,并返回一个sock资源 * 3. 将新创建sock结构挂接到监听sock结构的接收队列(sock->receive_queue)中 */ static int inet_accept(struct socket *sock, struct socket *newsock, int flags) { struct sock *sk1, *sk2; int err; /* 从老的socket中获取sock */ sk1 = (struct sock *) sock->data; /* 检测newsock中是否已经存在套接字,如果是则要释放 */ if (newsock->data) { struct sock *sk=(struct sock *)newsock->data; newsock->data=NULL; sk->dead = 1; destroy_sock(sk); } /* 检测会话层-->传输层接口函数指针是否存在,不存在则异常 */ if (sk1->prot->accept == NULL) return(-EOPNOTSUPP); /* Restore the state if we have been interrupted, and then returned. */ if (sk1->pair != NULL ) { /* 检查监听sock中是否挂有被中断的sock,有的话优先恢复被中断的 */ sk2 = sk1->pair; sk1->pair = NULL; } else { /* 老的socket经过accept后,会生成一个新的sock;以后就用这个新的sock进行通信管理 * 在传输层的accept会发送应答数据包; * 传入的是侦听sock */ sk2 = sk1->prot->accept(sk1,flags); if (sk2 == NULL) { if (sk1->err <= 0) printk("Warning sock.c:sk1->err <= 0. Returning non-error.\n"); err=sk1->err; sk1->err=0; return(-err); } } /* 将代表本次通话的sock赋给新的socket */ newsock->data = (void *)sk2; sk2->sleep = newsock->wait; sk2->socket = newsock; newsock->conn = NULL; /* */ if (flags & O_NONBLOCK) return(0); cli(); /* avoid the race. */ while(sk2->state == TCP_SYN_RECV) { interruptible_sleep_on(sk2->sleep); if (current->signal & ~current->blocked) { sti(); /* 如果socket在等待过程中被中断,则监听套接字sock结构之pair字段将被 * 初始化为该被中断套接字对应的sock结构; * 则在下一次调用accept函数时,会优先处理这个之前被中断的套接字 */ sk1->pair = sk2; sk2->sleep = NULL; sk2->socket=NULL; newsock->data = NULL; return(-ERESTARTSYS); } } sti(); /* 如果while正常结束,则sock应该进入TCP_ESTABLISHED状态 */ if (sk2->state != TCP_ESTABLISHED && sk2->err > 0) { err = -sk2->err; sk2->err=0; sk2->dead=1; /* ANK */ destroy_sock(sk2); newsock->data = NULL; return(err); } /* 新创建的套接字处于连接状态,原套接字依旧处理监听状态 */ newsock->state = SS_CONNECTED; return(0); } 

tcp_accept

/* * 传输层协议tcp,对应accept系统调用的实现 * 1. 检查监听sock的报文接收队列,查看是否存在已经完成连接的数据包 * 2. 如果没有则继续循环检查/阻塞等待 * 3. 将找到的已完成连接sock返回上层 */ static struct sock *tcp_accept(struct sock *sk, int flags) { struct sock *newsk; struct sk_buff *skb; /* 传入的不是侦听sock,则异常 */ if (sk->state != TCP_LISTEN) { sk->err = EINVAL; return(NULL); } /* Avoid the race. */ cli(); sk->inuse = 1; /* 检查监听sock->receive_queue队列,查看是否存在已经完成连接的数据包 */ while((skb = tcp_dequeue_established(sk)) == NULL) { /* 如果没有连接完成状态的数据包 */ if (flags & O_NONBLOCK) { /* 如果设置了非阻塞模式,则返回 */ sti(); /* 返回前将sock->back_log中缓存的数据包尽可能转移到sock->receive_queue */ release_sock(sk); sk->err = EAGAIN; return(NULL); } release_sock(sk); /* 否则等待睡眠,用户态阻塞在accept函数 */ interruptible_sleep_on(sk->sleep); if (current->signal & ~current->blocked) { sti(); sk->err = ERESTARTSYS; return(NULL); } sk->inuse = 1; } sti(); /* * Now all we need to do is return skb->sk. */ newsk = skb->sk; kfree_skb(skb, FREE_READ); /* 缓存的未应答数据包个数-1 */ sk->ack_backlog--; release_sock(sk); return(newsk); } 

tcp_dequeue_established

/* * 1. 通过tcp_find_established函数检查监听sock中是否有已经建立连接的数据包 * 2. 有则将该包从监听sock->receive_queue队列中去除,向上层返回该包 */ static struct sk_buff *tcp_dequeue_established(struct sock *s) { struct sk_buff *skb; unsigned long flags; save_flags(flags); cli(); skb=tcp_find_established(s); if(skb!=NULL) skb_unlink(skb); /* Take it off the queue */ restore_flags(flags); return skb; } 

tcp_find_established

/* * 对于监听sock,其sock->receive_queue中的数据包都是连接请求、连接完成数据包,即syn包 * 数据处理是会话sock负责 * 1. 从监听sock缓冲队列中检查是否存在已经完成连接的数据包 * 2. 如果有则直接返回sock */ static struct sk_buff *tcp_find_established(struct sock *s) { struct sk_buff *p=skb_peek(&s->receive_queue); if(p==NULL) return NULL; do { if(p->sk->state == TCP_ESTABLISHED || p->sk->state >= TCP_FIN_WAIT1) return p; p=p->next; } /* receive_queue 为环形队列,如果p指针回到起始点,则遍历已完成 */ while(p!=(struct sk_buff *)&s->receive_queue); return NULL; } 

connect

sock_connect

connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

  • 表示层、会话层的调用不需要多说 sock_connect->inet_connect()->tcp_connect(),在tcp_connect()中

tcp_connect

/* * This will initiate an outgoing connection. */ static int tcp_connect(struct sock *sk, struct sockaddr_in *usin, int addr_len) { struct sk_buff *buff; struct device *dev=NULL; unsigned char *ptr; int tmp; int atype; struct tcphdr *t1; struct rtable *rt; if (sk->state != TCP_CLOSE) { return(-EISCONN); } if (addr_len < 8) return(-EINVAL); if (usin->sin_family && usin->sin_family != AF_INET) return(-EAFNOSUPPORT); /* 如果source addr为空,则填入本机IP */ if(usin->sin_addr.s_addr==INADDR_ANY) usin->sin_addr.s_addr=ip_my_addr(); if ((atype=ip_chk_addr(usin->sin_addr.s_addr)) == IS_BROADCAST || atype==IS_MULTICAST) return -ENETUNREACH; sk->inuse = 1; sk->daddr = usin->sin_addr.s_addr; sk->write_seq = tcp_init_seq();/* 使用时间产生序列号 */ sk->window_seq = sk->write_seq; sk->rcv_ack_seq = sk->write_seq -1; sk->err = 0; sk->dummy_th.dest = usin->sin_port; release_sock(sk); buff = sk->prot->wmalloc(sk,MAX_SYN_SIZE,0, GFP_KERNEL); if (buff == NULL) { return(-ENOMEM); } sk->inuse = 1; buff->len = 24; buff->sk = sk; buff->free = 0; buff->localroute = sk->localroute; t1 = (struct tcphdr *) buff->data; /* 查询路由表获取可发送该数据包的路由表项;如果没有找到,则无法发送 */ rt=ip_rt_route(sk->daddr, NULL, NULL); /* 创建IP,MAC首部,并返回两个首部的总长度 * skb:待创建首部的数据包;saddr: 数据包发送的最初源地址,被转发的数据包无需进行IP首部创建 * daddr:最终远端接收端IP地址 * dev:根据daddr查询路由表,将返回的表项中绑定的接口赋值给dev参数,并传出 * type:上层协议类型,tcp,udp * tos,ttl:对应IP首部中的tos(服务类型),ttl(跳数); */ tmp = sk->prot->build_header(buff, sk->saddr, sk->daddr, &dev, IPPROTO_TCP, NULL, MAX_SYN_SIZE,sk->ip_tos,sk->ip_ttl); if (tmp < 0) { sk->prot->wfree(sk, buff->mem_addr, buff->mem_len); release_sock(sk); return(-ENETUNREACH); } buff->len += tmp; t1 = (struct tcphdr *)((char *)t1 +tmp); memcpy(t1,(void *)&(sk->dummy_th), sizeof(*t1)); t1->seq = ntohl(sk->write_seq++); sk->sent_seq = sk->write_seq; buff->h.seq = sk->write_seq; t1->ack = 0; t1->window = 2; t1->res1=0; t1->res2=0; t1->rst = 0; t1->urg = 0; t1->psh = 0; t1->syn = 1; t1->urg_ptr = 0; t1->doff = 6; /* use 512 or whatever user asked for */ if(rt!=NULL && (rt->rt_flags&RTF_WINDOW)) sk->window_clamp=rt->rt_window; else sk->window_clamp=0; if (sk->user_mss) sk->mtu = sk->user_mss; else if(rt!=NULL && (rt->rt_flags&RTF_MTU)) sk->mtu = rt->rt_mss; else { #ifdef CONFIG_INET_SNARL if ((sk->saddr ^ sk->daddr) & default_mask(sk->saddr)) #else if ((sk->saddr ^ sk->daddr) & dev->pa_mask) #endif sk->mtu = 576 - HEADER_SIZE; else sk->mtu = MAX_WINDOW; } if(sk->mtu <32) sk->mtu = 32; /* Sanity limit */ sk->mtu = min(sk->mtu, dev->mtu - HEADER_SIZE); ptr = (unsigned char *)(t1+1); ptr[0] = 2; ptr[1] = 4; ptr[2] = (sk->mtu) >> 8; ptr[3] = (sk->mtu) & 0xff; /* 计算tcp checksum */ tcp_send_check(t1, sk->saddr, sk->daddr, sizeof(struct tcphdr) + 4, sk); /* 修改TCP sock状态为 TCP_SYN_SENT */ tcp_set_state(sk,TCP_SYN_SENT); sk->rto = TCP_TIMEOUT_INIT; #if 0 /* we already did this */ init_timer(&sk->retransmit_timer); #endif sk->retransmit_timer.function=&retransmit_timer;/* 注册超时重发函数 */ sk->retransmit_timer.data = (unsigned long)sk; reset_xmit_timer(sk, TIME_WRITE, sk->rto); /* 重置超时重发定时器 */ sk->retransmits = TCP_SYN_RETRIES; sk->prot->queue_xmit(sk, dev, buff, 0); /* 发送tcp报文 */ reset_xmit_timer(sk, TIME_WRITE, sk->rto); tcp_statistics.TcpActiveOpens++; tcp_statistics.TcpOutSegs++; release_sock(sk); return(0); } 

tcp_rcv

在软中断函数(net_bh)中,通过从全局backlog函数中收包,再通过ip_packet_type->ip_rcv()处理后,数据包会进入各个sock的back_log队列

  • ip_rcv中,会检查网络防火墙设置,如果不满足网络防火墙条件,则会给对端发一个ICMP报文(ICMP_DEST/PORT_UNREACH)
  • 检查该IP包目的地址;如果dest addr不是本设备,而且也不是广播地址,则转发(ip_forward)
  • 检查是否为分片数据包,如果是则要在网络层重组(ip_defrag)后,在将重组后的数据包送往传输层进行处理
  • 通过ipheader->protocol确定传输层协议,传输层的所有协议都聚集在一个全局结构体struct inet_protocol中;然后调用传输层协议收包函数,将报文向上层传递;tcp->tcp_protocol;
/* * 1. 通过get_sock,从传输层的所有sock中找到请求对应的服务端sock服务节点TCB(目的端口号);如果找不到目标TCB,则返回TCP_reset * 2. 在skb_queue_tail中将报文发送到sock->back_log的队尾 * 3. 检查sock->state, 如果在TCP_LISTEN状态,则使用 tcp_conn_request 进行ACK;如果在 TCP_SYN_RECV 状态,则调用 tcp_ack() 进行响应 a. 在 tcp_conn_request 中,会新建sock,并将状态置为 TCP_SYN_RECV ,并响应ack报文 b. 当再次收到client端的ACK报文时,会进入 tcp_ack() 函数,首先检查ACK报文是否合法 */

tcp_conn_request

  • 侦听sock接收队列receive_queue中缓存的均是请求连接数据包,不包含普通数据包
  • accept系统调用从侦听sock的 receive_queue 中取数据包,获得该数据包对应的sock;如果状态为TCP_ESTABLISHED,则系统调用成功返回
  • 在 tcp_conn_request 中实现新创建对话sock,并将状态转为 TCP_SYN_RECV;
  • 在 tcp_ack 函数中,专门负责对方发送的ACK书包;当检测到某个ACK数据包是三路握手连接过程中完成连接建立的应答数据包时,会将sock->state置为 TCP_ESTABLISHED
  • 在 tcp_ack 函数中完成三路握手后,调用sock->state_change的回调函数,唤醒sock睡眠队列中的进程,从而继续accept(tcp_accept)函数的处理
/* * 专门用于处理连接请求: * 1. 创建一个sock,并对该sock结构进行初始化 * 2. 发送一个应答报文,并将新创建的sock结构体状态置为TCP_SYN_RECV * 3. 最后将新建的sock结构与请求连接数据包绑定并挂接到监听sock的receive_queue队列中 */ 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/47724.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信