Linux--网络编程
1.Linux的優(yōu)點之一就是在于它豐富而穩(wěn)定的網(wǎng)絡(luò)協(xié)議棧,其范圍是從協(xié)議無關(guān)層(如通用的socket層接口和設(shè)備層)到各種網(wǎng)絡(luò)協(xié)議的實現(xiàn)
2.對于網(wǎng)絡(luò)理論介紹一般采用OSI模型,但是Linux中網(wǎng)絡(luò)棧的介紹一般分為四層的Internet模型
2.1數(shù)據(jù)鏈路層
數(shù)據(jù)鏈路層實現(xiàn)了網(wǎng)卡接口的網(wǎng)絡(luò)驅(qū)動程序,以處理數(shù)據(jù)在網(wǎng)絡(luò)媒介上(比如以太網(wǎng))上的傳輸。不同的物理網(wǎng)絡(luò)層具有不同的電器特性,網(wǎng)絡(luò)驅(qū)動驅(qū)動程序隱藏了這些細節(jié),為上層協(xié)議提供一個統(tǒng)一的接口。
2.2網(wǎng)絡(luò)層
網(wǎng)絡(luò)層實現(xiàn)數(shù)據(jù)包的選路和轉(zhuǎn)發(fā)。WAN通常使用眾多分級的路由器來連接分散的主機或LAN,因此,通信的兩臺主機一般不是直接連接的,而是通過多個中間節(jié)點連接的。網(wǎng)絡(luò)層的任務(wù)就是選擇這些節(jié)點,以確定兩臺主機間的通信路徑。同時,網(wǎng)絡(luò)層對上層協(xié)議隱藏了網(wǎng)絡(luò)拓撲連接的細節(jié),使得在傳輸層和網(wǎng)絡(luò)應(yīng)用程序看來,通信的雙發(fā)是直接相連的。
2.3傳輸層
傳輸層為兩臺主機上的應(yīng)用程序提供端到端(end to end)的通信。與網(wǎng)絡(luò)層使用的逐跳的通信方式不同,傳輸層只關(guān)心通信的起始端和目的端,而不在話數(shù)據(jù)包的中轉(zhuǎn)過程。
2.4應(yīng)用層
應(yīng)用層負責處理應(yīng)用程序的邏輯。
應(yīng)用層協(xié)議很多:
ping:應(yīng)用程序,不是協(xié)議,調(diào)試網(wǎng)絡(luò)環(huán)境;
telnet:遠程登錄協(xié)議;
DNS:機器域名到ip的轉(zhuǎn)換;
HTTP:超文本傳輸協(xié)議(Hypertext transfer protocol)。是一種詳細規(guī)定了瀏覽器和萬維網(wǎng)(WWW = World Wide Web)服務(wù)器之間互相通信的規(guī)則,通過因特網(wǎng)傳送萬維網(wǎng)文檔的數(shù)據(jù)傳送協(xié)議。
DHCP:動態(tài)主機配置協(xié)議
3.數(shù)據(jù)封裝
應(yīng)用程序數(shù)據(jù)在發(fā)送到屋里網(wǎng)絡(luò)上之前,將沿著協(xié)議棧從上往下依次傳遞。每層協(xié)議都將在上層協(xié)議的基礎(chǔ)上加上自己的頭部信息(有時還包括尾部信息),以實現(xiàn)該層的功能,這個過程稱為封裝。
4.IP協(xié)議
IP的主要目的是為數(shù)據(jù)輸入/輸出網(wǎng)絡(luò)提供基本算法,為高層協(xié)議提供無連接的傳送服務(wù).這意味著在IP將數(shù)據(jù)遞交給接收站點以前不在傳輸站點和接收站點之間建立對話。它只是封裝和傳遞數(shù)據(jù),但不向發(fā)送者或接收者報告包的狀態(tài),不處理所遇到的故障
5.TCP協(xié)議
TCP協(xié)議(Transmission Control Protocol,傳輸控制協(xié)議)為應(yīng)用層提供可靠的、面向連接的、基于流(stream)的服務(wù)。TCP協(xié)議使用超時重傳、數(shù)據(jù)確認等方式來確保數(shù)據(jù)包被正確的發(fā)送到目的,因此TCP服務(wù)是可靠的。使用TCP協(xié)議通信的雙方必須先建立TCP連接,并且在內(nèi)核中為該連接維持一些必須的數(shù)據(jù)結(jié)構(gòu)。當通信結(jié)束時,雙方必須關(guān)閉連接以釋放這些內(nèi)核數(shù)據(jù)。
6.TCP三次握手
7.TCP四次揮手
8.UDP協(xié)議
UDP協(xié)議(User DataGram Protocol,用戶數(shù)據(jù)報協(xié)議)則與相反,它為應(yīng)用層TCP協(xié)議完全提供不可靠、無連接和基于數(shù)據(jù)報的服務(wù)。不可靠意味著UDP協(xié)議無法保證數(shù)據(jù)從發(fā)送端正確的傳送到接收端。如果數(shù)據(jù)在中途丟失,或者目的端通過數(shù)據(jù)校驗發(fā)現(xiàn)數(shù)據(jù)錯誤而將其丟棄,則UDP協(xié)議只是簡單的通知應(yīng)用層發(fā)送失敗。因此使用UDP協(xié)議的應(yīng)用程序通常要自己處理數(shù)據(jù)確認、超時重傳等邏輯性。
9.socket
Linux中的網(wǎng)絡(luò)編程通過Socket(套接字)接口實現(xiàn),Socket是一種文件描述符
數(shù)據(jù)報套接字(SOCK_DGRAM)
數(shù)據(jù)報套接字定義了一種無連接的服務(wù),數(shù)據(jù)通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠,無差錯,它使用數(shù)據(jù)報協(xié)議UDP。
原始套接字
原始套接字允許對低層協(xié)議如IP或ICMP直接訪
問,主要用于新的網(wǎng)絡(luò)協(xié)議的測試等
struct sockaddr
{
u_short sa_family;
char sa_data[14];
};
Sa_family:
地址族,采用“AF_xxx”的形式,如:AF_INET
Sa_data:
14字節(jié)的特定協(xié)議地址
地址結(jié)構(gòu):
struct sockaddr_in { short int sin_family; /* Internet地址族 / unsigned short int sin_port; / 端口號 / struct in_addr sin_addr; / IP地址 / unsigned char sin_zero[8]; / 填0 */ }; 編程中一般并不直接針對sockaddr數(shù)據(jù)結(jié)構(gòu)操作,而是使用與sockaddr等價的sockaddr_in數(shù)據(jù)結(jié)構(gòu)
10.字符序轉(zhuǎn)換
不同類型的 CPU 對變量的字節(jié)存儲順序可能不同:有的系統(tǒng)是高位在前,低位在后,而有的系統(tǒng)是低位在前,高位在后,而網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)順序是一定要統(tǒng)一的。所以當內(nèi)部字節(jié)存儲順序和網(wǎng)絡(luò)字節(jié)順序不同時,就一定要進行轉(zhuǎn)換
如果我們將0x1234abcd 寫入到以0x0000 開始的內(nèi)存中,則Little endian 和Big endian 模式的存放結(jié)果如下:
低地址: 小端: 0xcdab4312 :高地址
大端; 0x1234abcd
網(wǎng)絡(luò)字節(jié)順序是TCP/IP中規(guī)定好的一種數(shù)據(jù)表示格式,它與具體的CPU類型、操作系統(tǒng)等無關(guān),從而可以保證數(shù)據(jù)在不同主機之間傳輸時能夠被正確解釋。
網(wǎng)絡(luò)字節(jié)順序采用big endian排序方式
11.為什么要進行字節(jié)序轉(zhuǎn)換?
例:INTEL的CPU使用的小端字節(jié)序MOTOROLA 68k系列CPU使用的是大端字節(jié)序 MOTOROLA發(fā)一個16位數(shù)據(jù)0X1234給INTEL, 傳到INTEL時 ,就被INTEL解釋為0X3412
#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);
這些函數(shù)名很好記,h表示host,n表示network,l表示32位長整數(shù),s表示16位短整數(shù)。例如htonl表示將32位的長整數(shù)從主機字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,例如將IP地址轉(zhuǎn)換后準備發(fā)送。如果主機是小端字節(jié)序,這些函數(shù)將參數(shù)做相應(yīng)的大小端轉(zhuǎn)換然后返回,如果主機是大端字節(jié)序,這些函數(shù)不做轉(zhuǎn)換,將參數(shù)原封不動地返回
12地址轉(zhuǎn)換:
12.1.htons
把unsigned short類型從主機序轉(zhuǎn)換到網(wǎng)絡(luò)序
htonl
把unsigned long類型從主機序轉(zhuǎn)換到網(wǎng)絡(luò)序
ntohs
把unsigned short類型從網(wǎng)絡(luò)序轉(zhuǎn)換到主機序
ntohl
把unsigned long類型從網(wǎng)絡(luò)序轉(zhuǎn)換到主機序
12.2 IP地址通常由數(shù)字加點(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整數(shù)表示的,為了轉(zhuǎn)換我們可以使用下面兩個函數(shù):
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
函數(shù)里面 a 代表 ascii n 代表network.第一個函數(shù)表示將a.b.c.d形式的IP轉(zhuǎn)換為32位的IP,存儲在 inp指針里面。第二個是將32位IP轉(zhuǎn)換為a.b.c.d的格式
13.
進行Socket編程的常用函數(shù)有:
socket
創(chuàng)建一個socket
bind
用于綁定IP地址和端口號到socket
connect
該函數(shù)用于綁定之后的client端與服務(wù)器建立連接
listen
設(shè)置能處理的最大連接要求,Listen()并未開始接收連線,只是設(shè)置socket為listen模式。
accept
用來接受socket連接。
send
發(fā)送數(shù)據(jù)
recv
接收數(shù)據(jù)
int socket(int family, int type, int protocol);
socket()打開一個網(wǎng)絡(luò)通訊端口,如果成功的話,就像open()一樣返回一個文件描述符,應(yīng)用程序可以像讀寫文件一樣用read/write在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù),如果socket()調(diào)用出錯則返回-1
對于IPv4,family參數(shù)指定為AF_INET
對于TCP協(xié)議,type參數(shù)指定SOCK_STREAM,表示面向流的傳輸協(xié)議如果是UDP協(xié)議,則type參數(shù)指定為SOCK_DGRAM,表示面向數(shù)據(jù)報的傳輸協(xié)議
protocol參數(shù)的介紹從略,指定為0即可
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
服務(wù)器程序所監(jiān)聽的網(wǎng)絡(luò)地址和端口號通常是固定不變的,客戶端程序得知服務(wù)器程序的地址和端口號后就可以向服務(wù)器發(fā)起連接,因此服務(wù)器需要調(diào)用bind綁定一個固定的網(wǎng)絡(luò)地址和端口號.bind()成功返回0,失敗返回-1。bind()的作用是將參數(shù)sockfd和myaddr綁定在一起,使 sockfd這個用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽myaddr所描述的地址和端口號struct sockaddr *是一個通用指針類型,myaddr參數(shù)實際上可以接受多種協(xié)議的sockaddr結(jié)構(gòu)體,而它們的長度各不相同,所以需要第三個參數(shù)addrlen指定結(jié)構(gòu)體的長度client終止時自動關(guān)閉socket描述符,server的TCP連接收到client發(fā)的FIN段后處于TIME_WAIT狀態(tài)。TCP協(xié)議規(guī)定,主動關(guān)閉連接的一方要處于TIME_WAIT狀態(tài),等待兩個MSL(maximum segment lifetime)的時間后才能回到CLOSED狀態(tài),因為我們先Ctrl-C終止了server,所以server是主動關(guān)閉連接的一方,在TIME_WAIT期間仍然不能再次監(jiān)聽同樣的server端口。MSL在RFC1122中規(guī)定為兩分鐘,但是各操作系統(tǒng)的實現(xiàn)不同,在Linux上一般經(jīng)過半分鐘后就可以再次啟動server了。在server的TCP連接沒有完全斷開之前不允許重新監(jiān)聽是不合理的,因為,TCP連接沒有完全斷開指的是fd(127.0.0.1:8000)沒有完全斷開,而我們重新監(jiān)聽的是sockfd(0.0.0.0:8000),雖然是占用同一個端口,但IP地址不同,fd對應(yīng)的是不某個客戶端通訊的一個具體的IP地址,而sockfd對應(yīng)的是wildcard address。解決這個問題的方法是使用setsockopt()設(shè)置socket描述符的選項SO_REUSEADDR為1,表示允許創(chuàng)建端口號相同但IP地址不同的多個socket描述符。
在server代碼的socket()和bind()調(diào)用之間插入如下代碼:
int opt = 1;
setsockopt(sockfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
首先將整個結(jié)構(gòu)體清零,然后設(shè)置地址類型為AF_INET,網(wǎng)絡(luò)地址為INADDR_ANY,這個宏表示本地的任意IP地址,因為服務(wù)器可能有多個網(wǎng)卡,每個網(wǎng)卡也可能綁定多個IP地址,這樣設(shè)置可以在所有的IP地址上監(jiān)聽,直到不某個客戶端建立了連接時才確定下來到底用哪個IP地址,端口號為SERV_PORT,我們定義為8000
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
三方插手完成后,服務(wù)器調(diào)用accept()接受連接,如果服務(wù)器調(diào)用accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來,cliaddr是一個傳出參數(shù),accept()返回時傳出客戶端的地址和端口基于UDP-服務(wù)器號.addrlen參數(shù)是一個傳入傳出參數(shù)(value-result argument),傳入的是調(diào)用者提供的緩沖區(qū)cliaddr的長度以避免緩沖區(qū)溢出問題,傳出的是客戶端地址結(jié)構(gòu)體的實際長度(有可能沒有占滿調(diào)用者提供的緩沖區(qū))。
如果給cliaddr參數(shù)傳NULL,表示不關(guān)心客戶端的地址
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
客戶端需要調(diào)用connect()連接服務(wù)器,connect和bind的參數(shù)形式一致,區(qū)別在于bind的參數(shù)是自己的地址,而connect的參數(shù)是對方的地址.
connect()成功返回0,出錯返回-1
14.基于TCP-服務(wù)器
1 創(chuàng)建一個socket,用函數(shù)socket()
2. 綁定IP地址、端口等信息到socket上,用函數(shù)bind()
3.設(shè)置允許的最大連接數(shù),用函數(shù)listen()
4.接收客戶端上來的連接,用函數(shù)accept()
5.收發(fā)數(shù)據(jù),用函數(shù)send()和recv(),或者read()和write()
6.關(guān)閉網(wǎng)絡(luò)連接
15.基于TCP-客戶端
1.創(chuàng)建一個socket,用函數(shù)socket()
2.設(shè)置要連接的對方的IP地址和端口等屬性
3.連接服務(wù)器,用函數(shù)connect()
4.收發(fā)數(shù)據(jù),用函數(shù)send()和recv(),或者
read()和write()
5.關(guān)閉網(wǎng)絡(luò)連接
16.基于UDP-服務(wù)器
1.創(chuàng)建一個socket信息到socket上,用函數(shù)bind()
3.循環(huán)接收數(shù)據(jù),用函數(shù)recvfrom()
4.關(guān)閉網(wǎng)絡(luò)連接
17.基于UDP-客戶端
1.創(chuàng)建一個socket,用函數(shù)socket()
2.綁定IP地址、端口等信息到socket上,用函數(shù)bind()
3.設(shè)置對方的IP地址和端口等屬性
4.發(fā)送數(shù)據(jù),用函數(shù)sendto()
5.關(guān)閉網(wǎng)絡(luò)連接
18.TCP與UDP區(qū)別·
UDP循環(huán)服務(wù)器的實現(xiàn)方法:UDP服務(wù)器每次從套接字上讀取一個客戶端的請求->處理->然后將結(jié)果返回給客戶機
socket(…);
bind(…);
while(1)
{
recvfrom(…);
process(…);
sendto(…);
}
因為UDP是非面向連接的,沒有一個客戶端可以老是占住服務(wù)端, 服務(wù)器對于每一個客戶機的請求總是能夠滿足
TCP服務(wù)器接受一個客戶端的連接,然后處理,完成了這個客戶的所有請求后,斷開連接。算法如下:
socket(…);
bind(…);
listen(…);
while(1)
{
accept(…);
process(…);
close(…);
}
并發(fā)服務(wù)器的思想是每一個客戶機的請求并不由服務(wù)器直接處理,而是由服務(wù)器創(chuàng)建一個 子進程來處理。算法如下:
socket(…);
bind(…);
listen(…);
while(1) {
accept(…);
if(fork(…)==0) {
process(…);
close(…);
exit(…);
}
close(…);
}
CP并發(fā)服務(wù)器可以解決TCP循環(huán)服務(wù)器客戶機獨占服務(wù)器的情況。但同時也帶來了問題:為了響應(yīng)客戶的請求,服務(wù)器要創(chuàng)建子進程來處理,而創(chuàng)建子進程是一種非常消耗資源的操作
19.多路復(fù)用I/O
阻塞函數(shù)在完成其指定的任務(wù)以前不允許程序繼續(xù)向下執(zhí)行.例如:當服務(wù)器運行到accept語句時,而沒有客戶請求連接,服務(wù)器就會停止在accept語句上等待連接請求的到來.這種情況稱為阻塞(blocking),而非阻塞操作則可以立即完成。例如,如果你希望服務(wù)器僅僅檢查是否有客戶在等待連接,有就接受連接,否則就繼續(xù)做其他事情,則可以通過使用select系統(tǒng)調(diào)用來實現(xiàn).除此之外,select還可以同時監(jiān)視多個套接字
Timeout取不同的值,該調(diào)用有不同的表現(xiàn):
Timeout值為0,不管是否有文件滿足要求,都立刻返回,無文件滿足要求返回0,有文件滿足要求返回一個正值。
Timeout為NULL,select將阻塞進程,直到某個文件滿足要求
Timeout值為正整數(shù),就是等待的最長時間,即select在timeout時間內(nèi)阻塞進程
Select調(diào)用返回時,返回值有如下情況:
1 正常情況下返回滿足要求的文件描述符個數(shù);
3. 經(jīng)過了timeout等待后仍無文件滿足要求,返回值為0;
4. 如果select被某個信號中斷,它將返回-1并設(shè)置errno為EINTR。
5. 如果出錯,返回-1并設(shè)置相應(yīng)的errno
20.
1.系統(tǒng)提供了4個宏對描述符集進行操作:
#include <sys/select.h>
void FD_SET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
void FD_ISSET(int fd, fd_set *fdset)
宏FD_SET將文件描述符fd添加到文件描述符集fdset中;
宏FD_CLR從文件描述符集fdset中清除文件描述符fd;
宏FD_ZERO清空文件描述符集fdset;
在調(diào)用select后使用FD_ISSET來檢測文件描述符集fdset中的文件fd發(fā)生了變化
設(shè)計步驟:
1.設(shè)置要監(jiān)控的文件
2. 調(diào)用Select開始監(jiān)控
3. 判斷文件是否發(fā)生變化
20.
總結(jié)
以上是生活随笔為你收集整理的Linux--网络编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。