一、TCP基本概述
TCP是一种传输层的网络协议,是一种面向连接的,可靠的,基于字节流的网络协议。进行TCP通信的时候,双方一定要先建立连接,也就是我们所说的三次握手,建立稳定连接之后,接下来就是我们的通信了,接下来就可以进行正常的发送和收发数据。收发数据的时候是基于字节流的。
TCP编程模型
模型解析:
1.首先,服务器端先用户区创建一个socket文件,随后绑定内核空间中一个网卡设备的映射,(为何需要绑定呢,因为我们创建一个socket的时候只是创建了而已,并没有绑定到网卡设备),绑定的时候会进行ip地址和端口号的设置,接下来就监听相应的ip地址和端口号。accept会阻塞等待客户端的连接。
2.客户端会创建一个socket文件并且通过connect去连接服务器。
3.以上两点完成后,客户端和服务器端就创建好连接了,接下来就可以正常收发数据了
4.收发完数据之后,要结束连接,结束连接就要进行四次挥手。
二、socket文件
我们在写TCP代码之前先了解一下socket文件,我们之前学io编程的时候都知道,在Linux当中一切皆文件,文件类型我们分成三种,一种是字符型文件,第二种是块类型文件,第三者网络型设备。对于字符型文件,比如磁盘,我们会在内核空间映射出磁盘的一个file,并且在用户空间通过文件描述符去操控这个file,并且各个file是相互独立的。但是网卡设备略有不同,同样的,我们在用户空间有socket描述符,在内核空间有网卡设备的相应驱动,我们要自己绑定相应的网卡设备地址,唯一不同的是,我们绑定一块网卡地址好像可以同时操控网络上的所有网卡,比如客户端创建了一个socket号,这个socket号不是单单对客户端网卡设备有效,对其他网卡一样有效,所以我们就可以通过相同的socket号同时从客户端和服务器端两个网卡中进行通信。是跨网卡的。说白了客户端和服务器端组成了一个超级网卡,两端可以同时向这个超级网卡写数据,读数据,并且两端同时操作这个超级网卡的时候不会冲突,cpu会采用分时复用进行避免冲突
三、TCP基础通信代码
服务端代码
//服务器端
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
int main()
{
int sockfd;//服务端的socket号
int cnt;
char redBuff[128];//读取缓冲区
int connect_fd;//客户端的socket号
int ret_bind;//绑定函数返回值
struct sockaddr_in serve_addr;//服务器的网卡设备地址
socklen_t serve_len,client_len;
//1.创建服务器端socket号
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd < 0)
{
perror("sock failed\n");
exit(1);
}
//2.配置服务器端网卡设备相关参数
serve_addr.sin_family = AF_INET;//协议族:IPV4
serve_addr.sin_port = 8090;//端口号
serve_addr.sin_addr.s_addr = htons(INADDR_ANY);//将ip地址由主机字节序转化为网络字节序
serve_len = sizeof(serve_addr);//服务器端地址长度
client_len = sizeof(client_addr);//客户端地址长度
//3.socket号绑定网卡设备地址
ret_bind = bind(sockfd,(struct sockaddr*)&serve_addr,serve_len);
if(ret_bind < 0)
{
perror("bind failed\n");
exit(2);
}
//4.监听连接请求
listen(sockfd,3);
//5.等待客户端的连接
while(1)
{
connect_fd = accept(sockfd,(struct sockaddr*)&client_addr,&client_len);
if(connect_fd < 0)
{
perror("connect faile\n");
exit(1);
}
//6.建立连接,进行通信
while(1)
{
cnt = read(connect_fd,redBuff,128);//通过客户端socket号进行读取数据
if(cnt < 0)
{
perror("read fail\n");
exit(1);
}
else if(cnt == 0)
{
printf("client offline....\n");
break;
}else
{
printf("server:%s\n",redBuff);
write(connect_fd,"I receive",10);//通过客户端socket号进行发数据
}
}
}
return 0;
客户端代码
//客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
int main()
{
char redBuff[128];//读取缓冲区
int write_ret,read_ret;
int client_fd,server_fd,ret;
struct sockaddr_in serve_addr;//服务端的网卡设备地址
//1.创建客户端socket号
client_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(client_fd < 0)
{
perror("sock failed\n");
exit(1);
}
//2.设置服务端网卡设备地址参数
serve_addr.sin_family = AF_INET;//IPV4协议
serve_addr.sin_port = 8090;//服务端端口号
serve_addr.sin_addr.s_addr = inet_addr("192.168.1.172");//服务端的ip地址
//3.申请连接
ret = connect(client_fd,(struct sockaddr*)&serve_addr,sizeof(serve_addr));
if(ret == -1)
{
perror("connect failed\n");
exit(1);
}
//4.收发数据
write_ret = write(client_fd,"helloworld\n",16);//通过客户端socket号发送数据给服务端
if(write_ret < 0)
{
perror("write failed\n");
exit(1);
}
printf("client write end\n");
read_ret = read(client_fd,redBuff,128);//通过客户端socket号接收服务端数据
if(read_ret < 0)
{
perror("read fail\n");
exit(2);
}
printf("client:%s\n",redBuff);
//5.结束通信
close(client_fd);
return 0;
}
四、TCP之三次握手
三次握手是TCP通信中建立连接环节
1.首先客户端通过connect函数向服务端发送一个请求通信的syn包,并且进入SYN_SEND状态
2.服务端收到这个syn包,并且确认这个syn包,随后服务端通过accept函数再发送一个syn包给客户端表示确认请求
3.客户端再发送一个包给服务端,再次确认
通信图
五、关闭连接
我们建立连接,通完信后,我们要将连接关掉,即关闭相应的socket值,调用close函数,如果不关闭socket,内核会一直维护这个socket。我们关闭连接的时候需要进行四次挥手动作。
四次挥手图
详解:
1.客户端应用进程调用close函数,TCP会发送一个FIN M包,告知服务器说我要关闭连接了
2.服务器收到FIN M包后确认一下这个包,执行被动关闭,随后发一个ACK M+1包给客户端表示我已经收到了,随后就会关闭。
3.过一会,服务器主动调用close函数,同时发一个FIN N包告知客户端,说我要关闭了。
4.客户端收到FIN N包后,发一个ACK N+1包告知服务器说我已经收到你的关闭信息了
六、TCP通信之收发数据实例
服务器代码
//服务器端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<sys/socket.h>
#define RSIZE (4*1024)
/*--------------------
该函数具有以下功能:
1.处理客户端发送来的请求信息
2.形参1:cfd,是客户端的socket号
3.形参2: cmd,是客户端发送来的请求信息
-----------------------*/
void cmd_parse(int cfd,char *cmd)
{
int video_fd;
int video_readRet;
char redBuff[RSIZE] = {0};//读取缓冲区
//1.容错检测,如果是错误信息,则返回
if( cmd == NULL)
{
printf("nothing\n");
return;
}
//2.如果命令是getText,即获取文本
if(strcmp(cmd,"getText") == 0)
{
printf("get text\n");
write(cfd,"get text",20);
}
//3.如果命令是getPhoto,即获取照片
else if(strcmp(cmd,"getPhoto") == 0)
{
printf("getPhoto\n");
write(cfd,"getPhoto",20);
}
//4.如果命令是获取getVideo,即获取视频
else if(strcmp(cmd,"getVideo") == 0)
{
video_fd = open("../mp4/a.mp4",O_RDONLY);//打开一个文件视频
if(video_fd < 0) return;
while(1)
{
video_readRet = read(video_fd,redBuff,RSIZE);
printf("Ret:%d\n",video_readRet);
write(cfd,redBuff,video_readRet);
if(video_readRet < RSIZE) break;
}
close(video_fd);
}
else
{
write(cfd,"i do not understand",22);
}
}
int main()
{
int sockfd;//服务器端socket号
int cnt;
struct sockaddr_in client_addr = {0};//客户端的网卡设备地址
char redBuff[128];//读取缓冲区
int connect_fd;//客户端socket号
int ret_bind;//连接函数的返回值,用以检测
struct sockaddr_in serve_addr;//服务器的网卡设备地址
socklen_t serve_len,client_len;
//1.创建服务器端的socket号
sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd < 0)
{
perror("sock failed\n");
exit(1);
}
//2.设置服务器端的网卡设备相关参数
serve_addr.sin_family = AF_INET;//IPV4的协议
serve_addr.sin_port = 8090;//端口号
serve_addr.sin_addr.s_addr = htons(INADDR_ANY);//将协议族的主机字节序转化为网络字节序
serve_len = sizeof(serve_addr);
client_len = sizeof(client_addr);
//3.将socket号绑定服务器端的网卡设备地址
ret_bind = bind(sockfd,(struct sockaddr*)&serve_addr,serve_len);
if(ret_bind < 0)
{
perror("bind failed\n");
exit(2);
}
//4.监听客户端请求
listen(sockfd,3);
//5.等待客户端连接
while(1)
{
connect_fd = accept(sockfd,(struct sockaddr*)&client_addr,&client_len);
if(connect_fd < 0)
{
perror("connect faile\n");
exit(1);
}
//通信
while(1)
{
cnt = read(connect_fd,redBuff,128);
if(cnt < 0)
{
perror("read fail\n");
exit(1);
}
else if(cnt == 0)
{
printf("client offline....\n");
break;
}else
{
//请求处理函数
cmd_parse(connect_fd,redBuff);
}
}
}
close(sockfd);
close(connect_fd);
return 0;
客户端代码
//客户端
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include <sys/stat.h>
#include