基本知識:
1.協議
【作用】:使交換信息的兩個部分之間相互理解的一組規則、
約定和數據結構的集合。
即:爲了使不同版本的計算機能相互溝通而存在
在現在的網絡應用中使用最廣泛的協議是TCP/IP協議
ISO
OSI【【七層協議】】模型
【1】.應用層:爲應用(程序提供服務)並(規定)應用程序中(通信的相關細節),
如:ftp,tftp,smtp,ssh,telnet...
【2】.表示層:主要負責數據格式的轉換(設備固有格式 <==> 網絡格式)
【3】.會話層:主要建立和斷開通信連接
【4】.傳輸層:起着可靠傳輸的作用
【5】.網絡層:將數據傳輸到目標地址,主要負責尋址和路由選擇
【6】.數據鏈路層:負責物理層面上的互連的節點之間的通信傳輸
【7】.物理層:負責0、1比特流與電壓的高低、光的閃滅之間的互換
根據七層協議模型, tcp/IP協議可分爲四層
【1】.應用層:
【2】.傳輸層: TCP UDP
【3】.網絡層: IPv4 IPv6
【4】.網絡接口層
2.常用協議
【1】.TCP協議
傳輸控制協議,是一種【面向連接】的協議,類似打電話
【2】.UDP協議
用戶數據報協議,是一種【無連接】的協議,類似發短信
【3】.IP協議
互聯網協議,是上述兩種協議的底層協議,當需要【開發
新的通信協議】時,才需要關注
3.IP地址
【作用】:作爲通信設備在互聯網中的唯一地址標識
【本質】: 1.由32位二進制組成的【整數】(IPv4)
2.由128位二進制組成的【整數】(IPv6)
日常生活中採用點分十進制表示法來描述IP地址,也就是將每個字節的
二進制轉爲十進制的整數,不同的整數之間用小數點分隔
0x01020304 ==>1.2.3.4
2^32個地址,怎麼管理?
將【IP地址分爲兩部分】:網絡地址和主機地址
【網絡地址】:屬於哪個網絡 【可以定位在哪個網吧上網】
【主機地址】:網絡中主機的編號 【可以定位在哪臺電腦上網】
根據網絡地址和主機地址位數的不同分爲四類:
A: 0 + 7位的網絡號 + 24位主機地址
0.0.0.0 ~ 127.255.255.255
B: 10 + 14位網絡號 + 16位主機地址
128.0.0.0 ~ 191.255.255.255
C: 110 + 21位網絡號 + 8位主機地址
192.0.0.0 ~ 223.255.255.255
D: 1110 + 28位多(組)播地址
224.0.0.0 ~ 239.255.255.255
E: 備用
查看IP地址的命令
ipconfig
ifconfig
3.子網掩碼 【第一種分IP地址的方法】
【作用】:用於劃分IP地址中的網絡地址和主機地址,
也可以用於判斷兩個IP是否在同一局域網中
具體分法: 【IP地址】 跟 【子網掩碼】 做【與運算】 得到 【網絡地址】
【前提是換成二進制】
IP地址 & 子網掩碼 = 網絡地址
例:
172.30.100.64 IP
& 255.255.255.0 子網掩碼
--------------------------------
172.30.100.0 ---網絡地址
64---主機地址
例:
IP:166.111.160.1 和 166.111.161.45
子網掩碼:255.255.254.0
解析:
166.111.160.1
255.255.254.0 &
---------------
166.111.160 網絡號
166.111.161.45
255.255.254.0 &
---------------
166.111.160 網絡號
總結:上面兩個IP地址在同一個局域網中
【第二種分IP地址的方法】:
斜槓'/'後面的數字N表示前N位爲網絡號
166.111.160.1 /23
166.111.161.45 /23
23:表示前23位爲網絡號
4.端口號
【IP地址】 -- 定位到具體的某一臺主機/設備 【推斷出你在哪個網吧哪臺機子上網】
【端口號】 -- 定位到主機/設備上的某一個進程 【推斷出你在那臺機子玩什麼】
本質上就是一個16位的無符號整數 unsigned short,範圍是0~65535
其中0-1024之間的端口號被系統使用,建議【從5000開始使用】
【網絡編程】中需【要提供】兩個信息:【IP地址】+【端口號】
5.字節序(多字節整數)
小端模式:主要指將低位字節數據保存在低位內存地址的系統
大端模式:主要指將低位字節數據保存在高位內存地址的系統
【在準備通信地址和使用通信地址信息時需要注意類型的轉換】
----------------------------------------------------------
網絡編程【又名socket編程,因爲要使用socket函數得到信息載體】
一、網絡編程又叫socket編程
【套接字概念】:是一個網絡編程的接口,是網絡數據傳輸的軟設備。
【套接字作用】:用於網絡交互。
【網絡編程本質】:編寫程序【使兩臺連網的計算機】相互【交換數據】。
unix/linux系統作爲服務器操作系統存在至今,因此,
Linux的網絡功能應該是非常全面和強大。
網絡編程其實有很成熟的通信模型,並且windows也通用。
二、通信模型(基於TCP的一對一的通信模型)
【1】.一對一服務器:
【等待連接原理】:
1 有人從很遠很遠的地方嘗試調用 connect()來連接你的機器上的某個端口
(當然是你已經在 listen()的)。
2 他的連接將被 listen 加入等待隊列等待 accept()函數的調用(加入等待隊
列的最多數目由調用 listen()函數的第二個參數 backlog 來決定)。
3 你調用 accept()函數,告訴他你準備連接。
4 accept()函數將返回一個新的套接字描述符,這個描述符就代表了這個連接!
好,這時候你有了【兩個套接字描述符】,返回給你的那個就是和遠程計算機的連接,
而第一個套接字描述符仍然在你的機器上原來的那個端口上 listen()。
這時候你所得到的那個新的套接字描述符就可以進行 send()操作和recv()操作了。
http://blog.csdn.net/wukui1008/article/details/7669173 【套接字】
【監聽套接字】:創建通信載體時得到的返回值;
作用:用來監聽一個端口,當有客戶端過來時,給他指路
【連接套接字】:等待客戶端連接完成後得到的返回值;
作用:作爲和客戶端(遠程計算機)真正聯繫的【鏈接】
監聽套接字就是個牽線指路的,你實質上是跟它指的那個人說話。
因爲你要找的那個人不可能隨時等你來,而監聽套接字就是專職等你來問,
它回答你要找的人在哪,並喚醒你要找的人,於是通話就建立起來了,
就像現實生活中的接線員一樣。
1.創建監聽套接字 使用socket()函數
原型:int socket(int domain, int type, int protocol);
即:socket(協議族,通信類型,具體的協議);
功能:
主要用於創建可以實現通信的交流點,也就是socket通信載體(相當於電話機)
得到監聽套接字
返回值:
成功:返回新的socket描述符【監聽套接字】
失敗:返回-1,errno被設置
例如:
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
AF_INET:域/協議族,決定了是【本地通信還是網絡通信】
AF_UNIX/AF_LOCAL 用於實現本地通信
AF_INET 用於實現基於IPv4的網絡通信
AF_INET6 用於實現基於IPv6的網絡通信
SOCK_DGRAM:通信類型,決定了【具體的通信式】
SOCK_STREAM 提供有序的、可靠的、雙向的、面向連接的字節流通信方式,默認使用TCP協議
SOCK_DGRAM 提供無序的、不可靠的,非面向連接的數據報通信方式,默認使用UDP協議
0:指定具體的協議,默認爲0,使用默認協議
2.準備通信地址【要先清零再賦值】
通信地址數據【類型】:
1】.通用地址結構
struct sockaddr
{
sa_family_t sa_family; //域/協議族
char sa_data[14]; //??????
};
2】.IPv4通信地址結構
struct sockaddr_in
{
sa_family_t sin_family;//協議族,AF_INET
in_port_t sin_port;//16位的端口號
struct in_addr sin_addr;//IP地址
};
struct in_addr
{
in_addr_t s_addr; //16位的端口號
};
例如:(IPv4通信地址結構)
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
【作用】:(以IPv4通信地址結構爲例)
saddr.sin_family:決定該地址遵循何種協議
saddr.sin_port:決定該地址的端口 【哪臺機子】
saddr.sin_addr.s_addr:決定該地址的IP地址 【哪個網吧】
【IP地址格式轉換】:
IP地址從main()函數【輸入時是字符串】格式的,
初始化通信地址的時候要用inet_addr()函數轉換成結構體類型;
接收【調用】別人的IP地址時,得到的是【結構體類型】的;
若要【輸出】,則要用inet_ntoa()函數轉換成【字符串】格式;
【端口號格式轉換】:
端口號從main()函數【輸入時是字符串】格式的,
初始化通信地址的時候要先用atoi()函數轉換成int型;
是主機字節順序,要再用 htons()函數轉換成網絡字節順序(大端模式);
接收【調用】別人的IP地址時,得到的是【網絡字節順序】;
若要【輸出】,則要用ntohs()函數轉換成【主機字節順序】;
端口格式轉換相關【函數詳解】:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h:host 本地格式
n:network 網絡格式(大端模式)
s:short 2字節整數
l:long 4字節整數
功能:把本地格式轉成網絡格式,或者反過來。
IP地址格式轉換相關函數:
in_addr_t inet_addr(const char *cp);
功能:將字符串形式的IP地址轉爲整數類型
char *inet_ntoa(struct in_addr in);
功能:將結構類型的IP地址轉爲字符串形式
/*************服務器**************/
3.綁定(通信地址與監聽套接字) bind()函數
原型:int bind(int sockfd, const struct sockaddr *saddr,
socklen_t addrlen);
即:bind(監聽套接字描述符,強轉後的通信地址,通信地址大小) ;
功能:
主要用於綁定socket和具體的通信地址
返回值:
成功:返回0
失敗:返回-1,errno被設置
例如:
int ret = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret < 0) //很有必要!!
{
perror("bind");
exit(-1);
}
sockfd:socket描述符,即監聽套接字
(struct sockaddr*)&saddr:結構體指針,
不管是什麼協議的地址結構,都需要強轉爲該類型
sizeof(saddr):通信地址結構的大小,使用sizeof()計算即可
4.監聽 使用listen()函數
原型:int listen(int sockfd, int backlog);
即:listen(監聽套接字,監聽隊列大小);
功能:
爲套接字sockfd建立一個連接請求監聽隊列,在調用listen函數成功後,這個套接字便成爲服務套接字,即被動套接字
返回值:
成功:返回0
失敗:返回-1,errno被設置
例如:
listen(sockfd,10);
sockfd:socket描述符,即監聽套接字
10:監聽隊列的大小
5.等待連接 accept()函數
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
即:accept(監聽套接字,強轉後的通信地址,指針類型的通信地址大小);
功能:
用於接收服務套接字sockfd上的連接請求
返回值:
成功:返回一個【連接套接字】
(新的,在後面的代碼中起作用,代替監聽套接字出現)
失敗返回-1,errno被設置
例如:
socklen_t len = sizeof(caddr); //不能直接在等待中用sizeof(caddr)
//保存客戶地址的結構體的大小,必需由調用者初始化
int confd = accept(sockfd,(struct sockaddr*)&caddr,&len);
caddr: 結構體指針,用於保存接受的客戶端通信地址,
與被初始化的通信地址一起定義,用來存儲客戶端的地址
sockfd:socket函數的返回值,監聽套接字
(struct sockaddr*)&caddr:結構體指針,
不管是什麼協議的地址結構,都需要強轉爲該類型
&len:指針類型,用於保存客戶端網絡地址的信息的長度
注:如果對客戶地址不感興趣,那麼第二、三兩個參數寫NULL即可。
如果需要客戶的地址,那麼第三個參數必須由調用者初始化。
6.通信
【方法】:把連接套接字當成文件描述符操作,
可以讀取裏面的東西,也可以往裏面寫東西
7.關閉連接套接字和監聽套接字
close(confd);
close(sockfd);
/*********************服務器*************/
/*****************客戶端*************/
3.連接 使用connect()函數
原型:int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
即:connect(監聽套接字描述符,服務器的通信地址,通信地址結構的大小);
功能:
用於連接服務器
返回值:
成功:返回0
失敗:返回-1,errno被設置
例如:
int ret = connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(ret < 0)
{
perror("connect");
exit(-1);
}
4.通信
【方法】:把監聽套接字當成文件描述符操作,
可以讀取裏面的東西,也可以往裏面寫東西
5.關閉監聽套接字
close(sockfd);
/****************客戶端*************/
迭代服務器
併發服務器
多進程服務器
多線程服務器
多路複用服務器
【2】.IO多路複用服務器
【作用】:實現多個文件的查詢是否就緒,
在某個文件可讀的情況下,馬上讀到數據,
【方法】:運用select()函數
【原理】:對集合中的文件進行輪詢,每隔一段時間就會去
詢問文件描述符[0,nfds)中的每一個文件,查看是否就緒,
如果就緒那麼把相應的文件描述符集合中置1. .......
直到有文件就緒或超時或出錯。
超時時間結構體:
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 毫秒 */
};
例如:
struct timeval tv;//超時時間結構體
/* 超時時間設爲5秒. */
tv.tv_sec = 5;
tv.tv_usec = 0;
select()【函數原型】:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
即:select(感興趣的文件集合中最大的文件描述符,
對讀感興趣的文件描述符集合,
對寫感興趣的文件描述符集合,
對異常感興趣的文件描述符集合,
超時時間);
返回值:
1.有文件數據就緒,立馬返回,返回就緒的文件描述符個數 >0
2.超時返回,返回0
3.出錯了,返回-1 ,errno被設置
例如:
int retval;
retval = select(10, &tmpfds, NULL, NULL, &tv);
if(retavl < 0)
{ //出錯
perror("select");
}
else if(retavl > 0)
{ //有文件就緒
。。。。
}
else if(retval == 0)
{ //等待超時
。。。。
}
【輔助函數】:
1】. void FD_ZERO(fd_set *set);
功能:初始化指定的集合,全部清零
例如:
FD_ZERO(sockfd,&rfds);
2】. void FD_SET(int fd, fd_set *set);
功能:把指定的fd設置到集合中去
例如:
FD_SET(sockfd,&rfds);
3】. int FD_ISSET(int fd, fd_set *set);
功能:判斷指定的fd是否在這個集合中
返回值:
成功:返回非0
失敗:返回0
例如:
FD_ISSET(sockfd,&rfds);
4】. void FD_CLR(int fd, fd_set *set);
功能:把指定fd從集合中移除
例如:
FD_CLR(sockfd,&rfds);
select使用【步驟】:
1.設置文件描述符
fd_set rfds,tmpfds;//創建兩個文件描述符集合
struct timeval tv;//定義一個超時時間結構體
int retval; //定義一個描述符作爲輪詢函數的返回值,
//用於劃分輪詢結果以便分配任務
2. 初始化指定的文件集合,全部清零
FD_ZERO(&rfds);//把文件描述符集合清零,即初始化
3. 把指定的文件的文件描述符設置到集合中去
FD_SET(i, &rfds);//把文件描述符i,放到集合rfds中,即把對應的位置1
4.初始化時間結構體 ,
/* 超時時間設爲5秒. */
tv.tv_sec = 5;
tv.tv_usec = 0;
5.備份需要輪詢的文件描述符集合
tmpfds = rfds;//拷貝準備好的描述符集合,
//每循環一次,備份一次文件描述符,啓動一次輪詢函數
//防止源集合被覆蓋
6.啓動輪詢函數
retval = select(fd_max+1,&tmpfds,NULL,NULL,&tv);
7.判斷指定的fd是否在這個集合中
FD_ISSET(i,&rfds) //判斷指定的文件是否在集合中
8. 把指定fd從集合中移除
FD_CLR(i,&rfds);//從集合中移除這個連接套接字描述符
9. 關閉文件
close(i);
【TCP與UDP協議的比較】:
1.tcp協議:
傳輸控制協議 類似打電話
面向連接的 (建立連接-->進行通信-->斷開連接)
在通信的整個過程中必需保持連接
該協議保證了數據的傳遞是可靠且有序的
屬於全雙工的字節流通信方式
服務器壓力較大,資源消耗大,執行效率較低
2.udp協議:
用戶數據報協議
面向非連接的 類似發短信
在通信的整個過程中不需要保持連接
不保證數據的可靠性和有序性
屬於全雙工的數據報通信方式
服務器壓力較小,資源消耗小,執行效率高
【3】.基於【UDP協議】的通信模型
【注意】:
1.UDP不分服務器和客戶端
2. UDP調用接收函數必須要綁定地址,
要用socklen_t len = sizeof(saddr);
求出目標地址長度,在函數中用&len代表長度
3.調用方式函數不用綁定地址,也不要先求出長度
用法:
1.創建監聽套接字
使用socket()函數
2.準備通信地址
定義並初始化結構體變量
/******************接收方**************/
3.綁定監聽套接字和通信地址
使用bind()函數
4.通信
————————————————————————————
接收數據報函數:recvfrom()
原型:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
即:recvfrom(監聽套接字描述符,強轉的被髮送的數據首地址,
發送數據的大小,發送標誌,
發送數據的目標地址,目標地址的大小);
作用:
將目標地址發過來的N個字節存到緩衝區中
例如:
socklen_t len = sizeof(saddr);
ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&saddr,&len);
sockfd: socket函數的返回值,監聽套接字
buf: 被髮送的數據首地址
sizeof(buf): 發送數據的大小
0: 發送標誌
0:功能與write一樣,即阻塞
MSG_DONTWAIT: 非阻塞
(struct sockaddr *)&saddr:發送數據的目標地址
&len:目標地址的大小
**********************************/
/***************發送方*************/
3.通信
————————————————————————————
發送數據報函數:sendto()
原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
即:sendto(監聽套接字描述符,強轉的被髮送的數據首地址,
發送數據的大小,發送標誌,
發送數據的目標地址,目標地址的大小);
作用:
將準備好的數據中的N個發送給大小爲M的目標地址,進行通信
例如:
int res = sendto(sockfd,str,strlen(str),0,
(struct sockaddr *)&saddr,sizeof(saddr));
sockfd: socket函數的返回值,監聽套接字
str: 被髮送的數據首地址
strlen(str): 發送數據的大小
0: 發送標誌
0:功能與write一樣,即阻塞
MSG_DONTWAIT: 非阻塞
(struct sockaddr *)&saddr:發送數據的目標地址 【發給誰】
sizeof(saddr):目標地址的大小
**********************************/
5.關閉套接字
close(sockfd);
【UDP】應用
廣播 組播
發送方:1.要有一個套接字
2.要有一個目標地址(不是給自己用的,所以不用綁定)
3.要用專門的函數sendto()發送數據報
接收方:1.要有一個套接字
2.要有一個給自己用的通信地址(需要綁定)和
一個存儲發送者資料的地址
3.
方法一:
使用setsockopt()函數將默認接收的緩衝區設計爲一個小的緩衝區,
緩衝區地址和大小任意定義,使用getsockopt()函數得到套接字選項值
例如:
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,(void*)&j,sizeof(j));
getsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,(void*)&i,&size);
方法二:
定義一個多播組結構體變量,並初始化,使用setsockopt()函數將默認接收
的緩衝區設計爲一個小的緩衝區,緩衝區地址爲定義結構體變量的首地址,
大小爲結構體變量所佔字節數
例如://加入多播組的方式
struct ip_mreq join_addr;
memset(&join_addr,0,sizeof(join_addr));
join_addr.imr_multiaddr.s_addr = inet_addr(argv[1]);// 組播組的IP地址。
join_addr.imr_interface.s_addr = htonl(INADDR_ANY); // 本地某一網絡設備接口的IP地址。
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_addr,sizeof(join_addr));
4.綁定監聽套接字和通信地址
5.使用專門的函數recvfrom()接收數據報
http://blog.csdn.net/chary8088/article/details/2486377
【套接字】:
【概念】:通信的兩方的一種約定,用套接字中的相關函數來完成通信過程
【套接字選項】:
【概念】:每個【套接字】在不同的協議層次上【有】不同的【行爲屬性】,
那麼這個【行爲屬性】就稱爲套接字選項
【作用】: 設置地址複用
允許發送廣播消息
將主機加入多播組
設置發送與接收緩衝區的大小等
我們正常使用套接字編程時,一般只關注數據通信,而忽略套接字的不同特性
但有時需要設置地址複用,允許發送廣播消息,將主機加入多播組,設置發送與接收緩衝區的大小等
這些都需要對套接字選項進行設置。
【所用函數】:
原型:int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
即:getsockopt(監聽套接字,套接字選項所在的協議層次,
套接字選項的名稱,緩衝區首地址,
緩衝區的長度);
功能: 得到套接字選項值
返回值:
成功:返回0。
失敗:返回-1,errno被設置
例如:
原型:int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
即:setsockopt(監聽套接字,套接字選項所在的協議層次,
套接字選項的名稱,緩衝區首地址,
緩衝區的長度);
功能:如果認爲套接口的默認發送以及接收緩衝區的尺寸太大時,
作爲程序設計者的我們可以【將默認發送的緩衝區設計爲一個小的緩衝區】。
將socket加入一個組播組,因爲socket要接收組播地址224.0.0.1的
數據,它就必須加入該組播組。結構體struct ip_mreq mreq是
該操作的參數,下面是其定義:
struct ip_mreq
{
struct in_addr imr_multiaddr; // 組播組的IP地址。
struct in_addr imr_interface; // 本地某一網絡設備接口的IP地址。
};
一臺主機上可能有多塊網卡,接入多個不同的子網,
imr_interface參數就是指定一個特定的設備接口,
告訴協議棧只想在這個設備所在的子網中加入某個組播組。
有了這兩個參數,協議棧就能知道:在哪個網絡設備接口上
加入哪個組播組。爲了簡單起見,我們的程序中直接寫明瞭
IP地址:在172.16.48.2所在的設備接口上加入組播組224.0.1.1。
這個操作是在網絡層上的一個選項,所以級別是SOL_IP
IP_ADD_MEMBERSHIP選項把用戶傳入的參數拷貝成了
struct ip_mreqn結構體: