socket到底是個什么東西,socket是個TCP協議嗎?我們平時很多方面都會用到socket,但確定真的了解socket嗎?
一.說起Socket我們在說什么?
Wikipedia:A network socket is an endpoint of a connection in a computer network. In Internet Protocol (IP) networks, these are often called Internet sockets. It is a handle (abstract reference) that a program can pass to the networking application programming interface (API) to use the connection for receiving and sending data. Sockets are often represented internally as integers.
個人理解:socket其實就是一根通信電纜兩端的電話終端,電話接通后就相當兩個socket建立了連接,兩個電話之間可以相互通話,兩個socket之間就可以實時收發數據,socket僅僅是一個通信工具,通信工具,通信工具重要的事說三遍(OSI模型中的第四層傳輸層的API接口,這一層通常使用兩種協議TCP或UDP來傳輸)并不是一種協議。TCP、UDP、HTTP才是我們通常理解的協議。
也就是說,Socket這個工具一般使用TCP和UDP兩種協議來通信,否則光桿socket并沒有毛用。其實我們所認識到的互聯網中的各種通信:web請求、即時通訊、文件傳輸和共享等等底層都是通過Socket工具來實現的,所以說互聯網一切皆socket。搞懂了socket你就相當于打通了任督二脈。
二.Socket的8個必備函數
socket并不可怕,我們只需掌握下面幾個C語言socket函數個人覺得就夠用了。
1. ? ?int socket(int domain, int type, int protocol);
socket函數對應于普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用于創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,后續的操作都有用到它,把它作為參數,通過它來進行一些讀寫操作。
正如可以給fopen的傳入不同參數值,以打開不同的文件。創建socket的時候,也可以指定不同的參數創建不同的socket描述符。
socket函數的三個參數和return分別為:
domain:即協議域,又稱為協議族(family)。通常我們只需關心這兩個協議族就夠了AF_INET、AF_INET6。AF_INET表示創建IPv4的socket,那么AF_INET6就表示創建IPv6的socket。
type:指定socket類型。常用的socket類型有,通常我們只需關心SOCK_STREAM、SOCK_DGRAM這兩個類型也就夠了,SOCK_STREAM表示TCP類型的socket,SOCK_DGRAM表示UDP類型的socket。
protocol:故名思意,就是指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。通常使用中只需記住這個參數設為0就夠了。當protocol為0時,會自動選擇type類型對應的默認協議。
return:套接口描述字。如果出現錯誤,它返回-1,并設置errno為相應的值
當我們調用socket創建一個socket時,返回的socket描述字它存在于協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須調用bind()函數,否則就當調用connect()、listen()時系統會自動隨機分配一個端口。具體下文詳細說明。
2. ? int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
正如上面所說bind()函數把一個地址族中的特定地址(IP+Port)賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。作為服務端我們必須給它指定一個端口號,要不然客戶端就不知道該連哪個端口了。所以服務器一般初始化問socket必須調用bind()函數綁定地址然后才能listen()。而客戶端一般初始化完socket并且知道服務器IP地址和端口號就直接可以調用connect()函數進行連接了,不需要綁定自己的地址,因為系統隨機給客戶端分配的地址(IP+Port)已經默默發送到服務器了。
好了說了這么多,該說說三個參數及return的含義了:
sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。
addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同,如ipv4對應的是:
struct sockaddr_in {
? ? ? ? ? ? ? sa_family_t? ? sin_family; /* address family: AF_INET */
? ? ? ? ? ? ? in_port_t? ? ? sin_port;? /* port in network byte order */
? ? ? ? ? ? ? struct in_addr sin_addr;? /* internet address */
};
addrlen:對應的是地址的長度。
return:成功返回0,失敗返回-1
3. ? int listen(int sockfd, int backlog);
作為一個服務器,在調用socket()、bind()之后就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,服務器端就會接收到這個請求。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變為被動類型的,等待客戶的連接請求。
sockfd:即為要監聽的socket描述字
backlog:相應socket可以排隊的最大連接個數。
return:成功返回0,失敗返回-1
4. ? int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作為客戶端初始化完socket,不需要bind()就直接可以connect()與服務端建立連接了。因為系統會自動生成一個隨機的地址(具體應該為本機IP+隨機端口號)。
sockfd:還沒綁定客戶端具體地址的socket描述字
addr:即將要連接到服務端的地址(IP+port)
addrlen:地址長度
return:如果是阻塞連接,成功立即返回0,如果失敗,在iOS系統上超時大約一分鐘后返回-1
5. ?int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP服務器端依次調用socket()、bind()、listen()之后,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之后就向TCP服務器發送了一個連接請求。TCP服務器監聽到這個請求之后,就會調用accept()函數取接收請求,并把會這樣連接就建立好了。之后就可以開始網絡I/O操作了,即類同于普通文件的讀寫I/O操作。
sockfd:已經綁定具體服務端地址的socket描述字。
sockaddr:一般為NULL,這個參數可以理解為interface指定連接必須從哪里來的,比如是localhost、wifi還是以太網卡。NULL為任意方式。
addrlen:sockaddr地址長度
return:成功返回服務器端socket描述字否則錯誤。
6.? ssize_t write(int fd, const void *buf, size_t count);
服務端與客戶端建立了通信接下來就可以實現網絡通信了,可以調用網絡I/O進行讀寫操作了,即實現了網絡中不同進程之間的通信!
fd:要寫入的的socket文件描述符
buf:將要被寫入的緩沖區數據
count:被寫入的數據長度
return:返回值大于0,表示寫了部分數據或者是全部的數據,這樣用一個while循環不斷的寫入數據,但是循環過程中的buf參數和count參數是我們自己來更新的,也就是說,網絡編程中寫函數是不負責將全部數據寫完之后再返回的,說不定中途就返回了!返回值小于0表示出錯。代碼見第三部分
7. ssize_t read(int fd, void *buf, size_t count);
如果系統事件源有了一個讀取信號事件發生,那么我們可以調用read方法讀取網絡I/O中的數據。
fd:建立連接的socket文件描述符
buf:讀取數據后放入的緩沖區
count:緩沖區大小
return:當讀取成功時,read返回實際讀取到的字節數,這樣我們可以用一個while循環不斷的讀取數據,但是循環過程中的buf參數和count參數是我們自己來更新的,也就是說,網絡編程中寫函數是不負責將全部數據讀取完之后再返回的。如果返回值是0,表示已經讀取到文件的結束了,小于0表示是讀取錯誤。代碼見第三部分
8.? int close(int fd);
在服務器與客戶端建立連接之后,會進行一些讀寫操作,完成了讀寫操作就要關閉相應的socket描述字,好比操作完打開的文件要調用fclose關閉打開的文件。
三.循環讀取和寫入代碼
循環寫入代碼
循環讀取代碼