网络传输----TCP

TCP协议是UDP协议的改进版,TCP协议可以保证传输的可靠性,TCP协议的优势如下:

        1、TCP协议提供可靠的传输服务(不会出现错误、乱码、丢失、误码、重复)

        2、面向连接的传输方式

        3、一条TCP只能进行两个端点一对一通信,通信双方固定,就是连接的两个点。如果想实现多机则需要创建多条TCP。

        4、面向字节流传输(数据只有先后之分,也就是一个发数据一个收数据错开,不可能同时收发数据)半双工模式

        5、可靠传输:有拥塞控制、流量控制、超时重传。

        上面是一些TCP协议的优点,下面来介绍以下TCP通信如何确保数据的可靠的,它的传输形式又是怎么样的。

TCP协议传输数据的组成是        TCP头+用户数据        

 上图就是TPC的头,由很多部分构成:(加粗部分用于建立连接和释放连接所需要用到的)

源端口  16bit        发送数据方的端口号(也就是自己的),标识发送该TCP报文的应用程序

目标端口 16bit      接收方的端口号(发送到那的),标识接收该TCP报文的应用程序

序号        32bit       该TCP报文的数据的第一个字节的序号

确认号    32bit        这是接收收到数据,返回的内容〈TCP报文〉,标识接收到多少内容,如确认号为n,准确接收到n-1个字节

数据偏移:4bit        以四字节为单位,当前这个TCP报文距离整个TCP报文开始有多远(看TCP头有多大,向后偏移就多少)

保留:6bit        无用

确认ACK 1bit   只有确认ACK为1时,才表示确认号有效

同步:1bit        在建立连接时可以让客户端和服务端同步,只有在建立的时候才用

终止:1bit        在断开连接、释放TCP连接时需要将这位为1,平时为0

复位:1bit        复位TCP连接使用,

窗口:发送TCP报文对应接收方的窗口大小

校验和:用于校验信息是否正确

TCP通信

        TCP通信为了保证传输可靠,必须在通信双方间建立连接,只有建立了连接之后TCP通信才是可靠通信,且确定唯一的通信双方 。所以TCP要进行通信的双方操作就存在着区别,一个进程应该主动的去进行连接,另一个进程应该被动等待连接请求,然后建立连接进行通信,客户端的通信流程和服务端的通信流程不相同。

        TCP通信大致流程如上图所示,在理论通信中,在建立连接时采用三次握手进行连接,四次挥手进行断开连接。

何以为三次握手?

        就是为了确保在建立TCP通信连接时,能够成功的连接上,在上面图中写了TCP传输时需要将信息与TCP头进行捆绑,而在连接时也和TCP头有关。在建立连接时主要解决:知道双方、协商一部分数据、双方支援进行分配。下面开始说,如何进行三次握手:

第一次握手:TCP客户端打算建立连接,向服务端发送请求连接报文,客户端请求建立连接,客户端进入同步发送状态(SYN-SENT),既在发送的报文中把SYN 设置为1,表示这是一个请求连接报文把seq(序号)设置为x(这是一个初始值),作为TCP客户端的初始序号。

第二次握手:TCP服务端接收到客户端连接请求后,如果同意进行连接,则会向客户端发送确认请求报文,服务端进入同步接收状态(SYN-RECV),在返回客户端的报文中把SYNACK位都设置为1,把序号seq设置为一个初始值y,作为服务端的初始序号并将确认号字段ack = x + 1,作为对客户端的确认。

