一、概述
- 計算機網絡
客戶與服務器之間的信息流在其中一端是向下通過協議棧的,跨越網絡後,在另一端則是向上通過協議棧的。
- 客戶與服務器通常是用戶進程,而TCP和IP協議通常是內核中協議棧的一部分
- 同一網絡應用的客戶和服務器無需圖1-3所示處在同一個局域網,圖1-4展示了處於不同局域網環境中的客戶和服務器,而這兩個局域網是使用路由器連接到廣域網的。
二、一個簡單的時間獲取客戶程序
TCP當前時間查詢客戶程序的實現,客戶與服務器建立一個TCP連接後,服務器以直觀可讀格式簡單會送當前時間和日期
#include<iostream>
#include<netinet/in.h>
#include <arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
# define MAXLINE 1024
int main(int argc, char*argv[]) {
int sockfd, n;
char recvline[MAXLINE+1];
struct sockaddr_in servaddr;//需要頭文件<netinet/in.h>
if(argc!=2){
cerr<<"parameters error,Usage:./Daytimetcpcli <IPaddressr>"<<endl;
exit(1); //需要<stdlib.h>頭文件
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){
cerr<<"socket error"<<endl;
exit(1); //需要<stdlib.h>頭文件
}
bzero(&servaddr,sizeof(servaddr));//需要<string.h>,也可以使用memset
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(13);//需要使用<arpa/inet.h>頭文件
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<0){//需要使用<arpa/inet.h>頭文件
cerr<<"server address error"<<endl;
exit(1); //需要<stdlib.h>頭文件
}
if(connect(sockfd,(sockaddr*)&servaddr,sizeof(servaddr))<0){
cerr<<"connect error"<<endl;
exit(1); //需要<stdlib.h>頭文件
}
while((n=read(sockfd,recvline,MAXLINE))>0){//需要包含頭文件<unistd.h>
recvline[n]='\0';
cout<<recvline<<endl;
}
if(n)<0{
cerr<<"read error"<<endl;
}
return 0;
}
說明:
socket函數創建一個網際(AF_INET)字節流(SOCK_STREAM)套接字,它是TCP套接字的一個花哨名字。該函數返回一個小整數描述符,以後的所有函數調用(如connect、read)就用該描述符來標識這個套接字
我們把服務器的IP地址和端口號填入一個網際套接字地址結構(一個名爲servaddr的sockaddr_in結構變量),使用bzero把這個整個結構清零後,置地址族爲AF_INET,端口號爲13(這是時間地址獲取服務器衆所周知的端口,支持該服務器的任何主機都使用這個端口號)。IP地址爲第一個命令行參數的值(argv[1]),網際套接字地址結構中IP地址和端口號這兩個成員必須使用特定格式,爲此我們調用庫函數htons(主機到網絡短整數)去轉換二進制端口號,又調用庫函數inet_pton(呈現形式到數值)去把ASCII命令行參數轉換爲合適的格式。
inet_pton是一個支持IPv6的新函數,以前的代碼中使用inet_addr函數來把ASCII點分十進制數串變換爲正確的格式,不該它有不少侷限,這些侷限在inet_pton中得以糾正
bzero不是一個ANSI C函數,它起源於早期的Berkeley網絡編程代碼,在本書中使用它而不是memset,因爲memset帶有三個參數,並且第二個參數和第三個參數的類型相同,如果不小心出錯,無法檢測。幾乎所有支持套接字API的廠商都提供bzero
connect函數用於一個套接字時,將與由它指定的第二個參數指向的套接字地址結構指定的服務器建立一個TCP連接,該套接字地址結構的長度作爲該函數的第三個參數指定,對於網際套接字地址結構,我們總是使用sizeof操作符由編譯器來計算這個長度
read函數讀取服務器的應答,TCP 是一個沒有記錄邊界的字節流協議,所以加上’\0’,如果服務器返回的數據量很大,我們不能保證一次read調用能返回服務器的整個應答。因此從TCP套接字讀取數據時,我們總是需要把read編寫在某個循環中,read返回零表示對端關閉連接,read返回負值表示發生錯誤
exit終止程序運行,Unix在一個進程終止時總是關閉該進程所有打開的描述符,我們的TCP套接字就此被關閉
三、協議無關性
上面的程序是與IPv4協議相關的:我們分配並初始化了一個sockaddr_in類型的結構,把該結構的協議族成員設置爲AF_INET,並指定socket的函數的第一個參數爲AF_INET
五、一個簡單的時間獲取服務器程序
- TCP套接字的創建和客戶端程序相同
- 把服務器的衆所周知的端口綁定到套接字
- 把套接字轉換爲監聽套接字:調用listen函數把該套接字轉換爲一個監聽套接字,這樣來自客戶的外來連接就可以在該套接字上由內核接受。LISTENQ指定系統內核允許在這個監聽描述符上排隊的最大客戶連接數。
- 接收客戶連接,發送應當:通常情況下,服務器進程在accept調用中被投入睡眠,等待某個客戶的連接的到達並內核接收。TCP連接使用所謂的三次握手來建立連接。握手完畢後accept返回,其返回值是一個稱爲已連接描述符的新描述符。該描述符用於與新近連接的那個客戶通信。accept爲每個連接到本服務器的客戶返回一個新的描述符
- 終止連接:服務器通過調用close關閉與客戶的連接,該調用引發正常的TCP連接終止序列。每個方向上發送一個FIN,每個FIN又由各自的對端確認。
- 本服務器一次只能處理一個客戶。如果多個客戶連接差不多同時到達,系統內核在某個最大數目的限制下把它們排入隊列。然後每個返回一個給accept函數。
七、OSI模型
國際標準化組織(ISO)的計算機通信開放系統互連(OSI)模型:應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層、物理層
網絡層由IPv4和IPv6兩個協議組成,可以選擇的傳輸層協議有TCP和UDP,但是應用層可以繞過傳輸層直接使用IPv4和IPv6,這就是所謂的原始套接字