1. 網絡中進程之間如何通訊html
進 程通訊的概念最初來源於單機系統。因爲每一個進程都在本身的地址範圍內運行,爲保證兩個相互通訊的進linux
程之間既互不干擾又協調一致工做,操做系統爲進程通訊提供了相應設施,如c++
UNIX BSD有:管道(pipe)、命名管道(named pipe)軟中斷信號(signal)編程
UNIX system V有:消息(message)、共享存儲區(shared memory)和信號量(semaphore)等.ubuntu
他們都僅限於用在本機進程之間通訊。網間進程通訊要解決的是不一樣主機進程間的相互通訊問題(可把同機進程通訊看做是其中的特例)。爲此,首先要解決的是網間進程標識問題。同一主機上,不一樣進程可用進程號(process ID)惟一標識。但在網絡環境下,各主機獨立分配的進程號不能惟一標識該進程。例如,主機A賦於某進程號5,在B機中也能夠存在5號進程,所以,「5號進程」這句話就沒有意義了。 其次,操做系統支持的網絡協議衆多,不一樣協議的工做方式不一樣,地址格式也不一樣。所以,網間進程通訊還要解決多重協議的識別問題。 設計模式
其實TCP/IP協議族已經幫咱們解決了這個問題,網絡層的「ip地址」能夠惟一標識網絡中的主機,而傳輸層的「協議+端口」能夠惟一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就能夠標識網絡的進程了,網絡中的進程通訊就能夠利用這個標誌與其它進程進行交互。數組
使用TCP/IP協議的應用程序一般採用應用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通訊。就目前而言,幾乎全部的應用程序都是採用socket,而如今又是網絡時代,網絡中進程通訊是無處不在,這就是我爲何說「一切皆socket」。服務器
2. 什麼是TCP/IP、UDP網絡
TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是爲廣域網(WANs)設計的。 數據結構
TCP/IP協議存在於OS中,網絡服務經過OS提供,在OS中增長支持TCP/IP的系統調用——Berkeley套接字,如Socket,Connect,Send,Recv等
UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。如圖:
TCP/IP協議族包括運輸層、網絡層、鏈路層,而socket所在位置如圖,Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層。
3. Socket是什麼
socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,均可以用「打開open –> 讀寫write/read –> 關閉close」模式來操做。Socket就是該模式的一個實現, socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉).
說白了Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
注意:其實socket也沒有層的概念,它只是一個facade設計模式的應用,讓編程變的更簡單。是一個軟件抽象層。在網絡編程中,咱們大量用的都是經過socket實現的。
其實就是一個整數,咱們最熟悉的句柄是0、一、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、一、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、stderr
套接字API最初是做爲UNIX操做系統的一部分而開發的,因此套接字API與系統的其餘I/O設備集成在一塊兒。特別是,當應用程序要爲因特網通訊而建立一個套接字(socket)時,操做系統就返回一個小整數做爲描述符(descriptor)來標識這個套接字。而後,應用程序以該描述符做爲傳遞參數,經過調用函數來完成某種操做(例如經過網絡傳送數據或接收輸入的數據)。
在許多操做系統中,套接字描述符和其餘I/O描述符是集成在一塊兒的,因此應用程序能夠對文件進行套接字I/O或I/O讀/寫操做。
當應用程序要建立一個套接字時,操做系統就返回一個小整數做爲描述符,應用程序則使用這個描述符來引用該套接字須要I/O請求的應用程序請求操做系統打開一個文件。操做系統就建立一個文件描述符提供給應用程序訪問文件。從應用程序的角度看,文件描述符是一個整數,應用程序能夠用它來讀寫文件。下圖顯示,操做系統如何把文件描述符實現爲一個指針數組,這些指針指向內部數據結構。
對於每一個程序系統都有一張單獨的表。精確地講,系統爲每一個運行的進程維護一張單獨的文件描述符表。當進程打開一個文件時,系統把一個指向此文件內部數據結構的指針寫入文件描述符表,並把該表的索引值返回給調用者 。應用程序只需記住這個描述符,並在之後操做該文件時使用它。操做系統把該描述符做爲索引訪問進程描述符表,經過指針找到保存該文件全部的信息的數據結構。
針對套接字的系統數據結構:
1)、套接字API裏有個函數socket,它就是用來建立一個套接字。套接字設計的整體思路是,單個系統調用就能夠建立任何套接字,由於套接字是至關籠統的。一旦套接字建立後,應用程序還須要調用其餘函數來指定具體細節。例如調用socket將建立一個新的描述符條目:
2)、雖然套接字的內部數據結構包含不少字段,可是系統建立套接字後,大多數字字段沒有填寫。應用程序建立套接字後在該套接字可使用以前,必須調用其餘的過程來填充這些字段。
文件描述符:在linux系統中打開文件就會得到文件描述符,它是個很小的正整數。每一個進程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是這個表的索引,每一個表項都有一個指向已打開文件的指針。
文件指針:C語言中使用文件指針作爲I/O的句柄。文件指針指向進程用戶區中的一個被稱爲FILE結構的數據結構。FILE結構包括一個緩衝區和一個文件描述符。而文件描述符是文件描述符表的一個索引,所以從某種意義上說文件指針就是句柄的句柄(在Windows系統上,文件描述符被稱做文件句柄)。
詳細內容請看 linux文件系統: http://blog.csdn.net/hguisu/article/details/6122513#t7
4. 基本的SOCKET接口函數
服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。客戶端發送數據請求,服務器端接收請求並處理請求,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。
這些接口的實現都是內核來完成。具體如何實現,能夠看看linux的內核
int socket(int protofamily, int type, int protocol);//返回sockfd
sockfd是描述符。
socket函數對應於普通文件的打開操做。普通文件的打開操做返回一個文件描述字,而socket()用於建立一個socket描述符(socket descriptor),它惟一標識一個socket。這個socket描述字跟文件描述字同樣,後續的操做都有用到它,把它做爲參數,經過它來進行一些讀寫操做。
正如能夠給fopen的傳入不一樣參數值,以打開不一樣的文件。建立socket的時候,也能夠指定不一樣的參數建立不一樣的socket描述符,socket函數的三個參數分別爲:
注意:並非上面的type和protocol能夠隨意組合的,如SOCK_STREAM不能夠跟IPPROTO_UDP組合。當protocol爲0時,會自動選擇type類型對應的默認協議。
當咱們調用socket建立一個socket時,返回的socket描述字它存在於協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。若是想要給它賦值一個地址,就必須調用bind()函數,不然就當調用connect()、listen()時系統會自動隨機分配一個端口。
正如上面所說bind()函數把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數的三個參數分別爲:
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 */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
一般服務器在啓動的時候都會綁定一個衆所周知的地址(如ip地址+端口號),用於提供服務,客戶就能夠經過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是爲何一般服務器端在listen以前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。
網絡字節序與主機字節序
主機字節序就是咱們日常說的大端和小端模式:不一樣的CPU有不一樣的字節序類型,這些字節序是指整數在內存中保存的順序,這個叫作主機序。引用標準的Big-Endian和Little-Endian的定義以下:
a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
網絡字節序:4個字節的32 bit值如下面的次序傳輸:首先是0~7bit,其次8~15bit,而後16~23bit,最後是24~31bit。這種傳輸次序稱做大端字節序。因爲TCP/IP首部中全部的二進制整數在網絡中傳輸時都要求以這種次序,所以它又稱做網絡字節序。字節序,顧名思義字節的順序,就是大於一個字節類型的數據在內存中的存放順序,一個字節的數據沒有順序的問題了。
因此:在將一個地址綁定到socket的時候,請先將主機字節序轉換成爲網絡字節序,而不要假定主機字節序跟網絡字節序同樣使用的是Big-Endian。因爲這個問題曾引起過血案!公司項目代碼中因爲存在這個問題,致使了不少莫名其妙的問題,因此請謹記對主機字節序不要作任何假定,務必將其轉化爲網絡字節序再賦給socket。
若是做爲一個服務器,在調用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服務器的鏈接。
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); //返回鏈接connect_fd
若是accept成功返回,則服務器與客戶已經正確創建鏈接了,此時服務器經過accept返回的套接字來完成與客戶的通訊。
注意:
accept默認會阻塞進程,直到有一個客戶鏈接創建後返回,它返回的是一個新可用的套接字,這個套接字是鏈接套接字。
此時咱們須要區分兩種套接字,
監聽套接字: 監聽套接字正如accept的參數sockfd,它是監聽套接字,在調用listen函數以後,是服務器開始調用socket()函數生成的,稱爲監聽socket描述字(監聽套接字)
鏈接套接字:一個套接字會從主動鏈接的套接字變身爲一個監聽套接字;而accept函數返回的是已鏈接socket描述字(一個鏈接套接字),它表明着一個網絡已經存在的點點鏈接。
一個服務器一般一般僅僅只建立一個監聽socket描述字,它在該服務器的生命週期內一直存在。內核爲每一個由服務器進程接受的客戶鏈接建立了一個已鏈接socket描述字,當服務器完成了對某個客戶的服務,相應的已鏈接socket描述字就被關閉。
天然要問的是:爲何要有兩種套接字?緣由很簡單,若是使用一個描述字的話,那麼它的功能太多,使得使用很不直觀,同時在內核確實產生了一個這樣的新的描述字。
鏈接套接字socketfd_new 並無佔用新的端口與客戶端通訊,依然使用的是與監聽套接字socketfd同樣的端口號
萬事具有隻欠東風,至此服務器與客戶已經創建好鏈接了。能夠調用網絡I/O進行讀寫操做了,即實現了網咯中不一樣進程之間的通訊!網絡I/O操做有下面幾組:
我推薦使用recvmsg()/sendmsg()函數,這兩個函數是最通用的I/O函數,實際上能夠把上面的其它函數都替換成這兩個函數。它們的聲明以下:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read函數是負責從fd中讀取內容.當讀成功時,read返回實際所讀的字節數,若是返回的值是0表示已經讀到文件的結束了,小於0表示出現了錯誤。若是錯誤爲EINTR說明讀是由中斷引發的,若是是ECONNREST表示網絡鏈接出了問題。
write函數將buf中的nbytes字節內容寫入文件描述符fd.成功時返回寫的字節數。失敗時返回-1,並設置errno變量。 在網絡程序中,當咱們向套接字文件描述符寫時有倆種可能。1)write的返回值大於0,表示寫了部分或者是所有的數據。2)返回的值小於0,此時出現了錯誤。咱們要根據錯誤類型來處理。若是錯誤爲EINTR表示在寫的時候出現了中斷錯誤。若是爲EPIPE表示網絡鏈接出現了問題(對方已經關閉了鏈接)。
其它的我就不一一介紹這幾對I/O函數了,具體參見man文檔或者baidu、Google,下面的例子中將使用到send/recv。
在服務器與客戶端創建鏈接以後,會進行一些讀寫操做,完成了讀寫操做就要關閉相應的socket描述字,比如操做完打開的文件要調用fclose關閉打開的文件。
#include <unistd.h> int close(int fd);
close一個TCP socket的缺省行爲時把該socket標記爲以關閉,而後當即返回到調用進程。該描述字不能再由調用進程使用,也就是說不能再做爲read或write的第一個參數。
注意:close操做只是使相應socket描述字的引用計數-1,只有當引用計數爲0的時候,纔會觸發TCP客戶端向服務器發送終止鏈接請求。
5. Socket中TCP的創建(三次握手)
第一次握手:創建鏈接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
一個完整的三次握手也就是: 請求---應答---再次確認。
從圖中能夠看出,當客戶端調用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返回,至此三次握手完畢,鏈接創建。
6. TCP鏈接的終止(四次握手釋放)
因爲TCP鏈接是全雙工的,所以每一個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的鏈接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP鏈接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另外一方執行被動關閉。
(1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送(報文段4)。
(2)服務器B收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1(報文段5)。和SYN同樣,一個FIN將佔用一個序號。
(3)服務器B關閉與客戶端A的鏈接,發送一個FIN給客戶端A(報文段6)。
(4)客戶端A發回ACK報文確認,並將確認序號設置爲收到序號加1(報文段7)。
對應函數接口如圖:
過程以下:
這樣每一個方向上都有一個FIN和ACK。
1.爲何創建鏈接協議是三次握手,而關閉鏈接倒是四次握手呢?
這是由於服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它能夠把ACK和SYN(ACK起應答做用,而SYN起同步做用)放在一個報文裏來發送。但關閉鏈接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你全部的數據都所有發送給對方了,因此你能夠未必會立刻會關閉SOCKET,也即你可能還須要發送一些數據給對方以後,再發送FIN報文給對方來表示你贊成如今能夠關閉鏈接了,因此它這裏的ACK報文和FIN報文多數狀況下都是分開發送的。
2.爲何TIME_WAIT狀態還須要等2MSL後才能返回到CLOSED狀態?
這是由於雖然雙方都贊成關閉鏈接了,並且握手的4個報文也都協調和發送完畢,按理能夠直接回到CLOSED狀態(就比如從SYN_SEND狀態到ESTABLISH狀態那樣);可是由於咱們必需要假想網絡是不可靠的,你沒法保證你最後發送的ACK報文會必定被對方收到,所以對方處於LAST_ACK狀態下的SOCKET可能會由於超時未收到ACK報文,而重發FIN報文,因此這個TIME_WAIT狀態的做用就是用來重發可能丟失的ACK報文。
7. Socket編程實例
服務器端:一直監聽本機的8000號端口,若是收到鏈接請求,將接收請求並接收客戶端發來的消息,並向客戶端返回消息。
1 /* File Name: server.c */ 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include<string.h> 5 #include<errno.h> 6 #include<sys/types.h> 7 #include<sys/socket.h> 8 #include<netinet/in.h> 9 #define DEFAULT_PORT 8000 10 #define MAXLINE 4096 11 int main(int argc, char** argv) 12 { 13 int socket_fd, connect_fd; 14 struct sockaddr_in servaddr; 15 char buff[4096]; 16 int n; 17 //初始化Socket 18 if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ 19 printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); 20 exit(0); 21 } 22 //初始化 23 memset(&servaddr, 0, sizeof(servaddr)); 24 servaddr.sin_family = AF_INET; 25 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址設置成INADDR_ANY,讓系統自動獲取本機的IP地址。 26 servaddr.sin_port = htons(DEFAULT_PORT);//設置的端口爲DEFAULT_PORT 27 28 //將本地地址綁定到所建立的套接字上 29 if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ 30 printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); 31 exit(0); 32 } 33 //開始監聽是否有客戶端鏈接 34 if( listen(socket_fd, 10) == -1){ 35 printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno); 36 exit(0); 37 } 38 printf("======waiting for client's request======\n"); 39 while(1){ 40 //阻塞直到有客戶端鏈接,否則多浪費CPU資源。 41 if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){ 42 printf("accept socket error: %s(errno: %d)",strerror(errno),errno); 43 continue; 44 } 45 //接受客戶端傳過來的數據 46 n = recv(connect_fd, buff, MAXLINE, 0); 47 //向客戶端發送迴應數據 48 if(!fork()){ /*紫禁城*/ 49 if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1) 50 perror("send error"); 51 close(connect_fd); 52 exit(0); 53 } 54 buff[n] = '\0'; 55 printf("recv msg from client: %s\n", buff); 56 close(connect_fd); 57 } 58 close(socket_fd); 59 }
客戶端:
1 /* File Name: client.c */ 2 3 #include<stdio.h> 4 #include<stdlib.h> 5 #include<string.h> 6 #include<errno.h> 7 #include<sys/types.h> 8 #include<sys/socket.h> 9 #include<netinet/in.h> 10 11 #define MAXLINE 4096 12 13 14 int main(int argc, char** argv) 15 { 16 int sockfd, n,rec_len; 17 char recvline[4096], sendline[4096]; 18 char buf[MAXLINE]; 19 struct sockaddr_in servaddr; 20 21 22 if( argc != 2){ 23 printf("usage: ./client <ipaddress>\n"); 24 exit(0); 25 } 26 27 28 if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ 29 printf("create socket error: %s(errno: %d)\n", strerror(errno),errno); 30 exit(0); 31 } 32 33 34 memset(&servaddr, 0, sizeof(servaddr)); 35 servaddr.sin_family = AF_INET; 36 servaddr.sin_port = htons(8000); 37 if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ 38 printf("inet_pton error for %s\n",argv[1]); 39 exit(0); 40 } 41 42 43 if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){ 44 printf("connect error: %s(errno: %d)\n",strerror(errno),errno); 45 exit(0); 46 } 47 48 49 printf("send msg to server: \n"); 50 fgets(sendline, 4096, stdin); 51 if( send(sockfd, sendline, strlen(sendline), 0) < 0) 52 { 53 printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); 54 exit(0); 55 } 56 if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) { 57 perror("recv error"); 58 exit(1); 59 } 60 buf[rec_len] = '\0'; 61 printf("Received : %s ",buf); 62 close(sockfd); 63 exit(0); 64 }
inet_pton 是Linux下IP地址轉換函數,能夠在將IP地址在「點分十進制」和「整數」之間轉換 ,是inet_addr的擴展。
第一個參數af是地址族,轉換後存在dst中
af = AF_INET:src爲指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函數將該地址轉換爲in_addr的結構體,並複製在*dst中
af =AF_INET6:src爲指向IPV6的地址,函數將該地址轉換爲in6_addr的結構體,並複製在*dst中
若是函數出錯將返回一個負值,並將errno設置爲EAFNOSUPPORT,若是參數af指定的地址族和src格式不對,函數將返回0。
測試:
編譯server.c
gcc -o server server.c
啓動進程:
./server
顯示結果:
======waiting for client's request======
並等待客戶端鏈接。
編譯 client.c
gcc -o client server.c
客戶端去鏈接server:
./client 127.0.0.1
等待輸入消息
發送一條消息,輸入:c++
此時服務器端看到:
客戶端收到消息:
其實能夠不用client,可使用telnet來測試:
telnet 127.0.0.1 8000
注意:
在ubuntu 編譯源代碼的時候,頭文件types.h可能找不到。
使用dpkg -L libc6-dev | grep types.h 查看。
若是沒有,可使用
apt-get install libc6-dev安裝。
若是有了,但不在/usr/include/sys/目錄下,手動把這個文件添加到這個目錄下就能夠了。
(部份內容來自吳秦:http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html)