1、TCP/IP協議概述
1.1、OSI參考模型及TCP/IP參考模型
OSI協議參考模型是基于國際標準化組織(ISO)的建議發展起來的,從上到下工分為7層:應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層,物理層。與此相區別的TCP/IP協議模型一開始就遵循簡單明確的設計思路,它將OSI的7層參考模型簡化為4層,從而得到有利于實現和使用。TCP/IP協議參考模型和OSI協議參考模型的對應關系如下圖所示:
網絡接口層:負責將二進制流轉換為數據幀,并進行數據幀的發送和接收。要注意的是數據幀是獨立的網絡星系傳輸單元。
網絡層:負責將數據幀封裝成IP數據報,并運行必要的路由算法。
傳輸層:負責端對端之間的通信會話連接和簡歷,傳輸協議的選擇根據數據傳輸方式而定。
應用層:負責應用層序的網絡訪問,這里通過端口來識別各個不同的進程。
1.2、TCP/IP協議族
TCP/IP是一個龐大的協議族,它包括了各個層次上的眾多協議,下圖列舉了各層只能怪一些重要的協議,并給出了協議在不同層次中處的位置:
ARP:用于獲得同一物理網絡中的硬件主機地址
MPLS:多協議標簽協議。
IP:負責在主機和網絡之間尋址和路由數據包。
ICMP:用于發送報告有關數據包的傳送錯誤的協議;
IGMP:被IP主機用來想本地多路廣播路由器報告主機組成員的協議;
TCP:為應用程序提供可靠的通信連接,適合于一次傳輸大批數據的情況,并適用于要求得到響應的應用程序。
UDP:提供了無連接通信,且不對傳遞包進行可靠的保證,適合于一次傳輸少量的數據,可靠性則由應用層來負責。
1.3、TCP和UDP
1、TCP
(1)概述 同其他特任何協議一樣,TCP想相鄰的高層提供服務,因為TCP的上一層就是應用層,因此,TCP數據傳輸實現了從一個應用程序到另一個應用程序的數據傳遞。應用程序通過編程調用TCP并使用TCP服務,提供需要準備發送的數據,用來區分接收數據應用的目的地址和端口號。 通常應用程序通過打開一個socket來使用TCP服務,TCP管理到其他socket的數據傳遞。可以說,通過IP的源/目的可以唯一的區分網絡中兩個設備的關聯。
(2)三次握手協議
TCP對話通過三次握手來初始化的。三次握手的目的是使數據段的發送和接搜同步,告訴其他主機其一次可接搜的數據量,并建立虛鏈接。
三次握手的簡單過程: 1、初始化主機通過一個同步標志置位的數據段發出會話請求; 2、接收主機通過發回具有以下項目的數據段表示回復:同步標志置位、即將發送的數據段的起始字節的順序號、應答并帶有將收到的下一個數據段的字節順序號。 3、請求主機再回一個數據段,并帶有確認順序號和確認號。 流程示意圖如下: TCP實體所采用的基本協議是滑動窗口協議。當發送方傳送一個數據報時,它將啟動計時器。當該數據報到達目的地后,接收方的TCP實體向回發送一個數據報,其中包含一個確認序號,它的意思是希望收到下一個數據報的順序號。如果發送方的定時器在確認信息到達之前超時,那么發送方會重發該數據報。
(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載荷,窗口比例、選擇重發數據報等選項。
2、UDP
UDP協議并不需要建立一個明確的連接,因此建立UDP應用要比建立TCP應用簡單得多。 UDP數據包頭如下圖所示: 源地址、目的地址、:16位長。標識遠端和本地的端口號。 數據報的長度是指包括報頭和數據部分在內的總的字節數,因為報頭的長度是固定的。所以該域主要用來計算可變長度的數據部分(又稱數據負載)
3、協議的選擇
協議的選擇要考慮三個方面: (1)對數據可靠性的要求 對數據要求高可靠性的應用需要選擇TCP協議,如驗證、密碼字段的傳送都是不允許出錯的,而對數據的可靠性要求不那么高的應用科選擇UDP傳送。 (2)應用的實時性 由于TCP協議在傳送過程中要進行三次握手、重傳確認等手段來保證數據傳輸的可靠性。使用TCP協議會有較大的延時。因此不適合對實時性較高的應用。如VOIP、視頻監控等。相反,UDP協議則在這些應用中能發揮很好的作用。 (3)網絡的可靠性 在網絡狀況不是很好的情況下需選用TCP協議(如廣域網等情況),但是若在網絡狀況很好的情況下(如局域網)選擇UDP協議來減少網絡負荷。
4、網絡基礎編程
4.1、socket概述
1、socket定義
在Linux中的網絡編程都是通過stocket接口來完成的。socket接口是一種特殊的I/O接口,它也是一種文件描述符。每一個socket都用一個半相關描述{協議,本地地址,本地端口}來表示;一個完整的套接字則用一個相關描述{協議,本地地址、本地端口、遠程地址、遠程端口}。socket也有一個類似打開文件的函數調用,該函數返回一個整型的socket描述符,隨后的連接、數據傳輸等操作都是通過socket來實現的。
2、socket類型
常見的socket類型有3種: (1)流式socket(SOCK_STREAM) 流式套接字提供可靠的、面向連接的通信流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性。 (2)數據報socket(SOCK_DGRAM) 數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文傳輸,是無序的。并且不保證是可靠的、無差錯的。它使用數據報協議UDP。 (3)原始socket 原始套接字允許對底層協議如IP或IGMP進行直接訪問,它功能強大但使用較為不便,主要用于一些協議的開發。 2.2、地址及順序處理 1、地址結構相關處理 (1)數據結構介紹 sockaddr和sockaddr_in兩個結構體用來保存socket的信息,如下所示:
struct sockaddr {
unsigned short sa_family;/*地址族*/
char sa_data[14];/*14字節的協議地址,包含該socket的IP地址和端口*/
};struct sockaddr_in {
short nt sa_family;/*地址族*/
unsigned short int sin_port;/*端口號*/
struct in_addr sin_addr;/*IP地址*/
unsigned char sin_zero[8];/*填充0以保持與struct sockaddr同樣大小*/
}; (2)結構字段
| 結構頭文件 | #include |
| sa_family | AF_INET:IPv4協議 |
| AF_INET6:IPv6協議 |
| AF_LOCAL:UNIX域協議 |
| AF_LINK:鏈路地址協議 |
| AF_KEY:密鑰套接字(socket) |
2、數據存儲優先順序 (1)函數說明 計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,因此在有些情況下,需要對這這兩個字節存儲優先吮吸進行相互轉化。四個函數:htons、ntohs、htonl、ntohl。 (2)函數格式
| 所需頭文件 | #include |
| 函數原型 | uint16_t htons(uint16_t host16bit) uint32_t htonl(uint16_t host32bit) uint16_t ntohs(uint16_t net16bit) uint16_t ntohl(uint16_t net32bit) |
| 參數 | host16bit:主機字節序的16bit數據 |
| host32bit:主機字節序的32bit數據 |
| net16bit:網絡字節序的16bit數據 |
| net32bit:網絡字節序的32bit數據 |
| 返回值 | 成功:返回要轉換的字節 |
| 失敗:-1 |
????????3、地址格式化轉化 (1)函數說明 通常用戶在表達地址時采用點分十進制的數值。而通常使用的socket編程中使用的則是二進制,這就需要將這兩個數值進行轉換。這里IPv4用到的函數有:inet_aton、inet_addr、和inet_ntoa,而IPv4和IPv6兼容的函數有inet_pton(點分十進制地址映射為二進制地址)和inet_ntop(二進制地址映射為點分十進制地址)。 (2)函數格式 inet_pton函數:
| 所需頭文件 | #include |
| 函數原型 | int inet_pton(int family,const char *strptr, void *addrptr) |
| 參數 | family: AF_INET:IPv4協議;AF_INET6:IPv6協議 |
| strptr:要轉化的值 |
| addrptr:轉化后的地址 |
| 返回值 | 成功:0 |
| 失敗:-1 |
????inet_ntop函數:
| 所需頭文件 | #include |
| 函數原型 | int inet_ntop(int family,void *addrptr, char *strptr, size_t len) |
| 參數 | family: AF_INET:IPv4協議;AF_INET6:IPv6協議 |
| strptr:要轉化的值 |
| addrptr:轉化后的地址;len:轉化后值得大小 |
| 返回值 | 成功:0 |
| 失敗:-1 |
4、名字地址轉化: (1)函數說明 在Linux中,有一些函數可以實現主機名和地址的轉化,最為常見的有:gethostbyname(主機名轉化為IP)、gethostbyaddr(IP地址轉化無主機名)、getaddrinfo(自動識別IPv4地址和IPv6地址)等。 gethostbyname(主機名轉化為IP)和gethostbyaddr(IP地址轉化無主機名)都涉及到一個hostent結構體: struct hostent {
char *h_name;/*正式主機名*/
char **h_aliases;/*主機別名*/
int h_addrtype;/*地址類型*/
int h_length;/*地址長度*/
char **h_addr_list;/*指向IPv4或IPv6的地址指針數組*/
};調用該函數后就能返回hostent結構體的相關信息。
getaddrinfo函數涉及到一個addrinfo結構體,如下:
struct addrinfo{
int ai_flags;/*AI_PASSIVE,A_CANONNAME*/
int ai_family;/*地址族*/
int ai_socktype;/*socket類型*/
int ai_protocol;/*協議類型*/
size_t ai_addrlen;/*地址長度*/
char *ai_canoname;/*主機名*/
struct sockaddr *ai_addr;/socket結構體/
struct addrinfo *ai_next;/*下一個指針鏈表*/
}
gethostbyname函數:
| 所需頭文件 | #include |
| 函數原型 | struct hostent *gethostbyname(const char *hostname) |
| 參數 | hostname :主機名 |
| 返回值 | 成功:hostent類型指針 |
| 失敗:-1 |
調用該函數時可以先對addrinfo結構體中的h_addrtype和h_length進行設置若為Ipv4可設置為AF_INET和4,若為IPv6可設置為AF_INET6和16,如不設置則默認為IPv4地址類型。 getaddrinfo函數:
| 頭文件 | #include |
| 函數原型 | int getaddringo(const *hostname,const char *service,const struct addrinfo *hints,struct addrinfo **result) |
| 參數 | hostname:主機名 |
| service:服務名或十進制的串口字符串 |
| hints:服務線索 |
| result:返回結果 |
| 返回值 | 成功:0 |
| 失敗:-1 |
在調用前,首先對hints服務線索進行設置。它是一個addrinfo結構體 addrinfo結構體常見選項值
| 結構體頭文件 | ? |
| ai_flags | AI_PASSIVE:該套接口是用作被動的打開 |
| AI_CANONNAME:通知getaddrinfo函數返回主機名字 |
| family | AF_INET:IPv協議 |
| AF_INET6:IPv6協議 |
| AF_UNSPE:IPv4或IPv6均可: |
| ai_socktype | SOCK_STREAM:字節流套接字socket(TCP) |
| SOCK_DGRAM:數據報套接字socket(UDP) |
| ai_protocol | IPPROTO_IP:IP協議 |
| IPPROTO_IPV4:IPv4協議 |
| IPPROTO_IPV6:IPv6協議 |
| IPPROTO_UDP:UDP協議 |
| IPPROTO_TCP:TCP協議 |
(3)使用實例: /*getaddrinfo.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>int main()
{
struct addrinfo hints, *res = NULL;int rc;
memset(&hints,0,sizeof(hints));/*設置addrinfo結構體各參數*/hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;/*調用getaddrinfo函數*/rc = getaddrinfo("127.0.0.1","123",&hints,&res);
if(rc != 0)
{
perror("getaddrinfo");
exit(1);
}else
printf("getaddrinfo success\n");}
4.3、socket基礎編程
(1)函數說明 進行socket編程的基本函數有socket、bind、listen、accept、send、sendto、recv、recvform這幾個; socket:該函數用于建立一個socket連接,可指定socket類型等信息,在建立socket連接之后可對sockaddr或sockaddr_in進行初始化,以保存所建立的socket信息。 bind:該函數用于將本地IP地址綁定端口號,若綁定其他地址則不成功,另外,它主要用于TCP的連接,而在UDP中的連接中無必要。 connect:該函數在TCP中是用于bind的之后的client端,用于與服務器建立連接,而在UDP中由于沒有了bind函數,因此connect有點類似bind函數的作用。 send和recv:這兩個函數用于接收和發送數據,可一再TCP中,也可以在UDP中。當用在UDP時,可以在connect函數建立連接之后再用。 sendto和recvfrom:這兩個函數的作用同send和recv函數類似,。當用在TCP時后面幾個與地址有關的參數起不了作用。函作用等同于send和recv;當用在UDP時,可以在之前沒有使用connect的情況時,這兩個函數可以自動尋找指定地址進行連接。 服務器和客服端使用TCP協議的流程如如下圖: 服務器和客戶端使用UDP協議的流程圖如下圖: (2)函數格式 socket函數
| 所需頭文件 | #include |
| 函數原型 | int socket(int family, int type, int protocol) |
| 參數 | family: 協議族 | AF_INET:IPv4協議 |
| AF_INET6:IPv6協議 |
| AF_LOCAL:UNIX域協議 |
| AF_ROUTE:路由套接字 |
| AF_KEY:密鑰套接字 |
| type: 套接字類型 | SOCK_STREAM:字節流套接字 |
| SOCK_DGRAM:數據報套接字 |
| SOCK_RAW:原始套接字 |
| protocol:0(原始套接字除外) |
| 返回值 | 成功:非負套接字描述符 |
| 失敗:-1 |
bind函數:
| 所需頭文件 | #include |
| 函數原型 | int bind(int sockfd,struct sockaddr *my_addr, int addrlen) |
| 參數原型 | sockfd:套接字描述符 |
| my_addr:本地地址 |
| addrlen:地址長度 |
| 返回值 | 成功:0 |
| 失敗:-1 |
端口號和地址在my_addr中給出了,若不指定地址,則內核隨意分配一個臨時端口給該應用程序。 listen函數:
| 所需頭文件 | #include |
| 函數原型 | int listen(int socket, int backlog) |
| 參數原型 | sockfd:套接字描述符 |
| backlog:請求隊列中允許的最大請求數,大多數系統缺省值為20 |
| 返回值 | 成功:0 |
| 失敗:-1 |
accept函數:
| 所需頭文件 | #include |
| 函數原型 | int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen) |
| 參數 | sockfd:套接字描述符 |
| addr:客戶端地址 |
| addrlen:地址長度 |
| 返回值 | 成功:0 |
| 失敗:-1 |
connect函數:
| 所需頭文件 | #include |
| 函數原型 | int connect(int sockfd,struct sockaddr *serv_addr,int addrlen) |
| 參數 | sockfd:套接字描述符 |
| serv_add:服務器地址 |
| addrlen:地址長度 |
| 返回值 | 成功:0 |
| 失敗:-1 |
send函數:
| 所需頭文件 | #include |
| 函數原型 | int send(int sockfd, const void *msg, int len, int flags) |
| 參數 | sockfd:套接字描述符 |
| msg:指向發送數據的指針 |
| len:數據長度 |
| flags:一般為0 |
| 返回值 | 成功:發送的字節數 |
| 失敗:-1 |
recv函數:
| 所需頭文件 | #include |
| 函數原型 | int recv(int sockfd, void *buf, int len, unsigned int flags) |
| 參數 | sockfd:套接字 描述符 |
| buf:存放接收數據的緩沖區 |
| len:數據長度 |
| flags:一般為0 |
| 返回值 | 成功:接收的字節數 |
| 失敗:-1 |
sendo函數:
| 所需頭文件 | #include |
| 函數原型 | int sendto(int sockfd, const void *msg,int len ,unsigned int flags,const struct sockaddr *to, int tolen) |
| 參數 | soctfd:套接字描述符 |
| msg:指向要發送數據的指針 |
| len:數據長度 |
| flags:一般為0 |
| to:目的機的IP地址和端口信息 |
| tolen:地址長度 |
| 返回值 | 成功:發送的字節數 |
| 失敗:-1 |
recvfrom函數:
| 所需頭文件 | #include |
| 函數原型 | int recvfrom(int sockfd, void *buf,int len, unsigned int flags,struct sockaddr *from,int *fromlen) |
| 參數 | sockfd:套接字描述符 |
| buf:存放接收數據的緩沖區 |
| len:數據長度 |
| flags:一般為0 |
| from:源機的IP地址和端口號信息 |
| fromto:地址長度 |
| 返回值 | 成功:接收的字節數 |
| 失敗:-1 |
(3)使用實例 /*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define SERVPORT 3333
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define MAXDATASIZE 5
int main()
{
struct sockaddr_in server_sockaddr,client_sockaddr;
int sin_size,recvbytes;
int sockfd,client_fd;
char buf[MAXDATASIZE];
/*建立socket連接*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("socket");
exit(1);
}
printf("socket success! sockfd = %d\n", sockfd);
/*設置sockaddr_in結構體相關參數*/
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(SERVPORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_sockaddr.sin_zero),8);
/*綁定函數bind*/
if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
printf("bind success!\n");
/*調用listen函數*/
if(listen(sockfd,BACKLOG) == -1)
{
perror("listen");
exit(1);
}
printf("listening...\n");
/*調用accept函數*/
if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)
{
perror("accept");
exit(1);
}
/*調用recv函數接收客戶端的請求*/
if((recvbytes = recv(client_fd,buf,MAXDATASIZE,0)) == -1)
{
perror("recv");
exit(1);
}
printf("received a connection : %s\n",buf);
close(sockfd);
}
?
/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERVPORT 3333
#define MAXDATASIZE 100
int main(int argc,char **argv)
{
int sockfd,sendbytes;
char buf[MAXDATASIZE];
struct hostent *host;
struct sockaddr_in serv_addr;
if(argc < 2)
{
fprintf(stderr,"Please enter the sercer's hostname!\n");
exit(1);
}
/*地址解析函數*/
if((host = (struct hostent *)gethostname(argv[1]))==NULL){
perror("gethostname");
exit(1);
}
/*創建socket*/
if((sockfd = socket(AF_INET, SOCK_STREAM,0)) == -1)
{
perror("socket");
exit(1);
}
/*設置sockaddr_in結構體相關參數*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVPORT);
serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
/*調用connect函數主動發起對服務端的連接*/
if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}
/*發送消息給服務器*/
if((sendbytes = send(sockfd,"hello",5,0)) == -1){
perror("send");
exit(1);
}
close(sockfd);
}
轉載于:https://www.cnblogs.com/ginvip/p/6364303.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的Linux应用程序设计之网络基础编程的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。