网络编程之TCP编程

一、TCP基本概述

TCP是一种传输层的网络协议,是一种面向连接的,可靠的,基于字节流的网络协议。进行TCP通信的时候,双方一定要先建立连接,也就是我们所说的三次握手,建立稳定连接之后,接下来就是我们的通信了,接下来就可以进行正常的发送和收发数据。收发数据的时候是基于字节流的。

TCP编程模型

cf583c24b11b4bf9b0c05d87b0bf0351.png

 模型解析:

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.客户端再发送一个包给服务端,再次确认

通信图

13ae02f2a1544b62abbe1daa8bfde343.png

五、关闭连接

我们建立连接,通完信后,我们要将连接关掉,即关闭相应的socket值,调用close函数,如果不关闭socket,内核会一直维护这个socket。我们关闭连接的时候需要进行四次挥手动作。

四次挥手图

ecba690e2b1a4f0aa0e2ee5f64d854c1.png

详解:

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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值