精华内容
下载资源
问答
  • 2022-03-29 11:16:37

            linux内核的等待队列是在内核中运用非常广泛的数据结构,它是以双循环链表为基础的数据结构,与进程的休眠---唤醒机制紧密相连,可以用来同步对系统资源的访问、异步事件通知、跨进程通信等。

            假设进程A想要获取某资源(读网卡数据),但是此时资源没有准备好(网卡还未接收到数据),这时内核必须切换到其他进程进行,直到资源准备好再唤醒该进程。

    1、等待队列头

    struct wait_queue_head {
    	spinlock_t		lock;  //用于互斥访问的自旋锁
    	struct list_head	head;
    };
    typedef struct wait_queue_head wait_queue_head_t;

    可通过宏DECLARE_WAIT_QUEUE_HEAD(name)动态创建或者函数init_waitqueue_head(&name)创建类型为wait_queue_head_t的等待队列头name。

    //静态创建
    #define DECLARE_WAIT_QUEUE_HEAD(name) \
    	struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
    
    #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {					\
    	.lock		= __SPIN_LOCK_UNLOCKED(name.lock),			\
    	.head		= { &(name).head, &(name).head } }
    
    
    //动态创建
    #define init_waitqueue_head(wq_head)						\
    	do {									\
    		static struct lock_class_key __key;				\
    										\
    		__init_waitqueue_head((wq_head), #wq_head, &__key);		\
    	} while (0)
    
    void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *key)
    {
            spin_lock_init(&wq_head->lock);
            lockdep_set_class_and_name(&wq_head->lock, key, name);
            INIT_LIST_HEAD(&wq_head->head);
    }
    

    2、等待队列元素

    struct wait_queue_entry {
    	unsigned int		flags;
    	void			*private;  //指向等待队列的进程task_struct
    	wait_queue_func_t	func;  //唤醒函数
    	struct list_head	entry;  //链表元素,将wait_queue_entry 挂到wait_queue_head_t
    };

    类似的,队列元素的创建也类似:DECLARE_WAITQUEUE(name, task) 定义一个名为name的等待队列元素,或者使用init_waitqueue_entry(&name, tsk)动态创建

    //静态创建宏
    #define DECLARE_WAITQUEUE(name, tsk)						\
    	struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)
    
    #define __WAITQUEUE_INITIALIZER(name, tsk) {					\
    	.private	= tsk,							\
    	.func		= default_wake_function,				\
    	.entry		= { NULL, NULL } }
    
    
    //动态创建
    static inline void init_waitqueue_entry(struct wait_queue_entry *wq_entry, 
                                                struct task_struct *p)
    {
    	wq_entry->flags		= 0;
    	wq_entry->private	= p;
    	wq_entry->func		= default_wake_function;
    }
    
    //也可以使用init_waitqueue_func_entry函数来初始化为自定义的唤醒函数
    static inline void
    init_waitqueue_func_entry(struct wait_queue_entry *wq_entry, wait_queue_func_t func)
    {
    	wq_entry->flags		= 0;
    	wq_entry->private	= NULL;
    	wq_entry->func		= func;
    }
    

    3、添加移除等待队列

    内核提供了几个函数将元素添加删除至等待队列,实现位于kernel/sched/wait.c

    add_wait_queue():队列添加非独占普通等待队列(flag清除WQ_FLAG_EXCLUSIVE标志)

    add_wait_queue_exclusive():队列尾部添加独占等待队列(flag设置WQ_FLAG_EXCLUSIVE标志)

    remove_wait_queue():移除元素

    void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
    	unsigned long flags;
    
    	wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
    	spin_lock_irqsave(&wq_head->lock, flags);
    	__add_wait_queue_entry_tail(wq_head, wq_entry);
    	spin_unlock_irqrestore(&wq_head->lock, flags);
    }
    EXPORT_SYMBOL(add_wait_queue);
    
    void add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
    	unsigned long flags;
    
    	wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
    	spin_lock_irqsave(&wq_head->lock, flags);
    	__add_wait_queue_entry_tail(wq_head, wq_entry);
    	spin_unlock_irqrestore(&wq_head->lock, flags);
    }
    EXPORT_SYMBOL(add_wait_queue_exclusive);
    
    //移除
    void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
    	unsigned long flags;
    
    	spin_lock_irqsave(&wq_head->lock, flags);
    	__remove_wait_queue(wq_head, wq_entry);
    	spin_unlock_irqrestore(&wq_head->lock, flags);
    }
    EXPORT_SYMBOL(remove_wait_queue);
    
    
    /
    static inline void __add_wait_queue_entry_tail(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
    	list_add_tail(&wq_entry->entry, &wq_head->head); //添加到队列头部
    }
    
    static inline void
    __remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
    	list_del(&wq_entry->entry);
    }

    示意图如下:

    4、进程 休眠——唤醒

    把进程(task_struct)添加到等待队列后,就可以休眠该进程,让出cpu给其他进程运行,内核提供wait_event宏和它的几个变种来实现进程休眠,直到condition成立。

    wq_head为等待队列头,condition是一个bool表达式,

    wait_event(wq_head, condition)  //非中断休眠
    wait_event_timeout(wq_head, condition, timeout)  //同上,另外进程等待限定时间返回不论                                                                                                condition是否成立
    wait_event_interruptible(wq_head, condition) //进程可以被信号打断
    wait_event_interruptible_timeout(wq_head, condition, timeout)   //类似上面
    io_wait_event(wq_head, condition)

    #define wait_event(wq_head, condition)						\
    do {										\
    	might_sleep();								\
    	if (condition)								\
    		break;								\
    	__wait_event(wq_head, condition);					\
    } while (0)
    
    
    #define __wait_event(wq_head, condition)					\
    	(void)___wait_event(wq_head, condition, 
                            TASK_UNINTERRUPTIBLE,  \  //带interruptible为TASK_INTERRUPTIBLE
                            0,               \  //
                            0,	\  //timeout
    			    schedule())
    
    
    /* 定义等待队列元素,并将元素加入到等待队列中
     * 循环判断等待条件condition是否满足,若条件满足,或者接收到中断信号,等待结束,函数返回
     * 若condition满足,返回0;否则返回-ERESTARTSYS
     */
    #define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		\
    ({										\
    	__label__ __out;							\
    	struct wait_queue_entry __wq_entry;					\
    	long __ret = ret;	/* explicit shadow */				\
    			
        // 初始化等待队列元素__wq_entry,关联当前进程,根据exclusive参数初始化属性标志 
        // 唤醒函数为autoremove_wake_function()    							
    	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	\
        // 等待事件循环  
    	for (;;) {								\
    		//当检测进程是否有待处理信号则返回值__int不为0 
    		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
    			                                                                    \
            // 当前进程让出调度器前,判断condition是否成立。若成立,提前结束,后续将返回0 
    		if (condition)							\
    			break;							\
    										\
            // 当前进程让出调度器前,判断当前进程是否接收到中断信号(或KILL信号)       
            // 如果成立,将提前返回-ERESTARTSYS   
    		if (___wait_is_interruptible(state) && __int) {			\ 
    			__ret = __int;						\
    			goto __out;						\
    		}								\
    										\
            // 此处实际执行schedule(),当前进程让出调度器 
    		cmd;								\
    	}									\
        // 设置进程为可运行状态,并且将等待队列元素从等待队列中删除    
    	finish_wait(&wq_head, &__wq_entry);					\
    __out:	__ret;									\
    })
    
    
    void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
    {
    	wq_entry->flags = flags;
    	wq_entry->private = current;
    	wq_entry->func = autoremove_wake_function;
    	INIT_LIST_HEAD(&wq_entry->entry);
    }
    EXPORT_SYMBOL(init_wait_entry);
    
    //防止wait没有在队列中,但是事件已经产生导致无限等待
    long prepare_to_wait_event(struct wait_queue_head *wq_head, 
                                struct wait_queue_entry *wq_entry, int state)
    {
    	unsigned long flags;
    	long ret = 0;
    
    	spin_lock_irqsave(&wq_head->lock, flags);
        // 返回非0值条件:可被信号中断并且确实有信号挂起
    	if (unlikely(signal_pending_state(state, current))) {
            // 将等待队列元素从等待队列中删除,返回-ERESTARTSYS
    		list_del_init(&wq_entry->entry);
    		ret = -ERESTARTSYS;
    	} else {
            // 判断wq_entry->entry是否为空,即等待队列元素是否已经被添加到等待队列中
    		if (list_empty(&wq_entry->entry)) {
                // WQ_FLAG_EXCLUSIVE标志被设置时,将等待队列元素添加到等待队列尾部(独占等待)
                // 否则,将等待队列元素添加到等待队列头部。同2.1中对WQ_FLAG_EXCLUSIVE标志介绍。
    			if (wq_entry->flags & WQ_FLAG_EXCLUSIVE)
    				__add_wait_queue_entry_tail(wq_head, wq_entry);
    			else
    				__add_wait_queue(wq_head, wq_entry);
    		}
            // 改变当前进程的状态
    		set_current_state(state);
    	}
    	spin_unlock_irqrestore(&wq_head->lock, flags);
    
    	return ret;
    }
    EXPORT_SYMBOL(prepare_to_wait_event);
    
     用state_value改变当前的进程状态
    #define set_current_state(state_value)				\
    	do {							\
    		current->task_state_change = _THIS_IP_;		\
    		smp_store_mb(current->state, (state_value));	\
    	} while (0)
    
    
    /*  设置进程为可运行状态,并且将等待队列元素从等待队列中删除  */
    void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
    	unsigned long flags;
    
        // 将当前进程状态改为可运行状态(TASK_RUNNING)
        // 类似set_current_state(),差别在于未进行内存屏障
    	__set_current_state(TASK_RUNNING);
    
        // 等待队列元素若仍在等待队列中,则将其删除
    	if (!list_empty_careful(&wq_entry->entry)) {
    		spin_lock_irqsave(&wq_head->lock, flags);
    		list_del_init(&wq_entry->entry);
    		spin_unlock_irqrestore(&wq_head->lock, flags);
    	}
    }
    EXPORT_SYMBOL(finish_wait);

    简单总结下进程进入休眠的步骤:

    1、使用add_wait_queue将当前进程关联的等待队列元素添加到等待队列

    2、set_current_state设置中断状态

    3、判断资源是否拿到,或是否捕捉到中断信号

    4、没拿到进程让出调度器,调用schedule()进入休眠状态

    5、资源得到满足,将等待队列元素从等待队列删除

    唤醒等待队列

    当资源准备好后,就可以唤醒等待队列中的进程,内核通过wake_up()和它的几个变种来唤醒等待队列中的进程

    wake_up(&wq_head)  //唤醒等待队列上的所有进程
    wake_up_interruptible(&wq_head)  //只唤醒哪些执行可中断睡眠的进程
    wake_up_nr(&wq_head, nr) //唤醒给定数目的独占等待进程
    wake_up_interruptible_nr(&wq_head, nr)
    wake_up_interruptible_all(&wq_head)

    #define TASK_NORMAL         (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
    //可知TASK_NORMAL唤醒TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE的所有进程
    
    #define wake_up(x)			        __wake_up(x, TASK_NORMAL, 1, NULL)
    #define wake_up_nr(x, nr)	        __wake_up(x, TASK_NORMAL, nr, NULL)
    #define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
    
    void __wake_up(struct wait_queue_head *wq_head, unsigned int mode,
    			int nr_exclusive, void *key)
    {
    	unsigned long flags;
    
    	spin_lock_irqsave(&wq_head->lock, flags);
    	__wake_up_common(wq_head, mode, nr_exclusive, 0, key);
    	spin_unlock_irqrestore(&wq_head->lock, flags);
    }
    EXPORT_SYMBOL(__wake_up);
    
    
    static void __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
    			int nr_exclusive, int wake_flags, void *key)
    {
    	wait_queue_entry_t *curr, *next;
    
         在等待队列头指向的链表上,从curr指向的元素开始依次遍历元素
    	list_for_each_entry_safe(curr, next, &wq_head->head, entry) {
    		unsigned flags = curr->flags;
    
            // 调用等待队列元素绑定的唤醒回调函数
            // 注意,具体唤醒何种进程(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE),作为参数传递给唤        
            // 醒函数处理
            // 当进程不符合唤醒条件时,ret为0,详见try_to_wake_up()
    		int ret = curr->func(curr, mode, wake_flags, key);
    		if (ret < 0)
    			break;
    
            // 如果当前等待队列元素为独占等待,并且独占等待个数已经等于nr_exclusive,提前退出循环
            // 独占等待进程被加入到等待队列的尾部,因此此时表明所有唤醒工作已经完成
    		if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
    			break;
    	}
    }
    

    wake_up会编译等待队列上的所有元素,最终会调用等待队列元素所绑定的唤醒函数

    DECLARE_WAITQUEUE(name, tsk)使用default_wake_function()

    init_wait_entry(&name,flag)中使用autoremove_wake_function()

    default_wake_function

    int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags,
    			  void *key)
    {
    	return try_to_wake_up(curr->private, mode, wake_flags);
    }
    EXPORT_SYMBOL(default_wake_function);
    
    static int
    try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
    {
    	unsigned long flags;
    	int cpu, success = 0;
    
    	smp_mb__before_spinlock();
    	raw_spin_lock_irqsave(&p->pi_lock, flags);
    
        // 此处对进程的状态进行筛选,跳过不符合状态的进程(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)
    	if (!(p->state & state))
    		goto out;
    
    	trace_sched_waking(p);
    
    	/* We're going to change ->state: */
    	success = 1;
    	cpu = task_cpu(p);
    
    	smp_rmb();
    	if (p->on_rq && ttwu_remote(p, wake_flags)) //当前进程已处于rq运行队列,则无需唤醒
    		goto stat;
    
    ...
    
    	ttwu_queue(p, cpu, wake_flags);
    stat:
    	ttwu_stat(p, cpu, wake_flags);
    out:
    	raw_spin_unlock_irqrestore(&p->pi_lock, flags);
    
    	return success;
    }
    
    
    static void ttwu_queue_remote(struct task_struct *p, int cpu, int wake_flags)
    {
    	struct rq *rq = cpu_rq(cpu);  // 获取当前进程的运行队列
    
    	p->sched_remote_wakeup = !!(wake_flags & WF_MIGRATED);
    
    	if (llist_add(&p->wake_entry, &cpu_rq(cpu)->wake_list)) {
    		if (!set_nr_if_polling(rq->idle))
    			smp_send_reschedule(cpu);
    		else
    			trace_sched_wake_idle_without_ipi(cpu);
    	}
    }
    ...
    default_wake_function函数调用顺序为:
    

    default_wake_function() --> try_to_wake_up() --> ttwu_queue() --> ttwu_do_activate() --> ttwu_do_wakeup()

    autoremove_wake_function

    int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key)
    {
    	int ret = default_wake_function(wq_entry, mode, sync, key);
    
    	if (ret)
    		list_del_init(&wq_entry->entry);
    	return ret;
    }
    EXPORT_SYMBOL(autoremove_wake_function);
    
    int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags,
    			  void *key)
    {
    	return try_to_wake_up(curr->private, mode, wake_flags);
    }
    EXPORT_SYMBOL(default_wake_function);

    可以看到autoremove_wake_function相比default_wake_function,在成功执行唤醒工作后,会自动将等待队列元素从等待队列中移除,所以使用default_wake_function()时不能忘记将元素移除。

    更多相关内容
  • 以下是对pthread_cond_wait的用法进行了详细的分析介绍,需要的朋友可以过来参考下
  • 详细描述TCP的各个状态,初学者可以快速理解掌握tcp状态图
  • 对于服务器挂起中的CLOSE_WAIT & FIN_WAIT2 解决方案。
  • 3(深入理解Wait、Notify和Wait与sleep区别).rar
  • 书上说,处于 TIME_WAIT 状态的连接,在收到相同四元组的 SYN 后,会回 RST 报文,对方收到后就会断开连接。 书中作者只是提了这么一句话,没有给予源码或者抓包图的证据。 起初,我看到也觉得这个逻辑也挺符合常理...

    周末跟朋友讨论了一些 TCP 的问题,在查阅《Linux 服务器高性能编程》这本书的时候,发现书上写了这么一句话:

    图片

    书上说,处于 TIME_WAIT 状态的连接,在收到相同四元组的 SYN 后,会回 RST 报文,对方收到后就会断开连接。

    书中作者只是提了这么一句话,没有给予源码或者抓包图的证据。

    起初,我看到也觉得这个逻辑也挺符合常理的,但是当我自己去啃了 TCP 源码后,发现并不是这样的。

    所以,今天就来讨论下这个问题,「在 TCP 正常挥手过程中,处于 TIME_WAIT 状态的连接,收到相同四元组的 SYN 后会发生什么?

    问题现象如下图,左边是服务端,右边是客户端:

    图片

    先说结论

    在跟大家分析 TCP 源码前,我先跟大家直接说下结论。

    针对这个问题,关键是要看 SYN 的「序列号和时间戳」是否合法,因为处于 TIME_WAIT 状态的连接收到 SYN 后,会判断 SYN 的「序列号和时间戳」是否合法,然后根据判断结果的不同做不同的处理。

    先跟大家说明下, 什么是「合法」的 SYN?

    • 合法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要并且 SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要
    • 非法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要或者 SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要

    上面 SYN 合法判断是基于双方都开启了 TCP 时间戳机制的场景,如果双方都没有开启 TCP 时间戳机制,则 SYN 合法判断如下:

    • 合法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要
    • 非法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要

    收到合法 SYN

    如果处于 TIME_WAIT 状态的连接收到「合法的 SYN 」后,就会重用此四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程

    用下图作为例子,双方都启用了 TCP 时间戳机制,TSval 是发送报文时的时间戳:

    图片

    上图中,在收到第三次挥手的 FIN 报文时,会记录该报文的 TSval (21),用 ts_recent 变量保存。然后会计算下一次期望收到的序列号,本次例子下一次期望收到的序列号就是 301,用 rcv_nxt 变量保存。

    处于 TIME_WAIT 状态的连接收到 SYN 后,因为 SYN 的 seq(400) 大于 rcv_nxt(301),并且 SYN 的 TSval(30) 大于 ts_recent(21),所以是一个「合法的 SYN」,于是就会重用此四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。

    收到非法的 SYN

    如果处于 TIME_WAIT 状态的连接收到「非法的 SYN 」后,就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号(ack num),就回 RST 报文给服务端

    用下图作为例子,双方都启用了 TCP 时间戳机制,TSval 是发送报文时的时间戳:

    图片

    上图中,在收到第三次挥手的 FIN 报文时,会记录该报文的 TSval (21),用 ts_recent 变量保存。然后会计算下一次期望收到的序列号,本次例子下一次期望收到的序列号就是 301,用 rcv_nxt 变量保存。

    处于 TIME_WAIT 状态的连接收到 SYN 后,因为 SYN 的 seq(200) 小于 rcv_nxt(301),所以是一个「非法的 SYN」,就会再回复一个与第四次挥手一样的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号,就回 RST 报文给服务端

    客户端等待一段时间还是没收到 SYN + ACK 后,就会超时重传 SYN 报文,重传次数达到最大值后,就会断开连接。

    PS:这里先埋一个疑问,处于 TIME_WAIT 状态的连接,收到 RST 会断开连接吗?

    源码分析

    下面源码分析是基于 Linux 4.2 版本的内核代码。

    Linux 内核在收到 TCP 报文后,会执行 tcp_v4_rcv 函数,在该函数和 TIME_WAIT 状态相关的主要代码如下:

    int tcp_v4_rcv(struct sk_buff *skb)
    {
      struct sock *sk;
     ...
      //收到报文后,会调用此函数,查找对应的 sock
     sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
              th->dest, sdif, &refcounted);
     if (!sk)
      goto no_tcp_socket;
    
    process:
      //如果连接的状态为 time_wait,会跳转到 do_time_wait
     if (sk->sk_state == TCP_TIME_WAIT)
      goto do_time_wait;
    
    ...
    
    do_time_wait:
      ...
      //由tcp_timewait_state_process函数处理在 time_wait 状态收到的报文
     switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
        // 如果是TCP_TW_SYN,那么允许此 SYN 重建连接
        // 即允许TIM_WAIT状态跃迁到SYN_RECV
        case TCP_TW_SYN: {
          struct sock *sk2 = inet_lookup_listener(....);
          if (sk2) {
              ....
              goto process;
          }
        }
        // 如果是TCP_TW_ACK,那么,返回记忆中的ACK
        case TCP_TW_ACK:
          tcp_v4_timewait_ack(sk, skb);
          break;
        // 如果是TCP_TW_RST直接发送RESET包
        case TCP_TW_RST:
          tcp_v4_send_reset(sk, skb);
          inet_twsk_deschedule_put(inet_twsk(sk));
          goto discard_it;
         // 如果是TCP_TW_SUCCESS则直接丢弃此包,不做任何响应
        case TCP_TW_SUCCESS:;
     }
     goto discard_it;
    }
    

    该代码的过程:

    1. 接收到报文后,会调用 __inet_lookup_skb() 函数查找对应的 sock 结构;
    2. 如果连接的状态是 TIME_WAIT,会跳转到 do_time_wait 处理;
    3. tcp_timewait_state_process() 函数来处理收到的报文,处理后根据返回值来做相应的处理。

    先跟大家说下,如果收到的 SYN 是合法的,tcp_timewait_state_process() 函数就会返回 TCP_TW_SYN,然后重用此连接。如果收到的 SYN 是非法的,tcp_timewait_state_process() 函数就会返回 TCP_TW_ACK,然后会回上次发过的 ACK。

    接下来,看 tcp_timewait_state_process() 函数是如何判断 SYN 包的。

    enum tcp_tw_status
    tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
          const struct tcphdr *th)
    {
     ...
      //paws_reject 为 false,表示没有发生时间戳回绕
      //paws_reject 为 true,表示发生了时间戳回绕
     bool paws_reject = false;
    
     tmp_opt.saw_tstamp = 0;
      //TCP头中有选项且旧连接开启了时间戳选项
     if (th->doff > (sizeof(*th) >> 2) && tcptw->tw_ts_recent_stamp) { 
      //解析选项
        tcp_parse_options(twsk_net(tw), skb, &tmp_opt, 0, NULL);
    
      if (tmp_opt.saw_tstamp) {
       ...
          //检查收到的报文的时间戳是否发生了时间戳回绕
       paws_reject = tcp_paws_reject(&tmp_opt, th->rst);
      }
     }
    
    ....
    
      //是SYN包、没有RST、没有ACK、时间戳没有回绕,并且序列号也没有回绕,
     if (th->syn && !th->rst && !th->ack && !paws_reject &&
         (after(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt) ||
          (tmp_opt.saw_tstamp && //新连接开启了时间戳
           (s32)(tcptw->tw_ts_recent - tmp_opt.rcv_tsval) < 0))) { //时间戳没有回绕
        // 初始化序列号
        u32 isn = tcptw->tw_snd_nxt + 65535 + 2; 
        if (isn == 0)
          isn++;
        TCP_SKB_CB(skb)->tcp_tw_isn = isn;
        return TCP_TW_SYN; //允许重用TIME_WAIT四元组重新建立连接
     }
    
    
     if (!th->rst) {
        // 如果时间戳回绕,或者报文里包含ack,则将 TIMEWAIT 状态的持续时间重新延长
      if (paws_reject || th->ack)
        inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
            TCP_TIMEWAIT_LEN);
    
         // 返回TCP_TW_ACK, 发送上一次的 ACK
        return TCP_TW_ACK;
     }
     inet_twsk_put(tw);
     return TCP_TW_SUCCESS;
    }
    

    如果双方启用了 TCP 时间戳机制,就会通过 tcp_paws_reject() 函数来判断时间戳是否发生了回绕,也就是「当前收到的报文的时间戳」是否大于「上一次收到的报文的时间戳」:

    • 如果大于,就说明没有发生时间戳绕回,函数返回 false。
    • 如果小于,就说明发生了时间戳回绕,函数返回 true。

    从源码可以看到,当收到 SYN 包后,如果该 SYN 包的时间戳没有发生回绕,也就是时间戳是递增的,并且 SYN 包的序列号也没有发生回绕,也就是 SYN 的序列号「大于」下一次期望收到的序列号。就会初始化一个序列号,然后返回 TCP_TW_SYN,接着就重用该连接,也就跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。

    如果双方都没有启用 TCP 时间戳机制,就只需要判断 SYN 包的序列号有没有发生回绕,如果 SYN 的序列号大于下一次期望收到的序列号,就可以跳过 2MSL,重用该连接。

    如果 SYN 包是非法的,就会返回 TCP_TW_ACK,接着就会发送与上一次一样的 ACK 给对方。

    在 TIME_WAIT 状态,收到 RST 会断开连接吗?

    在前面我留了一个疑问,处于 TIME_WAIT 状态的连接,收到 RST 会断开连接吗?

    会不会断开,关键看 net.ipv4.tcp_rfc1337 这个内核参数(默认情况是为 0):

    • 如果这个参数设置为 0, 收到 RST 报文会提前结束 TIME_WAIT 状态,释放连接。
    • 如果这个参数设置为 1, 就会丢掉 RST 报文。

    源码处理如下:

    enum tcp_tw_status
    tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
          const struct tcphdr *th)
    {
    ....
      //rst报文的时间戳没有发生回绕
     if (!paws_reject &&
         (TCP_SKB_CB(skb)->seq == tcptw->tw_rcv_nxt &&
          (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || th->rst))) {
    
          //处理rst报文
          if (th->rst) {
            //不开启这个选项,当收到 RST 时会立即回收tw,但这样做是有风险的
            if (twsk_net(tw)->ipv4.sysctl_tcp_rfc1337 == 0) {
              kill:
              //删除tw定时器,并释放tw
              inet_twsk_deschedule_put(tw);
              return TCP_TW_SUCCESS;
            }
          } else {
            //将 TIMEWAIT 状态的持续时间重新延长
            inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);
          }
    
          ...
          return TCP_TW_SUCCESS;
        }
    }
    

    TIME_WAIT 状态收到 RST 报文而释放连接,这样等于跳过 2MSL 时间,这么做还是有风险。

    sysctl_tcp_rfc1337 这个参数是在 rfc 1337 文档提出来的,目的是避免因为 TIME_WAIT 状态收到 RST 报文而跳过 2MSL 的时间,文档里也给出跳过 2MSL 时间会有什么潜在问题。

    TIME_WAIT 状态之所以要持续 2MSL 时间,主要有两个目的:

    • 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
    • 保证「被动关闭连接」的一方,能被正确的关闭;

    详细的为什么要设计 TIME_WAIT 状态,我在这篇有详细说明:如果 TIME_WAIT 状态持续时间过短或者没有,会有什么问题?

    虽然 TIME_WAIT 状态持续的时间是有一点长,显得很不友好,但是它被设计来就是用来避免发生乱七八糟的事情。

    《UNIX网络编程》一书中却说道:TIME_WAIT 是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它

    所以,我个人觉得将 net.ipv4.tcp_rfc1337 设置为 1 会比较安全。

    总结

    在 TCP 正常挥手过程中,处于 TIME_WAIT 状态的连接,收到相同四元组的 SYN 后会发生什么?

    如果双方开启了时间戳机制:

    • 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要并且SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要。那么就会重用该四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。
    • 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要或者SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要。那么就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号,就回 RST 报文给服务端

    在 TIME_WAIT 状态,收到 RST 会断开连接吗?

    • 如果 net.ipv4.tcp_rfc1337 参数为 0,则提前结束 TIME_WAIT 状态,释放连接。
    • 如果 net.ipv4.tcp_rfc1337 参数为 1,则会丢掉该 RST 报文。

    完!

    展开全文
  • wait方法 与 notify方法

    千次阅读 2021-03-31 15:34:51
    多线程之间的通信主要用到4个方法,wait()、wait(long time)、notify()、notifyAll(),其他方法在多线程基础中都有介绍。 wait():作用是使当前线程从调用处中断并且释放锁转入等待队列,直到收到notify...

    1. 线程状态图

    在这里插入图片描述

    这是线程的7状态模型图,常见的7大状态之间的转换关系也在上面。多线程之间的通信主要用到4个方法,wait()、wait(long time)、notify()、notifyAll(),其他方法在多线程基础中都有介绍。

    1. wait():作用是使当前线程从调用处中断并且释放锁转入等待队列,直到收到notify或者notifyAll的通知才能从等待队列转入锁池队列,没有收到停止会一直死等。

    2. wait(long time):相比wait多了一个等待的时间time,如果经过time(毫秒)时间后没有收到notify或者notifyAll的通知,自动从等待队列转入锁池队列。

    3. notify():随机从等待队列中通知一个持有相同锁的一个线程,如果没有持有相同锁的wait线程那么指令忽略无效。注意是持有相同锁,并且是随机没有固定的,顺序这一点在生产者消费者模型中很重要,会造成假死的状态。

    4. notifyAll():通知等待队列中的持有相同锁的所有线程,让这些线程转入锁池队列。如果没有持有相同锁的wait线程那么指令忽略无效。

    5. wait的两个方法都需要注意中断的问题,wait中断是从语句处中断并且释放锁,当再次获得锁时是从中断处继续向下执行。

    6. notify 和 notifyAll方法通知是延迟通知,必须等待当前线程体执行完所有的同步方法/代码块中的语句退出释放锁才通知wait线程。

    7. 这四个方法都必须在获得锁的情况下才能调用,否则会出现非法监视状态异常。



    2. 基本使用

    2.1、wait立即释放锁 与 notify/notifyAll延迟通知
    class MyService{
        private Object  obj = new Object();
        public void waitfun(){
            try{
                synchronized (obj){
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    obj.wait();
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        public void notifyfun(){
            try{
                synchronized (obj){
                    System.out.println("begin notify: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    obj.notify();
                    System.out.println("end   notity: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class ThreadA extends Thread{
        private MyService myService;
        public ThreadA(String name, MyService myService) {
            super(name);
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.waitfun();
        }
    }
    
    class ThreadB extends Thread{
        private MyService myService;
        public ThreadB(String name, MyService myService) {
            super(name);
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.notifyfun();
        }
    }
    
    public class Demo {
    
        public static void main(String[] args) throws InterruptedException {
            MyService myService = new MyService();
            ThreadA a = new ThreadA("Thread", myService);
            a.start();
            
    
            Thread.sleep(1000);
            ThreadB b = new ThreadB("Thread", myService);
            b.start();
        }
    }
    

    在这里插入图片描述

    分析:a线程调用wait方法进行中断,立即释放锁;main线程睡眠1000ms后,b线程启动获得对象锁开始通知,然后执行.notify语句,最后输出了通知完毕;执行完了b线程同步方法/代码块里的所有语句才进行通知,a线程收到通知进行执行,从中断处继续向下执行最后得到上图结果。


    2.2、wait(long time)的自动唤醒
    class Service{ }
    
    class ServiceA extends Thread{
        private Service service;
    
        public ServiceA(String name, Service service) {
            super(name);
            this.service = service;
        }
    
        @Override
        public void run() {
            try{
                synchronized (service){
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    service.wait(5000);
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
                System.out.println(Thread.currentThread().getName()+"等待5000ms后自动唤醒!");
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class ServiceB extends Thread{
        private Service service;
    
        public ServiceB(String name, Service service) {
            super(name);
            this.service = service;
        }
    
        @Override
        public void run() {
            try{
                synchronized (service){
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    public class Demo01 {
        public static void main(String[] args) throws InterruptedException {
            Service service = new Service();
    
            ServiceA a = new ServiceA("ThreadA",service);
            ServiceB b = new ServiceB("ThreadB",service);
    
            a.start();
            Thread.sleep(100);
            b.start();
        }
    }
    
    

    在这里插入图片描述

    方法主体中并没有对wait的线程进行唤醒停止,但是经过time秒后A线程自动唤醒了。wait(long time)方法在经过time(毫秒)之后的等待即使没有收到 通知 会自动从等待队列转入锁池队列。


    2.3、提前唤醒,处于wait(long time)的线程如果正处在time等待的时间内提前收到通知会直接进入锁池队列,time时间提前失效。
    class Service{ }
    
    class ServiceA extends Thread{
        private Service service;
    
        public ServiceA(String name, Service service) {
            super(name);
            this.service = service;
        }
    
        @Override
        public void run() {
            try{
                long begin = 0;
                long end = 0;
                synchronized (service){
                    begin = System.currentTimeMillis();
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + begin);
                    service.wait(8000);
                    end = System.currentTimeMillis();
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + end);
                }
                System.out.println(Thread.currentThread().getName() + "等待" + (end-begin) + "ms后唤醒!");
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class ServiceB extends Thread{
        private Service service;
    
        public ServiceB(String name, Service service) {
            super(name);
            this.service = service;
        }
    
        @Override
        public void run() {
            try{
                synchronized (service){
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    service.notify();
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    public class Demo01 {
        public static void main(String[] args) throws InterruptedException {
            Service service = new Service();
            ServiceA a = new ServiceA("ThreadA",service);
            ServiceB b = new ServiceB("ThreadB",service);
    
            a.start();
            Thread.sleep(1000);
            b.start();
        }
    }
    

    在这里插入图片描述


    2.5、单一通知:notify方法随机的从等待队列中唤醒一个持有相同锁的线程进入锁池队列争抢同步锁,如果没有持有相同锁的wait的线程则notify指令无效。
    class MyService{
        private Object  obj = new Object();
        public void waitfun(){
            try{
                synchronized (obj){
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    obj.wait();
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        public void notifyfun(){
            try{
                synchronized (obj){
                    System.out.println("begin notify: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    obj.notify();
                    System.out.println("end   notity: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class ThreadA extends Thread{
        private MyService myService;
        public ThreadA(String name, MyService myService) {
            super(name);
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.waitfun();
        }
    }
    
    class ThreadB extends Thread{
        private MyService myService;
        public ThreadB(String name, MyService myService) {
            super(name);
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.notifyfun();
        }
    }
    
    public class notifyone {
    
        public static void main(String[] args) throws InterruptedException {
            MyService myService = new MyService();
            for(int i = 1;i <= 5;i++){
                new ThreadA("Thread"+i, myService).start();
            }
    
            Thread.sleep(1000);
            for(int i = 6;i <= 12;i++){
                new ThreadB("Thread"+i,myService).start();
                Thread.sleep(500);
            }
        }
    }
    
    

    在这里插入图片描述

    2.6、全部通知:notifyAll方法将等待队列中所有的持有相同锁的线程唤醒进入锁池队列争抢同步锁。
    /*
        notifyAll全部通知,通知顺序是随机的
        由JVM决定,并没有固定顺序
     */
    
    class MyService1{
        private Object obj = new Object();
        public void waitfun(){
            try{
                synchronized (obj){
                    System.out.println("begin   wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    obj.wait();
                    System.out.println("end     wait: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        public void notifyfun(){
            try{
                synchronized (obj){
                    System.out.println("begin notify: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    obj.notifyAll();
                    System.out.println("end   notity: " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class ThreadA1 extends Thread{
        private MyService1 myService;
        public ThreadA1(String name, MyService1 myService) {
            super(name);
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.waitfun();
        }
    }
    
    class ThreadB1 extends Thread{
        private MyService1 myService;
        public ThreadB1(String name, MyService1 myService) {
            super(name);
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.notifyfun();
        }
    }
    
    
    public class notifyall {
        public static void main(String[] args) throws InterruptedException {
    
            MyService1 myService = new MyService1();
            for(int i = 1;i <= 5;i++){
                new ThreadA1("ThreadA"+i,myService).start();
            }
            Thread.sleep(5000);
    
            new ThreadB1("noticeThread",myService).start();
        }
    }
    

    在这里插入图片描述



    3. 异常释放锁

    学习synchronized时,有几种情况当同步方法 / 块中遇到异常会提前结束线程体。

    • sleep + interrupt:这两个方法没有先后顺序,无论哪个先执行,发生异常时线程体提前中止。

    • 手动制造异常:当执行到某个程度的时候,如果想提前结束直接利用throw new Exception()。

    • 使用stop方法直接摧毁线程,这个方法已经废弃了不推荐使用。

    • 使用return 提前结束线程体的运行,但是这种方法并不是异常。

    3.1、手动制造异常提前终止
    import java.util.ArrayList;
    class MyList{
        volatile private ArrayList<String> list = new ArrayList<>();            //保证可见性
    
        public void add(){
            list.add("123");
        }
    
        public int size(){
            return list.size();
        }
    }
    class MyService{
    
        private MyList list = new MyList();
        private Object obj = new Object();
    
        public void waitMethod(){
            try{
                synchronized (obj){
                    if (list.size() != 5){
                        System.out.println("begin wait : "+ Thread.currentThread().getName() + " " + System.currentTimeMillis());
                        obj.wait();
                        System.out.println("end   wait : "+ Thread.currentThread().getName() + " " + System.currentTimeMillis());
                    }
                }
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        public void notifyMethod(){
            try{
                synchronized (obj){
                    for(int i = 1;i <= 10;i++){
                        list.add();
                        System.out.println("add添加了" + i +"个元素.");
                        if(list.size() == 5){
                            obj.notify();
                            throw new Exception();		//没有异常list中将会有10个元素,制造异常则只有5个
                            System.out.println("发出通知,等我执行完再执行.");		//不会执行
                        }
                    }
                }
            }catch (Exception e){
             //   e.printStackTrace();
            }
        }
    }
    
    class ThreadA extends Thread{
    
        private MyService myService;
    
        public ThreadA(MyService myService) {
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.notifyMethod();
        }
    }
    class ThreadB extends Thread{
    
        private MyService myService;
    
        public ThreadB(MyService myService) {
            this.myService = myService;
        }
    
        @Override
        public void run() {
            myService.waitMethod();
        }
    }
    
    public class Test01 {
        public static void main(String[] args) throws InterruptedException {
            MyService myService = new MyService();
            ThreadA a = new ThreadA(myService);
            a.setName("ThreadA");
            ThreadB b = new ThreadB(myService);
            b.setName("ThreadB");
            b.start();
            Thread.sleep(1000);
            a.start();
        }
    }
    
    

    在这里插入图片描述

    3.2、wait + interrupt方法提前终止线程
    class A{
        private Object obj;
    
        public A(Object object) {
            this.obj = object;
        }
    
        public void method(){
            synchronized (obj){
                System.out.println("begin wait : "+ Thread.currentThread().getName() + " " + System.currentTimeMillis());
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end   wait : "+ Thread.currentThread().getName() + " " + System.currentTimeMillis());
            }
        }
    }
    
    class MyThread extends Thread{
        private A a;
    
        public MyThread(A a) {
            this.a = a;
        }
    
        @Override
        public void run() {
            a.method();
        }
    }
    public class Test02 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
    
            A a = new A(object);
    
            MyThread myThread = new MyThread(a);
            myThread.start();
    
            Thread.sleep(5000);
            myThread.interrupt();
        }
    }
    
    

    在这里插入图片描述

    4. 总结

    1. sleep睡眠是抱锁睡眠,不会进入等待队列而是进入阻塞状态。如上图

    2. wait()方法会立即释放锁中断,进入等待队列。唤醒后仍然需要去争抢获得同步锁才能继续从中断处向下执行。

    3. wait(long time)方法会在time毫秒后自动唤醒,如果提前遇到通知会提前进入等待队列。

    4. notify / notifyAll 通知是随机顺序通知的,并没有固定的顺序进行通知具体的某个线程。并且只通知持有相同锁的wait线程。

    5. 灵活使用异常能够帮助线程体的提前终止。

    展开全文
  • TIME_WAIT状态总结

    千次阅读 2022-03-20 22:02:23
    一:TIME_WAIT状态是什么 首先,这个是TCP状态转换图里面的某个状态。这个可以参考unix网络编程。 从图中可以看出,在一个客户端与服务器通信的过程当中,主动关闭的一方会进入这个状态。 二:TIME_WAIT状态怎么...

    这个知识点在写代码上,感觉还是挺重要的,打算总结一下吧。
    打算从以下5个方面去总结。
    一:TIME_WAIT状态是什么
    首先,这个是TCP状态转换图里面的某个状态。这个可以参考unix网络编程。
    在这里插入图片描述
    从图中可以看出,在一个客户端与服务器通信的过程当中,主动关闭的一方会进入这个状态。
    二:TIME_WAIT状态怎么出现的
    好了,其实第一点已经说明了,这个状态是怎么出现的了,就是,主动关闭的一方会进入这个状态。

    #include <stdio.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    #define SERV_PORT 9000  //本服务器要监听的端口号,一般1024以下的端口很多都是属于周知端口,所以我们一般采用1024之后的数字做端口号
    
    int main(int argc, char *const *argv)
    {    
        //这些演示代码的写法都是固定套路,一般都这么写
    
        //服务器的socket套接字【文件描述符】
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);    //创建服务器的socket,大家可以暂时不用管这里的参数是什么,知道这个函数大概做什么就行
    
        struct sockaddr_in serv_addr;                  //服务器的地址结构体
        memset(&serv_addr,0,sizeof(serv_addr));
        
        //设置本服务器要监听的地址和端口,这样客户端才能连接到该地址和端口并发送数据
        serv_addr.sin_family = AF_INET;                //选择协议族为IPV4
        serv_addr.sin_port = htons(SERV_PORT);         //绑定我们自定义的端口号,客户端程序和我们服务器程序通讯时,就要往这个端口连接和传送数据
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有的IP地址;INADDR_ANY表示的是一个服务器上所有的网卡(服务器可能不止一个网卡)多个本地ip地址都进行绑定端口号,进行侦听。
    
        int result;
        result = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//绑定服务器地址结构体
        if(result  == -1)
        {
            char *perrorinfo = strerror(errno); 
            printf("bind返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
            return -1;
        }
        result = listen(listenfd, 32);     //参数2表示服务器可以积压的未处理完的连入请求总个数,客户端来一个未连入的请求,请求数+1,连入请求完成,c/s之间进入正常通讯后,请求数-1
        if(result == -1)
        {        
            char *perrorinfo = strerror(errno); 
            printf("listen返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
            return -1;
        }
    
        int connfd;
        const char *pcontent = "I sent sth to client!\n"; //指向常量字符串区的指针
        for(;;)
        {
            //卡在这里,等客户单连接,客户端连入后,该函数走下去【注意这里返回的是一个新的socket——connfd,后续本服务器就用connfd和客户端之间收发数据,而原有的lisenfd依旧用于继续监听其他连接】        
            connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
    
            //发送数据包给客户端
            write(connfd,pcontent,strlen(pcontent)); //注意第一个参数是accept返回的connfd套接字
            printf("本服务器给客户端发送了一串字符~~~~~~~~~~~!\n");
            
            //只给客户端发送一个信息,然后直接关闭套接字连接;
            close(connfd); 
        } //end for
        close(listenfd);     //实际本简单范例走不到这里,这句暂时看起来没啥用
        return 0;
    }
    

    使用以上程序作为服务端,然后用telnet作为客户端,发起连接,这个时候,根据程序说明,会马上关闭这个连接,然后,我们用netstat命令观察可以看到除了有一个端口处于监听状态意外,还有的就是出现了TIME_WAIT这个状态了。
    三:TIME_WAIT状态出现了有什么影响
    感觉第二点出现的情况,如果我们马上关闭这个服务端程序,然后又重新启动,这个时候你会发现这个程序启动失败失败的原因是bind error了,提示地址已复用,也就是同一个地址和端口只能同时绑定一次。我们试想,如果在服务端处理大量连接的时候,某个时候,服务端挂掉了,这个时候由于TIME_WAIT状态的存在,导致程序运行不起来,那不坏菜了,服务端还怎么提供服务??
    四:为什么要有TIME_WAIT状态
    我们再来谈谈为什么需要这个状态?什么,居然要有这个状态??第三点不是说有这个状态会有不好的情况吗?当然,先别急,既然这个状态的出现就有他出现的道理,对于第三点出现时会有一些影响,当然,我们也是可以避免的,我们在第五点去谈这个问题。

    为什么要有这个状态,参考了unix网络编程当中谈到的两点。
    一::可靠地实现TCP全双工的终止。
    在这里插入图片描述

    这句话怎么理解呢?我们知道tcp是全双工的,
    我们看上面的图,在左边,
    如果没有TIME_WAIT这个状态,
    那么服务端便会从FIN_WAIT2这个状态->CLOSED状态,
    这两个状态的转变有什么影响吗?我们看上图,
    如果服务端最后一次发送的ack包,
    由于某种原因丢了,客户端没有收到,
    那么根据tcp的特性,客户端会再次发送FIN包,
    如果此时服务端处于TIME_WAIT状态,
    就会重发这个ack包,如果此时服务端处于CLOSED状态,
    无论客户端有没有收到ack包,
    因为这个连接已经关闭了,所以服务端也就不会重发最
    后一个ack包了,这种断开就是不太友好的,
    如果有了TIME_WAIT状态,那么便有助于可靠
    的实现TCP全双工连接的终止。
    

    二:允许老的重复的TCP数据包在网络中消逝(丢弃)。

    怎么理解这句话呢?这个要结合TIME_WAIT
    为什么要存在的时间是1-4min有关了,
    根据资料说明,一个数据包在网络上存
    活的时间大概是1-4min,我们还是从反
    面来想这个问题,如果没有这个状态,
    那么在服务端发送最后一次ack包以后,
    服务端会马上处于CLOSE状态,然后,
    假设这个时候有一个新的客户端连进来,
    假设这个新的客户端的IP,PORT与之前
    的一个客户端是一样的,没有TIME_WAIT状态,
    最后一个ack的包可能就会发到这个新的连接
    上来。。。。。。如果有TIME_WAIT状态,
    即使有人伪造一个相同IP和PORT的连接,
    也不会让这个包发到刚刚建立起来的连接上。
    

    五:如何避免TIME_WAIT状态
    使用套接字选项可以避免这个状态的产生,从而可以让服务端程序在出现异常时可以快速的重启,恢复运行。
    setsockopt,这个函数一般位于socket与bind之间,如果想探讨为什么一般位于这两个函数之间的,可以网上搜索。

    #include <stdio.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    #define SERV_PORT 9000  //本服务器要监听的端口号,一般1024以下的端口很多都是属于周知端口,所以我们一般采用1024之后的数字做端口号
    
    int main(int argc, char *const *argv)
    {    
        //这些演示代码的写法都是固定套路,一般都这么写
    
        //服务器的socket套接字【文件描述符】
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);    //创建服务器的socket,大家可以暂时不用管这里的参数是什么,知道这个函数大概做什么就行
    
        struct sockaddr_in serv_addr;                  //服务器的地址结构体
        memset(&serv_addr,0,sizeof(serv_addr));
        
        //设置本服务器要监听的地址和端口,这样客户端才能连接到该地址和端口并发送数据
        serv_addr.sin_family = AF_INET;                //选择协议族为IPV4
        serv_addr.sin_port = htons(SERV_PORT);         //绑定我们自定义的端口号,客户端程序和我们服务器程序通讯时,就要往这个端口连接和传送数据
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有的IP地址;INADDR_ANY表示的是一个服务器上所有的网卡(服务器可能不止一个网卡)多个本地ip地址都进行绑定端口号,进行侦听。
    
    
        //setsockopt():设置一些套接字参数选项;
        //参数2:是表示级别,和参数3配套使用,也就是说,参数3如果确定了,参数2就确定了;
        //参数3:允许重用本地地址
        int  reuseaddr=1; //开启
        if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, (const void *) &reuseaddr,sizeof(reuseaddr)) == -1)
        {
             char *perrorinfo = strerror(errno); 
            printf("setsockopt(SO_REUSEADDR)返回值为%d,错误码为:%d,错误信息为:%s;\n",-1,errno,perrorinfo);
        }
    
        int result;
        result = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//绑定服务器地址结构体
        if(result  == -1)
        {
            char *perrorinfo = strerror(errno); 
            printf("bind返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
            return -1;
        }
        result = listen(listenfd, 32);     //参数2表示服务器可以积压的未处理完的连入请求总个数,客户端来一个未连入的请求,请求数+1,连入请求完成,c/s之间进入正常通讯后,请求数-1
        if(result == -1)
        {        
            char *perrorinfo = strerror(errno); 
            printf("listen返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
            return -1;
        }
        int connfd;
        const char *pcontent = "I sent sth to client!\n"; //指向常量字符串区的指针
        for(;;)
        {
            //卡在这里,等客户单连接,客户端连入后,该函数走下去【注意这里返回的是一个新的socket——connfd,后续本服务器就用connfd和客户端之间收发数据,而原有的lisenfd依旧用于继续监听其他连接】        
            connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
    
            //发送数据包给客户端
            write(connfd,pcontent,strlen(pcontent)); //注意第一个参数是accept返回的connfd套接字
            printf("本服务器给客户端发送了一串字符~~~~~~~~~~~!\n");
            
            //只给客户端发送一个信息,然后直接关闭套接字连接;
            close(connfd); 
        } //end for
        close(listenfd);     //实际本简单范例走不到这里,这句暂时看起来没啥用
        return 0;
    }
    

    可以使用以上程序配合telnet进行测试。

    展开全文
  • 深入理解Java中的wait() 方法

    千次阅读 2021-05-20 13:57:56
    当某个线程获取到锁后,发现当前还不满足执行的条件,就可以调用对象锁的wait方法,进入等待状态。 直到某个时刻,外在条件满足了,就可以由其他线程通过调用notify()或者notifyAll()方法,来唤醒此线程。 这篇...
  • 英文版:Oracle Wait Interface: A Practical Guide to Performance Diagnostics & Tuning 内容简介 《Oracle Wait Interface性能诊断与调整实践指南》详细讲述了如何充分利用革命性的Oracle Wait Interface(OWI)...
  • 他觉得服务端应该还是处于 TIME_WAIT 状态(因为 Linux 操作系统中,2MSL 的时间是 60 秒,也就是 TIME_WAIT 状态的持续时间),为什么收到客户端的 SYN 报文后可以正常建立连接? 抓包图手机端不好看,为了方便...
  • wait方法是当前线程等待,释放锁,这是Object的方法。同时要注意,Java 14 之后引入的 inline class 是没有 wait 方法的 Sleep()原理 public static native void sleep(long millis) throws InterruptedException; ...
  • CLOSE_WAIT和TIME_WAIT

    千次阅读 2022-03-25 14:52:51
    TIME_WAIT TIME_WAIT 是主动关闭链接时形成的,等待2MSL时间,约4分钟。主要是防止最后一个ACK丢失。 由于TIME_WAIT 的时间会非常长,因此server端应尽量减少主动关闭连接 CLOSE_WAIT 在被动关闭连接情况下,在...
  • pthread_cond_timedwait函数使用

    千次阅读 2021-05-15 17:08:08
    1 函数原型 #include <pthread.h> int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, ...int pthread_cond_wait(pthread_cond_t *restrict cond,
  • 目录 写在前面 面试这样问,我人傻了 ⬇️⬇️ 你知道TCP状态机里为什么需要TIME_WAIT吗? 你遇到过TIME_WAIT的问题吗? 什么是TIME-WAIT和CLOSE-WAIT? TIME_WAIT状态为什么会持续2MSL(2倍的max segment lifetime)...
  • 今天登陆服务器想查看一个端口的占用情况,...time_wait的作用 1 2 3 4 5 6 7 8 9 10 TIME_WAIT状态存在的理由: 1)可靠地实现TCP全双工连接的终止 ...
  • 解决TimeWait过多的问题

    千次阅读 2020-08-18 20:06:12
    1、 time_wait的作用: TIME_WAIT状态存在的理由: 1)可靠地实现TCP全双工连接的终止 在进行关闭连接四次挥手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,服务器将重发最终的FIN, 因此客户端...
  • 介绍condition_variable, waitwait_for 直接上代码如下: #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::...
  • 了解C语言中的wait()系统调用

    千次阅读 2021-02-09 16:48:34
    wait() 的调用会阻止调用进程,直到它的一个子进程退出或收到信号为止。子进程终止后,父进程在wait系统调用指令后继续执行。 子进程可能由于以下原因而终止: 调用exit(); 接收到main进程的return值; 接收一个...
  • PostgreSQL中的等待事件wait_event

    千次阅读 2020-07-12 18:47:48
    * Wait Classes * ---------- */ #define PG_WAIT_LWLOCK 0x01000000U /* 等待LWLock */ #define PG_WAIT_LOCK 0x03000000U /* 等待Lock */ #define PG_WAIT_BUFFER_PIN 0x04000000U /* 等待访问数据缓冲...
  • linux tcp的timewait如何解决

    千次阅读 2021-12-11 17:46:41
    本文从内核的角度看timewait是如何解决的。贴代码,和网上看到的挺多冲突的!
  • 解决进程的time_wait状态 查看进程连接状态 #netstat -an|awk ‘/tcp/ {print $6}’|sort|uniq -c 16 CLOSING 130 ESTABLISHED 298 FIN_WAIT1 13 FIN_WAIT2 9 LAST_ACK 7 LISTEN 103 SYN_RECV 5204 TIME_WAIT 状...
  • time_wait产生原因: time_wait过多产生原因: time_wait过多解决方法: 短连接 长连接 close_wait产生原因: close_wait产生太多原因: close_wait太多解决方法: Socket连接到底是个什么概念? 什么时候...
  • 记一次TIME_WAIT过多导致的服务异常

    千次阅读 2021-12-16 02:40:02
    文章目录前言排查问题为什么会有这么多TIME-WAIT呢?我们先来回忆一下TCP四次挥手的过程阻止延迟数据段保证连接被关闭开始解决问题方案1方案2总结 前言 前几天萌新遇到了一个问题,客户购买了我们的产品以后,发现...
  • time_wait 处理方法

    千次阅读 2022-03-18 16:17:46
    Socket中的TIME_WAIT状态 在高并发短连接的server端,当server处理完client的请求后立刻closesocket此时会出现time_wait状态然后如果client再并发2000个连接,此时部分连接就连接不上了,用linger强制关闭可以解决此...
  • wait()2. join()3. 共同点与区分度 前言 之前没怎么关注到这两个的区别以及源码探讨 后面被某个公司面试问到了,开始查漏补缺 1. wait() 使当前线程等待,直到它被唤醒,通常是通过被通知或被中断,或者直到经过...
  • 3、CLOSE_WAIT 3.1 CLOSE_WAIT产生的原因 由上面的TCP四次挥手断开连接的过程,可以知道 CLOSE_WAIT 是主动关闭方发生FIN之后,被动方收到 FIN 就进入了 CLOSE_WAIT 状态,此时如果被动方没有调用 close() 函数来...
  • 因为linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的通道就一直被占着,而且是“占着茅坑不使劲”,一旦达到句柄数上限,新的请求就无法被处理了,...
  • 解决Linux服务器中TCP的FIN_WAIT2,CLOSE_WAIT状态连接过多的问题
  • 解决Linux TIME_WAIT过多造成的问题

    万次阅读 多人点赞 2019-04-15 21:23:35
    1、 time_wait的作用: TIME_WAIT状态存在的理由: 1)可靠地实现TCP全双工连接的终止 在进行关闭连接四次挥手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,服务器将重发最终的FIN, 因此...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,072,698
精华内容 429,079
关键字:

WAIT