一、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;
}