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 :按照默认方式发送
返回值:
成功:实际发送的字节数
失败:-1
int listen(int sockfd, int backlog);
功能:监听建立三次握手的客户端
参数:
sockfd:监听套接字
backlog:最大允许监听的客户端个数
返回值:
成功:0
失败:-1
int 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粘包问题:发送方应用层发送的多包数据,将来在接收方可能一次读到,多包数据产生了粘连。
原因:
发送方速度较快,TCP底层可能对多包数据进行重新组帧;
接收方数据处理速度较慢,导致多包数据在接收缓冲区缓存,应用层读时,一次将多包数据读出。
解决粘包问题的常用方法:
调整发送速率
发送指定大小,将来接收方也接受指定大小。
结构体
注意:
- 跨平台之间的数据传输时,注意结构体对齐问题。
struct a
{
char a;
int b;
long c;
};
32bits平台《--》64位平台
- 应用层位发送的数据增加分隔符,利用分隔符解析
hello world\nhow are you\n
- 封装自定义数据帧格式进行发送(协议),严格根据协议进行解析。
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.代码练习
- tcp实现图片的传输
cs
//客户端
#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;
}
- tcp实现全双工聊天
cs
//客户端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可能与应用层数据同时发送;