一、TCP/IP協議概述
1.一、OSI參考模型及TCP/IP參考模型
OSI協議參考模型是基於國際標準化組織(ISO)的建議發展起來的,從上到下工分爲7層:應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層,物理層。與此相區別的TCP/IP洗衣模型一開始就遵循簡單明確的設計思路,它將OSI的7層參考模型簡化爲4層,從而獲得有利於實現和使用。TCP/IP協議參考模型和OSI協議參考模型的對應關係以下圖所示:
網絡接口層:負責將二進制流轉換爲數據幀,並進行數據幀的發送和接收。要注意的是數據幀是獨立的網絡星系傳輸單元。
網絡層:負責將數據幀封裝成IP數據報,並運行必要的路由算法。
傳輸層:負責端對端之間的通訊會話鏈接和簡歷,傳輸協議的選擇根據數據傳輸方式而定。
應用層:負責應用層序的網絡訪問,這裏經過端口來識別各個不一樣的進程。
1.二、TCP/IP協議族
TCP/IP是一個龐大的協議族,它包括了各個層次上的衆多協議,下圖列舉了各層只能怪一些重要的協議,並給出了協議在不一樣層次中處的位置:
ARP:用於得到同一物理網絡中的硬件主機地址
MPLS:多協議標籤協議。
IP:負責在主機和網絡之間尋址和路由數據包。
ICMP:用於發送報告有關數據包的傳送錯誤的協議;
IGMP:被IP主機用來想本地多路廣播路由器報告主機組成員的協議;
TCP:爲應用程序提供可靠的通訊鏈接,適合於一次傳輸大批數據的狀況,並適用於要求獲得響應的應用程序。
UDP:提供了無鏈接通訊,且不對傳遞包進行可靠的保證,適合於一次傳輸少許的數據,可靠性則由應用層來負責。
1.三、TCP和UDP
一、TCP
(1)概述
同其餘特任何協議同樣,TCP想相鄰的高層提供服務,由於TCP的上一層就是應用層,所以,TCP數據傳輸實現了從一個應用程序到另外一個應用程序的數據傳遞。應用程序經過編程調用TCP並使用TCP服務,提供須要準備發送的數據,用來區分接收數據應用的目的地址和端口號。
一般應用程序經過打開一個socket來使用TCP服務,TCP管理到其餘socket的數據傳遞。能夠說,經過IP的源/目的能夠惟一的區分網絡中兩個設備的關聯。
(2)三次握手協議
TCP對話經過三次握手來初始化的。三次握手的目的是使數據段的發送和接搜同步,告訴其餘主機其一次可接搜的數據量,並創建虛連接。
三次握手的簡單過程:
一、初始化主機經過一個同步標誌置位的數據段發出會話請求;
二、接收主機經過發回具備如下項目的數據段表示回覆:同步標誌置位、即將發送的數據段的起始字節的順序號、應答並帶有將收到的下一個數據段的字節順序號。
三、請求主機再回一個數據段,並帶有確認順序號和確認號。
流程示意圖以下:
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載荷,窗口比例、選擇重發數據報等選項。
二、UDP
UDP協議並不須要創建一個明確的鏈接,所以創建UDP應用要比創建TCP應用簡單得多。
UDP數據包頭以下圖所示:
源地址、目的地址、:16位長。標識遠端和本地的端口號。
數據報的長度是指包括報頭和數據部分在內的總的字節數,由於報頭的長度是固定的。因此該域主要用來計算可變長度的數據部分(又稱數據負載)
三、協議的選擇
協議的選擇要考慮三個方面:
(1)對數據可靠性的要求
對數據要求高可靠性的應用須要選擇TCP協議,如驗證、密碼字段的傳送都是不容許出錯的,而對數據的可靠性要求不那麼高的應用科選擇UDP傳送。
(2)應用的實時性
因爲TCP協議在傳送過程當中要進行三次握手、重傳確認等手段來保證數據傳輸的可靠性。使用TCP協議會有較大的延時。所以不適合對實時性較高的應用。如VOIP、視頻監控等。相反,UDP協議則在這些應用中能發揮很好的做用。
(3)網絡的可靠性
在網絡情況不是很好的狀況下需選用TCP協議(如廣域網等狀況),可是若在網絡情況很好的狀況下(如局域網)選擇UDP協議來減小網絡負荷。
二、網絡基礎編程
2.一、socket概述
一、socket定義
在Linux中的網絡編程都是經過stocket接口來完成的。socket接口是一種特殊的I/O接口,它也是一種文件描述符。每個socket都用一個半相關描述{協議,本地地址,本地端口}來表示;一個完整的套接字則用一個相關描述{協議,本地地址、本地端口、遠程地址、遠程端口}。socket也有一個相似打開文件的函數調用,該函數返回一個整型的socket描述符,隨後的鏈接、數據傳輸等操做都是經過socket來實現的。
二、socket類型
常見的socket類型有3種:
(1)流式socket(SOCK_STREAM)
流式套接字提供可靠的、面向鏈接的通訊流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性。
(2)數據報socket(SOCK_DGRAM)
數據報套接字定義了一種無鏈接的服務,數據經過相互獨立的報文傳輸,是無序的。而且不保證是可靠的、無差錯的。它使用數據報協議UDP。
(3)原始socket
原始套接字容許對底層協議如IP或IGMP進行直接訪問,它功能強大但使用較爲不便,主要用於一些協議的開發。
2.二、地址及順序處理
一、地址結構相關處理
(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<netinet/in.h> |
sa_family |
AF_INET:IPv4協議 |
AF_INET6:IPv6協議 |
AF_LOCAL:UNIX域協議 |
AF_LINK:鏈路地址協議 |
AF_KEY:密鑰套接字(socket) |
二、數據存儲優先順序
(1)函數說明
計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以在有些狀況下,須要對這這兩個字節存儲優先吮吸進行相互轉化。四個函數:htons、ntohs、htonl、ntohl。
(2)函數格式
所需頭文件 |
#include<netinet/in.h> |
函數原型 |
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 |
三、地址格式化轉化
(1)函數說明
一般用戶在表達地址時採用點分十進制的數值。而一般使用的socket編程中使用的則是二進制,這就須要將這兩個數值進行轉換。這裏IPv4用到的函數有:inet_aton、inet_addr、和inet_ntoa,而IPv4和IPv6兼容的函數有inet_pton(點分十進制地址映射爲二進制地址)和inet_ntop(二進制地址映射爲點分十進制地址)。
(2)函數格式
inet_pton函數:
所需頭文件 |
#include <arpa/inet.h> |
函數原型 |
int inet_pton(int family,const char *strptr, void *addrptr) |
參數 |
family: AF_INET:IPv4協議;AF_INET6:IPv6協議 |
strptr:要轉化的值 |
addrptr:轉化後的地址 |
返回值 |
成功:0 |
失敗:-1 |
inet_ntop函數:
所需頭文件 |
#include <arpa/inet.h> |
函數原型 |
int inet_ntop(int family,void *addrptr, char *strptr, size_t len) |
參數 |
family: AF_INET:IPv4協議;AF_INET6:IPv6協議 |
strptr:要轉化的值 |
addrptr:轉化後的地址;len:轉化後值得大小 |
返回值 |
成功:0 |
失敗:-1 |
四、名字地址轉化:
(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 <netdb.h> |
函數原型 |
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 <netdb.h> |
函數原型 |
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");
-
-
- }
(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 <sys/socket.h> |
函數原型 |
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<sys/socket.h> |
函數原型 |
int bind(int sockfd,struct sockaddr *my_addr, int addrlen) |
參數原型 |
sockfd:套接字描述符 |
my_addr:本地地址 |
addrlen:地址長度 |
返回值 |
成功:0 |
失敗:-1 |
端口號和地址在my_addr中給出了,若不指定地址,則內核隨意分配一個臨時端口給該應用程序。
listen函數:
所需頭文件 |
#include<sys/socket.h> |
函數原型 |
int listen(int socket, int backlog) |
參數原型 |
sockfd:套接字描述符 |
backlog:請求隊列中容許的最大請求數,大多數系統缺省值爲20 |
返回值 |
成功:0 |
失敗:-1 |
accept函數:
所需頭文件 |
#include<sys/socket.h> |
函數原型 |
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen) |
參數 |
sockfd:套接字描述符 |
addr:客戶端地址 |
addrlen:地址長度 |
返回值 |
成功:0 |
失敗:-1 |
connect函數:
所需頭文件 |
#include<sys/socket.h> |
函數原型 |
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen) |
參數 |
sockfd:套接字描述符 |
serv_add:服務器地址 |
addrlen:地址長度 |
返回值 |
成功:0 |
失敗:-1 |
send函數:
所需頭文件 |
#include<sys/socket.h> |
函數原型 |
int send(int sockfd, const void *msg, int len, int flags) |
參數 |
sockfd:套接字描述符 |
msg:指向發送數據的指針 |
len:數據長度 |
flags:通常爲0 |
返回值 |
成功:發送的字節數 |
失敗:-1 |
recv函數:
所需頭文件 |
#include<sys/socket.h> |
函數原型 |
int recv(int sockfd, void *buf, int len, unsigned int flags) |
參數 |
sockfd:套接字 描述符 |
buf:存放接收數據的緩衝區 |
len:數據長度 |
flags:通常爲0 |
返回值 |
成功:接收的字節數 |
失敗:-1 |
sendo函數:
所需頭文件 |
#include<sys/socket.h> |
函數原型 |
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<sys/socket.h> |
函數原型 |
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);
- }