一、概念
1、核心特点
面向连接(三次握手、四次挥手)>>>确保双方状态同步
可靠传输(四个"无",无丢失,无失序,无差错,无重复)
面向字节流(会导致tcp粘包)
占用资源开销大
2、三次握手
1)概念:
是 TCP 协议中建立连接时,通过三次交互确保通信双方都具备发送和接收数据的能力,为后续可靠的数据传输奠定基础
2)过程:
假设通信双方为 客户端(Client)服务器(Server),服务器先进入监听状态,等待客户端连接请求
① 第一次握手(Client → Server):客户端发送 SYN 报文(含初始序列号),请求建立连接,进入 SYN-SENT 状态
>>> 客户端告诉服务器 “我能发”
② 第二次握手(Server → Client):服务器收到后,回复 SYN+ACK 报文(确认客户端序列号,同时发送自己的初始序列号),进入 SYN-RCVD 状态
>>>服务器告诉客户端 “我能收,且我也能发”
③ 第三次握手(Client → Server):客户端收到后,发送 ACK 报文(确认服务器序列号),双方进入 ESTABLISHED 状态,连接建立完成
>>>客户端告诉服务器 “我能收你的数据了”
3)实际用到的函数接口
connect:发送三次握手链接请求
listen:监听三次握手链接请求
accept:处理三次握手等待队列中的第一个请求并建立一个用来通信的新套接字
3、四次挥手
1)概念:
TCP 协议中终止连接的过程,用于确保通信双方都已完成数据传输,并安全释放连接资源。TCP 是全双工通信(双方可同时发送数据),需要分别确认双方的数据发送结束,因此需要四次交互
2)过程:
类似于三次握手;断开一个tcp连接,需要客户端和服务端发送四个报文以确认断开。
4、TCP粘包问题
TCP协议是面向字节流的协议,接收方不知道消息的界限,不知道一次提取多少数据,这就造成了粘包问题。
粘包问题出现的原因:
① 发送端:需要等缓冲区满时才发送出去,造成粘包;
② 接收端:不及时的接收缓冲区内的包,造成多个包接收。
避免粘包问题的方法:让消息之间有边界
① 对于定长的包,保证每次都按固定大小读取即可;
② 对于变长的包,还可以在包和包之间使用明确的分隔符,这个分隔符是由程序员自己来定的,只要保证分隔符不和正文冲突即可。>>>结尾\r\n\r\n添加一个特殊的字符 表示消息的边界
③ 结构体:自定义消息结构体,大小,类型
5、TCP报文头
1. URG: 紧急指针标志, 为1时表示紧急指针有效, 该报文应该优先传送。
2. ACK: 确认应答标志
3. PSH: 表示发送数据,提示接收端从TCP接收缓冲区中读走数据,为接收后续数据腾出空间
4. RST: 重置连接标志
5. SYN: 表示请求建立一个连接
6. FIN: finish标志, 表示释放连接
6、TCP机制
TCP复杂是因为它既要保证可靠性,同时又要尽可能的提高性能。
可靠性:
① 三次握手和四次挥手机制
② 确认应答:TCP将每个字节的数据都进行了编号,即为序列号。每一个ACK都带有对应的确认序列号,保证数据不丢失的按序到达
③ 超时重传:当发送端发送的数据在网络中丢失时,在一定时间内没有收到接收端的ACK,则发送端会重新发送丢失数据。
④ 流量控制:按照ACK中“窗口大小”字段控制发送端的发送速度
二、TCP编程应用
1、c/s模型
客户端 主要函数接口
socket >>>创建套接字
connect >>>连接服务器
recv/send >>>接收/发送数据
close >>>关闭连接
服务器 主要函数接口
socket >>>创建套接字(为了监听)
bind >>>绑定服务器地址和端口
listen >>>监听客户端的连接请求---(有个请求队列)
accept >>>接收连接
recv/send >>>接收/发送数据
close >>>关闭连接
2、示例
1)客户端:
2)服务器
三、练习(基于TCP协议)
1、利用进程实现服务器与客户端进行通信
客户端
服务器
head.h
结果
2、客户端向服务器发文件
head.h
#ifndef _HEAD_H_
#define _HEAD_H
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#endif
客户端
#include "head.h"
int tcp_client_connect(char const *ip,char const * port)
{
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
//printf("fd = %d\n",fd);
struct sockaddr_in seraddr;
//ip 192.168.0.150
//port 50000
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(port)); //atoi
seraddr.sin_addr.s_addr = inet_addr(ip);
if ( connect(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return -1;
}
return fd;
}
//./cli 192.168.0.130 50000
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
int clifd = tcp_client_connect(argv[1],argv[2]);
if (clifd < 0)
{
printf("tcp_client_connect fail\n");
return -1;
}
char buf[1024];
printf("Input file name:");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = '\0';
write(clifd,buf,strlen(buf));
sleep(1);
int fd_s = open(buf,O_RDONLY);
if (fd_s < 0)
{
perror("open fail");
return -1;
}
while (1)
{
int ret = read(fd_s,buf,sizeof(buf));
if (ret == 0)
break;
write(clifd,buf,ret);
}
close(fd_s);
close(clifd);
return 0;
}
服务器
#include "head.h"
int tcp_accept(char const*ip,char const * port )
{
//step1 socket
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
printf("fd = %d\n",fd);
struct sockaddr_in seraddr;
//ip 192.168.0.150
//port 50000
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(port));
seraddr.sin_addr.s_addr = inet_addr(ip);
if (bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return -1;
}
if (listen(fd,5) < 0)
{
perror("listen fail");
return -1;
}
int connfd = accept(fd,NULL,NULL);
if (connfd < 0)
{
perror("accept fail");
return -1;
}
return connfd;
}
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
int connfd = tcp_accept(argv[1],argv[2]);
if (connfd < 0)
{
printf("tcp_accept fail");
return -1;
}
char buf[1024];
read(connfd,buf,sizeof(buf));
printf("buf = %s\n",buf);
int fd_d = open(buf,O_WRONLY|O_TRUNC|O_CREAT,0666);
if (fd_d < 0)
{
perror("open fail");
return -1;
}
while (1)
{
int ret = read(connfd,buf,sizeof(buf));
if (ret == 0)
break;
write(fd_d,buf,ret);
}
close(connfd);
close(fd_d);
return 0;
}
3、解决粘包问题-自定义消息结构体
head.h
#ifndef _HEAD_H_
#define _HEAD_H
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
typedef struct
{
int type;
char buf[256];
}msg_t;
#endif
客户端
#include "head.h"
int tcp_client_connect(char const *ip,char const * port)
{
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
//printf("fd = %d\n",fd);
struct sockaddr_in seraddr;
//ip 192.168.0.150
//port 50000
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(port)); //atoi
seraddr.sin_addr.s_addr = inet_addr(ip);
if ( connect(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return -1;
}
return fd;
}
//./cli 192.168.0.130 50000
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
int clifd = tcp_client_connect(argv[1],argv[2]);
if (clifd < 0)
{
printf("tcp_client_connect fail\n");
return -1;
}
msg_t msg;
printf("Input file name:");
msg.type = -1;
fgets(msg.buf,sizeof(msg.buf),stdin);
msg.buf[strlen(msg.buf)-1] = '\0';
write(clifd,&msg,sizeof(msg));
int fd_s = open(msg.buf,O_RDONLY);
if (fd_s < 0)
{
perror("open fail");
return -1;
}
while (1)
{
int ret = read(fd_s,msg.buf,sizeof(msg.buf));
msg.type = ret;
write(clifd,&msg,sizeof(msg));
if (ret == 0)
break;
}
close(fd_s);
close(clifd);
return 0;
}
服务器
#include "head.h"
int tcp_accept(char const*ip,char const * port )
{
//step1 socket
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd < 0)
{
perror("socket fail");
return -1;
}
printf("fd = %d\n",fd);
struct sockaddr_in seraddr;
//ip 192.168.0.150
//port 50000
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(port));
seraddr.sin_addr.s_addr = inet_addr(ip);
if (bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return -1;
}
if (listen(fd,5) < 0)
{
perror("listen fail");
return -1;
}
int connfd = accept(fd,NULL,NULL);
if (connfd < 0)
{
perror("accept fail");
return -1;
}
return connfd;
}
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("Usage: %s <ip> <port>\n",argv[0]);
return -1;
}
int connfd = tcp_accept(argv[1],argv[2]);
if (connfd < 0)
{
printf("tcp_accept fail");
return -1;
}
msg_t msg;
read(connfd,&msg,sizeof(msg));
printf("buf = %s\n",msg.buf);
int fd_d = open(msg.buf,O_WRONLY|O_TRUNC|O_CREAT,0666);
if (fd_d < 0)
{
perror("open fail");
return -1;
}
while (1)
{
int ret = read(connfd,&msg,sizeof(msg));
if (msg.type == 0)
break;
write(fd_d,msg.buf,msg.type);
}
close(connfd);
close(fd_d);
return 0;
}