Linux 系统应用编程——网络编程(socket编程)
二、網絡編程基礎
1、套接字概述
?????? 套接字就是網絡編程的ID。網絡通信,歸根到底還是進程間的通信(不同計算機上的進程間的通信)。在網絡中,每一個節點(計算機或路由器)都有一個網絡地址,也就是IP地址,兩個進程通信時,首先要確定各自所在網絡節點的網絡地址。但是,網絡地址只能確定進程所在的計算機,而一臺計算機上很可能同時運行著多個進程,所以僅憑網絡地址還不能確定到底是和網絡中哪一個進程通信,因此套接口中還需要有其他的信息,也就是端口號(port)。在一臺計算機中,一個端口號一次只能分配給一個進程,也就是說,在一臺計算機中,端口號和進程之間是一一對應的關系。所以,使用端口號和網絡地址的組合就能唯一確定整個網絡中的一個網絡進程。
????? 把網絡地址和端口號信息放在一個結構體中,也就是套接口地址結構,大多數的套接口函數都需要一個指向套接口地址結構的指針作為參數,并以此來傳遞地址信息。每個協議族都定義了它自己的套接口地址結構,套接字地址結構都以"sockaddr_” 開頭,并以每個協議族名中兩個字母作為結尾。
???? 下面是socket所在位置:
可以看到套接口有3種類型:
1)流式套接字(SOCK_STREAM)
????? 流式套接字提供可靠的、面向連接的通信劉,保證數據傳輸的可靠性和按序收發。TCP通信使用的就是流式套接字。
2)數據包套接字(SOCK_DGRAM)
????? 數據報套接字實現了一種不可靠、無連接的服務。數據通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠的傳輸。UDP通信使用的就是數據報套接字。
3)原始套接字(SOCK_RAW)
???? 原始套接字允許對底層協議(如IP或ICMP)進行直接訪問,它功能強大但使用較為不便,主要用于一些協議的開發。
?
2、端口號
????? 這里的端口號是邏輯意義上的端口,一般是指TCP/IP 協議中的端口,端口號的范圍為0~65535,比如用于瀏覽網頁服務(HTTP協議)的80端口,用于FTP服務的21端口等。其中, 0 到1023 一般被系統程序所使用。
???? 那么TCP/IP協議中的端口指的是什么呢?舉個例子,如果IP地址唯一指定了地球上某個地理位置的一間房子,端口號就是出入這間房子的門,只不過這個房子的門有65536個之多,端口是通過端口號來標記的,端口號是一個16位的整數,范圍是從0~65535。
??? 端口號只具有本地意義,即端口號只是為了標識本地計算機上的各個進程。在互聯網中不同計算機的相同端口號是沒有聯系的。16bit 的端口號可允許有64K個端口號,這個數目對一個計算機來說是足夠用的。
?
3、IP地址
1)IP地址的作用
?????? IP地址用來表示網絡中的一臺主機。準確的說,IP地址是一臺主機到一個網絡的一個連接,因為現在一個主機中會有多個網卡。
??????一個IP地址包含兩部分:網絡號和主機號。其中,網絡號和主機號根據子網掩碼來區分。簡單的說,有了源IP 和目標 IP,數據包就能在不同主機之間傳輸。
2)IP地址格式轉換
??????IP地址有兩種不同格式:十進制點分形式和32位二進制形式。前者是用戶熟悉的形式,而后者則是網絡傳輸中IP地址的存儲方式。
????? 這里主要介紹IPV4地址轉換函數,主要有?inet_addr() 、inet_aton() 、inet_ntoa()?。前兩者的功能都是將字符串轉換成32位網絡字節序二進制值,第三個將32位網絡字節序二進制地址轉換成點分十進制的字符串。
inet_addr() 函數語法如下:
| 所需頭文件 | #include <arpa/inet.h> |
| 函數原型 | int? inet_addr(const char *strptr); |
| 參數 | strptr :要轉換的IP地址字符串 |
| 函數返回值 | 成功:32位二進制IP地址(網絡字節序) 出錯:-1 |
inet_aton() 函數語法如下:
| 所需頭文件 | #include <arpa/inet.h> |
| 函數原型 | int? inet_aton(int family, const char *src , void *drt); |
| 參數 | family?? AF_INET:IPV4協議 ?????????????AF_INET6:IPV6協議 src:要轉換的IP地址字符串 |
| 函數返回值 | 成功:32位二進制IP地址(網絡字節序) 出錯:-1 |
inet_ntoa() 函數語法如下:
| 所需頭文件 | #include <arpa/inet.h> |
| 函數原型 | int ?inet_ntoa(int family, const char *src , void *dst, size_t len); |
| 參數 | family?? AF_INET:IPV4協議 ?????????????AF_INET6:IPV6協議 src:要轉換的二進制IP地址; |
| 函數返回值 | 成功:返回dst 出錯:NULL |
?
4、字節序
? ? ??字節序又稱為主機字節序 Host Byte Order,HBO,是指計算機中多字節整型數據的存儲方式。字節序有兩種:大端(高位字節存儲在低位地址,低位字節存儲在高位地址)和小端(和大端序相反,PC通常采用小端模式)。
? ? ? 為什么需要字節序?在網絡通信中,發送方和接收方有可能使用不同的字節序;
為了保證數據接受后能被正確的解析處理,統一規定:數據以高位字節優先順序在網絡上傳輸。因此數據在發送前和接收后都需要在主機字節序和網絡字節序之間轉換。
1)函數說明
? ? ? 字節序轉換涉及4個函數:htons() 、ntohs() 、htonl() 和 ntohl()?。這里的 h 代表 host , n 代表 network , s 代表 short , l 代表 long 。通常 16bit 的IP端口號用前兩個函數處理,而 IP 地址用后兩個函數來轉換。調用這些函數只是使其得到相應的字節序,用戶不需要知道該系統的主機字節序和網絡字節序是否真的相等。如果兩個相同不需要轉換的話,該系統的這些函數會定義成空宏。
2)函數格式
| 所需頭文件 | #include <netinet/in.h> |
| 函數原型 | unit16_t htons(unit 16_t hostshort); unit32_t htonl(unit 32_t hostlong); unit16_t ntohs(unit 16_t netshort); unit32_t ntohl(unit 32_t netlong); |
| 函數傳入值 | hostshort:主機字節序的16bit 數據 hostlong :主機字節序的32t 數據 netshort ?:網絡字節序的16bit 數據 netlong:網絡字節序的32bitt 數據 |
| 函數返回值 | 成功:返回轉換字節序后的數值 出錯:-1 |
5、TCP編程
函數說明
socket()編程的基本函數有socket() 、bind()、listen()、accept()、send()、sendto()、recv()以及recvfrom()等。下面先簡單介紹上述函數的功能,再結合流程圖具體說明
1)socket() :該函數用于創建一個套接字,同時指定協議和類型。
2)bind() ? ? :該函數將保存在相應地址結構中的地址信息與套接字進行綁定。它主要用于服務器端,客戶端創建的套接字可以不綁定地址。
3)listen() ? :在服務端程序成功建立套接字并與地址進行綁定以后,通過調用listen() 函數將TCP連接后,該函數會返回一個新的已連接套接字。
5)connect():客戶端通過該函數向服務器端的監聽套接字發送連接請求。
6)send() 和 recv():這兩個函數通常在TCP通信過程中用于發送和接收數據,也可用于UDP中。
7)sendto()和recvfrom() :這兩個函數一般在UDP通信過程中用于發送和接受數據。當用于TCP時,后面的幾個與地址有關的參數不起作用,函數作用等同于 send() 和 recv()
服務器端和客戶端使用TCP的流程如下:
可以看到通信工作的大致流程如下:
1)服務器先用socket() 函數來建立一個套接口,用這個套接口完成通信的監聽及數據的收發;
2)服務器用bind() 函數來綁定一個端口號和IP地址,使套接口與制定的端口號和IP地址相關聯;
3)服務器調用listen()函數,使服務器的這個端口和IP處于監聽狀態,等待網絡中某一客戶機的連接請求。
4)客戶機調用socket()函數建立一個套接口,設定遠程IP和端口。
5)客戶機調用 connect() 函數鏈接遠程計算機指定的端口。
6)服務器調用 accept() 函數來接受遠程計算機的連接請求,建立起與客戶機之間的通信連接。
7)建立連接以后,客戶機用write() 函數 (或send()函數)向socket() 中寫入數據,也可以用 read() 函數(或recv()函數)讀取服務器發送來的數據。
8)服務器用 read()函數(或recv()函數)讀取客戶機發送來的數據,也可以用 write() 函數(或send()函數)來發送數據。
9)完成通信以后,使用close()函數關閉socket 連接。
函數格式:
1)創建套接口 socket() 函數
其語法要點
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int socket(int family, int type,int protocol); |
| 函數傳入值 | family:協議族 type:套接字類型 protocol:0(原始套接字除外) |
| 函數返回值 | 成功:非負套接字描述符 出錯:-1 |
參數family 指明協議族,取值如:
AF_INET:IPv4協議
AF_INET6:IPv6協議
AF_LOCAL:UNIX域協議
AF_ROUTE:路由套接字
AF_KEY:密鑰套接字
這里“AF”代表“Adress Family”(地址族)
types指明通信字節流類型,其取值如:
SOCK_STREAM:流式套接字(TCP方式)
SOCK_DGRAM:數據包套接字(UDP方式)
SOCK_RAM:原始套接字
2)綁定端口 bind()函數
? ? ?用socket() 函數創建一個套接口后,需要使用bind 函數在這個套接口上綁定一個指定的端口號和IP地址。bind函數原型如下:
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int bind(int sockfd, struct sockaddr *my_addr, int addrlen); |
| 函數傳入值 | sockfd:套接字描述符 my_addr:綁定的地址 addrlen:地址長度 |
| 函數返回值 | 成功:0 出錯:-1 |
這里my_addr是IPv4地址,IPv4 套接口地址數據結構以socketaddr_in 命名,定義在 <netinet/in.h>頭文件中,形式如下:
[cpp]?view plaincopy
sin_famliy 為套接字結構協議族,如IPv4為AF_INET;
sin_port ?是16位 TCP或UDP端口號,網絡字節順序;
結構體成員in_addr也是一個結構體,定義如下:
[cpp]?view plaincopy字節排序函數上面已經介紹,下面看一個實例:
[cpp]?view plaincopy
3)等待監聽函數
??????所謂監聽,指的是socket 的端口一直處于等待的狀態,監聽網絡中的所有客戶機,耐心等待某一客戶機發送請求。如果客戶端有連接請求,端口就會接受這個連接。listen 函數用于實現服務器的監聽等待功能,它的函數原型如下:
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int listen(int sockfd, int backlog); |
| 函數傳入值 | sockfd:套接字描述符 backlog:請求隊列中允許的最大請求數,大多數系統默認值為5 |
| 函數返回值 | 成功:0 出錯:-1 |
需要注意的是listen 并未真正的接受連接,只是設置socket 的狀態為監聽模式,真正接受客戶端連接的是accept 函數 。通常情況下,listen 函數會在 socket ,bind 函數之后調用,然后才會調用 accept 函數。
???? listen函數只適用于SOCK_STREAM或SOCK_SEQPACKET 的socket 類型。如果socket 為 AF_INET ,則參數 backlog 最大值可設至128,即最多可以同時接受128個客戶端的請求。
?
4)接受連接函數
????? 服務器處于監聽狀態時,如果模式可獲得客戶機的連接請求,此時并不是立即處理這個請求,而是將這個請求放在等待隊列中,當系統空閑時,再處理客戶機的連接請求,接受連接請求的函數時accept,函數原型如下:
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); |
| 函數傳入值 | sockfd:套接字描述符 my_addr:用于保存客戶端的地址,入參 addrlen:地址長度 |
| 函數返回值 | 成功:建立好連接的套接字描述符 出錯:-1 |
?當 accept 函數接受一個連接時,會返回一個新的 socket 標識符,以后的數據傳輸與讀取就是通過這個新的socket 編號來處理,原來參數中的 socket 也可以繼續使用。接受連接以后,遠程主機的地址和端口信息會保存在 addr 所指的結構體內。
下面是個實例,體驗listen 、accept函數的使用:
[cpp]?view plaincopy執行程序,得到輸出結果:
[cpp]?view plaincopy程序運行到這停止,并一直在這里等待,說明本機計算機的 2345號端口正處于監聽的狀態,等待本機上的連接服務請求。此時打開瀏覽器,在瀏覽器地址欄中輸入下列形式的地址:
[cpp]?view plaincopy這個地址是筆者個人IP地址,按"ENTER" 鍵,這樣瀏覽器會請求連接本地計算機上的2345號端口。此時終端中顯示如下結果:
[cpp]?view plaincopy表明程序已經接受了這個連接,并創建了一個新的套接口(ID為4),然后退出了程序。
?
5、請求連接函數
?????? 所謂請求連接,是指在客戶機向服務器發送信息之前,需要先發送一個連接請求,請求與服務器建立TCP通信連接。connect 函數可以完成這項功能,函數原型如下:
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int connect(int sockfd, struct sockaddr * serv_addr, int addrlen); |
| 函數傳入值 | sockfd:套接字描述符 sock_addr:服務器端地址 addrlen:地址長度 |
| 函數返回值 | 成功:0 出錯:-1 |
這里ser_addr 是一個結構體指針,指向一個sockaddr 結構體,這個結構體存儲著遠處服務器的IP與端口號信息。
6、數據讀寫函數
TCP/UDP讀寫函數總結,注意函數要成對使用
1)send函數
???? 建立套接口并完成通信連接以后,可以把信息傳送到遠程主機上,這個過程就是信息的發送。而對于遠程主機發送來的信息,本地主機需要進行接收處理。下面開始講述這種面向連接的套接口信息發送與接收操作。
????用connect 函數連接到遠程計算機以后,可以用 send 函數將應答信息發送給請求服務的本地主機,通信時雙向的,并且通信的雙方是對等的。
??? send() 函數原型如下:
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int send(int sockfd, const void*buf, int len, int flags); |
| 函數傳入值 | sockfd:套接字描述符 buf:發送緩沖區的地址 入參 len:發送數據的長度 flags:一般為0 |
| 函數返回值 | 成功:實際發送的字節數 出錯:-1 |
?
2)recv()函數
?????? 函數recv 可以接收遠程主機發送來的數據,并將這些數據保存到一個數組中,函數原型如下:
| 所需頭文件 | #include <sys/socket.h> |
| 函數原型 | ?int recv(int sockfd, const void*buf, int len, int flags); |
| 函數傳入值 | sockfd:套接字描述符 buf:存放接收數據的緩沖區 出參 len:接收數據的長度 flags:一般為0 |
| 函數返回值 | 成功:實際接收的字節數 出錯:-1 |
總結
以上是生活随笔為你收集整理的Linux 系统应用编程——网络编程(socket编程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oa人员导入模板_别拿OA不当系统,让C
- 下一篇: MySql ALTER用法