跟指導老師聊到學校伺服器的配置管理
然後就問他伺服器之間的通訊協定
聊完覺得對通訊這部份有興趣,所以就打算寫寫看Socket
其實以前有用php玩過Socket連線
當時是想用php收Arduino的訊號儲存到資料庫
但是當時對Socket完全沒有概念,純粹只有找實作方法
但是最後還是沒有實作出來,這次打算先簡單用c++實作簡單的TCP/IP連線
第一次寫筆記型式的文章,想先強調幾點給有點進來的朋友
- 不保證所有資料全部正確,因為我的資料來源除了原文書籍、中文書籍,也有一些中英文非官方的網路文章。
- 筆記純粹給自己看,所以文字省略傳統邏輯、簡略,可能跳這跳那的
- 本次參考網頁:
ITREAD01網頁 C++ Socket學習筆記
LinuxHowtos Sockets Tutorial
楊凱州github網頁 TCP Socket 學習筆記 Programming0
鳥哥的 Linux 私房菜 - 本次參考書籍:
Linux Socket Programming by Example ,(Warren Gay,2000/04/01)
UNIX Network Programming (W. Richard Stevens,1998/01/15)
套接字(Socket)
Socket概念
Socket是對TCP/IP的封裝及應用
其本身不是協議,而是一個調用接口(API)
可支援不同的傳輸層協議(TCP或UDP)
應用層的資料傳輸需要網路層的TCP連線,其需要在相對應的同一TCP Port
而為了區別各個不同的TCP連線,作業系統為TCP/IP提供Socket介面
而Socket即所謂長連線(http屬於短連線),一旦client & server建立起連線將不會主動斷掉
除環境因素可能影響連線:防火牆、任何方主機死機、網路斷線…等
建立Socket連線
建立Socket連線至少需一對套接字,分別為ClientSocket及ServerSocket
建立連線過程分為三步驟:
- 伺服器監聽:
ServerSocket不定位特定的具體ClientSocket,而是處於等待連線狀態 - 客戶端請求:
ClientSocket提出連線請求,目標為ServerSocket(指向ip + TCP port) - 連線確認:
ServerSocket收到連線請求,建立執行序發送套接字描述,建立連線
OSI 網路模型
全名為開放式系統互連通訊參考模型(Open System Interconnection Refereance Model, OSI)
國際化標準組織(ISO)制定網路電腦互連的標準
分層
- 第一層:實體層(Physical Layer)
傳遞0,1訊號
ex: hub, RJ45網路線 - 第二層:資料鏈結層(Data-Link Layer)
分兩部份:
- 邏輯連結控制(Logical Link Control, LLC):訊框(Frame)傳遞、流程控制
- 媒介存取控制(Media Access Control ,MAC): 定義傳輸媒體存取方式
ex: 交換器
※ 訊框(Frame):封裝數位訊號成一組資料,其為資料訊框(Data Frame)
- 邏輯連結控制(Logical Link Control, LLC):訊框(Frame)傳遞、流程控制
- 第三層:網路層(Network Layer)
定義網路路徑及定址,以IP協定為基礎
觀念:IP Address+data=Packet
ex: ADSL小烏龜、路由器 - 第四層:傳輸層(Transport Layer)
負責資料傳輸及控制,即處理封包順序、流量、偵錯
ex: TCP、UDP - 第五層:會議層(Session Layer)
管理、設定通訊連接 ex: SQL - 第六層:表達層(Presentation Layer)
將資料轉換為receiver可接受的格式,處理轉碼、加解密等
- 第七層:應用層(Application Layer)
處理應用程式,提供使用者網路服務
ex: 常用通訊協定:DHCP、FTP、HTTP
ex: 應用軟體:瀏覽器
待學習Keyword: DHCP,Switch,Router,PPP
電腦網路通訊協定 TCP/IP
觀念
封包格式為連接導向的TCP或非連接導向的UDP
並且儲存含IP的表頭,並傳送Packet
※TCP: 完整協定,包含完整的連線過程:連線、傳送、結束
※UDP: 未建立完整連線即傳送封包
TCP補充觀念:TCP兩端的host透過連線來確保封包的傳遞,並且跟中間任何的router等等結點(node)無任何關聯
TCP的三向交握
目的: 建立可靠的連線
順序:
- 封包發起:
客戶端想要對伺服端連線時,必須送出連線封包。
隨機使用1個大於1024的port,並在TCP表頭當中帶有SYN的主動連線,並記下發給伺服端的序號(Sequence number = 10001) - 封包接收與確認封包傳送
伺服器接到這個封包,並且確定要接收這個封包後,就會開始製作一個同時帶有 SYN=1, ACK=1 的封包
其中那個 acknowledge 的號碼是要給 client 端確認用的,所以該數字會比(A 步驟)裡面的 Sequence 號碼多一號 (ack = 10001+1 = 10002)
那我們伺服器也必須要確認用戶端確實可以接收我們的封包才行,所以也會發送出一個 Sequence (seq=20001) 給用戶端,並且開始等待回應 - 回送確認封包
用戶端收到來自伺服器端的 ACK 數字後 (10002) 就能夠確認之前那個要求封包被正確的收受
接下來如果用戶端也同意與伺服器端建立連線時,就會再次的發送一個確認封包 (ACK=1) 給伺服器,亦即是 acknowledge = 20001+1 = 20002 - 最後確認
伺服器端收到帶有 ACK=1 且 ack=20002 序號的封包後,就能夠建立起這次的連線
Linux環境下c++時實作socket連線
該段參考部份楊凱州github網頁 TCP Socket 學習筆記 Programming0之程式碼
socket()函數
使用socket(int ,int ,int )
在kernel中建立一個socket,並傳回該socket的檔案描述符
int socket(int domain, int type, int protocol);
Domain
domain定義socket要在哪個領域溝通,以下列常用
* (1)AF_INET IPv4因特網域 (兩台主機透過網路進行資料傳輸)
* (2)AF_INET6 IPv6因特網域 (兩台主機透過網路進行資料傳輸)
* (3)AF_UNIX Unix域 (程序與程序間的傳輸)
Type
定義socket傳輸手段,以下列常用
* (1)SOCK_STREAM 序列化的連接導向通訊。對應的protocol為TCP。
* (2)SOCK_DGRAM 提供的是一個一個的資料包(datagram)。對應的protocol為UDP
Protocol
設定socket的協定標準,一般來說都會設為0,讓kernel選擇type對應的默認協議。
Socket()函數回傳值Return Value
成功產生socket時,會返回該socket的檔案描述符(socket file descriptor)
我們可以透過它來操作socket。若socket創建失敗則會回傳-1,
//建立一個socket
#include <iostream>
#include <sys/socket.h>
using namespace std;
int main()
{
int sockfd=0;
sockfd=socket(AF_INET , SOCK_STREAM , 0);
if (sockfd== -1){
cout<<"Fail to create a socket.";
}
return 0;
}
connect()函數
處理網路服務時,如TCP,ClientSocket需與ServerSocket建立連接
TCP Client可以connect()函式與Server端建立連結
int connect(int sockfd, struct sockaddr_in *server, int addr_len);
sockfd
sockfd即socket()函數之回傳值,為socket的描述符
* (1)AF_INET IPv4因特網域 (兩台主機透過網路進行資料傳輸)
* (2)AF_INET6 IPv6因特網域 (兩台主機透過網路進行資料傳輸)
* (3)AF_UNIX Unix域 (程序與程序間的傳輸)
server
關於這個socket的所有信息
netinet/in.h
已經為我們定義好了一個struct sockaddr_in
來儲存這些資訊
// IPv4 AF_INET sockets:
// IPv6參見 sockaddr_in6
struct sockaddr_in {
short sin_family; // AF_INET
unsigned short sin_port; // 儲存port number
struct in_addr sin_addr;
char sin_zero[8]; // Not used, must be zero
};
struct in_addr {
unsigned long s_addr; // load with inet_pton()
};
以下為設定
struct sockaddr_in info;
bzero(&info,sizeof(info)); //初始化 將struct涵蓋的bits設為0
info.sin_family = PF_INET; //sockaddr_in為Ipv4結構
info.sin_addr.s_addr = inet_addr("123.123.13.12"); //IP address
info.sin_port = htons(8080);
PF_INET ?
AF = Address Family
PF = Protocol Family
AF_INET = PF_INET
理論上建立socket時是指定協議,應該用PF_xxxx,設置地址時應該用AF_xxxx,但混用不會有太大問題
inet_addr?
一般163.23.148.100這種ip地址為ASCII表示法
用inet_addr轉為整數型式ip,其為binary data
hton ?
網路端的字節序與本機端的字節序可能不一致 網路端總是用Big endian,而本機端卻要視處理器體系而定,使用的是Little endian。 htons()就是Host TO Network Short integer的縮寫,它將本機端的字節序(endian)轉換成了網路端的字節序
addr_len
就是*server的大小
Return value
成功回傳0,失敗傳-1