您现在的位置是:首页 >学无止境 >TCP并发服务器模型网站首页学无止境

TCP并发服务器模型

m0_37565374 2023-05-26 12:00:03
简介TCP并发服务器模型

1. 循环服务器

  1. 一次只能处理一个客户端,等这个客户端退出后,才能处理下一个客户端
  2. 缺点:循环服务器所处理的客户端最好不要有耗时操作。

2. 并发服务器

  1. 可以同时处理多个客户端的请求,创建子进程 或者 分支线程来处理客户端的请求
  2. 父进程/主线程 只负责连接,子进程/分支线程 只负责与客户端交互。

2.1 多进程并发服务器

// TCP并发服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>  /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#define IP "192.168.8.225"                            // 设置服务器IP地址
#define PORT 1024                                      // 设置服务器端口号为1024
int _newfd_server(int newfd, struct sockaddr_in cin); // 生成新的服务器
void handler(int sig);                                 // 回收僵尸进程
int main()
{

    __sighandler_t sig = signal(17, handler);
    if (SIG_ERR == sig)
    {
        perror("signal");
        return -1;
    }

    /*创建字节套,以tcp方式 ipv4*/
    // int socket(int domain, int type, int protocol);
    int serve_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (serve_fd < 0)
    {
        perror("socket");
        return -1;
    }
    /*允许端口快速重用成功*/
    int resue = 1;
    if (setsockopt(serve_fd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
    /*填充服务器地址信息结构体*/
    struct sockaddr_in server_in;
    server_in.sin_family = AF_INET;            // 必须填AF_INET
    server_in.sin_port = htons(PORT);          // 端口号,主机转网络
    server_in.sin_addr.s_addr = inet_addr(IP); // 服务器IP,
    /*将服务器IP和端口绑定到套接字上*/
    // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    if (bind(serve_fd, (struct sockaddr *)&server_in, sizeof(server_in)) < 0)
    {
        perror("bind");
        return -1;
    }
    /*将套接字设置为被动监听模式*/
    // int listen(int sockfd, int backlog);
    if (listen(serve_fd, 128) < 0) // 第二个参数为队列大小,一般设置为大于1
    {
        perror("listen");
        return -1;
    }
    printf("listen success __%d__
", __LINE__);
    /*获取连接成功的套接字,此套接字用于客户端*/
    struct sockaddr_in cin;           // 存储连接成功的客户端地址信息
    socklen_t addrlent = sizeof(cin); // 客户端字节大小

    while (1)
    {
        int newfd = accept(serve_fd, (struct sockaddr *)&cin, &addrlent); // 阻塞等待客户端连接
        if (newfd < 0)
        {
            perror("accept");
            return -1;
        }
        printf("[%s:%d] newfd=%d 连接成功:__%d__
", inet_ntoa(cin.sin_addr),
               ntohs(cin.sin_port), newfd, __LINE__);

        __pid_t cpid = fork(); // 创建新进程
        if (cpid > 0)          // 父进程执行部分
        {
            close(newfd); // 父进程关掉newfd,对子进程无影响
        }
        else if (0 == cpid) // 子进程执行部分,
        {
            close(serve_fd);
            _newfd_server(newfd, cin);
            exit(0);
        }
        else
        {
            perror("fork");
            return -1;
        }
    }
    close(serve_fd);
    return 0;
}
int _newfd_server(int newfd, struct sockaddr_in cin) // 生成新的服务器
{
    char buf[128] = "";
    ssize_t res = 0;
    while (1)
    {
        /*接受消息*/
        // ssize_t recv(int sockfd, void *buf, size_t len, int flags);
        bzero(buf, sizeof(buf));                // 清空buf
        res = recv(newfd, buf, sizeof(buf), 0); // 以阻塞方式接收消息
        if (res < 0)
        {
            perror("recv");
            return -1;
        }
        else if (0 == res)
        {
            printf("[%s:%d] newfd=%d 客户端下线:__%d__
", inet_ntoa(cin.sin_addr),
                   ntohs(cin.sin_port), newfd, __LINE__);
            break;
        }
        printf("newfd=%d: %s
", newfd, buf);
        /*发送消息*/
        // ssize_t send(int sockfd, const void *buf, size_t len, int flags);
        printf("请输入要发送的消息>>>");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;
        if (send(newfd, buf, sizeof(buf), 0) < 0)
        {
            perror("send");
            return -1;
        }
        printf("发送成功
");
    }
}
void handler(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ; // 非阻塞等待子进程退出
}

2.2 多线程并发服务器

// TCP并发服务器端,线程方式实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>  /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

#define IP "192.168.8.225" // 设置服务器IP地址
#define PORT 1024          // 设置服务器端口号为1024
struct server
{
    int newfd;
    struct sockaddr_in cin;
};

void *_newfd_server(void *arg); // 多线程生成新服务器

int main()
{
    /*创建字节套,以tcp方式 ipv4*/
    // int socket(int domain, int type, int protocol);
    int serve_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (serve_fd < 0)
    {
        perror("socket");
        return -1;
    }
    /*允许端口快速重用成功*/
    int resue = 1;
    if (setsockopt(serve_fd, SOL_SOCKET, SO_REUSEADDR, &resue, sizeof(resue)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
    /*填充服务器地址信息结构体*/
    struct sockaddr_in server_in;
    server_in.sin_family = AF_INET;            // 必须填AF_INET
    server_in.sin_port = htons(PORT);          // 端口号,主机转网络
    server_in.sin_addr.s_addr = inet_addr(IP); // 服务器IP,
    /*将服务器IP和端口绑定到套接字上*/
    // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    if (bind(serve_fd, (struct sockaddr *)&server_in, sizeof(server_in)) < 0)
    {
        perror("bind");
        return -1;
    }
    /*将套接字设置为被动监听模式*/
    // int listen(int sockfd, int backlog);
    if (listen(serve_fd, 128) < 0) // 第二个参数为队列大小,一般设置为大于1
    {
        perror("listen");
        return -1;
    }
    printf("listen success __%d__
", __LINE__);
    /*获取连接成功的套接字,此套接字用于客户端*/
    struct sockaddr_in cin;           // 存储连接成功的客户端地址信息
    socklen_t addrlent = sizeof(cin); // 客户端字节大小

    pthread_t pth;
    struct server ser;
    while (1)
    {
        int newfd = accept(serve_fd, (struct sockaddr *)&cin, &addrlent); // 阻塞等待客户端连接
        if (newfd < 0)
        {
            perror("accept");
            return -1;
        }
        printf("[%s:%d] newfd=%d 连接成功:__%d__
", inet_ntoa(cin.sin_addr),
               ntohs(cin.sin_port), newfd, __LINE__);
        ser.cin = cin;
        ser.newfd = newfd;
        pthread_create(&pth, NULL, _newfd_server, &ser); // 创建线程
        pthread_detach(pth);                             // 分离线程
    }
    close(serve_fd);
    return 0;
}
void *_newfd_server(void *arg) // 生成新的服务器
{

    struct sockaddr_in cin = ((struct server *)arg)->cin;
    int newfd = ((struct server *)arg)->newfd;
    char buf[128] = "";
    ssize_t res = 0;
    while (1)
    {
        /*接受消息*/
        // ssize_t recv(int sockfd, void *buf, size_t len, int flags);
        bzero(buf, sizeof(buf));                // 清空buf
        res = recv(newfd, buf, sizeof(buf), 0); // 以阻塞方式接收消息
        if (res < 0)
        {
            perror("recv");
            break;
        }
        else if (0 == res)
        {
            printf("[%s:%d] newfd=%d 客户端下线:__%d__
", inet_ntoa(cin.sin_addr),
                   ntohs(cin.sin_port), newfd, __LINE__);
            break;
        }
        printf("[%s:%d]cfd=%d: %s
", inet_ntoa(cin.sin_addr),
               ntohs(cin.sin_port), newfd, buf);
    }
    close(newfd);
    pthread_exit(NULL);
}

3. 基于TCP的文件传输服务(目前只有下载)

1.tftp下载模型

image-20230412215132210

2.TFTP通信过程总结

  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。

image-20230412215208032

3.tftp下载协议分析

image-20230412215241440

差错码:当操作码是5的时候

0 未定义,差错错误信息

1 File not found.

2 Access violation.

3 Disk full or allocation exceeded.

4 illegal TFTP operation.

5 Unknown transfer ID.

6 File already exists.

7 No such user.

8 Unsupported option(s) requested.

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

#define IP "192.168.31.108" // 本机的ip地址
#define PORT 69             // tftp连接的端口号
#define ERR_MSG(msg)            
    {                           
        printf("%d", __LINE__); 
        perror(msg);            
    }
int fun_download(int cfd, struct sockaddr_in sin); // 用于文件的下载
// void fun_upload();   // 用于文件的上传

int main()
{
    // 创建报式套接字
    int cfd;
    cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (cfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    // 填充服务器首次链接的地址信息结构体(69号端口)
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;            // 必须填AF_INET
    sin.sin_port = htons(PORT);          // 端口号的网络字节序
    sin.sin_addr.s_addr = inet_addr(IP); // 服务器的ip地址
    char c;
    while (1)
    {
        printf("####    1.下载  ####
");
        printf("####    2.上传  ####
");
        printf("####    3.退出  ####
");
        printf("请输入选项:	");
        c = getchar();
        while (getchar() != 10)
            ; // 吸收垃圾字符,只有输入回车键才向下执行
        switch (c)
        {
        case '1':
            fun_download(cfd, sin);
            break;
        case '2':
            // fun_upload();
            break;
        case '3':
            return 0;
        default:
            printf("请输入正确字符
");
            break;
        }
    }
}
int fun_download(int cfd, struct sockaddr_in sin)
{
    /* 组下载请求协议*/
    char buf[516] = {0}; // 最大为516字节
    char filename[128] = "";
    printf("请输入需要下载的文件名	");
    scanf("%s", filename);
    while (getchar() != 10)
        ; // 吸收垃圾字符,只有输入回车键才向下执行
#if 0
        //采用逐个赋值的方法
       short* p1 = (short*)buf;
      *p1 = htons(1);
      
      char* p2 = buf+2;
      strcpy(p2, filename);
      
      char* p4 = p2+strlen(p2)+1;
      strcpy(p4, "octet");
      
      int size = 4+strlen(p2)+strlen(p4);
#endif

    sprintf(buf, "%c%c%s%c%s%c", 0, 1, filename, 0, "octet", 0); // 用sprintf的方式一次性赋值
    // printf("%d %d %d  %d
", buf[0], buf[1], buf[2], buf[3]);

    int size = strlen(filename) + strlen("octet") + 4;
    //   1. 发送读写请求
    if (sendto(cfd, buf, size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("sendto");
        return -1;
    }
    printf("读写请求发送成功
");

    int fd = -1; // 文件描述符,赋值为-1是为了防止出错

    socklen_t addrlen = sizeof(sin);
    ssize_t len = 0; // 获取到的字节长度
    int ret = 0;     // 用于函数返回值
    int count = 0;   // 块编码计数器
    while (1)
    {
        bzero(buf, sizeof(buf));
        len = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &addrlen); // 接收数据
                                                                                     // printf("%d %d %d  %d
", buf[0], buf[1], buf[2], buf[3]);
        if (len < 0)                                                                 // 函数打开失败
        {
            ERR_MSG("recefrom");
            ret = -1;
            break;
        }
        // 如果位操作码是3的话说明接收正常
        if (3 == buf[1])
        {
            // 判断块编号是否正确
            if (*(unsigned short *)(buf + 2) == htons((count + 1)))
            {
                count++;
                // 如果是第一个数据包,也就是块编号==1,那么就新建文件
                if (*(unsigned short *)(buf + 2) == ntohs(1))
                {
                    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664); // 创建同样的文件
                    if (fd < 0)
                    {
                        ERR_MSG("open");
                        ret = -1;
                        break;
                    }
                }
                // 将读取到的数据保存到文件中
                if (write(fd, buf + 4, len - 4) < 0)
                {
                    ERR_MSG("write");
                    ret = -1;
                    break;
                }
                // 回复ACK,此时只有操作码不同,只需要改操作码
                buf[1] = 4;
                if (sendto(cfd, buf, 4, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
                {
                    ERR_MSG("sendto");
                    ret = -1;
                    break;
                }
                // 如果res<516-4,那么就代表接收失败
                if (len - 4 < 512)
                {
                    printf("文件拷贝完毕
");
                    ret = 0;
                    break;
                }
            }
        }
        else if (5 == buf[1]) // 收到错误包
        {
            unsigned short err = 0;
            err = ntohs(*(unsigned short *)(buf + 2));
            switch (err)
            {
            case 1:
                printf("文件未找到
");
                ret = -1;
                break;
            case 2:
                printf("访问违规
");
                ret = -1;
                break;
            case 3:
                printf("磁盘已满,或超出分配
");
                ret = -1;
                break;
            case 4:
                printf("TFTP操作违法
");
                ret = -1;
                break;
            case 5:
                printf("传输ID未知
");
                ret = -1;
                break;
            case 6:
                printf("文件已存在
");
                ret = -1;
                break;
            case 7:
                printf("无此用户
");
                ret = -1;
                break;

            default:
                break;
            }
            break;
        }
    }
    close(fd);
    return ret;
}
            ret = -1;
            break;
        case 5:
            printf("传输ID未知
");
            ret = -1;
            break;
        case 6:
            printf("文件已存在
");
            ret = -1;
            break;
        case 7:
            printf("无此用户
");
            ret = -1;
            break;

        default:
            break;
        }
        break;
    }
}
close(fd);
return ret;

}


风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。