1. 程式人生 > >C#Socket通訊原理(基礎篇)

C#Socket通訊原理(基礎篇)

這幾天博主花了4天時間去學習,整理Socket通訊。大致懂了一點,現在我來總結一下關於C#Socket通訊的原理

首先我們要知道網路中的程序是如何進行通訊的

在本地可以通過程序PID來唯一標識一個程序,但是在網路中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網路層的ip地址可以唯一標識網路中的主機,而傳輸層的協議+埠可以唯一標識主機中的應用程式(程序)。這樣利用三元組(ip地址,協議,埠)就可以標識網路的程序了,網路中的程序通訊就可以利用這個標誌與其它程序進行互動。

什麼是Socket? 通俗一點的說法

網路上的兩個程式通過一個雙向的通訊連線實現資料的交換,這個連線的一端稱為一個socket。建立網路通訊連線至少要一對埠號(socket)。socket本質是程式設計介面(API),對TCP/IP的封裝,TCP/IP也要提供可供程式設計師做網路開發所用的介面,這就是Socket程式設計介面;HTTP是轎車,提供了封裝或者顯示資料的具體形式;Socket是發動機,提供了網路通訊的能力。Socket的英文原義是“孔”或“插座”。作為BSD UNIX的
程序通訊
機制,取後一種意思。通常也稱作"套接字",用於描述IP地址和埠,是一個通訊鏈的控制代碼,可以用來實現不同虛擬機器或不同計算機之間的通訊。在Internet上的主機一般運行了多個服務軟體,同時提供幾種服務。每種服務都開啟一個Socket,並繫結到一個埠上,不同的埠對應於不同的服務。Socket正如其英文原義那樣,像一個多孔插座。一臺主機猶如佈滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟體將插頭插到不同編號的插座,就可以得到不同的服務。

Socket有關的函式:

socket()函式

int socket(int domain, int type, int protocol);

· domain:即協議域,又稱為協議族(family)。常用的協議族有,AF_INETAF_INET6AF_LOCAL(或稱AF_UNIXUnix域socket)、AF_ROUTE等等。協議族決定了socket的地址型別,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與埠號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。

· type:指定socket型別。常用的socket型別有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的型別有哪些?)。

· 

protocol:故名思意,就是指定協議。常用的協議有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議(這個協議我將會單獨開篇討論!)。

bind()函式

正如上面所說bind()函式把一個地址族中的特定地址賦給socket。例如對應AF_INETAF_INET6就是把一個ipv4或ipv6地址和埠號組合賦給socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:即socket描述字,它是通過socket()函式建立了,唯一標識一個socket。bind()函式就是將給這個描述字繫結一個名字。

addr:一個const struct sockaddr *指標,指向要繫結給sockfd的協議地址。這個地址結構根據地址建立socket時的地址協議族的不同而不同

addrlen:對應的是地址的長度。

通常伺服器在啟動的時候都會繫結一個眾所周知的地址(如ip地址+埠號),用於提供服務,客戶就可以通過它來接連伺服器;而客戶端就不用指定,有系統自動分配一個埠號和自身的ip地址組合。這就是為什麼通常伺服器端在listen之前會呼叫bind(),而客戶端就不會呼叫,而是在connect()時由系統隨機生成一個。

listen()、connect()函式

如果作為一個伺服器,在呼叫socket()bind()之後就會呼叫listen()來監聽這個socket,如果客戶端這時呼叫connect()發出連線請求,伺服器端就會接收到這個請求。

int listen(int sockfd, int backlog);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函式的第一個引數即為要監聽的socket描述字,第二個引數為相應socket可以排隊的最大連線個數。socket()函式建立的socket預設是一個主動型別的,listen函式將socket變為被動型別的,等待客戶的連線請求。

connect函式的第一個引數即為客戶端的socket描述字,第二引數為伺服器的socket地址,第三個引數為socket地址的長度。客戶端通過呼叫connect函式來建立與TCP伺服器的連線。

accept()函式

TCP伺服器端依次呼叫socket()bind()listen()之後,就會監聽指定的socket地址了。TCP客戶端依次呼叫socket()connect()之後就TCP伺服器傳送了一個連線請求。TCP伺服器監聽到這個請求之後,就會呼叫accept()函式取接收請求,這樣連線就建立好了。之後就可以開始網路I/O操作了,即類同於普通檔案的讀寫I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函式的第一個引數為伺服器的socket描述字,第二個引數為指向struct sockaddr *的指標,用於返回客戶端的協議地址,第三個引數為協議地址的長度。如果accpet成功,那麼其返回值是由核心自動生成的一個全新的描述字,代表與返回客戶的TCP連線。

注意accept的第一個引數為伺服器的socket描述字,是伺服器開始呼叫socket()函式生成的,稱為監聽socket描述字;而accept函式返回的是已連線的socket描述字。一個伺服器通常通常僅僅只建立一個監聽socket描述字,它在該伺服器的生命週期內一直存在。核心為每個由伺服器程序接受的客戶連線建立了一個已連線socket描述字,當伺服器完成了對某個客戶的服務,相應的已連線socket描述字就被關閉。


read()、write()等函式

萬事具備只欠東風,至此伺服器與客戶已經建立好連線了。可以呼叫網路I/O進行讀寫操作了,即實現了網咯中不同程序之間的通訊!網路I/O操作有下面幾組:

· read()/write()

· recv()/send()

· readv()/writev()

· recvmsg()/sendmsg()

· recvfrom()/sendto()

我推薦使用recvmsg()/sendmsg()函式,

close()函式

在伺服器與客戶端建立連線之後,會進行一些讀寫操作,完成了讀寫操作就要關閉相應的socket描述字,好比操作完開啟的檔案要呼叫fclose關閉開啟的檔案。

#include <unistd.h>int close(int fd);

close一個TCP socket的預設行為時把該socket標記為以關閉,然後立即返回到呼叫程序。該描述字不能再由呼叫程序使用,也就是說不能再作為read或write的第一個引數。

注意:close操作只是使相應socket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向伺服器傳送終止連線請求。

講完了Socket有關的函式,相信你對Socket有一定的瞭解了,下面講講臭名昭著的TCP三次握手和TCP四次放手

先上圖

http://images.cnblogs.com/cnblogs_com/skynet/201012/201012122157467258.png

客戶端先發送一個SYN J 的報文,

伺服器接收 向客戶端傳送一個確認的 ACK J+1 ,並響應一個SYN k的報文,

客戶端接收到 向伺服器端確認一個ACK K+1

從圖中可以看出,當客戶端呼叫connect時,觸發了連線請求,向伺服器傳送了SYN J包,這時connect進入阻塞狀態;伺服器監聽到連線請求,即收到SYN J包,呼叫accept函式接收請求向客戶端傳送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到伺服器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;伺服器收到ACK K+1時,accept返回,至此三次握手完畢,連線建立。

總結:客戶端的connect在三次握手的第二個次返回,而伺服器端的accept在三次握手的第三次返回。

四次放手圖

image

· 

某個應用程序首先呼叫close主動關閉連線,這時TCP傳送一個FIN M;

另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作為檔案結束符傳遞給應用程序,因為FIN的接收意味著應用程序在相應的連線上再也接收不到額外資料;

一段時間之後,接收到檔案結束符的應用程序呼叫close關閉它的socket。這導致它的TCP也傳送一個FIN N;

接收到這個FIN的源傳送端TCP對它進行確認。

這些是C#Socket通訊的基礎知識,下一篇我們講具體實現的程式碼