1.TCP特點
1.面向數據流;
2.有連接通信;
3.安全可靠的通信方式;
4.機制復雜,網絡資源開銷大;
5.本質只能實現一對一的通信(可使用TCP的并發方式實現一對多通信);
2.TCP的三次握手與四次揮手
1.TCP的三次握手
TCP建立連接時,需要進行三次握手,以確保收發雙方通信之前都已就緒;
2.TCP的四次揮手
TCP斷開連接時,需要四次揮手,以確保斷開連接前雙方都以通信結束;
SYN:請求建立連接標志;
FIN:請求斷開連接標志;
ACK:響應報文標志位;
3.TCP的編程流程
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:請求與服務端建立連接
參數:
sockfd:套接字
addr:要連接的服務端的地址信息
addrlen:服務端地址大小
返回值:
成功:0
失敗:-1?ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:發送網絡數據
參數:
sockfd:網絡套接字
buf:要發送的數據首地址
len:發送的字節數
flags:0 :按照默認方式發送
返回值:
成功:實際發送的字節數
失敗:-1int listen(int sockfd, int backlog);
功能:監聽建立三次握手的客戶端
參數:
sockfd:監聽套接字
backlog:最大允許監聽的客戶端個數
返回值:
成功:0
失敗:-1int accept(int socket, struct sockaddr *restrict address,
socklen_t *restrict address_len);
功能:接收建立三次握手的客戶端,并產生一個通訊套接字
參數:
socket:監聽套接字
address:客戶端的地址信息
address_len:客戶端地址長的指針
返回值:
成功:通訊套接字
失敗:-1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:從網絡套接字上接收數據
參數:
sockfd:通訊套接字
buf:存放接收數據的首地址
len:期望接收到的字節數
flag : 0:默認方式接收(阻塞)
返回值:
成功:實際接收到的字節數
失敗:-1
對方斷開連接:0
4.TCP的粘包問題
TCP粘包問題:發送方應用層發送的多包數據,將來在接收方可能一次讀到,多包數據產生了粘連。
? ? ? 原因:
1. 發送方速度較快,TCP底層可能對多包數據進行重新組幀;
2. 接收方數據處理速度較慢,導致多包數據在接收緩沖區緩存,應用層讀時,一次將多包數據讀出。? ? ?
解決粘包問題的常用方法:
1. ?調整發送速率
2. ?發送指定大小,將來接收方也接受指定大小。
結構體
注意:
1. 跨平臺之間的數據傳輸時,注意結構體對齊問題。
struct a
{
char a;
int b;
long c;
};
32bits平臺《--》64位平臺? ? ? ? ?3. 應用層位發送的數據增加分隔符,利用分隔符解析
hello world\nhow are you\n? ? ? ? ?4. 封裝自定義數據幀格式進行發送(協議),嚴格根據協議進行解析。
AA ?C0 ?00 00 00 F0 00 BB 10 A0 ?00 00 00 10 校驗 BB ?AA ?C0 ?00 00 00 F0 00 BB 10 A0 ?00 00 00 10 校驗 BB AA ?C0 ?00 00 00 F0 00 BB 10 A0 ?00 00 00 10 校驗 BB
幀頭:AA
幀尾:BB
有效數據長度:C0
有效數據:00 00 00 F0 00 BB 10 A0 ?00 00 00 10
校驗:
8位和校驗
16位和校驗
CRC校驗
5.代碼練習
1. tcp實現圖片的傳輸
//客戶端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{if(argc != 2){printf("./a.out <filename>\n");return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");return -1;}struct sockaddr_in sockaddr;sockaddr.sin_family = AF_INET;sockaddr.sin_port = htons(50002);sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");int connfd = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));int fd = open(argv[1], O_RDONLY);if(fd < 0){perror("open error");return -1;}char buf[1024] = {0};int ret = 0;do{ ret = read(fd, buf, sizeof(buf));size_t conter = send(sockfd, buf, ret, 0);}while(ret > 0);close(fd);close(sockfd);return 0;
}//服務端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{if(argc != 2){printf("./a.out <filename>\n");return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");return -1;}struct sockaddr_in sockaddr;sockaddr.sin_family = AF_INET;sockaddr.sin_port = htons(50002);sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");int ret = bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));if(ret < 0){perror("bind error");return -1;}int n = listen(sockfd, 10);if(n < 0){perror("listen error");return -1;}struct sockaddr_in dest_sockaddr;socklen_t len = sizeof(dest_sockaddr);int connfd = accept(sockfd, (struct sockaddr *)&dest_sockaddr, &len);printf("[%s][%d] online\n", inet_ntoa(dest_sockaddr.sin_addr), ntohs(dest_sockaddr.sin_port));int fd = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0664);if(fd < 0){perror("open error");return -1;}char buf[1024] = {0};ssize_t cont = 0;do{ memset(buf, 0, sizeof(buf));cont = recv(connfd, buf, sizeof(buf), 0);write(fd, buf, cont); }while(cont > 0);close(connfd);close(sockfd);return 0;
}
2. tcp實現全雙工聊天
//客戶端A:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>int sockfd = 0;
struct sockaddr_in sockaddr;int send_t()
{char buf[1024] = {0};while(1){ fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;size_t conter = send(sockfd, buf, strlen(buf), 0);if(conter > 0){printf("conter=%ld\n", conter);}if(conter < 0){perror("send error");return -1;}}close(sockfd);
}int recv_t()
{char buf[1024] = {0};do{ memset(buf, 0, sizeof(buf));ssize_t cont = recv(sockfd, buf, sizeof(buf), 0);if(cont > 0){printf("cont = %ld, buf = %s\n", cont, buf);}if(cont < 0){perror("send error");return -1;} }while(1);close(sockfd);
}int main(int argc, char const *argv[])
{sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");return -1;}sockaddr.sin_family = AF_INET;sockaddr.sin_port = htons(50001);sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");int connfd = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));//send_t();pid_t pid = fork();if(pid > 0){send_t(); }else if(pid == 0){recv_t(); }else{perror("fork error");return -1;}return 0;
}//服務端B
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>struct sockaddr_in dest_sockaddr;
socklen_t len = sizeof(dest_sockaddr);
int sockfd = 0;
int connfd = 0;int recv_t()
{char buf[1024] = {0};do{ memset(buf, 0, sizeof(buf));ssize_t cont = recv(connfd, buf, sizeof(buf), 0);if(cont > 0){printf("cont = %ld, buf = %s\n", cont, buf);}if(cont < 0){perror("send error");return -1;} }while(1);close(connfd);close(sockfd);
}int send_t()
{ char buf[1024] = {0};while(1){ fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;size_t conter = send(connfd, buf, strlen(buf), 0);if(conter > 0){printf("conter=%ld\n", conter);}if(conter < 0){perror("send error");return -1;}}close(connfd);close(sockfd);
}int main(int argc, char const *argv[])
{sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");return -1;}struct sockaddr_in sockaddr;sockaddr.sin_family = AF_INET;sockaddr.sin_port = htons(50001);sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");int ret = bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));if(ret < 0){perror("bind error");return -1;}int n = listen(sockfd, 10);if(n < 0){perror("listen error");return -1;}connfd = accept(sockfd, (struct sockaddr *)&dest_sockaddr, &len);printf("[%s][%d] online\n", inet_ntoa(dest_sockaddr.sin_addr), ntohs(dest_sockaddr.sin_port));//recv_t();pid_t pid = fork();if(pid > 0){recv_t();}else if(pid == 0){send_t();}else {perror("fork error");return -1;}return 0;
}
6.TCP頭部標志位
TCP報文頭部:
SYN:請求建立連接標志位;
ACK:響應標志位;
FIN:請求斷開連接標志位;
PSH:攜帶數據標志位,通知接收方從緩沖區讀取數據;
URG:緊急指針標志位;
RST:復位標志位/重置標志位;
7.TCP的其他機制
(1)確保安全可靠
1.三次握手與四次揮手機制
2.應答機制
sequence number:序列號;
Acknowledgment number:響應序列號;
TCP對于每一包數據都會有相應的應答;
發送數據時,序列號表示這包數據的起始編號,確認時,響應報文中的響應序列號為接收方收到的最后一個字節編號+1(即為期待下次希望收到數據的起始號,以確保數據的安全可靠)
3.超時重傳機制
當數據發出,在指定時間內(根據當前網絡狀態,TCP實時更新)未收到響應,此時認為數據丟失,則會重新發送這包數據;
4.滑動窗口機制
使用緩沖區實現TCP已發送未響應、準備發送的數據的緩存,確保數據重新傳時,可以找到相應數據;
(2)提高效率
1.延遲應答機制
連續發送數據的同時,等待對方的響應;
2.流量控制機制
結合TCP頭部窗口大小,動態調整接受速率;
3.捎帶應答機制
ACK可能與應用層數據同時發送;