socket 的原意是「插座」,在計算機通訊領域,socket 被翻譯爲「套接字」,它是計算機之間進行通訊的一種約定或一種方式。經過 socket 這種約定,一臺計算機能夠接收其餘計算機的數據,也能夠向其餘計算機發送數據。
咱們把插頭插到插座上就能從電網得到電力供應,一樣,爲了與遠程計算機進行數據傳輸,須要鏈接到因特網,而 socket 就是用來鏈接到因特網的工具。
html
socket 的典型應用就是 Web 服務器和瀏覽器:瀏覽器獲取用戶輸入的 URL,向服務器發起請求,服務器分析接收到的 URL,將對應的網頁內容返回給瀏覽器,瀏覽器再通過解析和渲染,就將文字、圖片、視頻等元素呈現給用戶。
學習 socket,也就是學習計算機之間如何通訊,並編寫出實用的程序。linux
在 UNIX/Linux 系統中,爲了統一對各類硬件的操做,簡化接口,不一樣的硬件設備也都被當作一個文件。對這些文件的操做,等同於對磁盤上普通文件的操做。
你也許聽不少高手說過,UNIX/Linux 中的一切都是文件!那個傢伙說的沒錯。
爲了表示和區分已經打開的文件,UNIX/Linux 會給每一個文件分配一個 ID,這個 ID 就是一個整數,被稱爲文件描述符(File Descriptor)。例如:算法
UNIX/Linux 程序在執行任何形式的 I/O 操做時,都是在讀取或者寫入一個文件描述符。一個文件描述符只是一個和打開的文件相關聯的整數,它的背後多是一個硬盤上的普通文件、FIFO、管道、終端、鍵盤、顯示器,甚至是一個網絡鏈接。
請注意,網絡鏈接也是一個文件,它也有文件描述符!你必須理解這句話。
咱們能夠經過 socket() 函數來建立一個網絡鏈接,或者說打開一個網絡文件,socket() 的返回值就是文件描述符。有了文件描述符,咱們就可使用普通的文件操做函數來傳輸數據了,例如:編程
你看,只要用 socket() 建立了鏈接,剩下的就是文件操做了,網絡編程原來就是如此簡單!數組
Windows 也有相似「文件描述符」的概念,但一般被稱爲「文件句柄」。所以,本教程若是涉及 Windows 平臺將使用「句柄」,若是涉及 Linux 平臺則使用「描述符」。
與 UNIX/Linux 不一樣的是,Windows 會區分 socket 和文件,Windows 就把 socket 當作一個網絡鏈接來對待,所以須要調用專門針對 socket 而設計的數據傳輸函數,針對普通文件的輸入輸出函數就無效了。瀏覽器
這個世界上有不少種套接字(socket),好比 DARPA Internet 地址(Internet 套接字)、本地節點的路徑名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。但本教程只講第一種套接字——Internet 套接字,它是最具表明性的,也是最經典最經常使用的。之後咱們說起套接字,指的都是 Internet 套接字。
根據數據的傳輸方式,能夠將 Internet 套接字分紅兩種類型。經過 socket() 函數建立鏈接時,必須告訴它使用哪一種數據傳輸方式。服務器
Internet 套接字其實還有不少其它數據傳輸方式,可是我可不想嚇到你,本教程只講經常使用的兩種。
流格式套接字(Stream Sockets)也叫「面向鏈接的套接字」,在代碼中使用 SOCK_STREAM 表示。
SOCK_STREAM 是一種可靠的、雙向的通訊數據流,數據能夠準確無誤地到達另外一臺計算機,若是損壞或丟失,能夠從新發送。網絡
流格式套接字有本身的糾錯機制,在此咱們就不討論了。
SOCK_STREAM 有如下幾個特徵:併發
能夠將 SOCK_STREAM 比喻成一條傳送帶,只要傳送帶自己沒有問題(不會斷網),就能保證數據不丟失;同時,較晚傳送的數據不會先到達,較早傳送的數據不會晚到達,這就保證了數據是按照順序傳遞的。
socket
爲何流格式套接字能夠達到高質量的數據傳輸呢?這是由於它使用了 TCP 協議(The Transmission Control Protocol,傳輸控制協議),TCP 協議會控制你的數據按照順序到達而且沒有錯誤。
你也許見過 TCP,是由於你常常據說「TCP/IP」。TCP 用來確保數據的正確性,IP(Internet Protocol,網絡協議)用來控制數據如何從源頭到達目的地,也就是常說的「路由」。
那麼,「數據的發送和接收不一樣步」該如何理解呢?
假設傳送帶傳送的是水果,接收者須要湊齊 100 個後才能裝袋,可是傳送帶可能把這 100 個水果分批傳送,好比第一批傳送 20 個,第二批傳送 50 個,第三批傳送 30 個。接收者不須要和傳送帶保持同步,只要根據本身的節奏來裝袋便可,不用管傳送帶傳送了幾批,也不用每到一批就裝袋一次,能夠等到湊夠了 100 個水果再裝袋。
流格式套接字的內部有一個緩衝區(也就是字符數組),經過 socket 傳輸的數據將保存到這個緩衝區。接收端在收到數據後並不必定當即讀取,只要數據不超過緩衝區的容量,接收端有可能在緩衝區被填滿之後一次性地讀取,也可能分紅好幾回讀取。
也就是說,無論數據分幾回傳送過來,接收端只須要根據本身的要求讀取,不用非得在數據到達時當即讀取。傳送端有本身的節奏,接收端也有本身的節奏,它們是不一致的。
流格式套接字有什麼實際的應用場景嗎?瀏覽器所使用的 http 協議就基於面向鏈接的套接字,由於必需要確保數據準確無誤,不然加載的 HTML 將沒法解析。
數據報格式套接字(Datagram Sockets)也叫「無鏈接的套接字」,在代碼中使用 SOCK_DGRAM 表示。
計算機只管傳輸數據,不做數據校驗,若是數據在傳輸中損壞,或者沒有到達另外一臺計算機,是沒有辦法補救的。也就是說,數據錯了就錯了,沒法重傳。
由於數據報套接字所作的校驗工做少,因此在傳輸效率方面比流格式套接字要高。
能夠將 SOCK_DGRAM 比喻成高速移動的摩托車快遞,它有如下特徵:
衆所周知,速度是快遞行業的生命。用摩托車發往同一地點的兩件包裹無需保證順序,只要以最快的速度交給客戶就行。這種方式存在損壞或丟失的風險,並且包裹大小有必定限制。所以,想要傳遞大量包裹,就得分配發送。
另外,用兩輛摩托車分別發送兩件包裹,那麼接收者也須要分兩次接收,因此「數據的發送和接收是同步的」;換句話說,接收次數應該和發送次數相同。
總之,數據報套接字是一種不可靠的、不按順序傳遞的、以追求速度爲目的的套接字。
數據報套接字也使用 IP 協議做路由,可是它不使用 TCP 協議,而是使用 UDP 協議(User Datagram Protocol,用戶數據報協議)。
QQ 視頻聊天和語音聊天就使用 SOCK_DGRAM 來傳輸數據,由於首先要保證通訊的效率,儘可能減少延遲,而數據的正確性是次要的,即便丟失很小的一部分數據,視頻和音頻也能夠正常解析,最多出現噪點或雜音,不會對通訊質量有實質的影響。
注意:SOCK_DGRAM 沒有想象中的糟糕,不會頻繁的丟失數據,數據錯誤只是小几率事件。
流格式套接字(Stream Sockets)就是「面向鏈接的套接字」,它基於 TCP 協議;數據報格式套接字(Datagram Sockets)就是「無鏈接的套接字」,它基於 UDP 協議。
這給你們形成一種印象,面向鏈接就是可靠的通訊,無鏈接就是不可靠的通訊,實際狀況是這樣嗎?
另外,無論是哪一種數據傳輸方式,都得經過整個 Internet 網絡的物理線路將數據傳輸過去,從這個層面理解,全部的 socket 都是有物理鏈接的呀,爲何還有無鏈接的 socket 呢?
本節就來給你們解開種種謎團,加深你們對數據傳輸方式的認識。
從字面上理解,面向鏈接好像有一條管道,它鏈接發送端和接收端,數據包都經過這條管道來傳輸。固然,兩臺計算機在通訊以前必須先搭建好管道。
無鏈接好像沒頭蒼蠅亂撞,數據包從發送端到接收端並無固定的線路,愛怎麼走就怎麼走,只要能到達就行。每一個數據包都比較自私,不和別人分享本身的線路,可是,你們最終都能異曲同工,到達接收端。
這樣理解沒錯,可是我相信這還不夠深刻,你們仍是感受雲裏霧裏,沒有看到本質。好,接下來就是見證奇蹟的時刻,我會用實例給你們演示!
上圖是一個簡化的互聯網模型,H1 ~ H6 表示計算機,A~E 表示路由器,發送端發送的數據必須通過路由器的轉發才能到達接收端。
假設 H1 要發送若干個數據包給 H6,那麼有多條路徑能夠選擇,好比:
數據包的傳輸路徑是路由器根據算法來計算出來的,算法會考慮不少因素,好比網絡的擁堵情況、下一個路由器是否忙碌等。
對於無鏈接的套接字,每一個數據包能夠選擇不一樣的路徑,好比第一個數據包選擇路徑④,第二個數據包選擇路徑①,第三個數據包選擇路徑②……固然,它們也能夠選擇相同的路徑,那也只不過是巧合而已。
每一個數據包之間都是獨立的,各走各的路,誰也不影響誰,除了迷路的或者發生意外的數據包,最後都能到達 H6。可是,到達的順序是不肯定的,好比:
還有一些意外狀況會發生,好比:
總之,對於無鏈接的套接字,數據包在傳輸過程當中會發生各類不測,也會發生各類奇蹟。H1 只負責把數據包發出,至於它何時到達,先到達仍是後到達,有沒有成功到達,H1 都無論了;H6 也沒有選擇的權利,只能被動接收,收到什麼算什麼,愛用不用。
無鏈接套接字遵循的是「盡最大努力交付」的原則,就是盡力而爲,實在作不到了也沒辦法。無鏈接套接字提供的沒有質量保證的服務。
面向鏈接的套接字在正式通訊以前要先肯定一條路徑,沒有特殊狀況的話,之後就固定地使用這條路徑來傳遞數據包了。固然,路徑被破壞的話,好比某個路由器斷電了,那麼會從新創建路徑。
這條路徑是由路由器維護的,路徑上的全部路由器都要存儲該路徑的信息(實際上只須要存儲上游和下游的兩個路由器的位置就行),因此路由器是有開銷的。
H1 和 H6 通訊完畢後,要斷開鏈接,銷燬路徑,這個時候路由器也會把以前存儲的路徑信息擦除。
在不少網絡通訊教程中,這條預先創建好的路徑被稱爲「虛電路」,就是一條虛擬的通訊電路。
爲了保證數據包準確、順序地到達,發送端在發送數據包之後,必須獲得接收端的確認才發送下一個數據包;若是數據包發出去了,一段時間之後仍然沒有獲得接收端的迴應,那麼發送端會從新再發送一次,直到獲得接收端的迴應。這樣一來,發送端發送的全部數據包都能到達接收端,而且是按照順序到達的。
發送端發送一個數據包,如何獲得接收端的確認呢?很簡單,爲每個數據包分配一個 ID,接收端接收到數據包之後,再給發送端返回一個數據包,告訴發送端我接收到了 ID 爲 xxx 的數據包。
面向鏈接的套接字會比無鏈接的套接字多出不少數據包,由於發送端每發送一個數據包,接收端就會返回一個數據包。此外,創建鏈接和斷開鏈接的過程也會傳遞不少數據包。
不可是數量多了,每一個數據包也變大了:除了源端口和目的端口,面向鏈接的套接字還包括序號、確認信號、數據偏移、控制標誌(一般說的 URG、ACK、PSH、RST、SYN、FIN)、窗口、校驗和、緊急指針、選項等信息;而無鏈接的套接字則只包含長度和校驗和信息。
有鏈接的數據包比無鏈接大不少,這意味着更大的負載和更大的帶寬。許多即時聊天軟件採用 UDP 協議(無鏈接套接字),與此有莫大的關係。
兩種套接字各有優缺點:
兩種套接字的特色決定了它們的應用場景,有些服務對可靠性要求比較高,必須數據包可以完整無誤地送達,那就得選擇有鏈接的套接字(TCP 服務),好比 HTTP、FTP 等;而另外一些服務,並不須要那麼高的可靠性,效率和實時纔是它們所關心的,那就能夠選擇無鏈接的套接字(UDP 服務),好比 DNS、即時聊天工具等。
無論是 Windows 仍是 Linux,都使用 socket() 函數來建立套接字。socket() 在兩個平臺下的參數是相同的,不一樣的是返回值。
在《socket是什麼》一節中咱們講到了 Windows 和 Linux 在對待 socket 方面的區別。
Linux 中的一切都是文件,每一個文件都有一個整數類型的文件描述符;socket 也是一個文件,也有文件描述符。使用 socket() 函數建立套接字之後,返回值就是一個 int 類型的文件描述符。
Windows 會區分 socket 和普通文件,它把 socket 當作一個網絡鏈接來對待,調用 socket() 之後,返回值是 SOCKET 類型,用來表示一個套接字。
在 Linux 下使用 <sys/socket.h> 頭文件中 socket() 函數來建立套接字,原型爲:
int socket(int af, int type, int protocol);
1) af 爲地址族(Address Family),也就是 IP 地址類型,經常使用的有 AF_INET 和 AF_INET6。AF 是「Address Family」的簡寫,INET是「Inetnet」的簡寫。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
你們須要記住127.0.0.1
,它是一個特殊IP地址,表示本機地址,後面的教程會常常用到。
你也可使用 PF 前綴,PF 是「Protocol Family」的簡寫,它和 AF 是同樣的。例如,PF_INET 等價於 AF_INET,PF_INET6 等價於 AF_INET6。
2) type 爲數據傳輸方式/套接字類型,經常使用的有 SOCK_STREAM(流格式套接字/面向鏈接的套接字) 和 SOCK_DGRAM(數據報套接字/無鏈接的套接字),咱們已經在《套接字有哪些類型》一節中進行了介紹。
3) protocol 表示傳輸協議,經常使用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議。
有了地址類型和數據傳輸方式,還不足以決定採用哪一種協議嗎?爲何還須要第三個參數呢?
正如你們所想,通常狀況下有了 af 和 type 兩個參數就能夠建立套接字了,操做系統會自動推演出協議類型,除非遇到這樣的狀況:有兩種不一樣的協議支持同一種地址類型和數據傳輸類型。若是咱們不指明使用哪一種協議,操做系統是沒辦法自動推演的。
本教程使用 IPv4 地址,參數 af 的值爲 PF_INET。若是使用 SOCK_STREAM 傳輸數據,那麼知足這兩個條件的協議只有 TCP,所以能夠這樣來調用 socket() 函數:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP協議
這種套接字稱爲 TCP 套接字。
若是使用 SOCK_DGRAM 傳輸方式,那麼知足這兩個條件的協議只有 UDP,所以能夠這樣來調用 socket() 函數:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP協議
這種套接字稱爲 UDP 套接字。
上面兩種狀況都只有一種協議知足條件,能夠將 protocol 的值設爲 0,系統會自動推演出應該使用什麼協議,以下所示:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //建立TCP套接字 int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字
後面的教程中多采用這種簡化寫法。
Windows 下也使用 socket() 函數來建立套接字,原型爲:
SOCKET socket(int af, int type, int protocol);
除了返回值類型不一樣,其餘都是相同的。Windows 不把套接字做爲普通文件對待,而是返回 SOCKET 類型的句柄。請看下面的例子:
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //建立TCP套接字
socket() 函數用來建立套接字,肯定套接字的各類屬性,而後服務器端要用 bind() 函數將套接字與特定的 IP 地址和端口綁定起來,只有這樣,流經該 IP 地址和端口的數據才能交給套接字處理。相似地,客戶端也要用 connect() 函數創建鏈接。
bind() 函數的原型爲:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); //Windows
下面以 Linux 爲例進行講解,Windows 與此相似。
sock 爲 socket 文件描述符,addr 爲 sockaddr 結構體變量的指針,addrlen 爲 addr 變量的大小,可由 sizeof() 計算得出。
下面的代碼,將建立的套接字與IP地址 127.0.0.一、端口 1234 綁定:
這裏咱們使用 sockaddr_in 結構體,而後再強制轉換爲 sockaddr 類型,後邊會講解爲何這樣作。
接下來不妨先看一下 sockaddr_in 結構體,它的成員變量以下:
1) sin_family 和 socket() 的第一個參數的含義相同,取值也要保持一致。
2) sin_prot 爲端口號。uint16_t 的長度爲兩個字節,理論上端口號的取值範圍爲 0~65536,但 0~1023 的端口通常由系統分配給特定的服務程序,例如 Web 服務的端口號爲 80,FTP 服務的端口號爲 21,因此咱們的程序要儘可能在 1024~65536 之間分配端口號。
端口號須要用 htons() 函數轉換,後面會講解爲何。
3) sin_addr 是 struct in_addr 結構體類型的變量,下面會詳細講解。
4) sin_zero[8] 是多餘的8個字節,沒有用,通常使用 memset() 函數填充爲 0。上面的代碼中,先用 memset() 將結構體的所有字節填充爲 0,再給前3個成員賦值,剩下的 sin_zero 天然就是 0 了。
sockaddr_in 的第3個成員是 in_addr 類型的結構體,該結構體只包含一個成員,以下所示:
in_addr_t 在頭文件 <netinet/in.h> 中定義,等價於 unsigned long,長度爲4個字節。也就是說,s_addr 是一個整數,而IP地址是一個字符串,因此須要 inet_addr() 函數進行轉換,例如:
運行結果:
16777343
圖解 sockaddr_in 結構體
爲何要搞這麼複雜,結構體中嵌套結構體,而不用 sockaddr_in 的一個成員變量來指明IP地址呢?socket() 函數的第一個參數已經指明瞭地址類型,爲何在 sockaddr_in 結構體中還要再說明一次呢,這不是囉嗦嗎?
這些繁瑣的細節確實給初學者帶來了必定的障礙,我想,這或許是歷史緣由吧,後面的接口總要兼容前面的代碼。各位讀者必定要有耐心,暫時不理解沒有關係,根據教程中的代碼「照貓畫虎」便可,時間久了天然會接受。
bind() 第二個參數的類型爲 sockaddr,而代碼中卻使用 sockaddr_in,而後再強制轉換爲 sockaddr,這是爲何呢?
sockaddr 結構體的定義以下:
下圖是 sockaddr 與 sockaddr_in 的對比(括號中的數字表示所佔用的字節數):
sockaddr 和 sockaddr_in 的長度相同,都是16字節,只是將IP地址和端口號合併到一塊兒,用一個成員 sa_data 表示。要想給 sa_data 賦值,必須同時指明IP地址和端口號,例如」127.0.0.1:80「,遺憾的是,沒有相關函數將這個字符串轉換成須要的形式,也就很難給 sockaddr 類型的變量賦值,因此使用 sockaddr_in 來代替。這兩個結構體的長度相同,強制轉換類型時不會丟失字節,也沒有多餘的字節。
能夠認爲,sockaddr 是一種通用的結構體,能夠用來保存多種類型的IP地址和端口號,而 sockaddr_in 是專門用來保存 IPv4 地址的結構體。另外還有 sockaddr_in6,用來保存 IPv6 地址,它的定義以下:
純文本複製
正是因爲通用結構體 sockaddr 使用不便,才針對不一樣的地址類型定義了不一樣的結構體。
connect() 函數用來創建鏈接,它的原型爲:
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
各個參數的說明和 bind() 相同,再也不贅述。
對於服務器端程序,使用 bind() 綁定套接字後,還須要使用 listen() 函數讓套接字進入被動監聽狀態,再調用 accept() 函數,就能夠隨時響應客戶端的請求了。
經過 listen() 函數可讓套接字進入被動監聽狀態,它的原型爲:
sock 爲須要進入監聽狀態的套接字,backlog 爲請求隊列的最大長度。
所謂被動監聽,是指當沒有客戶端請求時,套接字處於「睡眠」狀態,只有當接收到客戶端請求時,套接字纔會被「喚醒」來響應請求。
當套接字正在處理客戶端請求時,若是有新的請求進來,套接字是無法處理的,只能把它放進緩衝區,待當前請求處理完畢後,再從緩衝區中讀取出來處理。若是不斷有新的請求進來,它們就按照前後順序在緩衝區中排隊,直到緩衝區滿。這個緩衝區,就稱爲請求隊列(Request Queue)。
緩衝區的長度(能存放多少個客戶端請求)能夠經過 listen() 函數的 backlog 參數指定,但究竟爲多少並無什麼標準,能夠根據你的需求來定,併發量小的話能夠是10或者20。
若是將 backlog 的值設置爲 SOMAXCONN,就由系統來決定請求隊列長度,這個值通常比較大,多是幾百,或者更多。
當請求隊列滿時,就再也不接收新的請求,對於 Linux,客戶端會收到 ECONNREFUSED 錯誤,對於 Windows,客戶端會收到 WSAECONNREFUSED 錯誤。
注意:listen() 只是讓套接字處於監聽狀態,並無接收請求。接收請求須要使用 accept() 函數。
當套接字處於監聽狀態時,能夠經過 accept() 函數來接收客戶端請求。它的原型爲:
純文本複製
它的參數與 listen() 和 connect() 是相同的:sock 爲服務器端套接字,addr 爲 sockaddr_in 結構體變量,addrlen 爲參數 addr 的長度,可由 sizeof() 求得。
accept() 返回一個新的套接字來和客戶端通訊,addr 保存了客戶端的IP地址和端口號,而 sock 是服務器端的套接字,你們注意區分。後面和客戶端通訊時,要使用這個新生成的套接字,而不是原來服務器端的套接字。
最後須要說明的是:listen() 只是讓套接字進入監聽狀態,並無真正接收客戶端請求,listen() 後面的代碼會繼續執行,直到遇到 accept()。accept() 會阻塞程序執行(後面代碼不能被執行),直到有新的請求到來。
在 Linux 和 Windows 平臺下,使用不一樣的函數發送和接收 socket 數據,下面咱們分別講解。
Linux 不區分套接字文件和普通文件,使用 write() 能夠向套接字中寫入數據,使用 read() 能夠從套接字中讀取數據。
前面咱們說過,兩臺計算機之間的通訊至關於兩個套接字之間的通訊,在服務器端用 write() 向套接字寫入數據,客戶端就能收到,而後再使用 read() 從套接字中讀取出來,就完成了一次通訊。
write() 的原型爲:
ssize_t write(int fd, const void *buf, size_t nbytes);
fd 爲要寫入的文件的描述符,buf 爲要寫入的數據的緩衝區地址,nbytes 爲要寫入的數據的字節數。
size_t 是經過 typedef 聲明的 unsigned int 類型;ssize_t 在 "size_t" 前面加了一個"s",表明 signed,即 ssize_t 是經過 typedef 聲明的 signed int 類型。
write() 函數會將緩衝區 buf 中的 nbytes 個字節寫入文件 fd,成功則返回寫入的字節數,失敗則返回 -1。
read() 的原型爲:
ssize_t read(int fd, void *buf, size_t nbytes);
fd 爲要讀取的文件的描述符,buf 爲要接收數據的緩衝區地址,nbytes 爲要讀取的數據的字節數。
read() 函數會從 fd 文件中讀取 nbytes 個字節並保存到緩衝區 buf,成功則返回讀取到的字節數(但遇到文件結尾則返回0),失敗則返回 -1。
Windows 和 Linux 不一樣,Windows 區分普通文件和套接字,並定義了專門的接收和發送的函數。
從服務器端發送數據使用 send() 函數,它的原型爲:
int send(SOCKET sock, const char *buf, int len, int flags);
sock 爲要發送數據的套接字,buf 爲要發送的數據的緩衝區地址,len 爲要發送的數據的字節數,flags 爲發送數據時的選項。
返回值和前三個參數再也不贅述,最後的 flags 參數通常設置爲 0 或 NULL,初學者沒必要深究。
在客戶端接收數據使用 recv() 函數,它的原型爲:
int recv(SOCKET sock, char *buf, int len, int flags);