Linux--传输层通信--TCP协议(上)

版权声明:允许转载,请注明文章出处 https://blog.csdn.net/Vickers_xiaowei/article/details/86485644

TCP报头解析

在这里插入图片描述

  1. 作为传输层协议,源端口目的端口用来唯一标识源进程和目的进程。
  2. 序号和确认序号是本报文第一个字段的序号和期望下一次接受的报文的第一字段序号,用来保证可靠传输的机制之一。
  3. TCP面向字节流,需要将TCP首部与数据部分分割开来,TCP报头长度可变20~60,TCP首部长度也成了必要字段。
  4. 保留位:为特定的应用程序保留,初始为0。
  5. URG: 紧急指针是否有效,如果URG=1说明该报文有紧急数据需要处理(相当于高优先级的数据);ACK: 确认号是否有效,ACK=1确认序号有效;PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走RST: 对方要求重新建立连接, 我们把携带RST标识的称为复位报文段;SYN: 请求建立连接,我们把携带SYN标识的称为同步报文段;FIN: 通知对方, 本端要关闭连接了, 我们称携带FIN标识的为结束报文段。
  6. 16位窗口大小告诉对方我们的接受缓冲区的大小。 以保证对方发生的数据都能被我接受。
  7. 16位校验和:采用CRC校验方法对数据进行校验, 发送端填充,接收端校验。校验的是报头和数据。
  8. 16位紧急指针标识哪部分数据是紧急数据, 只有紧急指针字段URG=1,这部分数据才有效。

TCP通信过程

在这里插入图片描述

  • 服务器在明处,客户端在暗处。 对于客户端来说,客户端有服务器的IP地址和端口号,而对于服务器来讲,服务器不知道客户端在哪里,只有当客户端主动发送请求之后才能知道客户端的IP和端口。
  • TCP请求由客户端发起。 当客户端需要服务器的服务时候,客户端要首先建立连接。在建立连接之前要创建套接字socket。
  • 客户端不建议绑定地址,因为对于客户端,我并不关心我的数据使用哪个地址哪个端口发送。而且我也不能实时了解操作系统的哪个端口处于闲置状态。所以我不绑定地址和端口,操作系统可以给我分配端口,并添加IP地址。
  • 服务器必须绑定地址这些socket都是由服务器自己创建并绑定地址的,而这个IP地址和端口都是唯一确定的。(服务器地址如果修改,就需要对原地址进行重定向,否则,客户端会找不到服务器。)所以在socket创建好之后,就必须绑定地址。
  • 客户端连接之前,服务器一直有socket在等待,服务端的任务就是提供服务,按理说,服务器应该实时具有处于LISTEN状态的socket,以保证客户端的连接请求都能被处理。

更多内容请参阅:TCP建立连接和拆除连接过程详解:https://blog.csdn.net/Vickers_xiaowei/article/details/85131980

1、创建套接字

建立与网卡的关联,协议版本的选择,传输层协议的选择

int socket(int domain, int type, int protocol);
  • domain: 地址域
    AF_INET(协议类型)一般使用ipv4协议
  • type: 套接字类型
    SOCK_STREAM 流式套接字
    SOCK_DGRAM 数据报套接字
  • protocol(协议类型):0-默认;流式套接字默认TCP协议IPPROTO_TCP,数据报套接字默认UDP协议IPPROTO_UDP
  • 返回值:套接字描述符,失败:-1
On  success, a file descriptor for the new socket is returned.  On error, -1 is
       returned, and errno is set appropriately.

2、为套接字绑定地址信息(ip、port)

bind函数既可以绑定IPV4版本协议,也可以绑定IPV6版本,两个版本的IP头结点的大小不同,定义sockaddr为了寻求接口的统一,都要使用struct sockaddr

struct sockaddr_in		//IPV4
struct sockaddr_in6		//IPV6

为socket绑定地址信息,确定socket能够操作缓冲区中的哪些数据

int bind(int sockfd, struct sockaddr *addr,socklen_t addrlen);
  • sockfd: 套接字描述符
  • addr: 要绑定的地址信息
  • addrlen:地址信息的长度

3、监听 对于服务器而言

int listen(int sockfd, int backlog);
  • sockfd:sockfd描述符,backlog:最大同时并发连接数(已完成连接队列中允许存放的socket个数)
  • 半连接的socket:当客户端发送来SYN连接请求时,服务器知道了客户端想要建立连接。可是客户端并不知道自己的连接请求是否已经到达服务器。这个状态下的连接处于半连接,服务器会将自己的这个处于半连接的请求放入一个半连接请求的队列来维护。同时发送给客户端ACK+SYN,告诉客户端“你发送的连接请求我已经收到了,我也要建立连接了。”。
  • 已经完成连接的socket:当客户端收到服务器发送的ACK和SYN之后,客户端会给服务一个应答报文ACK,服务器接受到这个报文之后,服务器关于这个连接的socket会放入已完成连接队列进行维护。这个时候,服务器才会为这个连接开辟一个数数据结构,开辟更多资源,以满足这个连接的服务。
  • 无论是已经完成连接接的socket还是处于半连接的socket,操作系统都要有一个队列去维护。
    在这里插入图片描述

