一、TCP
TCP?:?傳輸控制協議???傳輸層
1.?TCP特點
(1).面向連接,避免部分數據丟失
(2).安全、可靠
(3).面向字節流
(4).占用資源開銷大
2.TCP安全可靠機制
三次握手:指建立tcp連接時,需要客戶端和服務端總共發送三次報文確認連接。確保雙方均已做好? ? ? ? ? ? ? ? ? 收發數據的準備-----只能由客戶端發起
ACK:響應報文? ? SYN(TPC頭部標志位)請求建立連接? ? ?FIN請求斷開連接
四次揮手:斷開一個tcp連接,需要客戶端和服務端發送四個報文以確認斷開。確保斷開連接前收? ? ? ? ? ? ? ? ? ? 發數據均已完成 ,當服務端沒有數據需要發送,即不需要建立下一次連接時,服務端將? ? ? ? ? ? ? ? ? ? ? ? ? ACK和FIN一起發送出去,即三次揮手. 三次揮手不能確認服務端每一次有無下一次連接.
3.TCP
注:服務端創建了兩個套接字,recv里接接收的是第二個套接字(通信套接字)
(1).socket
?socket(AF_INET,?SOCK_STREAM,?0);
(2).connect
? int?connect(int?sockfd,?const?struct?sockaddr?*addr,socklen_t?addrlen);
功能:發送三次握手鏈接請求
參數:sockfd:套接字文件描述符
addr:存放目的地址空間首地址
addrlen:目的地址長度
返回值: 成功返回0? 失敗返回-1
(3).send
?ssize_t?send(int?sockfd,?const?void?*buf,?size_t?len,?int?flags);
功能:發送數據
參數:sockfd:套接字文件描述符
buf:存放數據空間首地址
len:數據長度
flag:屬性默認為0?
返回值:成功返回發送字節數? ;? 失敗返回-1?
(4).recv
?ssize_t?recv(int?sockfd,?void?*buf,?size_t?len,?int?flags);
功能:接收數據?
參數:sockfd:套接字文件描述符
buf:存放數據空間首地址?
len:最多接收數據長度?
flags:接收屬性默認為0?
返回值:成功返回實際接收字節數? ;? 失敗返回-1 ;? 連接斷開返回0?
(5).bind
int?bind(int?sockfd,?const?struct?sockaddr?*addr,socklen_t?addrlen);
(6).listen
int?listen(int?sockfd,?int?backlog);
功能:監聽三次握手鏈接請求
參數:sockfd:套接字文件描述符
backlog:最多允許等待尚未處理的三次握手鏈接個數
返回值:成功返回0 ; 失敗返回-1?
(7).accept
? int?accept(int?sockfd,?struct?sockaddr?*addr,?socklen_t?*addrlen);
功能:處理三次握手等待隊列中的第一個請求并建立一個用來通信的新套接字
參數:sockfd:套接字文件描述符
addr:存放發送端IP地址空間首地址?
addrlen:想要接收的IP地址的長度?
返回值:成功返回新文件描述符(通訊套接字) ; 失敗返回-1?
4.?TCP報文頭
標志位:
(1).?URG:?緊急指針標志,?為1時表示緊急指針有效,?該報文應該優先傳送。
(2).?ACK:?確認應答標志
(3).?PSH:??表示發送數據,提示接收端從TCP接收緩沖區中讀走數據,為接收后續數據騰出空間
(4).?RST:?重置連接標志
(5).?SYN:?表示請求建立一個連接
(6).?FIN:?finish標志,?表示釋放連接
滑動窗口大小:是TCP流量控制得一個手段。目的是告訴對方,?本端得TCP接受緩沖區還能容納? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 多少字節得數據,
這樣對方就可以控制發送數據的速度,從而達到流量控制,16bit,2字節,因而窗口最大65535.
5.?TCP的機制
TCP復雜是因為它既要保證可靠性,同時又要盡可能的提高性能。
可靠性:
(1) 三次握手和四次揮手機制
(2)?確認應答:TCP將每個字節的數據都進行了編號,即為序列號。每一個ACK都帶有對應的確認 序列號,保證數據不丟失的按序到達
(3) 超時重傳:當發送端發送的數據在網絡中丟失時,在一定時間內沒有收到接收端的ACK,則發送端會重新發送丟失數據。
(4)流量控制:按照ACK中“窗口大小”字段控制發送端的發送速度
提高性能:
(1)滑動窗口(緩沖區):可以按照“窗口大小”,?一次發送多條后,?再等待應答。
(2)延遲應答:當接收方處理速度很快時,可以延遲發送ACK,此時"窗口大小"會自動增大
(3)捎帶應答:搭載應用層的響應報文發送ACK。
?6.?TCP粘包問題----面試題
TCP協議是面向字節流的協議,接收方不知道消息的界限,不知道一次提取多少數據,這就造成了粘包問題。
粘包問題出現的原因: (接受發送速率不匹配)
(1).?發送端:發送方發送數據過快,造成粘包;
(2).?接收端:不及時的接收緩沖區內的包,造成多個包接收。接收過慢.
避免粘包問題的方法:(usleep())
(1).?對于定長的包,發送固定大小字節的數據,保證每次都按固定大小讀取即可;//??結構體
? ? ?問題:1)結構體對齊問題(比如:指定按1字節對齊),不能放指針
? ? ? ? ? ? ?2)發送數據類型多樣化時,接收方難區分接受大小
(2).?對于變長的包,還可以在包和包之間使用明確的數據分隔符,這個分隔符是由程序員自己來定的,只要保證分隔符不和正文沖突即可。
eg:hello world\n how are you\n xxxx\n
應用層可以根據\n進行解析
(3).自定義應用層的協議幀
幀頭: AA 1字節
有效數據長度: len 1字節
幀尾: BB 1字節
校驗:8bits和校驗(1字節)? 16bits和校驗(2字節)? ? CRC校驗
二、練習?
1.使用tcp實現雙人聊天
//cli.c
#include "head.h"int init_tcp_cli(const char *ip, unsigned short port)
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("fail socket");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(port);seraddr.sin_addr.s_addr = inet_addr(ip);int ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("fail connect");return -1;}return sockfd;
}void *send_msg(void *arg)
{char buff[1024] = {0};int sockfd = *((int *)arg);while (1){memset(buff, 0, sizeof(buff));fgets(buff ,sizeof(buff), stdin);send(sockfd, buff, strlen(buff), 0);}return NULL;
}void *recv_msg(void *arg)
{char buff[1024] = {0};int sockfd = *((int *)arg);while (1){memset(buff, 0, sizeof(buff));ssize_t size = recv(sockfd, buff, sizeof(buff), 0);if (size < 0){perror("fail recv");break;}else if (0 == size){printf("connect lost\n");break;}printf("B-->A: %s\n", buff);}return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid[2];int sockfd = init_tcp_cli("192.168.1.162", 50000);if (sockfd < 0){return -1;}pthread_create(&tid[0], NULL, send_msg, &sockfd);pthread_create(&tid[1], NULL, recv_msg, &sockfd);pthread_join(tid[0], NULL);pthread_join(tid[1], NULL);close(sockfd);return 0;
}
//ser.c
#include "head.h"int init_tcp_ser(const char *ip, unsigned short port)
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("fail socket");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(port);seraddr.sin_addr.s_addr = inet_addr(ip);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("fail bind");return -1;}ret = listen(sockfd, 10);if (ret < 0){perror("fail listen");return -1;}return sockfd;
}void *send_msg(void *arg)
{char buff[1024] = {0};int sockfd = *((int *)arg);while (1){memset(buff, 0, sizeof(buff));fgets(buff ,sizeof(buff), stdin);send(sockfd, buff, strlen(buff), 0);}return NULL;
}void *recv_msg(void *arg)
{char buff[1024] = {0};int connfd = *((int *)arg);while (1){memset(buff, 0, sizeof(buff));ssize_t size = recv(connfd, buff, sizeof(buff), 0);if (size < 0){perror("fail recv");break;}else if (0 == size){printf("connect lost\n");break;}printf("A-->B: %s\n", buff);}return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid[2];int sockfd = init_tcp_ser("192.168.1.162", 50000);if (sockfd < 0){return -1;}int connfd = accept(sockfd, NULL, NULL);if (connfd < 0){perror("fail accpet");return -1;}pthread_create(&tid[0], NULL, send_msg, &connfd);pthread_create(&tid[1], NULL, recv_msg, &connfd);pthread_join(tid[0], NULL);pthread_join(tid[1], NULL);close(connfd);close(sockfd);return 0;
}
#ifndef __HEAD_H__
#define __HEAD_H__#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 <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>#endif
?2.使用tcp實現文件發送()
//cli.c
#include "head.h"int init_tcp_cli(const char *ip, unsigned short port)
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("fail socket");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(port);seraddr.sin_addr.s_addr = inet_addr(ip);int ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("fail connect");return -1;}return sockfd;
}int send_file(int sockfd, const char *filename)
{send(sockfd, filename, strlen(filename), 0);usleep(10);int fd = open(filename, O_RDONLY);if (fd < 0){perror("fail open");return -1;}char buff[1024] = {0};while (1){ssize_t size = read(fd, buff, sizeof(buff));if (size <= 0){break;}send(sockfd, buff, size, 0);}close(fd);return 0;
}int main(int argc, const char *argv[])
{int sockfd = init_tcp_cli("192.168.1.162", 50000);if (sockfd < 0){return -1;}send_file(sockfd, "1.jpg");close(sockfd);return 0;
}
//ser.c
#include "head.h"int init_tcp_ser(const char *ip, unsigned short port)
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("fail socket");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(port);seraddr.sin_addr.s_addr = inet_addr(ip);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("fail bind");return -1;}ret = listen(sockfd, 10);if (ret < 0){perror("fail listen");return -1;}return sockfd;
}int recv_file(int connfd)
{char buff[1024] = {0};char fileneme[1024] = {0};recv(connfd, fileneme, sizeof(fileneme), 0);printf("fileneme = %s\n", fileneme);int fd = open(fileneme, O_WRONLY | O_CREAT | O_TRUNC, 0664);if (fd < 0){perror("fail open");return -1;}while (1){ssize_t size = recv(connfd, buff, sizeof(buff), 0);if (size <= 0){break;}write(fd, buff, size);}close(fd);return 0;
}int main(int argc, const char *argv[])
{int sockfd = init_tcp_ser("192.168.1.162", 50000);if (sockfd < 0){return -1;}int connfd = accept(sockfd, NULL, NULL);if (connfd < 0){perror("fail accpet");return -1;}recv_file(connfd);close(connfd);close(sockfd);return 0;
}