第三次握手:TCP客户端在接收了服务端的确认后,还要向服务端发送一个确认报文,并进入已连接状态(ESTABLISHED),在发送的报文中ACK设置为1seq设置为x + 1,确认号ack =y+1,这是对tcp服务端的确认的确认,做到这里服务端也将进入已连接状态(ESTABLISHED

什么叫四次挥手?

        关闭(释放)连接步骤有四步,叫做四次挥手

第一次挥手:TCP客户端主动通知服务端要断开连接,关闭TCP连接;TCP客户端发送连接释放(关闭)报文,并进入终止等待1状态。发送的报文当中需要将TCP头中部分置位,把FIN(终止位)设置为1(表示断开),把ACK设置为1,对之前的报文做确认,序号seq设置为u,等于之前传送的数据的最后一个字节+1确认号ack设置为v,等于之前接收的数据最后一个字节+1

第二次挥手:TCP服务端接收到TCP客户端断开连接请求,返回一个普通的确认报文给客户端,服务端并进入关闭等待状态,客户端进入终止等待2状态(只能服务端发送,客户端接收)。返回的报文中将确认号ACK设置为1,表示这是一个普通的确认报文,服务端序号seq设置为v,表示服务端最后发送的一个字节序号+1,确认号ack = u+1,继续把之前没有发送完的数据继续完成发送。

第三次挥手:服务端给客户端发送连接释放报文,并进入最后确认状态,发送断开请求报文:设置FIN(终止)设置为1,表示这是一个TCP连接释放报文 ,ACK设置为1,表示对之前的报文做确认 ,seq序号设置w,服务器进入半关闭状态 ,确认号ack设置为u+1,表示释放的重复确认。

第四次挥手:tcp客户端收到连接释放请求,发送普通确认报文,进入时间等待状态。ACK设置为1,表示是一个普通确认 ,序号设置为u+1,表示这是一个释放报文 ,确认号ack设置为w+1,表示是一个确认报文。

而实际通信中TCP通信客户端和服务端操作流程如下:

1.客户端

2.服务端

也就是如下操作流程,上面是案例描述:

在这里需要学习一些新的API函数:

1、请求建立连接(TCP协议属于面向连接方式进行通信,这样才提供可靠性)

#include <sys/types.h> /* See NOTES */ 
#include <sys/socket.h>

使用当前进程的套接字与某个服务端建立连接 建立连接:与服务端建立连接,把套接字与服务端建立连接

int connect(int socket, const struct sockaddr *addr,socklen_t addrlen);

参数1:客户端套接字,表示用客户端哪个套接字(ip、port)与服务端建立连接, 进行通信。

参数2:客户端套接字要与哪个服务端建立建立,结构体中是服务端的ip、prot。

参数3:结构体大小(参数2的结构体)

成功返回0    失败返回-1

2、监听套接字

服务端监视自己的ip、port,看是否有客户端连接 等待客户端的连接请求,只要做监视操作,就让内核创建一个队列,把所有的客户端连接请求都放在队列 去(作为等待,等待接受),之后如果也有客户端连接请求,也会放入等待队列.

int listen(int sockfd, int backlog);

参数1:要监视的套接字,服务端的网络套接字

参数2:等待客户端个数,也就是允许最多连接多少个。好确定队列的大小

成功返回0    失败返回-1

3、接收客户端连接

从之前listen监听等待的客户端连接中,取出第一个,进行接受,进行通信,如果在刚才listen监视队 列中没有客户端连接请求,就会阻塞等待,一定要取一个客户端连接请求进行通信.

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数1:监视的套接字

参数2:客户端的ip、port

参数3:结构体大小

成功:返回客户端进行通信的套接字文件描述符,之前listen的套接字被用来做监视作用
失败:返回-1

示例:

        客户端

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0)

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.a_addr = inet_addr("xxx.xxx.xxx.xxx");    自己的网络地址
    
    bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));    将自己ip、port绑定到sockfd上
    
    struct sockaddr_in serveraddr;    要连接的服务端信息
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(1111);
    serveraddr.sin_addr.a_addr = inet_addr("xxx.xxx.xxx.xxx");    要连接的的网络地址

    //sockfd就是当前进程的套接字(有当前进程网络信息),创建连接,失败打印
    if( connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0 )
	{
		perror("connect error\n");
		return -1;
	}
    
    char buf[20];
    while(1)
    {
        //要进行做的事情
        read(sockfd,buf,20);    //读取服务端传过来的数据
        write(sockfd,buf,20);   //往服务端写内容
    }
}

服务端

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in addr;//网络信息结构体    //服务端的网络信息
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9999);
	addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");

    bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));

    listen(sockfd,10);//不会阻塞,告诉内核,要监视这个ip、port,把客户端的连接请求存放等待
   
    //接受客户端连接请求
    struct sockaddr_in addr_in;
	socklen_t len;
	int fd = accept(sockfd,(struct sockaddr *)&addr_in,&len);    //fd是接收客户端的文件描述符

    char buf[20];
	while(1)
	{
		int num = read(fd,buf,20);
		printf("server : %s\n",buf);	
		write(fd,"ok",3);
	}
	return 0;
}

配置好这些后就可以进行。有些函数是在UDP里面进行学习的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啵啵520520

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值