目錄
OSI的七層模型的主要功能
tcp是什么
TCP三次握手
為什么需要三次握手,兩次握手不行嗎
TCP四次揮手
揮手會什么需要四次
什么是TCP粘包問題?發生的原因
原因
解決方案
UDP是什么
TCP和UDP的區別
網絡層常見協議
利用socket進行tcp傳輸代碼示例
客戶端
服務端
利用socket進行udp傳輸代碼示例
客戶端
服務端
注意tcp的send/recv和udp的sendto/recvfrom有一點區別
OSI的七層模型的主要功能
- **物理層:**利用傳輸介質為數據鏈路層提供物理連接,實現比特流的透明傳輸。
- **數據鏈路層:**接收來自物理層的位流形式的數據,并封裝成幀,傳送到上一層
- **網絡層:**將網絡地址翻譯成對應的物理地址,并通過路由選擇算法為分組通過通信子網選擇最適當的路徑。
- **傳輸層:**在源端與目的端之間提供可靠的透明數據傳輸
- **會話層:**負責在網絡中的兩節點之間建立、維持和終止通信
- **表示層:**處理用戶信息的表示問題,數據的編碼,壓縮和解壓縮,數據的加密和解密
- **應用層:**為用戶的應用進程提供網絡通信服務
tcp是什么
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議,即位于傳輸層。
TCP三次握手
三次握手(Three-way Handshake)其實就是指建立一個TCP連接時,需要客戶端和服務器總共發送3個包。進行三次握手的主要作用就是為了確認雙方的接收能力和發送能力是否正常、指定自己的初始化序列號為后面的可靠性傳送做準備。實質上其實就是連接服務器指定端口,建立TCP連接,并同步連接雙方的序列號和確認號,交換TCP窗口大小 信息。
為什么需要三次握手,兩次握手不行嗎
TCP四次揮手
建立一個連接需要三次握手,而終止一個連接要經過四次揮手(也有將四次揮手叫做四次握手的)。這由TCP的半關閉(half-close)造成的。所謂的半關閉,其實就是TCP提供了連接的一端在結束它的發送后還能接收來自另一端數據的能力。
TCP 的連接的拆除需要發送四個包,因此稱為四次揮手(Four-way handshake),客戶端或服務器均可主動發起揮手動作。
揮手會什么需要四次
什么是TCP粘包問題?發生的原因
一個完整的業務可能會被TCP拆分成多個包進行發送,也有可能把多個小的包封裝成一個大的數據包發送,即是無邊界的流傳輸,這個就是TCP的粘包問題。
- 封包:封包就是在發送數據報的時候為每個TCP數據包加上一個包頭,將數據報分為包頭和包體兩個部分。包頭是一個固定長度的結構體,里面包含該數據包的總長度。
- 拆包:接收方在接收到報文后提取包頭中的長度信息進行截取。
原因
1、應用程序寫入數據的字節大小大于套接字發送緩沖區的大小.
2、進行MSS大小的TCP分段。( MSS=TCP報文段長度-TCP首部長度)
3、以太網的payload大于MTU進行IP分片。(MTU指:一種通信協議的某一層上面所能通過的最大數據包大小。)
解決方案
1、消息定長。
2、在包尾部增加回車或者空格符等特殊字符進行分割
3、將消息分為消息頭和消息尾
4、使用其它復雜的協議,如RTMP協議等。
UDP是什么
是一種提供無連接的,盡最大努力的數據傳輸服務(不保證數據傳輸的可靠性)的通信協議,即位于傳輸層。
TCP和UDP的區別
1、TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的前不需要建立連接
2、TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付
3、TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流:UDP是面向報文的,UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
4、每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信
5、TCP首部開銷20字節;UDP的首部開銷小,只有8個字節
6、TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道
7、UDP是面向報文的,發送方的UDP對應用層交下來的報文,不合并,不拆分,只是在其上面加上首部后就交給了下面的網絡層,論應用層交給UDP多長的報文,它統統發送,一次發送一個。而對接收方,接到后直接去除首部,交給上面的應用層就完成任務了。因此,它需要應用層控制報文的大小
TCP是面向字節流的,它把上面應用層交下來的數據看成無結構的字節流會發送,可以想象成流水形式的,發送方TCP會將數據放入“蓄水池”(緩存區),等到可以發送的時候就發送,不能發送就等著TCP會根據當前網絡的擁塞狀態來確定每個報文段的大小。
網絡層常見協議
利用socket進行tcp傳輸代碼示例
客戶端
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080int main() {int sock = 0;struct sockaddr_in serv_addr;char buffer[1024] = {0};// 創建 socket 文件描述符if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {std::cout << "Socket creation error" << std::endl;return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 將服務端地址轉換為二進制if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {std::cout << "Invalid address / Address not supported" << std::endl;return -1;}// 連接到服務器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {std::cout << "Connection Failed" << std::endl;return -1;}// 發送數據到服務器const char *hello = "Hello from client";send(sock, hello, strlen(hello), 0);std::cout << "Hello message sent" << std::endl;// 讀取服務器發送的數據int valread = read(sock, buffer, 1024);std::cout << "Received: " << buffer << std::endl;close(sock);return 0;
}
服務端
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};// 創建 socket 文件描述符if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 將 socket 綁定到端口 8080if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");close(server_fd);exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 監聽連接if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}// 接受客戶端連接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}// 讀取客戶端發送的數據int valread = read(new_socket, buffer, 1024);std::cout << "Received: " << buffer << std::endl;// 發送數據到客戶端const char *hello = "Hello from server";send(new_socket, hello, strlen(hello), 0);std::cout << "Hello message sent" << std::endl;close(new_socket);close(server_fd);return 0;
}
利用socket進行udp傳輸代碼示例
客戶端
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080int main() {int sockfd;char buffer[1024];struct sockaddr_in servaddr;// 創建 socket 文件描述符if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));// 填寫服務器信息servaddr.sin_family = AF_INET;servaddr.sin_port = htons(PORT);servaddr.sin_addr.s_addr = INADDR_ANY;// 發送消息到服務器const char *hello = "Hello from client";sendto(sockfd, hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));std::cout << "Hello message sent." << std::endl;// 接收服務器消息int n;socklen_t len;n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);buffer[n] = '\0';std::cout << "Server : " << buffer << std::endl;close(sockfd);return 0;
}
服務端
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080int main() {int sockfd;char buffer[1024];struct sockaddr_in servaddr, cliaddr;// 創建 socket 文件描述符if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}memset(&servaddr, 0, sizeof(servaddr));memset(&cliaddr, 0, sizeof(cliaddr));// 填寫服務器信息servaddr.sin_family = AF_INET; // IPv4servaddr.sin_addr.s_addr = INADDR_ANY;servaddr.sin_port = htons(PORT);// 綁定 socketif (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}int n;socklen_t len;len = sizeof(cliaddr); // 客戶端地址長度// 接收客戶端消息n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);buffer[n] = '\0';std::cout << "Client : " << buffer << std::endl;// 發送消息到客戶端const char *hello = "Hello from server";sendto(sockfd, hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&cliaddr, len);std::cout << "Hello message sent." << std::endl;close(sockfd);return 0;
}