1. LINUX網絡編程基礎知識 1java
1.1. TCP/IP協議概述 1linux
1.2. OSI參考模型及TCP/IP參考模型 1算法
1.3. TCP協議 3編程
1.4. UDP協議 5緩存
1.5. 協議的選擇 6安全
2. 網絡相關概念 6服務器
2.1. socket概念 7網絡
2.2. socket類型 8數據結構
2.3. socket信息數據結構 8多線程
2.4. 數據存儲優先順序的轉換 8
2.5. 地址格式轉化 9
2.6. 名字地址轉化 10
3. socket編程 13
3.1. 使用TCP協議的流程圖 13
3.2. 使用UDP協議的流程圖 24
3.3. 設置套接口的選項setsockopt的用法 31
3.4. 單播、廣播、組播(多播) 32
協議protocol:通訊雙方必須遵循的規矩 由iso規定 rpc文檔
osi參考模型:(應-表-會-傳-網-數-物)
è 應用層 表示層 會話層 傳輸層 網絡層 數據鏈路層 物理層
tcp/ip模型4層:
應用層{http超文本傳輸協議 ftp文件傳輸協議 telnet遠程登陸 ssh安全外殼協議 stmp簡單郵件發送 pop3收郵件}
傳輸層{tcp傳輸控制協議,udp用戶數據包協議}
網絡層{ip網際互聯協議 icmp網絡控制消息協議 igmp網絡組管理協議}
網絡接口層{arp地址轉換協議,rarp反向地址轉換協議,mpls多協議標籤交換}
TCP協議:傳輸控制協議 面向鏈接的協議 能保證傳輸安全可靠 速度慢(有3次握手)
UDP協議:用戶數據包協議 非面向鏈接 速度快 不可靠
一般是ip地址後面跟上端口號:ip用來定位主機 port區別應用(進程)
http的端口號80 ssh-->22 telnet-->23 ftp-->21 用戶本身定義的一般要大於1024
TCP/IP協議族的每一層的做用:
·網絡接口層:負責將二進制流轉換爲數據幀,並進行數據幀的發送和接收。要注意的是數據幀是獨立的網絡信息傳輸單元。
·網絡層:負責將數據幀封裝成IP數據報,並運行必要的路由算法。
·傳輸層:負責端對端之間的通訊會話鏈接和創建。傳輸協議的選擇根據數據傳輸方式而定。
·應用層:負責應用程序的網絡訪問,這裏經過端口號來識別各個不一樣的進程。
TCP/IP協議族的每一層協議的相關注解:
·ARP:(地址轉換協議)用於得到同一物理網絡中的硬件主機地址。
·MPLS:(多協議標籤交換)頗有發展前景的下一代網絡協議。
·IP:(網際互聯協議)負責在主機和網絡之間尋址和路由數據包。
·ICMP:(網絡控制消息協議)用於發送報告有關數據包的傳送錯誤的協議。
·IGMP:(網絡組管理協議)被IP主機用來向本地多路廣播路由器報告主機組成員的協議。
·TCP:(傳輸控制協議)爲應用程序提供可靠的通訊鏈接。適合於一次傳輸大批數據的狀況。並適用於要求獲得相應的應用程序。
·UDP:(用戶數據包協議)提供了無鏈接通訊,且不對傳送包進行可靠的保證。適合於一次傳輸少許數據。
1.3. TCP協議
(1) 概述
TCP是TCP/IP體系中面向鏈接的運輸層協議,它提供全雙工和可靠交付的服務。它採用許多機制來確保端到端結點之間的可靠數據傳輸,如採用序列號、確認重傳、滑動窗口等。
首先,TCP要爲所發送的每個報文段加上序列號,保證每個報文段能被接收方接收,並只被正確的接收一次。
其次,TCP採用具備重傳功能的積極確認技術做爲可靠數據流傳輸服務的基礎。這裏「確認」是指接收端在正確收到報文段以後向發送端回送一個確認(ACK)信息。發送方將每一個已發送的報文段備份在本身的緩衝區裏,並且在收到相應的確認以前是不會丟棄所保存的報文段的。「積極」是指發送發在每個報文段發送完畢的同時啓動一個定時器,加入定時器的定時期滿而關於報文段的確認信息尚未達到,則發送發認爲該報文段已經丟失並主動重發。爲了不因爲網絡延時引發遲到的確認和重複的確認,TCP規定在確認信息中捎帶一個報文段的序號,使接收方能正確的將報文段與確認聯繫起來。
最後,採用可變長的滑動窗口協議進行流量控制,以防止因爲發送端與接收端之間的不匹配而引發的數據丟失。這裏所採用的滑動窗口協議與數據鏈路層的滑動窗口協議在工做原理上徹底相同,惟一的區別在於滑動窗口協議用於傳輸層是爲了在端對端節點之間實現流量控制,而用於數據鏈路層是爲了在相鄰節點之間實現流量控制。TCP採用可變長的滑動窗口,使得發送端與接收端可根據本身的CPU和數據緩存資源對數據發送和接收能力來進行動態調整,從而靈活性更強,也更合理。
(2) 三次握手協議
在利用TCP實現源主機和目的主機通訊時,目的主機必須贊成,不然TCP鏈接沒法創建。爲了確保TCP鏈接的成功創建,TCP採用了一種稱爲三次握手的方式,三次握手方式使得「序號/確認號」系統可以正常工做,從而使它們的序號達成同步。若是三次握手成功,則鏈接創建成功,能夠開始傳送數據信息。
其三次握手分別爲:
1)源主機A的TCP向主機B發送鏈接請求報文段,其首部中的SYN(同步)標誌位應置爲1,表示想跟目標主機B創建鏈接,進行通訊,併發送一個同步序列號X(例:SEQ=100)進行同步,代表在後面傳送數據時的第一個數據字節的序號爲X+1(即101)。
2)目標主機B的TCP收到鏈接請求報文段後,如贊成,則發回確認。再確認報中應將ACK位和SYN位置爲1.確認號爲X+1,同時也爲本身選擇一個序號Y。
3)源主機A的TCP收到目標主機B的確認後要想目標主機B給出確認。其ACK置爲1,確認號爲Y+1,而本身的序號爲X+1。TCP的標準規定,SYN置1的報文段要消耗掉一個序號。
運行客戶進程的源主機A的TCP通知上層應用進程,鏈接已經創建。當源主機A向目標主機B發送第一個數據報文段時,其序號仍爲X+1,由於前一個確認報文段並不消耗序號。
當運行服務進程的目標主機B的TCP收到源主機A的確認後,也通知其上層應用進程,鏈接已經創建。至此創建了一個全雙工的鏈接。
三次握手:爲應用程序提供可靠的通訊鏈接。適合於一次傳輸大批數據的狀況。並適用於要求獲得響應的應用程序。
(3) TCP數據報頭
TCP頭信息
·源端口、目的端口:16位長。標識出遠端和本地的端口號。
·序號:32位長。標識發送的數據報的順序。
·確認號:32位長。但願收到的下一個數據報的序列號。
·TCP頭長:4位長。代表TCP頭中包含多少個32位字。
·6位未用。
·ACK:ACK位置1代表確認號是合法的。若是ACK爲0,那麼數據報不包含確認信息,確認字段被省略。
·PSH:表示是帶有PUSH標誌的數據。接收方所以請求數據報一到即可送往應用程序而沒必要等到緩衝區裝滿時才發送。
·RST:用於復位因爲主機崩潰或其餘緣由而出現的錯誤的鏈接。還能夠用於拒絕非法的數據報或拒絕鏈接請求。
·SYN:用於創建鏈接。
·FIN:用於釋放鏈接。
·窗口大小:16位長。窗口大小字段表示在確認了字節以後還能夠發送多少個字節。
·校驗和:16位長。是爲了確保高可靠性而設置的。它校驗頭部、數據和僞TCP頭部之和。
·可選項:0個或多個32位字。包括最大TCP載荷,窗口比例、選擇重複數據報等選項。
(1) 概述
UDP即用戶數據報協議,它是一種無鏈接協議,所以不須要像TCP那樣經過三次握手來創建一個鏈接。同時,一個UDP應用可同時做爲應用的客戶或服務器方。因爲UDP協議並不須要創建一個明確的鏈接,所以創建UDP應用要比創建TCP應用簡單得多。
它比TCP協議更爲高效,也能更好地解決實時性的問題。現在,包括網絡視頻會議系統在內的衆多的客戶/服務器模式的網絡應用都使用UDP協議。
(2) Udp數據包頭格式
1.5. 協議的選擇
(1)對數據可靠性的要求
對數據要求高可靠性的應用需選擇TCP協議,如驗證、密碼字段的傳送都是不容許出錯的,而對數據的可靠性要求不那麼高的應用可選擇UDP傳送。
(2)應用的實時性
TCP協議在傳送過程當中要使用三次握手、重傳確認等手段來保證數據傳輸的可靠性。使用TCP協議會有較大的時延,所以不適合對實時性要求較高的應用,如VOIP、視頻監控等。相反,UDP協議則在這些應用中能發揮很好的做用。
(3)網絡的可靠性
因爲TCP協議的提出主要是解決網絡的可靠性問題,它經過各類機制來減小錯誤發生的機率。所以,在網絡情況不是很好的狀況下需選用TCP協議(如在廣域網等狀況),可是若在網絡情況很好的狀況下(如局域網等)就不須要再採用TCP協議,而建議選擇UDP協議來減小網絡負荷。
1)套接口的概念:
套接口,也叫「套接字」。是操做系統內核中的一個數據結構,它是網絡中的節點進行相互通訊的門戶。它是網絡進程的ID。網絡通訊,歸根到底仍是進程間的通訊(不一樣計算機上的進程間通訊)。在網絡中,每個節點(計算機或路由)都有一個網絡地址,也就是IP地址。兩個進程通訊時,首先要肯定各自所在的網絡節點的網絡地址。可是,網絡地址只能肯定進程所在的計算機,而一臺計算機上極可能同時運行着多個進程,因此僅憑網絡地址還不能肯定究竟是和網絡中的哪個進程進行通訊,所以套接口中還須要包括其餘的信息,也就是端口號(PORT)。在一臺計算機中,一個端口號一次只能分配給一個進程,也就是說,在一臺計算機中,端口號和進程之間是一一對應關係。因此,使用端口號和網絡地址的組合能夠惟一的肯定整個網絡中的一個網絡進程。
例如,如網絡中某一臺計算機的IP爲10.92.20.160,操做系統分配給計算機中某一應用程序進程的端口號爲1500,則此時 10.92.20.160 1500就構成了一個套接口。
2)端口號的概念:
在網絡技術中,端口大體有兩種意思:一是物理意義上的端口,如集線器、交換機、路由器等用於鏈接其餘網絡設備的接口。二是指TCP/IP協議中的端口,端口號的範圍從0~65535,一類是由互聯網指派名字和號碼公司ICANN負責分配給一些經常使用的應用程序固定使用的「周知的端口」,其值通常爲0~1023.例如http的端口號是80,ftp爲21,ssh爲22,telnet爲23等。還有一類是用戶本身定義的,一般是大於1024的整型值。
3)ip地址的表示:
一般用戶在表達IP地址時採用的是點分十進制表示的數值(或者是爲冒號分開的十進制Ipv6地址),而在一般使用的socket編程中使用的則是二進制值,這就須要將這兩個數值進行轉換。
ipv4地址:32bit, 4字節,一般採用點分十進制記法。
例如對於:10000000 00001011 00000011 00011111
點分十進制表示爲:128.11.3.31
ip地址的分類:
特殊的ip地址:
Linux中的網絡編程是經過socket接口來進行的。socket是一種特殊的I/O接口,它也是一種文件描述符。它是一種經常使用的進程之間通訊機制,經過它不只能實現本地機器上的進程之間的通訊,並且經過網絡可以在不一樣機器上的進程之間進行通訊。
每個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示。socket也有一個相似於打開文件的函數調用,該函數返回一個整型的socket描述符,隨後的鏈接創建、數據傳輸等操做都是經過socket來實現的;
(1)流式socket(SOCK_STREAM) à用於TCP通訊
流式套接字提供可靠的、面向鏈接的通訊流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性。
(2)數據報socket(SOCK_DGRAM) à用於UDP通訊
數據報套接字定義了一種無鏈接的服務,數據經過相互獨立的報文進行傳輸,是無序的,而且不保證是可靠、無差錯的。它使用數據報協議UDP。
(3)原始socket (SOCK_RAW) à用於新的網絡協議實現的測試等
原始套接字容許對底層協議如IP或ICMP進行直接訪問,它功能強大但使用較爲不便,主要用於一些協議的開發。
struct sockaddr
{
unsigned short sa_family; /*地址族*/
char sa_data[14]; /*14字節的協議地址,包含該socket的IP地址和端口號。*/
};
struct sockaddr_in
{
short int sa_family; /*地址族*/
unsigned short int sin_port; /*端口號*/
struct in_addr sin_addr; /*IP地址*/
unsigned char sin_zero[8]; /*填充0 以保持與struct sockaddr一樣大小*/
};
struct in_addr
{
unsigned long int s_addr; /* 32位IPv4地址,網絡字節序 */
};
頭文件<netinet/in.h>
sa_family:AF_INET àIPv4協議 AF_INET6 àIPv6協議
計算機數據存儲有兩種字節優先順序:高位字節優先(稱爲大端模式)和低位字節優先(稱爲小端模式)。內存的低地址存儲數據的低字節,高地址存儲數據的高字節的方式叫小端模式。內存的高地址存儲數據的低字節,低地址存儲數據高字節的方式稱爲大端模式。
eg:對於內存中存放的數0x12345678來講
若是是採用大端模式存放的,則其真實的數是:0x12345678
若是是採用小端模式存放的,則其真實的數是:0x78563412
若是稱某個系統所採用的字節序爲主機字節序,則它多是小端模式的,也多是大端模式的。而端口號和IP地址都是以網絡字節序存儲的,不是主機字節序,網絡字節序都是大端模式。要把主機字節序和網絡字節序相互對應起來,須要對這兩個字節存儲優先順序進行相互轉化。這裏用到四個函數:htons(),ntohs(),htonl()和ntohl().這四個地址分別實現網絡字節序和主機字節序的轉化,這裏的h表明host,n表明network,s表明short,l表明long。一般16位的IP端口號用s表明,而IP地址用l來表明。
函數原型以下:
一般用戶在表達地址時採用的是點分十進制表示的數值(或者是爲冒號分開的十進制Ipv6地址),而在一般使用的socket編程中使用的則是32位的網絡字節序的二進制值,這就須要將這兩個數值進行轉換。這裏在Ipv4中用到的函數有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函數有inet_pton()和inet_ntop()。
IPv4的函數原型:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *straddr, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
in_addr_t inet_addr(const char *straddr);
函數inet_aton():將點分十進制數的IP地址轉換成爲網絡字節序的32位二進制數值。返回值:成功,則返回1,不成功返回0.
參數straddr:存放輸入的點分十進制數IP地址字符串。
參數addrptr:傳出參數,保存網絡字節序的32位二進制數值。
函數inet_ntoa():將網絡字節序的32位二進制數值轉換爲點分十進制的IP地址。
函數inet_addr():功能與inet_aton相同,可是結果傳遞的方式不一樣。inet_addr()若成功則返回32位二進制的網絡字節序地址。
IPv4和IPv6的函數原型:
#include <arpa/inet.h>
int inet_pton(int family, const char *src, void *dst);
const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);
函數inet_pton跟inet_aton實現的功能相似,只是多了family參數,該參數指定爲AF_INET,表示是IPv4協議,若是是AF_INET6,表示IPv6協議。
函數inet_ntop跟inet_ntoa相似,其中len表示表示轉換以後的長度(字符串的長度)。
Example:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
char ip[] = "192.168.0.101";
struct in_addr myaddr;
/* inet_aton */
int iRet = inet_aton(ip, &myaddr);
printf("%x\n", myaddr.s_addr);
/* inet_addr */
printf("%x\n", inet_addr(ip));
/* inet_pton */
iRet = inet_pton(AF_INET, ip, &myaddr);
printf("%x\n", myaddr.s_addr);
myaddr.s_addr = 0xac100ac4;
/* inet_ntoa */
printf("%s\n", inet_ntoa(myaddr));
/* inet_ntop */
inet_ntop(AF_INET, &myaddr, ip, 16);
puts(ip);
return 0;
}
一般,人們在使用過程當中都不肯意記憶冗長的IP地址,尤爲到Ipv6時,地址長度多達128位,那時就更加不可能一次性記憶那麼長的IP地址了。所以,使用主機名或域名將會是很好的選擇。主機名與域名的區別:主機名一般在局域網裏面使用,經過/etc/hosts文件,主機名能夠解析到對應的ip;域名一般是再internet上使用。
衆所周知,百度的域名爲:www.baidu.com,而這個域名其實對應了一個百度公司的IP地址,那麼百度公司的IP地址是多少呢?咱們能夠利用ping www.baidu.com來獲得百度公司的ip地址,如圖。那麼,系統是如何將www.baidu.com 這個域名轉化爲IP地址220.181.111.148的呢?
在linux中,有一些函數能夠實現主機名和地址的轉化,最多見的有gethostbyname()、gethostbyaddr()等,它們均可以實現IPv4和IPv6的地址和主機名之間的轉化。其中gethostbyname()是將主機名轉化爲IP地址,gethostbyaddr()則是逆操做,是將IP地址轉化爲主機名。
函數原型:
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
結構體:
struct hostent
{
char *h_name; /*正式主機名*/
char **h_aliases; /*主機別名*/
int h_addrtype; /*主機IP地址類型 IPv4爲AF_INET*/
int h_length; /*主機IP地址字節長度,對於IPv4是4字節,即32位*/
char **h_addr_list; /*主機的IP地址列表*/
}
#define h_addr h_addr_list[0] /*保存的是ip地址*/
函數gethostbyname():用於將域名(www.baidu.com)或主機名轉換爲IP地址。參數hostname指向存放域名或主機名的字符串。
函數gethostbyaddr():用於將IP地址轉換爲域名或主機名。參數addr是一個IP地址,此時這個ip地址不是普通的字符串,而是要經過函數inet_aton()轉換。len爲IP地址的長度,AF_INET爲4。family可用AF_INET:Ipv4或AF_INET6:Ipv6。
Example1:將百度的www.baidu.com 轉換爲ip地址
#include <netdb.h>
#include <sys/socket.h>
#include <stdio.h>
int main(int argc, char **argv)
{
char *ptr, **pptr;
struct hostent *hptr;
char str[32] = {'\0'};
/* 取得命令後第一個參數,即要解析的域名或主機名 */
ptr = argv[1]; //如www.baidu.com
/* 調用gethostbyname()。結果存在hptr結構中 */
if((hptr = gethostbyname(ptr)) == NULL)
{
printf(" gethostbyname error for host:%s\n", ptr);
return 0;
}
/* 將主機的規範名打出來 */
printf("official hostname:%s\n", hptr->h_name);
/* 主機可能有多個別名,將全部別名分別打出來 */
for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
printf(" alias:%s\n", *pptr);
/* 根據地址類型,將地址打出來 */
switch(hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
pptr = hptr->h_addr_list;
/* 將剛纔獲得的全部地址都打出來。其中調用了inet_ntop()函數 */
for(; *pptr!=NULL; pptr++)
printf(" address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
printf(" first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
break;
default:
printf("unknown address type\n");
break;
}
return 0;
}
編譯運行
#gcc test.c
#./a.out www.baidu.com
official hostname:www.a.shifen.com
alias:www.baidu.com
address: 220.181.111.148
……
first address: 220.181.111.148
(注:這裏須要聯網才能訪問www.baidu.com。能夠嘗試用本身的虛擬機的主機名,利用命令hostname能夠查看本身的主機名,用hostname –i能夠查看主機名對應的ip地址。那麼如何修改主機名呢?直接用hostname wangxiao只是暫時有效,重啓以後就沒有了,想要永久有效,須要修改/etc/sysconfig/network將HOSTNAME修改,固然要重啓虛擬機。若是ip地址不對,你能夠修改/etc/hosts這個文件,添加你本身的主機名對應的ip地址)
TCP通訊的基本步驟以下:
服務端:socket---bind---listen---while(1){---accept---recv---send---close---}---close
客戶端:socket----------------------------------connect---send---recv-----------------close
服務器端
頭文件包含:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
2. socket函數:生成一個套接口描述符。
原型:int socket(int domain,int type,int protocol);
參數:domainà{ AF_INET:Ipv4網絡協議 AF_INET6:IPv6網絡協議}
typeà{tcp:SOCK_STREAM udp:SOCK_DGRAM}
protocolà指定socket所使用的傳輸協議編號。一般爲0.
返回值:成功則返回套接口描述符,失敗返回-1。
經常使用實例:int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){perror("socket");exit(-1);}
3. bind函數:用來綁定一個端口號和IP地址,使套接口與指定的端口號和IP地址相關聯。
原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
參數:sockfdà爲前面socket的返回值。
my_addrà爲結構體指針變量
對於不一樣的socket domain定義了一個通用的數據結構 struct sockaddr //此結構體不經常使用 { unsigned short int sa_family; //調用socket()時的domain參數,即AF_INET值。 char sa_data[14]; //最多使用14個字符長度 }; 此sockaddr結構會因使用不一樣的socket domain而有不一樣結構定義, 例如使用AF_INET domain,其socketaddr結構定義便爲 struct sockaddr_in //經常使用的結構體 { unsigned short int sin_family; //即爲sa_family èAF_INET uint16_t sin_port; //爲使用的port編號 struct in_addr sin_addr; //爲IP 地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr { uint32_t s_addr; }; addrlenàsockaddr的結構體長度。一般是計算sizeof(struct sockaddr);
返回值:成功則返回0,失敗返回-1
經常使用實例:struct sockaddr_in my_addr; //定義結構體變量
memset(&my_addr, 0, sizeof(struct sockaddr)); //將結構體清空
//或bzero(&my_addr, sizeof(struct sockaddr));
my_addr.sin_family = AF_INET; //表示採用Ipv4網絡協議
my_addr.sin_port = htons(8888); //表示端口號爲8888,一般是大於1024的一個值。
//htons()用來將參數指定的16位hostshort轉換成網絡字符順序
my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); // inet_addr()用來將IP地址字符串轉換成網絡所使用的二進制數字,若是爲INADDR_ANY,這表示服務器自動填充本機IP地址。
if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1)
{perror("bind");close(sfd);exit(-1);}
(注:經過將my_addr.sin_port置爲0,函數會自動爲你選擇一個未佔用的端口來使用。一樣,經過將my_addr.sin_addr.s_addr置爲INADDR_ANY,系統會自動填入本機IP地址。)
4. listen函數:使服務器的這個端口和IP處於監聽狀態,等待網絡中某一客戶機的鏈接請求。若是客戶端有鏈接請求,端口就會接受這個鏈接。
原型:int listen(int sockfd, int backlog);
參數:sockfdà爲前面socket的返回值.即sfd
backlogà指定同時能處理的最大鏈接要求,一般爲10或者5。 最大值可設至128
返回值:成功則返回0,失敗返回-1
經常使用實例:if(listen(sfd, 10) == -1)
{perror("listen");close(sfd);exit(-1);}
5. accept函數:接受遠程計算機的鏈接請求,創建起與客戶機之間的通訊鏈接。服務器處於監聽狀態時,若是某時刻得到客戶機的鏈接請求,此時並非當即處理這個請求,而是將這個請求放在等待隊列中,當系統空閒時再處理客戶機的鏈接請求。當accept函數接受一個鏈接時,會返回一個新的socket標識符,之後的數據傳輸和讀取就要經過這個新的socket編號來處理,原來參數中的socket也能夠繼續使用,繼續監聽其它客戶機的鏈接請求。(也就是說,相似於移動營業廳,若是有客戶打電話給10086,此時服務器就會請求鏈接,處理一些事務以後,就通知一個話務員接聽客戶的電話,也就是說,後面的全部操做,此時已經於服務器沒有關係,而是話務員跟客戶的交流。對應過來,客戶請求鏈接咱們的服務器,咱們服務器先作了一些綁定和監聽等等操做以後,若是容許鏈接,則調用accept函數產生一個新的套接字,而後用這個新的套接字跟咱們的客戶進行收發數據。也就是說,服務器跟一個客戶端鏈接成功,會有兩個套接字。)
原型:int accept(int s,struct sockaddr * addr,int * addrlen);
參數:sà爲前面socket的返回值.即sfd
addrà爲結構體指針變量,和bind的結構體是同種類型的,系統會把遠程主機的信息(遠程主機的地址和端口號信息)保存到這個指針所指的結構體中。
addrlenà表示結構體的長度,爲整型指針
返回值:成功則返回新的socket處理代碼new_fd,失敗返回-1
經常使用實例:struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(struct sockaddr));
int addrlen = sizeof(struct sockaddr);
int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);
if(new_fd == -1)
{perror("accept");close(sfd);exit(-1);}
printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
6. recv函數:用新的套接字來接收遠端主機傳來的數據,並把數據存到由參數buf 指向的內存空間
原型:int recv(int sockfd,void *buf,int len,unsigned int flags);
參數:sockfdà爲前面accept的返回值.即new_fd,也就是新的套接字。
bufà表示緩衝區
lenà表示緩衝區的長度
flagsà一般爲0
返回值:成功則返回實際接收到的字符數,可能會少於你所指定的接收長度。失敗返回-1
經常使用實例:char buf[512] = {0};
if(recv(new_fd, buf, sizeof(buf), 0) == -1)
{perror("recv");close(new_fd);close(sfd);exit(-1);}
puts(buf);
7. send函數:用新的套接字發送數據給指定的遠端主機
原型:int send(int s,const void * msg,int len,unsigned int flags);
參數:sà爲前面accept的返回值.即new_fd
msgà通常爲常量字符串
lenà表示長度
flagsà一般爲0
返回值:成功則返回實際傳送出去的字符數,可能會少於你所指定的發送長度。失敗返回-1
經常使用實例:if(send(new_fd, "hello", 6, 0) == -1)
{perror("send");close(new_fd);close(sfd);exit(-1);}
8. close函數:當使用完文件後若已再也不須要則可以使用close()關閉該文件,而且close()會讓數據寫回磁盤,並釋放該文件所佔用的資源
原型:int close(int fd);
參數:fdà爲前面的sfd,new_fd
返回值:若文件順利關閉則返回0,發生錯誤時返回-1
經常使用實例:close(new_fd);
close(sfd);
客戶端:
1. connect函數:用來請求鏈接遠程服務器,將參數sockfd 的socket 連至參數serv_addr 指定的服務器IP和端口號上去。
原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
參數:sockfdà爲前面socket的返回值,即sfd
serv_addrà爲結構體指針變量,存儲着遠程服務器的IP與端口號信息。
addrlenà表示結構體變量的長度
返回值:成功則返回0,失敗返回-1
經常使用實例:struct sockaddr_in seraddr;//請求鏈接服務器
memset(&seraddr, 0, sizeof(struct sockaddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888); //服務器的端口號
seraddr.sin_addr.s_addr = inet_addr("192.168.0.101"); //服務器的ip
if(connect(sfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr)) == -1)
{perror("connect");close(sfd);exit(-1);}
將上面的頭文件以及各個函數中的代碼所有拷貝就能夠造成一個完整的例子,此處省略。
還能夠不寫客戶端程序,使用telnet遠程登陸來檢測咱們的服務器端程序。好比咱們的服務器程序在監聽8888端口,咱們能夠用telnet 192.168.0.101 8888來查看服務端的情況。
Example:將一些通用的代碼所有封裝起來,之後要用直接調用函數便可。以下:
通用網絡封裝代碼頭文件: tcp_net_socket.h
#ifndef __TCP__NET__SOCKET__H
#define __TCP__NET__SOCKET__H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
extern int tcp_init(const char* ip,int port);
extern int tcp_accept(int sfd);
extern int tcp_connect(const char* ip,int port);
extern void signalhandler(void);
#endif
具體的通用函數封裝以下: tcp_net_socket.c
#include "tcp_net_socket.h"
int tcp_init(const char* ip, int port) //用於初始化操做
{
int sfd = socket(AF_INET, SOCK_STREAM, 0);//首先建立一個socket,向系統申請
if(sfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr(ip);//或INADDR_ANY
if(bind(sfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)) == -1)
//將新的socket與制定的ip、port綁定
{
perror("bind");
close(sfd);
exit(-1);
}
if(listen(sfd, 10) == -1)//監聽它,並設置其容許最大的鏈接數爲10個
{
perror("listen");
close(sfd);
exit(-1);
}
return sfd;
}
int tcp_accept(int sfd) //用於服務端的接收
{
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(struct sockaddr));
int addrlen = sizeof(struct sockaddr);
int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);
//sfd接受客戶端鏈接,並建立新的socket爲new_fd,將請求鏈接的客戶端的ip、port保存在結構體clientaddr中
if(new_fd == -1)
{
perror("accept");
close(sfd);
exit(-1);
}
printf("%s %d success connect...\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
return new_fd;
}
int tcp_connect(const char* ip, int port) //用於客戶端的鏈接
{
int sfd = socket(AF_INET, SOCK_STREAM, 0);//向系統註冊申請新的socket
if(sfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr(ip);
if(connect(sfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)) == -1)
//將sfd鏈接至制定的服務器網絡地址serveraddr
{
perror("connect");
close(sfd);
exit(-1);
}
return sfd;
}
void signalhandler(void) //用於信號處理,讓服務端在按下Ctrl+c或Ctrl+\的時候不會退出
{
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet,SIGINT);
sigaddset(&sigSet,SIGQUIT);
sigprocmask(SIG_BLOCK,&sigSet,NULL);
}
服務器端: tcp_net_server.c
#include "tcp_net_socket.h"
int main(int argc, char* argv[])
{
if(argc < 3)
{
printf("usage:./servertcp ip port\n");
exit(-1);
}
signalhandler();
int sfd = tcp_init(argv[1], atoi(argv[2])); //或int sfd = tcp_init("192.168.0.164", 8888);
while(1) //用while循環表示能夠與多個客戶端接收和發送,但還是阻塞模式的
{
int cfd = tcp_accept(sfd);
char buf[512] = {0};
if(recv(cfd, buf, sizeof(buf), 0) == -1)//從cfd客戶端接收數據存於buf中
{
perror("recv");
close(cfd);
close(sfd);
exit(-1);
}
puts(buf);
if(send(cfd, "hello world", 12, 0) == -1)//從buf中取向cfd客戶端發送數據
{
perror("send");
close(cfd);
close(sfd);
exit(-1);
}
close(cfd);
}
close(sfd);
}
客戶端: tcp_net_client.c
#include "tcp_net_socket.h"
int main(int argc, char* argv[])
{
if(argc < 3)
{
printf("usage:./clienttcp ip port\n");
exit(-1);
}
int sfd = tcp_connect(argv[1],atoi(argv[2]));
char buf[512] = {0};
send(sfd, "hello", 6, 0); //向sfd服務端發送數據
recv(sfd, buf, sizeof(buf), 0); //從sfd服務端接收數據
puts(buf);
close(sfd);
}
#gcc –o tcp_net_server tcp_net_server.c tcp_net_socket.c
#gcc –o tcp_net_client tcp_net_client.c tcp_net_socket.c
#./tcp_net_server 192.168.0.164 8888
#./tcp_net_client 192.168.0.164 8888
/* 備註
能夠經過將上述常常用到的函數作成動態庫,這樣之後再用到的時候就能夠直接用。步驟以下:
gcc –fpic –c tcp_net_socket.c –o tcp_net_socket.o
gcc –shared tcp_net_socket.o –o libtcp_net_socket.so
cp lib*.so /lib //這樣之後就能夠直接使用該庫了
cp tcp_net_socket.h /usr/include/ //這樣頭文件包含能夠用include <tcp_net_socket.h>了
gcc –o main main.c –ltcp_net_socket //其中main.c要包含頭文件 : include <tcp_net_socket.h>
./main
*/
注:上面的雖然能夠實現多個客戶端訪問,可是仍然是阻塞模式(即一個客戶訪問的時候會阻塞不讓另外的客戶訪問)。解決辦法有:
#include <tcp_net_socket.h>
int main()
{
int sfd = tcp_init("192.168.0.101", 8888);
while(1)
{
int cfd = tcp_accept(sfd);
if(fork() == 0)
{
send(cfd, "hello", 6, 0);
sleep(10);
close(cfd);
}
else
{
close(cfd);
}
}
close(sfd);
return 0;
}
#include <tcp_net_socket.h>
#include <pthread.h>
void* pthfunc(void* arg)
{
int cfd = (int)arg;
send(cfd, "hello", 6, 0);
sleep(10);
close(cfd);
}
int main()
{
int sfd = tcp_init("192.168.0.101", 8888);
pthread_t pthid = 0;
while(1)
{
int cfd = tcp_accept(sfd);
pthread_create(&pthid, NULL, pthfunc, (void*)cfd);
}
close(sfd);
return 0;
}
/* 備註 讀寫大容量的文件時,經過下面的方法效率很高
ssize_t readn(int fd, char *buf, int size)//讀大量內容
{
char *pbuf = buf;
int total ,nread;
for(total = 0; total < size; )
{
nread=read(fd,pbuf,size-total);
if(nread==0)
return total;
if(nread == -1)
{
if(errno == EINTR)
continue;
else
return -1;
}
total += nread;
pbuf += nread;
}
return total;
}
ssize_t writen(int fd, char *buf, int size)//寫大量內容
{
char *pbuf=buf;
int total ,nwrite;
for(total = 0; total < size; )
{
nwrite=write(fd,pbuf,size-total);
if( nwrite <= 0 )
{
if( nwrite == -1 && errno == EINTR )
continue;
else
return -1;
}
total += nwrite;
pbuf += nwrite;
}
return total;
}
*/
#include <unistd.h>
#include <fcntl.h>
……
sockfd = socket(AF_INET,SOCK_STREAM,0);
iflags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd,F_SETFL,O_NONBLOCK | iflags);
……
#include <sys/select.h>
#include "tcp_net_socket.h"
#define MAXCLIENT 10
main()
{
int sfd = tcp_init("192.168.0.164", 8888);
int fd = 0;
char buf[512] = {0};
fd_set rdset;
while(1)
{
FD_ZERO(&rdset);
FD_SET(sfd,&rdset);
if(select(MAXCLIENT + 1, &rdset, NULL, NULL, NULL) < 0)
continue;
for(fd = 0; fd < MAXCLIENT; fd++)
{
if(FD_ISSET(fd,&rdset))
{
if(fd == sfd)
{
int cfd = tcp_accept(sfd);
FD_SET(cfd,&rdset);
//……
}
else
{
bzero(buf, sizeof(buf));
recv(fd, buf, sizeof(buf), 0);
puts(buf);
send(fd, "java", 5, 0);
//FD_CLR(fd, &rdset);
close(fd);
}
}
}
}
close(sfd);
}
具體例子請參考《網絡編程之select.doc》或《tcp_select》
UDP通訊流程圖以下:
服務端:socket---bind---recvfrom---sendto---close
客戶端:socket----------sendto---recvfrom---close
·sendto()函數原型:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen經常被賦值爲sizeof (struct sockaddr)。sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
·recvfrom()函數原型:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一個struct sockaddr類型的變量,該變量保存鏈接機的IP地址及端口號。fromlen常置爲sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。Recvfrom()函數返回接收到的字節數或 當出現錯誤時返回-1,並置相應的errno。
Example:UDP的基本操做
服務器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in saddr;
bzero(&saddr, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = INADDR_ANY;
if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
close(sfd);
exit(-1);
}
char buf[512] = {0};
while(1)
{
struct sockaddr_in fromaddr;
bzero(&fromaddr, sizeof(fromaddr));
int fromaddrlen = sizeof(struct sockaddr);
if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
{
perror("recvfrom");
close(sfd);
exit(-1);
}
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
sendto(sfd, "world", 6, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
}
close(sfd);
}
客戶端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in toaddr;
bzero(&toaddr, sizeof(toaddr));
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(atoi(argv[2])); //此處的端口號要跟服務器同樣
toaddr.sin_addr.s_addr = inet_addr(argv[1]); //此處爲服務器的ip
sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));
char buf[512] = {0};
struct sockaddr_in fromaddr;
bzero(&fromaddr, sizeof(fromaddr));
int fromaddrlen = sizeof(struct sockaddr);
if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
{
perror("recvfrom");
close(sfd);
exit(-1);
}
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
close(sfd);
}
Example:UDP發送文件 先發文件大小 再發文件內容
服務器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in saddr;
bzero(&saddr, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = INADDR_ANY;
if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
close(sfd);
exit(-1);
}
char buf[512] = {0};
struct sockaddr_in fromaddr;
bzero(&fromaddr, sizeof(fromaddr));
int fromaddrlen = sizeof(struct sockaddr);
if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)
{
perror("recvfrom");
close(sfd);
exit(-1);
}
printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);
FILE* fp = fopen("1.txt","rb");
struct stat st; //用於獲取文件內容的大小
stat("1.txt", &st);
int filelen = st.st_size;
sendto(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
while(!feof(fp)) //表示沒有到文件尾
{
int len = fread(buf,1,sizeof(buf),fp);
sendto(sfd, buf, len, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));
}
close(sfd);
}
客戶端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 512
int main(int argc, char* argv[])
{
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd == -1)
{
perror("socket");
exit(-1);
}
struct sockaddr_in toaddr;
bzero(&toaddr, sizeof(toaddr));
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(atoi(argv[2]));
toaddr.sin_addr.s_addr = inet_addr(argv[1]);
sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));
char buf[BUFSIZE] = {0};
struct sockaddr_in fromaddr;
bzero(&fromaddr, sizeof(fromaddr));
int fromaddrlen = sizeof(struct sockaddr);
int filelen = 0; //用於保存文件長度
FILE* fp = fopen("2.txt","w+b");
//接收文件的長度
recvfrom(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);
printf("the length of file is %d\n",filelen);
printf("Create a new file!\n");
printf("begin to reveive file content!\n");
//接收文件的內容
while(1)
{
int len = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);
if(len < BUFSIZE)
//若是接收的長度小於BUFSIZE,則表示最後一次接收,此時要用break退出循環
{
fwrite(buf,sizeof(char),len,fp);
break;
}
fwrite(buf,sizeof(char),len,fp);
}
printf("receive file finished!\n");
close(sfd);
}
函數原型:
#include <sys/types.h >
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:標識一個套接口的描述字
level:選項定義的層次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
optname:需設置的選項
optval:指針,指向存放選項值的緩衝區
optlen:optval緩衝區長度
所有都必需要放在bind以前,另外一般是用於UDP的。
1. 若是在已經處於 ESTABLISHED狀態下的socket(通常由端口號和標誌符區分)調用closesocket(通常不會當即關閉而經歷TIME_WAIT的過程)後想繼續重用該socket:
int reuse=1;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));
2. 若是要已經處於鏈接狀態的soket在調用closesocket後強制關閉,不經歷TIME_WAIT的過程:
int reuse=0;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));
3.在send(),recv()過程當中有時因爲網絡情況等緣由,發收不能預期進行,而設置收發時限:
int nNetTimeout=1000; // 1秒
// 發送時限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
// 接收時限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的時候,返回的是實際發送出去的字節(同步)或發送到socket緩衝區的字節(異步),系統默認的狀態發送和接收一次爲8688字節(約爲8.5K);在實際的過程當中發送數據和接收數據量比較大,能夠設置socket緩衝區,而避免了send(),recv()不斷的循環收發:
// 接收緩衝區
int nRecvBuf=32*1024; // 設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
// 發送緩衝區
int nSendBuf=32*1024; // 設置爲32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5. 若是在發送數據時,但願不經歷由系統緩衝區到socket緩衝區的拷貝而影響程序的性能:
int nZero=0;
setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(int));
6.同上在recv()完成上述功能(默認狀況是將socket緩衝區的內容拷貝到系統緩衝區):
int nZero=0;
setsockopt(socket,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.通常在發送UDP數據報的時候,但願該socket發送的數據具備廣播特性:
int bBroadcast = 1;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(int));
多播廣播是用於創建分步式系統:例如網絡遊戲、ICQ聊天構建、遠程視頻會議系統的重要工具。使用多播廣播的程序和UDP的單播程序類似。區別在於多播廣播程序使用特殊的IP地址。
對於單播而言,單播用於兩個主機之間的端對端通訊。
對於廣播而言,廣播用於一個主機對整個局域網上全部主機上的數據通訊。廣播只能用於客戶機向服務器廣播,由於客戶機要指明廣播的IP地址「192.168.0.255」和廣播的端口號。服務器端bing的時候,綁定的端口號要跟廣播的端口號是同一個。這樣才能收到廣播消息。實例請參考《udp_廣播》。
對於多播而言,也稱爲「組播」,將網絡中同一業務類型主機進行了邏輯上的分組,進行數據收發的時候其數據僅僅在同一分組中進行,其餘的主機沒有加入此分組不能收發對應的數據。單播和廣播是兩個極端,要麼對一個主機進行通訊,要麼對整個局域網上的主機進行通訊。實際狀況下,常常須要對一組特定的主機進行通訊,而不是整個局域網上的全部主機,這就是多播的用途。例如,咱們一般所說的討論組。IPv4多播地址採用D類IP地址肯定多播的組。在Internet中,多播地址範圍是從224.0.0.0到234.255.255.255。
多播的程序設計也要使用setsockopt()函數和getsockopt()函數來實現。其中對於setsockopt的第二個參數level再也不是SOL_SOCKET,而是IPPROTO_IP;並且第三個參數optname常見的選項有:
optname |
含 義 |
IP_ADD_MEMBERSHIP |
在指定接口上加入組播組 |
IP_DROP_MEMBERSHIP |
退出組播組 |
選項IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP加入或者退出一個組播組,經過選項IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP,對一個結構struct ip_mreq類型的變量進行控制。
struct ip_mreq原型以下:
struct ip_mreq
{
struct in_addr imr_multiaddr; /*加入或者退出的多播組IP地址*/
struct in_addr imr_interface; /*加入或者退出的網絡接口IP地址,本機IP*/
};
選項IP_ADD_MEMBERSHIP用於加入某個多播組,以後就能夠向這個多播組發送數據或者從多播組接收數據。此選項的值爲mreq結構,成員imr_multiaddr是須要加入的多播組IP地址,成員imr_interface是本機須要加入多播組的網絡接口IP地址。例如:
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(struct ip_mreq));
mreq.imr_interface.s_addr = INADDR_ANY;
mreq.imr_multiaddr.s_addr = inet_addr("224.1.1.1");
if(-1 == setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)))
{
perror("setsockopt");
exit(-1);
}
接下來再綁定組播的port號(如65000),就能夠接收組播消息了。實例請參考《udp_組播》
選項IP_ADD_MEMBERSHIP每次只能加入一個網絡接口的IP地址到多播組,但並非一個多播組僅容許一個主機IP地址加入,能夠屢次調用IP_ADD_MEMBERSHIP選項來實現多個IP地址加入同一個廣播組,或者同一個IP地址加入多個廣播組。
選項IP_DROP_MEMBERSHIP用於從一個多播組中退出。例如:
if(-1 == setsockopt(sfd, IPPROTP_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)))
{
perror("setsockopt");
exit(-1);
}