1.概念
套接字是專門進行網絡間數據通信的一種文件類型,可以實現不同主機之間雙向通信,包含了需要交換的數據和通信雙方的IP地址和port端口號。
2.套接字文件的創建
int socket(int domain, int type, int protocol);
功能:該函數用來創建各種各樣不同的套接字
參數參數 domain:套接字所依賴的網絡介質AF_INET:表明是 ipv4AF_INET6:表明是 ipv6AF_LOCAL/AF_UNIX:表明是本地通信,不是網絡通信,專門指代域套接字參數 type:套接字的類型,常用的類型就一下2種SOCK_STREAM:提供一個有序的,可靠的,雙向的(全雙工),基于連接的 字節流套接字一個超大的數據傳輸都是有可能支持SOCK_DGRAM:提供一個數據包(非連接的,不可靠的,有最大長度要求的)套接字字節流優點:允許發送無窮大的數據,只不過在內核中給這些數據做了分割而是,但是實際上由于連續發送的原因,這些數據最終還是粘連在一起字節流缺點:對于接受端來說,接受到的多組數據都粘連在一起了,所以需要額外花功夫去區分從哪到哪是一組數據數據報優點:數據不會粘連,發幾次數據就是幾次數據數據報缺點:發送的數據由于不會粘連,需要手動的將超大數據分批次發送,每次發送的數據大小有上限參數 protocol : 套接字依賴的通信協議一般直接寫 0 ,表示根據 參數type 和 參數 domain,自動選擇通信協議一般情況下:AF_INET + SOCK_STREAM + 0 ,最終創建是一個 TCP 套接字AF_INET + SOCK_DGRAM + 0 ,最終創建的是一個 UDP 套接字
3.TCP和UDP區別
tcp是可靠的,基于連接的字節流協議
tcp 擁有流量控制功能,順序控制功能,應答重發功能,以保證在網絡不擁堵的時候,所有數據都能正確發送
udp協議由于非連接,沒有可靠的應答手段
所以 udp協議傳輸效率高于tcp協議,傳輸的穩定性低于tcp協議
udp協議容易丟包,但是速度快
4.向套接字中寫入ip和port
4.1目的
客戶端:
寫入 ip 的目的:通過ip地址,找到該客戶端想要連接的服務器在哪
寫入 port的目的:通過port明確,想要發送的數據,應該發送到服務器的哪個進程里面、哪個端口里面
服務器:
寫入 ip 的目的:過濾掉一些不想接受連接的客戶端,指定僅僅只接受哪些客戶端的連接
如果寫入:
192.168.1.1 : 表示,只接受ip地址為 192.168.1.1 客戶端的連接
192.168.1.0 :表示,只接受這個網段下,所有客戶端的連接,這個網段下有幾個客戶端取決于子網掩碼
0.0.0.0 :表示,不做任何過濾,允許所有客戶端的連接,服務器套接字ip地址一般都寫這個
寫入port的目的:由于客戶端發送數據時候,只會向特定port中發送數據,所以服務器在讀取客戶端所發送數據的時候,一定要去客戶端所填寫的port中讀取數據
4.2為套接字寫入ip和port的函數
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:為套接字 sockfd 寫入 ip 和 port
參數 sockfd:準備寫入ip和port的套接字
參數 addr:類型為 sockaddr * ,是一個通用套接字結構體地址這里根據套接字種類的不同(ipv4,還是ipv6,還是本地域套接字),真實傳入的套接字結構體地址是不同的但是無論怎么不同,功能都是一樣這個參數的最終目的,都是用來描述套接字中的一些信息的比如說:tcp用的ipv4套接字,結構體里面就應該記錄了一個 ipv4的地址和一個port端口號tcp用的ipv6套接字,這里就應該傳入一個結構體,里面記錄了一個 ipv6地址和一個port端口號本地域套接字,這里就應該傳入一個結構體,里面記錄了一個本地套接字文件的路徑名我們如果使用的是 tcp套接字的話,這里要求提前準備一個struct sockaddr_in 類型的結構體
參數 addrlen:實際上就是參數 addr 的長度
5.通過套接字發送數據
專門針對套接字的發送函數
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:通過套接字sockfd,將數據發送到接收端
參數 sockfd、buf、len : 這3個參數和 write 一模一樣,意義也一樣
參數 flags : 唯一和write 不一樣的參數,這個參數一般只有2個選項0 :默認屬性,默認屬性下,send和write 一會兒事MSG_DONTWAIT : 填寫這個宏的話,會讓send函數稱為一個非阻塞型函數send 和 write 默認是阻塞函數send 和 write 什么時候會產生阻塞?當寫入數據的目標地點,接收區滿了之后,再次寫入數據,就會產生阻塞,等待接收區產生空余空間位置如果 send 和 write 變成 非阻塞函數之后接收區寫滿,再次send 或者 write 新寫入的數據將被丟棄,寫入失敗
返回值:成功返回寫入的數據的字節數,失敗返回 -1所以:send對比write的優點就是:send可以很輕松的在阻塞和非阻塞之間切換write雖然也可以在阻塞和非阻塞之間切換,但是操作比較復雜
6.通過套接字接收數據
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:通過套接字 sockfd接受網絡中發來的數據
參數 sockfd、buf、len 和read一樣
參數 flags :有2個選項0:默認屬性,默認狀態下,recv和read一模一樣MSG_DONTWAIT:使read函數稱為一個非阻塞函數返回值:阻塞模式下:成功接受數據,返回接受到的數據的字節數,套接子損壞,返回-1當阻塞模式 變成非阻塞模式后,recv就會一直返回0當客戶端與服務器連接中的時候,recv函數默認是一個阻塞函數當客戶端與服務器斷開鏈接后,recv函數就會從阻塞模式瞬間變成非阻塞模式所以,我們可以通過recv函數的返回值,判斷,客戶端\服務器是否下線非阻塞模式下:成功接受數據,返回接受到的數據的字節數如果沒有數據可接受,返回0如何客戶端與服務器斷開鏈接,返回-1總結:recv對比read優點recv可以輕松的切換成非阻塞模式read稍微要花點功夫
7.tcp服務器創建流程
1.創建服務器套接字
int server = socket(AF_INET,SOCK_STREAM,0);
2.準備struct sockaddr_in 結構體
將ip 和 port提前放在結構體中
struct sockaddr_in addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");
3.用bind函數講準備好的結構體中的信息寫入套接字
bind(server,(struct sockaddr*)&addr,sizeof(addr));
4.接收客戶端的連接
accept(server,(struct sockaddr*)&client_addr,&client_len);
5.用read/recv讀取客戶端發來的消息
6.用write/snd發送消息