1.socket初探
2.socket分析
3.socket內(nèi)核源碼分析
//1.生成內(nèi)核socket簿寂;2茬贵。與文件描述符綁定
socket(AF_UNIX, SOCK_STREAM, 0);
//建立連接练链,包含三次握手
connect(sockfd, (struct sockaddr *)&address, len);
//綁定一個(gè)IP地址和端口到socket套接字上
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
//半連接隊(duì)列储狭、全連接隊(duì)列
int listen(int sockfd, int backlog)
//返回一個(gè)new的socket文件描述符(不占用端口號(hào))
accept(server_sockfd,(struct sockaddr *)&client_address, client_len);
//斷開連接胃榕,包含四次揮手(TCP將嘗試發(fā)送已排隊(duì)等待發(fā)送到對(duì)端的任何數(shù)據(jù)俘枫,發(fā)送完畢后發(fā)生的是正常的TCP連接終止序列译断。TIME_WAIT原因)
int close(int sockfd);
//可選擇性的斷開連接
系統(tǒng)調(diào)用的過程:
1.int socket(int domain,int type,int protocol)
作用:根據(jù)用戶定義的網(wǎng)絡(luò)類型谦炒、協(xié)議類型、和具體的協(xié)議標(biāo)號(hào)于个,生成一個(gè)套接字文件描述符供用戶使用氛魁,實(shí)現(xiàn)各種初始化工作(文件系統(tǒng)初始化暮顺、socket初始化等)
- //[sock_create]
- [ ] 分配socket結(jié)構(gòu):1.在socket文件系統(tǒng)中創(chuàng)建i節(jié)點(diǎn);2.創(chuàng)建socket專用inode;
- [ ] 根據(jù)inode取得socket對(duì)象:
- [ ] 使用協(xié)議族來初始化socket:1) 注冊(cè)AF_INET協(xié)議域 2)套接字類型(如AF_INET域下存在流套接字(SOCK_STREAM)厅篓,數(shù)據(jù)報(bào)套接字(SOCK_DGRAM),原始套接字(SOCK_RAW)捶码,在這三種類型的套接字上建立的協(xié)議分別是TCP, UDP羽氮,ICMP/IGMP);3) 使用協(xié)議域來初始化socket
- [ ] 分配sock結(jié)構(gòu):
- [ ] 建立socket結(jié)構(gòu)與sock結(jié)構(gòu)的關(guān)系:
- [ ] 使用tcp協(xié)議初始化sock:
- //[sock_map_fd]
- [ ] socket與文件系統(tǒng)關(guān)聯(lián);
2.int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen)
- [ ] bind()的Socket層實(shí)現(xiàn)
- [ ] bind()的tcp層實(shí)現(xiàn)、端口的沖突處理
- Q: 什么情況下會(huì)出現(xiàn)沖突呢惫恼?
同時(shí)符合以下條件才會(huì)沖突:
綁定的設(shè)備相同(不允許自動(dòng)選擇設(shè)備)
綁定的IP地址相同(不允許自動(dòng)選擇IP)
3 以下條件有一個(gè)成立:
3.1 要綁定的socket不允許重用 3.2 已綁定的socket不允許重用 3.3 已綁定的socket處于監(jiān)聽狀態(tài) 3.4 relax參數(shù)為false
端口區(qū)間(0--65535)
我們可以指定系統(tǒng)自動(dòng)分配端口號(hào)時(shí)档押,端口的區(qū)間:
/proc/sys/net/ipv4/ip_local_port_range,默認(rèn)為:32768 61000
也可以指定要保留的端口區(qū)間:
/proc/sys/net/ipv4/ip_local_reserved_ports祈纯,默認(rèn)為空
系統(tǒng)自動(dòng)選擇端口時(shí):不優(yōu)先選擇沒被使用過的端口令宿。只要沒有沖突,直接重用端口腕窥。
- 一個(gè)網(wǎng)絡(luò)應(yīng)用程序只能綁定一個(gè)端口( 一個(gè)套接字只能 綁定一個(gè)端口 )
- 一般情況下服務(wù)器需要綁定端口號(hào)粒没,而客戶端可以不綁定端口號(hào),在send的時(shí)候簇爆,系統(tǒng)隨機(jī)分配一個(gè)端口號(hào)癞松。
- 端口復(fù)用技術(shù)//設(shè)置socket的SO_REUSEADDR選項(xiàng),即可實(shí)現(xiàn)端口復(fù)用
- SO_REUSEADDR可以用在以下四種情況下入蛆。 (摘自《Unix網(wǎng)絡(luò)編程》卷一响蓉,即UNPv1)
1、當(dāng)有一個(gè)有相同本地地址和端口的socket1處于TIME_WAIT狀態(tài)時(shí)哨毁,而你啟動(dòng)的程序的socket2要占用該地址和端口枫甲,你的程序就要用到該選項(xiàng)。
2、SO_REUSEADDR允許同一port上啟動(dòng)同一服務(wù)器的多個(gè)實(shí)例(多個(gè)進(jìn)程)想幻。但每個(gè)實(shí)例綁定的IP地址是不能相同的软能。在有多塊網(wǎng)卡或用IP Alias技術(shù)的機(jī)器可以測(cè)試這種情況。
3举畸、SO_REUSEADDR允許單個(gè)進(jìn)程綁定相同的端口到多個(gè)socket上查排,但每個(gè)socket綁定的ip地址不同。這和2很相似抄沮,區(qū)別請(qǐng)看UNPv1跋核。
4、SO_REUSEADDR允許完全相同的地址和端口的重復(fù)綁定叛买。但這只用于UDP的多播砂代,不用于TCP
- 端口復(fù)用最常用的用途應(yīng)該是防止服務(wù)器重啟時(shí)之前綁定的端口還未釋放或者程序突然退出而系統(tǒng)沒有釋放端口
- 當(dāng)在一個(gè)應(yīng)用或是進(jìn)程中多個(gè)socket同時(shí)綁定到相同的端口時(shí),這些套接字并不是所有都能讀取信息率挣,只有最后一個(gè)套接字會(huì)正常接收數(shù)據(jù)刻伊。
淺析套接字中SO_REUSEPORT和SO_REUSEADDR的區(qū)別
3.int listen(int sockfd,int backlog)
backlog的定義
Now it specifies the queue length for completely established sockets waiting to be accepted,instead of the number of incomplete connection requests. The maximum length of the queuefor incomplete sockets can be set using the tcp_max_syn_backlog sysctl. When syncookiesare enabled there is no logical maximum length and this sysctl setting is ignored.If the socket is of type AF_INET, and the backlog argument is greater than the constant SOMAXCONN(128 default), it is silently truncated to SOMAXCONN.
全連接隊(duì)列的最大長(zhǎng)度:
- backlog保存的是完成三次握手、等待accept的全連接隊(duì)列
- 負(fù)載不高時(shí)椒功,backlog不用太大捶箱。(For complete connections)
- 系統(tǒng)最大的、未處理的全連接數(shù)量為:min(backlog,somaxconn)动漾,net.core.somaxconn默認(rèn)為128丁屎。這個(gè)值最終存儲(chǔ)于sk->sk_max_ack_backlog
半連接隊(duì)列的最大長(zhǎng)度:
- tcp_max_syn_backlog默認(rèn)值為256。(For incomplete connections)
- 當(dāng)使用SYN Cookie時(shí)旱眯,這個(gè)參數(shù)變?yōu)闊o效晨川。
- 半連接隊(duì)列的最大長(zhǎng)度為backlog、somaxconn删豺、tcp_max_syn_backlog的最小值共虑。
- 檢查套接口的狀態(tài)、當(dāng)前連接的狀態(tài)是否合法呀页,然后調(diào)用inet_csk_listen_start()啟動(dòng)監(jiān)聽妈拌。
- 啟動(dòng)監(jiān)聽時(shí),做的工作主要包括:
創(chuàng)建半連接隊(duì)列的實(shí)例赔桌,初始化全連接隊(duì)列供炎。
初始化sock的一些變量,把它的狀態(tài)設(shè)為TCP_LISTEN疾党。
檢查端口是否可用音诫,防止bind()后其它進(jìn)程修改了端口信息。
把sock鏈接進(jìn)入監(jiān)聽哈希表listening_hash中雪位。
- listen_sock結(jié)構(gòu)用于保存SYN_RECV狀態(tài)的連接請(qǐng)求塊竭钝,所以也叫半連接隊(duì)列
- 銷毀連接請(qǐng)求塊中的listen_sock實(shí)例,釋放半連接隊(duì)列
- inet_hash()用于把sock鏈入監(jiān)聽哈希表listening_hash,或者已建立連接的哈希表ehash香罐。
4.int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
It extracts the first connection request on the queue of pending connections (backlog), creates a newconnected socket, and returns a new file descriptor referring to that socket.If no pending connections are present on the queue, and the socket is not marked as non-blocking,accept() blocks the caller until a connection is present. If the socket is marked non-blocking and no pending connections are present on the queue, accept() fails with the error EAGAIN.
- 在sys_socketcall()中會(huì)調(diào)用sys_accept4():
- 創(chuàng)建了一個(gè)新的socket和inode卧波,以及它所對(duì)應(yīng)的fd、file庇茫。
- 調(diào)用Socket層操作函數(shù)inet_accept()港粱。
- 保存對(duì)端地址到指定的用戶空間地址
- SOCK_STREAM套接口的Socket層操作函數(shù)集實(shí)例為inet_stream_ops,連接接收函數(shù)為inet_accept():
- 調(diào)用TCP層的操作函數(shù)旦签,獲取已建立的連接sock查坪。
- 把新socket和sock關(guān)聯(lián)起來。
- 把新socket的狀態(tài)設(shè)為SS_CONNECTED宁炫。
- SOCK_STREAM套接口的TCP層操作函數(shù)集實(shí)例為tcp_prot偿曙,其中連接接收函數(shù)為inet_csk_accept().inet_csk_accept()用于從backlog隊(duì)列(全連接隊(duì)列)中取出一個(gè)ESTABLISHED狀態(tài)的連接請(qǐng)求塊,返回它所對(duì)應(yīng)的連接sock,同時(shí)更新backlog隊(duì)列的全連接數(shù)羔巢,釋放取出的連接控制塊.
- 非阻塞的望忆,且當(dāng)前沒有已建立的連接,則直接退出竿秆,返回-EAGAIN启摄。
- 阻塞的,且當(dāng)前沒有已建立的連接:
2.1 用戶沒有設(shè)置超時(shí)時(shí)間袍辞,則無限期阻塞鞋仍。
2.2 用戶設(shè)置了超時(shí)時(shí)間,超時(shí)后會(huì)退出搅吁。
accept()是如何避免驚群現(xiàn)象(當(dāng)內(nèi)核接收到一個(gè)客戶連接后,只會(huì)喚醒等待隊(duì)列上的第一個(gè)進(jìn)程或線程)的:
初始化等待任務(wù)時(shí)落午,flags|=WQ_FLAG_EXCLUSIVE谎懦。傳入的nr_exclusive為1,表示只允許喚醒一個(gè)等待任務(wù)溃斋。
所以這里只會(huì)喚醒一個(gè)等待的進(jìn)程界拦,不會(huì)導(dǎo)致驚群現(xiàn)象。
Nginx中使用mutex互斥鎖解決這個(gè)問題梗劫,具體措施有使用全局互斥鎖享甸,每個(gè)子進(jìn)程在epoll_wait()之前先去申請(qǐng)鎖,申請(qǐng)到則繼續(xù)處理梳侨,獲取不到則等待蛉威,并設(shè)置了一個(gè)負(fù)載均衡的算法(當(dāng)某一個(gè)子進(jìn)程的任務(wù)量達(dá)到總設(shè)置量的7/8時(shí),則不會(huì)再嘗試去申請(qǐng)鎖)來均衡各個(gè)進(jìn)程的任務(wù)量走哺。使用mutex鎖住多個(gè)線程是不會(huì)驚群的蚯嫌,在某個(gè)線程解鎖后,只會(huì)有一個(gè)線程會(huì)獲得鎖,其它的繼續(xù)等待.
5.int connect(int sockfd,struct sockaddr *,int addrlen)
- SOCK_STREAM套接口的socket層操作函數(shù)集實(shí)例為inet_stream_ops择示,其中主動(dòng)建立連接的函數(shù)為inet_stream_connect()束凑。
檢查socket地址長(zhǎng)度和使用的協(xié)議族。
檢查socket的狀態(tài)栅盲,必須是SS_UNCONNECTE或SS_CONNECTING汪诉。
調(diào)用tcp_v4_connect()來發(fā)送SYN包。
-
等待后續(xù)握手的完成:
如果socket是非阻塞的谈秫,那么就直接返回錯(cuò)誤碼-EINPROGRESS摩瞎。
如果socket為阻塞的,就調(diào)用inet_wait_for_connect()孝常,通過睡眠來等待旗们。在以下三種情況下會(huì)被喚醒:
(1) 使用SO_SNDTIMEO選項(xiàng)時(shí),睡眠時(shí)間超過設(shè)定值构灸,返回0上渴。connect()返回錯(cuò)誤碼-EINPROGRESS。
(2) 收到信號(hào)喜颁,返回剩余的等待時(shí)間稠氮。connect()返回錯(cuò)誤碼-ERESTARTSYS或-EINTR。
(3) 三次握手成功半开,sock的狀態(tài)從TCP_SYN_SENT或TCP_SYN_RECV變?yōu)門CP_ESTABLISHED隔披,sock I/O事件的狀態(tài)變化處理函數(shù)sock_def_wakeup()就會(huì)喚醒進(jìn)程。connect()返回0寂拆。
進(jìn)程的睡眠: connect()的超時(shí)時(shí)間為sk->sk_sndtimeo奢米,在sock_init_data()中初始化為MAX_SCHEDULE_TIMEOUT,表示無限等待纠永,可以通過SO_SNDTIMEO選項(xiàng)來修改鬓长。
進(jìn)程的喚醒:三次握手中,當(dāng)客戶端收到SYNACK尝江、發(fā)出ACK后涉波,連接就成功建立了。此時(shí)連接的狀態(tài)從TCP_SYN_SENT變?yōu)門CP_ESTABLISHED炭序,sock的狀態(tài)發(fā)生變化啤覆,會(huì)調(diào)用sock_def_wakeup()來處理連接狀態(tài)變化事件,喚醒進(jìn)程惭聂,connect()就能成功返回了窗声。
close()與shutdown()
int close(int sockfd); //返回成功為0,出錯(cuò)為-1.
int shutdown(int sockfd,int howto); //返回成功為0彼妻,出錯(cuò)為-1.
1.SHUT_RD:值為0嫌佑,關(guān)閉連接的讀這一半豆茫。
2.SHUT_WR:值為1,關(guān)閉連接的寫這一半屋摇。
3.SHUT_RDWR:值為2揩魂,連接的讀和寫都關(guān)閉。
close函數(shù)會(huì)關(guān)閉套接字ID炮温,如果有其他的進(jìn)程共享著這個(gè)套接字火脉,那么它仍然是打開的,這個(gè)連接仍然可以用來讀和寫柒啤,并且有時(shí)候這是非常重要的倦挂,特別是對(duì)于多進(jìn)程并發(fā)服務(wù)器來說。在多進(jìn)程并發(fā)服務(wù)器中担巩,父子進(jìn)程共享著套接字方援,套接字描述符引用計(jì)數(shù)記錄著共享著的進(jìn)程個(gè)數(shù),當(dāng)父進(jìn)程或某一子進(jìn)程close掉套接字時(shí)涛癌,描述符引用計(jì)數(shù)會(huì)相應(yīng)的減一犯戏,當(dāng)引用計(jì)數(shù)仍大于零時(shí),這個(gè)close調(diào)用就不會(huì)引發(fā)TCP的四路握手?jǐn)噙B過程拳话。
shutdown會(huì)切斷進(jìn)程共享的套接字的所有連接先匪,不管這個(gè)套接字的引用計(jì)數(shù)是否為零,那些試圖讀得進(jìn)程將會(huì)接收到EOF標(biāo)識(shí)弃衍,那些試圖寫的進(jìn)程將會(huì)檢測(cè)到SIGPIPE信號(hào)呀非,同時(shí)可利用shutdown的第二個(gè)參數(shù)選擇斷連的方式。利用shutdown()可以避免用close()過程出現(xiàn)死鎖現(xiàn)象
//close()
/* First Sample client fragment,
* 多余的代碼及變量的聲明已略 */
s=connect(...);
if( fork() ){ /* The child, it copies its stdin to the socket */
while( gets(buffer) >0)
write(s,buf,strlen(buffer));
close(s);
exit(0);
}
else { /* The parent, it receives answers */
while( (n=read(s,buffer,sizeof(buffer)){
do_something(n,buffer);
/* Connection break from the server is assumed */
/* ATTENTION: deadlock here */
wait(0); /* Wait for the child to exit */
exit(0);
}
//shutdown()
if( fork() ) { /* The child */
while( gets(buffer)
write(s,buffer,strlen(buffer));
shutdown(s,1); /* Break the connection
*for writing, The server will detect EOF now. Note: reading from
*the socket is still allowed. The server may send some more data
*after receiving EOF, why not? */
exit(0);
}