套接字
應用層通過傳輸層進行通訊時,tcp和udp會同時遇到要為多個應用程序提供併發服務的問題,多個tcp鏈結或者應用程式可能需要通過乙個tcp協議埠傳輸資料,為了將不同的應用程式區分開來,作業系統為應用程式與tcp/udp互動提供了介面,成為套接字。
埠號:
源埠號 & 目的埠號
傳輸層協議(tcp/ip)的資料段中有兩個埠號,分別叫做源埠號和目的埠號,就是在描述「資料是誰的?發給誰?"
socket可以看成在兩個程式進行通訊連線中的乙個端點,乙個程式將一段資訊寫入socket中,該socket將這段資訊傳送給另外乙個socket中,使這段資訊能傳送到其他程式中。
host a上的程式a將一段資訊寫入socket中,socket的內容被host a的網路管理軟體訪問,並將這段資訊通過host a的網路介面卡傳送到host b,host b的網路介面卡接收到這段資訊後,傳送給host b的網路管理軟體,網路管理軟體將這段資訊儲存在host b的socket中,然後程式b才能在socket中閱讀這段資訊。
要通過網際網路進行通訊,至少需要一對套接字,乙個執行於客戶機端,稱之為clientsocket,另乙個執行於伺服器端,稱之為serversocket。
套接字鏈結步驟
伺服器監聽(lisen)
客戶端請求(accept)
連線確認
bind()函式
//繫結埠號 (tcp/ip,伺服器)
int bind
(int socket,
const struct sockaddr *address, socklen_t address_len)
;
引數1(socket) : 是由socket()呼叫返回的並且未作連線的套接字描述符(套接字型大小)。
引數2(address):指向特定協議的位址指標。
引數3(address_len):上面位址結構的長度。
返回值:沒有錯誤,bind()返回0,否則socket_error。
lisen函式
#include
#include
int listen
(int sockfd, int backlog)
;
在網路通訊中,客戶端往往處於主動的一方,伺服器處於被動的一方,伺服器是被鏈結的,所以它時時刻刻準備著被連線,所以呼叫lisen()函式來監聽
lisen()函式的作用就是將socket()函式得到的sockfd變成乙個被動的套接字,用來被動等待客戶端,而引數backlog的作用就是設定連線佇列的長度,
三次握手不是lisen()函式完成的,而是核心完成的,lisen()監聽到就把sockfd和backlog告訴核心就直接返回了,一切工作都是由核心來完成的。
這裡還需要再分析一下 listen() 函式的第二個引數 backlog,
connect()函式
#include
#include
int connect
(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen)
;
客戶端通過connect()函式主動向伺服器發起連線,
但建立這個連線也不是connect函式完成的,它也只是告訴核心,通知核心進行三次握手後,將結果返回給這個函式,
這個函式會一直阻塞,直到核心建立連線成功或者超時失敗後才返回;
所以客戶端通過connect()函式通知核心建立連線,伺服器通過lisen()函式通知核心建立連線,
因此lisen()之後連線就已建立好了,建立好的連線儲存在已連線佇列中
之後, 如果有客戶端通過 connect() 發起連線請求, 核心就會通過三次握手建立連線, 然後將建立好的連線放到乙個佇列中, 這個佇列稱為: 已完成連線佇列
這裡還需要再分析一下 listen() 函式的第二個引數 backlog,實際上,核心為每乙個套接字維護兩個佇列:
未連線佇列:其中儲存著尚未建立連線的套接字
**已連線佇列:**儲存著已建立連線的套接字
一般backlog的大小是這兩個佇列之和,收到客戶端的請求之後,核心建立乙個套接字存在未連線佇列之中,然後進行三次握手建立連線,
當這個連線建立成功之後,核心就把這個套接字放置在已連線佇列的隊尾,伺服器從已完成佇列中取走乙個就空出乙個位置,然後已經完成連線的套接字由補充進來, 就這樣實現動態平衡。
所以說, 如果在 listen 之後不進行 accept , connect 也是會成功返回的, 其實此時連線就已經建立好了。
accept()函式
#include
#include
int accept
(int sockfd,
struct sockaddr *addr,
socklen_t *addrlen)
;
所以accept()函式作用就是在已完成佇列中取乙個連線好的套接字
如果伺服器來不及呼叫 accept() 取走佇列中已經建立好的連線, 導致佇列中的連線滿了, 會怎麼樣呢 ?
這取決於核心的具體實現, 在linux中會延時建立連線.
函式的第乙個引數用來標識服務端套接字(也就是listen函式中設定為監聽狀態的套接字),第二個引數是用來儲存客戶端套接字對應的「地方」(包括客戶端ip和埠資訊等), 第三個引數是「地方」的占地大小。返回值對應客戶端套接字標識。
實際上是這樣的: accept函式指定服務端去接受客戶端的連線,接收後,返回了客戶端套接字的標識,且獲得了客戶端套接字的「地方」(包括客戶端ip和埠資訊等)。
accept函式非常地痴情,痴心不改:如果沒有客戶端套接字去請求,它便會在那裡一直痴痴地等下去,直到永遠(注意, 此處討論的是阻塞式的socket. 如果是非阻塞式的socket, 那麼accept函式就沒那麼痴情了, 而是會立即返回, 並意猶未盡地對未來的客戶端扔下一句話: 我等了你, 你不來, 那就算了, 我懶得鳥你)。
socket()函式
#include
#include
int socket
(int domain, int type, int protocol)
;
domain函式socket()的引數domain用於設定網路通訊的域,函式socket()根據這個引數選擇通訊協議的族。通訊協議族在檔案sys/socket.h中定義
type
函式socket()的引數type用於設定套接字通訊的型別,主要有socket_stream(流式套接字)、sock——dgram(資料報套接字)等
protocol
函式socket()的第3個引數protocol用於制定某個協議的特定型別,即type型別中的某個型別。通常某協議中只有一種特定型別,這樣protocol引數僅能設定為0;但是有些協議有多種特定的型別,就需要設定這個引數來選擇特定的型別。
網路基礎 網路程式設計套接字
埠號 port 是傳輸層協議的內容,標示了這台機器上唯一的程序。為什麼要有套接字,舉個例子 郵寄一封信,我們不僅需要知道郵編號碼,還需要知道收件人的門牌號,而此處的郵編號相當於ip位址,而埠號就相當於門牌號。我們已經知道,記憶體中的多位元組資料相對於記憶體位址有 端和小端之分,磁碟 件中的多位元組資...
網路套接字程式設計基礎
struct sockaddr和struct sockaddr in這兩個結構體用來處理網路通訊的位址。struct sockaddr sockaddr in在標頭檔案 include或 include中定義,該結構體解決了sockaddr的缺陷,把port和addr 分開儲存在兩個變數中,如下 s...
網路基礎 原生套接字,域間套接字
原生套接字 sock raw 可以用來自行組裝ip資料報,然後將資料報傳送到其他終端,也就是說原生套接字是基於ip資料報的程式設計。sock packet是基於資料鏈路層的程式設計 另外,必須在管理員許可權下才能夠使用原生套接字。原生套介面提供了普通tcp和udp socket不能提供的3個能力 1...