4、连接请求 对于客户端而言

 int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  • sockfd:客户端的socket文件描述符
  • addr:要连接的服务端地址
  • addrlen:要连接的服务端的socket结构体的长度,单位是字节
  • 返回值:成功:0,失败:-1

5、接受连接请求 对于服务器而言

accept函数是一个阻塞型的函数,连接成功队列中如果没有新的连接到来,那么就会一直阻塞直到有新的客户端连接到来

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:这里的sockfd是服务器新创建的socket的描述符
  • addr:新建立连接的客户端地址信息
  • addrlen:客户端的socket结构体的长度,单位是字节
  • 返回值
    成功:返回新的socket描述符 失败返回:-1
On success, these system calls return a nonnegative integer that is a  descriptor
 for the accepted socket.  On error, -1 is returned, and errno is set appropriately.

当服务端成功接受连接请求之后,会为新连接重新创建一个socket用于专门和这个连接的通信,而这个重新创建的socket是由accept这个函数完成的。 而原来socket函数返回的socket只是用于接受连接,并不用来进行通信。

在这里插入图片描述

6、接收/发送数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd:连接成功的socket描述符
  • 返回值
    错误:-1,连接关闭:0,成功:实际接受长度>0
  • flags:默认0-阻塞式接收
  • buf: 用于指定接收数据的存放位置
  • len: 用于指定希望接收的数据长度

7、关闭套接字

int close(int fd);

我们说socket函数的返回值是socket描述符,实际也是文件描述符。因为系统的文件描述符有限,我们不再使用这个文件时候,就应该关闭这个描述符,否则可能造成文件描述符泄漏问题。

基于TCP协议的应用层聊天程序

TCP服务端

创建套接字——>绑定地址——>监听——>accept——>recv——>send——>close。

//tcp服务端的代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<arpa/inet.h>

int main()
{
    //1、创建socket套接字
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0){
        perror("socket error");
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(atoi("9000"));
    ser.sin_addr.s_addr=inet_addr("192.168.117.130");

    //2、绑定地址
    socklen_t len=sizeof(struct sockaddr_in);
    if(bind(sockfd,(struct sockaddr*)&ser,len)<0)
    {
        perror("bind error");
        close(sockfd);
        return -1;
    }

    //3、监听
    if(listen(sockfd,5)<0)
    {
        perror("listen error");
        close(sockfd);
        return -1;
    }
    while(1)
    {
        int n_sockfd;
        struct sockaddr_in cli;
        //4、accept
        n_sockfd=accept(sockfd,(struct sockaddr*)&cli,&len);
        if(n_sockfd<0){
            perror("accept error");
            continue;
        }
        //5、recv
        while(1){
            //6、send
            char buff[1024]={0};
            int ret=recv(n_sockfd,buff,1023,0);
            if(ret<0){
                perror("recv error");
                continue;
            }
            printf("client %s[%d]say:%s",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port),buff);

            memset(buff,0x00,1024);
            scanf("%s",buff);
            send(n_sockfd,buff,sizeof(buff),0);
            //7、close(sockfd)
        }
        close(n_sockfd);
    }
}

TCP客户端

不推荐手动绑定地址
创建套接字——>connect——>send——>recv——>close。

//tcp客户端端的代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<arpa/inet.h>

int main()
{
    //1、创建socket套接字
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0){
        perror("socket error");
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(atoi("9000"));
    ser.sin_addr.s_addr=inet_addr("192.168.117.130");
    //2、连接
    socklen_t len=sizeof(struct sockaddr_in);
    if(connect(sockfd,(struct sockaddr*)&ser,len)<0)
    {
        perror("connect error");
        return -1;
    }
    //3、发送数据
    while(1)
    {
            char buff[1024]={0};
            scanf("%s",buff);
            send(sockfd,buff,sizeof(buff),0);
            memset(buff,0x00,1024);

            int ret=recv(sockfd,buff,1023,0);
            if(ret<0){
                perror("recv error");
                continue;
            }
            printf("client %s[%d]say:%s",inet_ntoa(ser.sin_addr),ntohs(ser.sin_port),buff);
    }
    close(sockfd);
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Vickers_xiaowei/article/details/86485644
今日推荐