网络:tcp
服务器//客户端模型(常见模型分析)
1.c//s 服务器·(c)专用 HTTP协议,自定义协议,标准协议。 资源大部分在cli客户端
不受协议影响,较复杂。
2.b//s 服务器(c)通用 HTTP协议。 资源大部分服务器发送至客户端(ser->cli)
受到HTTP协议影响,功能较简单。
3.p2p 在线直播,文件下载。
TCP:传输控制协议。
流式套接字:数据和数据之间连续;先发先收;数据连续;可靠传输。
TCP传输,当链路搭建后,会维持通道。建立连接过程包含“三次握手,四次挥手”。全双工通信。
TCP传输可靠原因:应答,超时重传。(只要发送就会返回应答。已发送未应答,数据会重发,等待应答信号。)
“三次握手,四次挥手”流程图
SYN:建立连接;FIN:释放连接。
TCP三次握手 (Three-way Handshake)
目的: 建立一条TCP连接。
过程:
第一次握手 (SYN): 客户端先向服务器发送一个SYN包(
SYN=1
),并随机生成一个序列号(seq=x),进入SYN_SENT
状态。第二次握手 (SYN+ACK): 服务器收到SYN包后,必须确认客户端的SYN。同时自己也发送一个SYN包(
SYN=1
)。因此会发送一个SYN-ACK包(SYN=1, ACK=1
),并确认号ack=x+1,同时自己也随机生成一个序列号seq=y,进入SYN_RCVD
状态。第三次握手 (ACK): 客户端收到服务器的SYN-ACK包后,向服务器发送一个确认包ACK(
ACK=1
),确认号ack=y+1,序列号seq=x+1。完成后,客户端和服务器都进入ESTABLISHED
状态,连接建立成功。
TCP四次挥手 (Four-way Wavehand)
目的: 终止一条TCP连接。
过程:
第一次挥手 (FIN): 主动关闭方(可以是客户端或服务器)发送一个FIN包(
FIN=1
),并指定一个序列号(seq=u),进入FIN_WAIT_1
状态。第二次挥手 (ACK): 被动关闭方收到FIN包后,发送一个ACK包(
ACK=1
)作为确认,确认号为ack=u+1,并带上自己的序列号seq=v。此时被动关闭方进入CLOSE_WAIT
状态,主动关闭方收到这个ACK后进入FIN_WAIT_2
状态。第三次挥手 (FIN): 被动关闭方处理完所有数据后,也发送一个FIN包(
FIN=1
),并指定一个序列号(seq=w),进入LAST_ACK
状态。第四次挥手 (ACK): 主动关闭方收到FIN后,发送一个ACK包(
ACK=1
)作为确认,确认号为ack=w+1,序列号为seq=u+1。然后进入TIME_WAIT
状态,等待一段时间(2MSL)后彻底关闭。被动关闭方收到这个ACK后,立即关闭连接。
TCP协议->流程(函数)
服务器端:tcp ser->(建立)socket->(绑定)bind->(监听)listen->(通信)accept->(接收)recv->(发送)send
客户端:tcp cli->(建立)socket->(主动发起连接->三次握手)connect->(发送)send->(接收)recv
TCP/UDP,缓冲区64k。TCP缓冲区是双缓冲区。收发都是64k,不干扰。UDP是单个缓冲区64k。
数据的粘包(发送方发送数据,接收方无法解析数据。)解决办法:
1.设置边界。
2.固定大小。
3.自定义协议。
具体例子:代码能运行成功,就是图片加载不出来。
服务端:
typedef struct sockaddr*(SA);
int main(int argc, char **argv)
{//监听套接字int listfd=socket(AF_INET,SOCK_STREAM,0);if (-1==listfd){perror("listfd err...");return 1;}//man 7 ipstruct sockaddr_in ser,cli;//清空bzero(&ser,sizeof(ser));bzero(&cli, sizeof(cli));ser.sin_family=AF_INET;ser.sin_port =htons(50000);//INADDR_ANY代表网卡信息ser.sin_addr.s_addr=INADDR_ANY;int ret= bind(listfd,(SA)&ser,sizeof(ser));if (-1==ret){perror("ret err...");return 1;}//listen中3的原因——服务器三次握手的排队数,不是连接客户端个数。listen(listfd, 3);socklen_t len = sizeof(cli);//通信套接字int conn = accept(listfd, (SA)&cli, &len);if(-1==conn){perror("conn err...");return 1;}int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);if (-1==fd){perror("fd err...");return 1;}time_t tm;while (1){char buf[1024]={0};int ret= recv(conn, buf, sizeof(buf), 0);if (ret<=0){break;}time(&tm);write(fd, buf, ret);}close(listfd);close(conn);close(fd);//system("pause");return 0;
}
客户端:
typedef struct sockaddr*(SA);
int main(int argc, char **argv)
{//监听套接字int conn=socket(AF_INET,SOCK_STREAM,0);if (-1==conn){perror("conn err...");return 1;}//man 7 ipstruct sockaddr_in ser;//清空bzero(&ser,sizeof(ser));ser.sin_family=AF_INET;ser.sin_port =htons(50000);//INADDR_ANY代表网卡信息ser.sin_addr.s_addr=INADDR_ANY;int ret= connect(conn,(SA)&ser,sizeof(ser));if (-1==ret){perror("ret err...");return 1;}int fd = open("test.png", O_RDONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[1024]={0};bzero(buf, sizeof(buf));int ret = read(fd, buf, sizeof(buf));if (ret <= 0){break;}send(conn, buf, ret, 0);bzero(buf, sizeof(buf));recv(conn, buf, sizeof(buf), 0);// printf("ser:%s\n",buf); }close(conn);close(fd);//system("pause");return 0;
}
解决方案
1. 使用协议头
c
复制下载
// 发送文件前先发送文件信息 struct file_info {uint32_t file_size;uint32_t file_name_len;// 后续跟文件名和数据 };
2. 使用固定长度的结束标记
c
复制下载
// 使用独特的结束标记,如8字节的特殊序列 const char END_MARKER[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xAA, 0x55, 0xCC};
3. 使用文件大小作为传输结束条件
c
复制下载
// 客户端先发送文件大小 uint32_t file_size = get_file_size("test.png"); send(conn, &file_size, sizeof(file_size), 0);// 服务器端根据文件大小接收 uint32_t total_received = 0; while (total_received < file_size) {int ret = recv(conn, buf, sizeof(buf), 0);write(fd, buf, ret);total_received += ret; }