TCP

发布时间 2023-10-08 08:39:29作者: 常羲和

TCP

1. TCP概述

1.1 TCP协议的特点

TCP是面向连接的传输层,有序号、确认序号、排序检错、失败重传、大文件传输、不支持广播和多播。

TCP客户端:主动连接服务器。

TCP服务器:被动被客户端连接。

1.2 TCP与UDP的差异

image-20231007080151431

1.3 TCP C/S架构

image-20231007080229983

2. TCP套接字

创建一个TCP套接字SOCK_STREAM

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

3. TCP客户端API

#include <sys/socket.h>

3.1 connect连接TCP服务器

int connect(int socket,const struct sockaddr *address,
socklen_t address_len);

功能:主动跟服务器建立链接

参数:

  • sockfd:socket 套接字
  • address: 连接的服务器地址结构
  • len: 地址结构体长度

返回值:成功 0 ;失败 其他

3.2 send发送数据

ssize_t send(int socket, const void *buffer, size_t length, int flags);

参数:

  • sockfd:发送的套接字

  • buffer:需要发送的内容

  • length:需要发送的内容的实际长度 不能发送0长度报文

  • flags:通常为0

返回值:成功:发送的字节数, 失败:-1

3.3 recv接收数据

ssize_t recv(int socket, void *buffer, size_t length, int flags);

参数:

  • sockfd:套接字

  • buffer: 接收网络数据的缓冲区的地址

  • length: 接收缓冲区的大小(以字节为单位)

  • flags: 套接字标志(常为 0)

返回值:

  • 成功接收到字节数,失败-1,

  • 如果对方关闭,recv将收到0长度报文。

3.4 close关闭

close(scokfd)

4. TCP服务器API

TCP服务器 是被动的(必须客户端主动来连接)

创建socket同TCP客户端: int sockfd = socket(AF_INET, SOCK_STREAM, 0);

4.1 bind绑定

同UDP,绑定固定的端口号和IP

int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);

参数:

  • sockfd:socket套接字
  • myaddr:指向特定协议的地址结构指针
  • addrlen:该地址结构的长度

返回值:成功0,失败非0

4.2 listen监听客户端的连接

功能:创建连接队列、监听连接的到来

int listen(int sockfd, int backlog);

参数:

  • sockfd:监听套接字(主要用于监听客户端的连接到来)

  • backlog:连接队列的大小(单位为客户单的个数)

返回值:成功返回0,失败-1

连接队列分为两部分:未完成连接、完成连接。

4.3 accept函数

从已连接队列中取出一个已经建立的连接,如果没有任何连接可用, 则进入睡眠等待(阻塞)

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

参数:

  • sockfd: socket 监听套接字

  • cliaddr: 用于存放客户端套接字地址结构

  • addrlen:套接字地址结构体长度的地址

注意: 返回的是一个已连接套接字,这个套接字代表当前这个连接

5. TCP的三次握手与四次挥手

5.1 创建连接的三次握手

image-20231007081703747

第一次握手:客户端向服务端发送报文,证明客户端的发送能力正常

第二次握手:服务端接收到报文并向客户端发送报文,证明服务器端的接收能力、发送能力正常

第三次握手:客户端向服务端发送报文,证明客户端的接收能力正常

5.2 关闭连接的四次挥手

image-20231007082408319

第一次挥手:客户端发出连接释放报文,并且停止发送数据

第二次挥手:服务端接收到连接释放报文后,发出确认报文

第三次挥手:客户端接收到服务端的确认请求后,等待服务端发送连接释放报文,服务端将最后的数据发送完毕后,就会向客户端发送连接释放报文

第四次挥手:客户端收到服务器的连接释放报文后,发出确认,服务端收到确认报文后,关闭。

6. TCP并发服务器

实现ECHO服务器:服务器收到客户端的数据后,原样将数据回发给客户端

6.1 多进程实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

int main(int argc,char const *argv[])
{
    //1.通过socket
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd < 0)
    {
        perror("socket");
        return 1;
    }
    //2.bind绑定
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[1]));
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    int flag = bind(sock_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(flag != 0)
    {
        perror("bind");
        close(sock_fd);
        return 1;
    }
    //3.创建监听队列
    listen(sock_fd,100);
    //4.开始接收客户端的连接(并发接收多个客户端)
    while(1)
    {
        struct sockaddr_in client_addr;
        bzero(&client_addr,sizeof(client_addr));
        socklen_t client_addr_len = sizeof(client_addr);
        int client_fd = accept(sock_fd,(struct sockaddr *)&client_addr,&client_addr_len);
        char ip[16]="";
        inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16);
        printf("%s 连接成功\n",ip);
        
        //创建进程实现并发通信
        if (fork() == 0)
        {
            //子进程的作用于限制此范围
            close(sock_fd);
            while(1)
            {
                char buf[128] = "";
                ssize_t len = recv(client_fd,buf,128,0);
                if(len>0)
                {
                    int i = 0;
                    while(buf[i])
                    {
                        buf[i] = toupper(buf[i]);
                        i++;
                    }
                    send(client_fd,buf,len,0);
                    
                    if(strncmp(buf,"EXIT",4)==0)
                    {
                        break;
                    }
                }
            }
            close(client_fd);
            printf("%s 关闭连接\n",ip);
            _exit(0);
        }
    }
    //主进程的范畴
    close(sock_fd);
    return 0;
}

6.2 多线程实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <pthread.h>

//定义结构体:描述连接的客户端信息
typedef struct client_s
{
    unsigned char ip[16];
    int fd;
}Client;

void *client_task(void *arg)
{
    Client *client = (Client *)arg;
    while(1)
    {
        char buf[128]="";
        ssize_t len = recv(client->fd,buf,128,0);
        if(len > 0)
        {
            int i = 0;
            while(buf[i])
            {
                buf[i] = toupper(buf[i]);
                i++;
            }
            if(strncmp(buf,"EXIT",4)==0)
            {
                break;
            }
        }
    }
    close(client->fd);
    printf("%s 关闭连接\n",client->ip);
    free(client);
}

int main(int argc,char const *argv[])
{
    //1.通过socket
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd < 0)
    {
        perror("socket");
        return 1;
    }
    //2.bind绑定
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[1]));
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    
    int flag = bind(sock_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(flag != 0)
    {
        perror("bind");
        close(sock_fd);
        return 1;
    }
    //3.创建监听队列
    listen(sock_fd,100);
    printf("-----ECHO服务器已开启%s------\n",argv[1]);
    //4.开始接收客户端的连接(并发接收多个客户端)
    while(1)
    {
        struct sockaddr_in client_addr;
        bzero(&client_addr,sizeof(client_addr));
        socklen_t client_addr_len = sizeof(client_addr);
        int client_fd = accept(sock_fd,(struct sockaddr *)&client_addr,&client_addr_len);
        char ip[16] = "";
        inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16);
        printf("%s 连接成功\n",ip);
        
        //创建线程实现并发通信
        Client *client = malloc(sizeof(Client));
        strcpy(client->ip,ip);
        client->fd = client_fd;
        
        pthread_t tid;
        pthread_create(&tid,NULL,client_task,client);
        pthread_detach(tid);//分离线程
    }
    close(sock_fd);
    return 0;
}