电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

瀏覽:229日期:2024-04-14 13:00:59

前言

筆者一直覺得如果能知道從應(yīng)用到框架再到操作系統(tǒng)的每一處代碼,是一件Exciting的事情。今天筆者就來從Linux源碼的角度看下Client端的Socket在進(jìn)行Connect的時(shí)候到底做了哪些事情。由于篇幅原因,關(guān)于Server端的Accept源碼講解留給下次給大家介紹。(基于Linux 3.10內(nèi)核)

一個(gè)最簡單的Connect例子

int clientSocket;if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {// 創(chuàng)建socket失敗失敗 return -1;}......if(connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {// connect 失敗return -1;}.......

首先我們通過socket系統(tǒng)調(diào)用創(chuàng)建了一個(gè)socket,其中指定了SOCK_STREAM,而且最后一個(gè)參數(shù)為0,也就是建立了一個(gè)通常所有的TCP Socket。在這里,我們直接給出TCP Socket所對(duì)應(yīng)的ops也就是操作函數(shù)。

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

如果你想知道上圖中的結(jié)構(gòu)是怎么來的,可以看下筆者以前的文章:

https://www.jb51.net/article/106563.htm

值得注意的是,由于socket系統(tǒng)調(diào)用操作做了如下兩個(gè)代碼的判斷

sock_map_fd|->get_unused_fd_flags|->alloc_fd|->expand_files (ulimit)|->sock_alloc_file|->alloc_file|->get_empty_filp (/proc/sys/fs/max_files)

第一個(gè)判斷,ulmit超限:

int expand_files(struct files_struct *files, int nr{......if (nr >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)return -EMFILE;......}

這邊的判斷即是ulimit的限制!在這里返回-EMFILE對(duì)應(yīng)的描述就是'Too many open files'

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

第二個(gè)判斷max_files超限

struct file *get_empty_filp(void){ ....../* * 由此可見,特權(quán)用戶可以無視文件數(shù)最大大小的限制! */if (get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)) {/* * percpu_counters are inaccurate. Do an expensive check before * we go and fail. */if (percpu_counter_sum_positive(&nr_files) >= files_stat.max_files)goto over;} ......}

所以在文件描述符超過所有進(jìn)程能打開的最大文件數(shù)量限制(/proc/sys/fs/file-max)的時(shí)候會(huì)返回-ENFILE,對(duì)應(yīng)的描述就是'Too many open files in system',但是特權(quán)用戶確可以無視這一限制,如下圖所示:

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

connect系統(tǒng)調(diào)用

我們再來看一下connect系統(tǒng)調(diào)用:

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

這個(gè)系統(tǒng)調(diào)用有三個(gè)參數(shù),那么依據(jù)規(guī)則,它肯定在內(nèi)核中的源碼長下面這個(gè)樣子

SYSCALL_DEFINE3(connect, ......

筆者全文搜索了下,就找到了具體的實(shí)現(xiàn):

socket.cSYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,int, addrlen){ ......err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags);......}

前面圖給出了在TCP下的sock->ops == inet_stream_ops,然后再陷入到更進(jìn)一步的調(diào)用棧中,即下面的:

SYSCALL_DEFINE3(connect|->inet_stream_ops|->inet_stream_connect|->tcp_v4_connect|->tcp_set_state(sk, TCP_SYN_SENT);設(shè)置狀態(tài)為TCP_SYN_SENT |->inet_hash_connect|->tcp_connect

首先,我們來看一下inet_hash_connect這個(gè)函數(shù),里面有一個(gè)端口號(hào)的搜索過程,搜索不到可用端口號(hào)就會(huì)導(dǎo)致創(chuàng)建連接失敗!內(nèi)核能夠建立一個(gè)連接也是跋涉了千山萬水的!我們先看一下搜索端口號(hào)的邏輯,如下圖所示:

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

獲取端口號(hào)范圍

首先,我們從內(nèi)核中獲取connect能夠使用的端口號(hào)范圍,在這里采用了Linux中的順序鎖(seqlock)

void inet_get_local_port_range(int *low, int *high){unsigned int seq;do {// 順序鎖seq = read_seqbegin(&sysctl_local_ports.lock);*low = sysctl_local_ports.range[0];*high = sysctl_local_ports.range[1];} while (read_seqretry(&sysctl_local_ports.lock, seq));}

順序鎖事實(shí)上就是結(jié)合內(nèi)存屏障等機(jī)制的一種樂觀鎖,主要依靠一個(gè)序列計(jì)數(shù)器。在讀取數(shù)據(jù)之前和之后,序列號(hào)都被讀取,如果兩者的序列號(hào)相同,說明在讀操作的時(shí)候沒有被寫操作打斷過。這也保證了上面的讀取變量都是一致的,也即low和high不會(huì)出現(xiàn)low是改前值而high是改后值得情況。low和high要么都是改之前的,要么都是改之后的!內(nèi)核中修改的地方為:

cat /proc/sys/net/ipv4/ip_local_port_range 32768 61000

通過hash決定端口號(hào)起始搜索范圍

在Linux上進(jìn)行connect,內(nèi)核給其分配的端口號(hào)并不是線性增長的,但是也符合一定的規(guī)律。先來看下代碼:

int __inet_hash_connect(...){// 注意,這邊是static變量static u32 hint;// 這邊的port_offset是用對(duì)端ip:port hash的一個(gè)值// 也就是說對(duì)端ip:port固定,port_offset固定u32 offset = hint + port_offset;for (i = 1; i <= remaining; i++) {port = low + (i + offset) % remaining;/* port是否占用check */....goto ok;}.......ok:hint += i;......}

這里面有幾個(gè)小細(xì)節(jié),為了安全原因,Linux本身用對(duì)端ip:port做了一次hash作為搜索的初始o(jì)ffset,所以不同遠(yuǎn)端ip:port初始搜索范圍可以基本是不同的!但同樣的對(duì)端ip:port初始搜索范圍是相同的!

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

在筆者機(jī)器上,一個(gè)完全干凈的內(nèi)核里面,不停的對(duì)同一個(gè)遠(yuǎn)端ip:port,其以2進(jìn)行穩(wěn)定增長,也即38742->38744->38746,如果有其它的干擾,就會(huì)打破這個(gè)規(guī)律。

端口號(hào)范圍限制

由于我們指定了端口號(hào)返回ip_local_port_range是不是就意味著我們最多創(chuàng)建high-low+1個(gè)連接呢?當(dāng)然不是,由于檢查端口號(hào)是否重復(fù)是將(網(wǎng)絡(luò)命名空間,對(duì)端ip,對(duì)端port,本端port,Socket綁定的dev)當(dāng)做唯一鍵進(jìn)行重復(fù)校驗(yàn),所以限制僅僅是在同一個(gè)網(wǎng)絡(luò)命名空間下,連接同一個(gè)對(duì)端ip:port的最大可用端口號(hào)數(shù)為high-low+1,當(dāng)然可能還要減去ip_local_reserved_ports。如下圖所示:

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

檢查端口號(hào)是否被占用

端口號(hào)的占用搜索分為兩個(gè)階段,一個(gè)是處于TIME_WAIT狀態(tài)的端口號(hào)搜索,另一個(gè)是其它狀態(tài)端口號(hào)搜索。

TIME_WAIT狀態(tài)端口號(hào)搜索

眾所周知,TIME_WAIT階段是TCP主動(dòng)close必經(jīng)的一個(gè)階段。如果Client采用短連接的方式和Server端進(jìn)行交互,就會(huì)產(chǎn)生大量的TIME_WAIT狀態(tài)的Socket。而這些Socket由占用端口號(hào),所以當(dāng)TIME_WAIT過多,打爆上面的端口號(hào)范圍之后,新的connect就會(huì)返回錯(cuò)誤碼:

C語言connect返回錯(cuò)誤碼為-EADDRNOTAVAIL,對(duì)應(yīng)描述為Cannot assign requested address 對(duì)應(yīng)Java的異常為java.net.NoRouteToHostException: Cannot assign requested address (Address not available)

ip_local_reserved_ports。如下圖所示:

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

由于TIME_WAIT大概一分鐘左右才能消失,如果在一分鐘內(nèi)Client端和Server建立大量的短連接請求就容易導(dǎo)致端口號(hào)耗盡。而這個(gè)一分鐘(TIME_WAIT的最大存活時(shí)間)是在內(nèi)核(3.10)編譯階段就確定了的,無法通過內(nèi)核參數(shù)調(diào)整。 如下代碼所示:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT * state, about 60 seconds*/

Linux自然也考慮到了這種情況,所以提供了一個(gè)tcp_tw_reuse參數(shù)使得在搜索端口號(hào)時(shí)可以在某些情況下重用TIME_WAIT。代碼如下:

__inet_hash_connect|->__inet_check_establishedstatic int __inet_check_established(......){....../* Check TIME-WAIT sockets first. */sk_nulls_for_each(sk2, node, &head->twchain) {tw = inet_twsk(sk2);// 如果在time_wait中找到一個(gè)match的port,就判斷是否可重用if (INET_TW_MATCH(sk2, net, hash, acookie,saddr, daddr, ports, dif)) {if (twsk_unique(sk, sk2, twp))goto unique;elsegoto not_unique;}}......}

如上面代碼中寫的那樣,如果在一堆TIME-WAIT狀態(tài)的Socket里面能夠有當(dāng)前要搜索的port,則判斷是否這個(gè)port可以重復(fù)利用。如果是TCP的話這個(gè)twsk_unique的實(shí)現(xiàn)函數(shù)是:

int tcp_twsk_unique(......){......if (tcptw->tw_ts_recent_stamp && (twp == NULL || (sysctl_tcp_tw_reuse && get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {tp->write_seq = tcptw->tw_snd_nxt + 65535 + 2......return 1;}return 0;}

上面這段代碼邏輯如下所示:

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

在開啟了tcp_timestamp以及tcp_tw_reuse的情況下,在Connect搜索port時(shí)只要比之前用這個(gè)port的TIME_WAIT狀態(tài)的Socket記錄的最近時(shí)間戳>1s,就可以重用此port,即將之前的1分鐘縮短到1s。同時(shí)為了防止?jié)撛诘男蛄刑?hào)沖突,直接將write_seq加上在65537,這樣,在單Socket傳輸速率小于80Mbit/s的情況下,不會(huì)造成序列號(hào)沖突。同時(shí)這個(gè)tw_ts_recent_stamp設(shè)置的時(shí)機(jī)如下圖所示:

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

所以如果Socket進(jìn)入TIME_WAIT狀態(tài)后,如果一直有對(duì)應(yīng)的包發(fā)過來,那么會(huì)影響此TIME_WAIT對(duì)應(yīng)的port是否可用的時(shí)間。我們可以通過下面命令開始tcp_tw_reuse:

echo ’1’ > /proc/sys/net/ipv4/tcp_tw_reuse

ESTABLISHED狀態(tài)端口號(hào)搜索

ESTABLISHED的端口號(hào)搜索就簡單了許多

/* And established part... */sk_nulls_for_each(sk2, node, &head->chain) {if (INET_MATCH(sk2, net, hash, acookie,saddr, daddr, ports, dif))goto not_unique;}

以(網(wǎng)絡(luò)命名空間,對(duì)端ip,對(duì)端port,本端port,Socket綁定的dev)當(dāng)做唯一鍵進(jìn)行匹配,如果匹配成功,表明此端口無法重用。

端口號(hào)迭代搜索

Linux內(nèi)核在[low,high]范圍按照上述邏輯進(jìn)行port的搜索,如果沒有搜索到port,即port耗盡,就會(huì)返回-EADDRNOTAVAIL,也即Cannot assign requested address。但還有一個(gè)細(xì)節(jié),如果是重用TIME_WAIT狀態(tài)的Socket的端口的話,就會(huì)將對(duì)應(yīng)的TIME_WAIT狀態(tài)的Socket給銷毀。

__inet_hash_connect(......){......if (tw) {inet_twsk_deschedule(tw, death_row);inet_twsk_put(tw);}......}

尋找路由表

在我們找到一個(gè)可用端口號(hào)port后,就會(huì)進(jìn)入搜尋路由階段:

ip_route_newports|->ip_route_output_flow|->__ip_route_output_key|->ip_route_output_slow|->fib_lookup

這也是一個(gè)非常復(fù)雜的過程,限于篇幅,就不做詳細(xì)闡述了。如果搜索不到路由信息的話,會(huì)返回。

-ENETUNREACH,對(duì)應(yīng)描述為Network is unreachable

Client端的三次握手

在前面一大堆前置條件就緒后,才進(jìn)入到真正的三次握手階段。

tcp_connect |->tcp_connect_init 初始化tcp socket |->tcp_transmit_skb 發(fā)送SYN包 |->inet_csk_reset_xmit_timer 設(shè)置SYN重傳定時(shí)器

tcp_connect_init初始化了一大堆TCP相關(guān)的設(shè)置,例如mss_cache/rcv_mss等一大堆。而且如果開啟了TCP窗口擴(kuò)大選項(xiàng)的話,其窗口擴(kuò)大因子也在此函數(shù)里進(jìn)行計(jì)算:

tcp_connect_init|->tcp_select_initial_windowint tcp_select_initial_window(...){......(*rcv_wscale) = 0;if (wscale_ok) {/* Set window scaling on max possible window * See RFC1323 for an explanation of the limit to 14 */space = max_t(u32, sysctl_tcp_rmem[2], sysctl_rmem_max);space = min_t(u32, space, *window_clamp);while (space > 65535 && (*rcv_wscale) < 14) {space >>= 1;(*rcv_wscale)++;}}......}

如上面代碼所示,窗口擴(kuò)大因子取決于Socket最大可允許的讀緩沖大小和window_clamp(最大允許滑動(dòng)窗口大小,動(dòng)態(tài)調(diào)整)。搞完了一票初始信息設(shè)置后,才開始真正的三次握手。在tcp_transmit_skb中才真正發(fā)送SYN包,同時(shí)在緊接著的inet_csk_reset_xmit_timer里設(shè)置了SYN超時(shí)定時(shí)器。如果對(duì)端一直不發(fā)送SYN_ACK,將會(huì)返回-ETIMEDOUT。

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

重傳的超時(shí)時(shí)間和

/proc/sys/net/ipv4/tcp_syn_retries

息息相關(guān),Linux默認(rèn)設(shè)置為5,建議設(shè)置成3,下面是不同設(shè)置的超時(shí)時(shí)間參照圖。

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

在設(shè)置了SYN超時(shí)重傳定時(shí)器后,tcp_connnect就返回,并一路返回到最初始的inet_stream_connect。在這里我們就等待對(duì)端返回SYN_ACK或者SYN定時(shí)器超時(shí)。

int __inet_stream_connect(struct socket *sock,...,){// 如果設(shè)置了O_NONBLOCK則timeo為0timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);......// 如果timeo=0即O_NONBLOCK會(huì)立刻返回// 否則等待timeo時(shí)間if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))goto out;}

Linux本身提供一個(gè)SO_SNDTIMEO來控制對(duì)connect的超時(shí),不過Java并沒有采用這個(gè)選項(xiàng)。而是采用別的方式進(jìn)行connect的超時(shí)控制。僅僅就C語言的connect系統(tǒng)調(diào)用而言,不設(shè)置SO_SNDTIMEO,就會(huì)將對(duì)應(yīng)用戶進(jìn)程進(jìn)行睡眠,直到SYN_ACK到達(dá)或者超時(shí)定時(shí)器超時(shí)才將次用戶進(jìn)程喚醒。

從Linux源碼看Socket(TCP)Client端的Connect的示例詳解

如果是NON_BLOCK的話,則是通過select/epoll等多路復(fù)用機(jī)制去捕獲超時(shí)或者連接成功事件。

對(duì)端SYN_ACK到達(dá)

在Server端SYN_ACK到達(dá)之后會(huì)按照下面的代碼路徑傳遞,并喚醒用戶態(tài)進(jìn)程:

tcp_v4_rcv|->tcp_v4_do_rcv|->tcp_rcv_state_process|->tcp_rcv_synsent_state_process|->tcp_finish_connect|->tcp_init_metrics 初始化度量統(tǒng)計(jì)|->tcp_init_congestion_control 初始化擁塞控制|->tcp_init_buffer_space 初始化buffer空間|->inet_csk_reset_keepalive_timer 開啟包活定時(shí)器|->sk_state_change(sock_def_wakeup) 喚醒用戶態(tài)進(jìn)程|->tcp_send_ack 發(fā)送三次握手的最后一次握手給Server端|->tcp_set_state(sk, TCP_ESTABLISHED) 設(shè)置為ESTABLISHED狀態(tài)

總結(jié)

Client(TCP)端進(jìn)行Connect的過程真是跋山涉水,從一開始文件描述符的限制到端口號(hào)的搜索再到路由表的搜索再到最后的三次握手,任何一個(gè)環(huán)節(jié)有問題就會(huì)導(dǎo)致創(chuàng)建連接失敗,筆者詳細(xì)的描述了這些機(jī)制的源碼實(shí)現(xiàn)。希望本篇文章可以對(duì)讀者在以后遇到Connect失敗問題時(shí)候有所幫助。

到此這篇關(guān)于從Linux源碼看Socket(TCP)Client端的Connect的文章就介紹到這了,更多相關(guān)Linux源碼看Socket內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Linux系統(tǒng)
相關(guān)文章:
主站蜘蛛池模板: 【法利莱住人集装箱厂家】—活动集装箱房,集装箱租赁_大品牌,更放心 | 佛山商标注册_商标注册代理|专利注册申请_商标注册公司_鸿邦知识产权 | 电动球阀_不锈钢电动球阀_电动三通球阀_电动调节球阀_上海湖泉阀门有限公司 | 西门子伺服控制器维修-伺服驱动放大器-828D数控机床维修-上海涌迪 | 珠海冷却塔降噪维修_冷却塔改造报价_凉水塔风机维修厂家- 广东康明节能空调有限公司 | 罗茨真空机组,立式无油往复真空泵,2BV水环真空泵-力侨真空科技 | 环保袋,无纺布袋,无纺布打孔袋,保温袋,环保袋定制,环保袋厂家,环雅包装-十七年环保袋定制厂家 | 校服厂家,英伦校服定做工厂,园服生产定制厂商-东莞市艾咪天使校服 | 软启动器-上海能曼电气有限公司| 山东信蓝建设有限公司官网 | 胜为光纤光缆_光纤跳线_单模尾纤_光纤收发器_ODF光纤配线架厂家直销_北京睿创胜为科技有限公司 - 北京睿创胜为科技有限公司 | H型钢切割机,相贯线切割机,数控钻床,数控平面钻,钢结构设备,槽钢切割机,角钢切割机,翻转机,拼焊矫一体机 | 垃圾压缩设备_垃圾处理设备_智能移动式垃圾压缩设备--山东明莱环保设备有限公司 | 包装设计公司,产品包装设计|包装制作,包装盒定制厂家-汇包装【官方网站】 | 定制奶茶纸杯_定制豆浆杯_广东纸杯厂_[绿保佳]一家专业生产纸杯碗的厂家 | 砖机托板价格|免烧砖托板|空心砖托板厂家_山东宏升砖机托板厂 | 东莞压铸厂_精密压铸_锌合金压铸_铝合金压铸_压铸件加工_东莞祥宇金属制品 | RFID电子标签厂家-上海尼太普电子有限公司| 合同书格式和范文_合同书样本模板_电子版合同,找范文吧 | 百度关键词优化_网站优化_SEO价格 - 云无限好排名 | 欧必特空气能-商用空气能热水工程,空气能热水器,超低温空气源热泵生产厂家-湖南欧必特空气能公司 | 手持式3d激光扫描仪-便携式三维立体扫描仪-北京福禄克斯 | 瓶盖扭矩仪(扭力值检测)-百科 | 真石漆,山东真石漆,真石漆厂家,真石漆价格-山东新佳涂料有限公司 | 比亚迪叉车-比亚迪电动叉车堆垛车托盘车仓储叉车价格多少钱报价 磁力去毛刺机_去毛刺磁力抛光机_磁力光饰机_磁力滚抛机_精密金属零件去毛刺机厂家-冠古科技 | 中视电广_短视频拍摄_短视频推广_短视频代运营_宣传片拍摄_影视广告制作_中视电广 | 沈阳庭院景观设计_私家花园_别墅庭院设计_阳台楼顶花园设计施工公司-【沈阳现代时园艺景观工程有限公司】 | 定时排水阀/排气阀-仪表三通旋塞阀-直角式脉冲电磁阀-永嘉良科阀门有限公司 | 玻纤土工格栅_钢塑格栅_PP焊接_单双向塑料土工格栅_复合防裂布厂家_山东大庚工程材料科技有限公司 | 胜为光纤光缆_光纤跳线_单模尾纤_光纤收发器_ODF光纤配线架厂家直销_北京睿创胜为科技有限公司 - 北京睿创胜为科技有限公司 | 分子精馏/精馏设备生产厂家-分子蒸馏工艺实验-新诺舜尧(天津)化工设备有限公司 | 临时厕所租赁_玻璃钢厕所租赁_蹲式|坐式厕所出租-北京慧海通 | 欧盟ce检测认证_reach检测报告_第三方检测中心-深圳市威腾检验技术有限公司 | 焊缝跟踪系统_激光位移传感器_激光焊缝跟踪传感器-创想智控 | 医用酒精_84消毒液_碘伏消毒液等医用消毒液-漓峰消毒官网 | 都江堰招聘网-都江堰人才网 都江堰人事人才网 都江堰人才招聘网 邢台人才网_邢台招聘网_邢台123招聘【智达人才网】 | 对夹式止回阀_对夹式蝶形止回阀_对夹式软密封止回阀_超薄型止回阀_不锈钢底阀-温州上炬阀门科技有限公司 | 浙江华锤电器有限公司_地磅称重设备_防作弊地磅_浙江地磅售后维修_无人值守扫码过磅系统_浙江源头地磅厂家_浙江工厂直营地磅 | 细胞染色-流式双标-试剂盒免费代做-上海研谨生物科技有限公司 | 耐磨陶瓷管道_除渣器厂家-淄博浩瀚陶瓷科技有限公司 | 湖南教师资格网-湖南教师资格证考试网 |