TCP并发服务器模型

news2024/11/22 6:35:30

文章目录

  • 1. 循环服务器
  • 2. 并发服务器
    • 2.1 多进程并发服务器
    • 2.2 多线程并发服务器
  • 3. 基于TCP的文件传输服务(目前只有下载)
    • 1.tftp下载模型
    • 2.TFTP通信过程总结
    • 3.tftp下载协议分析

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__\n", __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__\n", 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__\n", inet_ntoa(cin.sin_addr),
                   ntohs(cin.sin_port), newfd, __LINE__);
            break;
        }
        printf("newfd=%d: %s\n", 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("发送成功\n");
    }
}
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__\n", __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__\n", 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__\n", inet_ntoa(cin.sin_addr),
                   ntohs(cin.sin_port), newfd, __LINE__);
            break;
        }
        printf("[%s:%d]cfd=%d: %s\n", 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.下载  ####\n");
        printf("####    2.上传  ####\n");
        printf("####    3.退出  ####\n");
        printf("请输入选项:\t");
        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("请输入正确字符\n");
            break;
        }
    }
}
int fun_download(int cfd, struct sockaddr_in sin)
{
    /* 组下载请求协议*/
    char buf[516] = {0}; // 最大为516字节
    char filename[128] = "";
    printf("请输入需要下载的文件名\t");
    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\n", 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("读写请求发送成功\n");

    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\n", 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("文件拷贝完毕\n");
                    ret = 0;
                    break;
                }
            }
        }
        else if (5 == buf[1]) // 收到错误包
        {
            unsigned short err = 0;
            err = ntohs(*(unsigned short *)(buf + 2));
            switch (err)
            {
            case 1:
                printf("文件未找到\n");
                ret = -1;
                break;
            case 2:
                printf("访问违规\n");
                ret = -1;
                break;
            case 3:
                printf("磁盘已满,或超出分配\n");
                ret = -1;
                break;
            case 4:
                printf("TFTP操作违法\n");
                ret = -1;
                break;
            case 5:
                printf("传输ID未知\n");
                ret = -1;
                break;
            case 6:
                printf("文件已存在\n");
                ret = -1;
                break;
            case 7:
                printf("无此用户\n");
                ret = -1;
                break;

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

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

}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/422570.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue大坑:v-for的key以及props传参不当导致的闭包

为什么props传参在模版中使用没问题&#xff0c;在函数中使用不变化 场景 当我们点击上方的月份时&#xff0c;会改变下方加载的卡片信息 代码&#xff1a; 父组件&#xff1a; <divv-for"(item, index) in vocalStore.getCardMonthData":key"index"…

电梯导航案例

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>小兔鲜儿 - 新鲜 惠民 快捷!</title><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"render…

WPS关闭不了后台一直运行的解决办法(wpscloudsvr.exe)

问题描述 前几天&#xff0c;发现每次打开wps时机箱风扇就转得厉害&#xff0c;把WPS界面叉掉后&#xff0c;桌面的任务栏—就是桌面最下面得黑框框—显示Windows图标和时间日期的那个地方也没有WPS任务&#xff0c;但是机箱还是响的厉害&#xff0c;检查了任务管理器发现一直…

SpringBoot自动配置的原理是什么?

自动配置的核心就在SpringBootApplication注解上&#xff0c;SpringBootApplication这个注解底层包含了3个注解&#xff0c;分别是&#xff1a; SpringBootConfiguration ComponentScan EnableAutoConfiguration EnableAutoConfiguration这个注解才是自动配置的核心。 它封…

速Raysync v6.6.8.0版本发布

最近镭速发布了v6.6.8.0版本&#xff0c;已经发布上线了。主要更新内容有服务器下发任务支持指定客户端&#xff0c;客户端增加日志清理和日志压缩&#xff0c;自动删除源文件保持源目录结构&#xff0c;支持将文件投递给其他成员等功能&#xff0c;详细的更新内容如下&#xf…

Flink DataStream读写Hudi

一、pom依赖 测试案例中&#xff0c;pom依赖如下&#xff0c;根据需要自行删减。 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-ins…

HTML+CSS+JS 学习笔记(一)———HTML(下)

&#x1f331;博客主页&#xff1a;大寄一场. &#x1f331;系列专栏&#xff1a;前端 &#x1f331;往期回顾&#xff1a;HTMLCSSJS 学习笔记&#xff08;一&#xff09;———HTML(上) HTMLCSSJS 学习笔记&#xff08;一&#xff09;———HTML(中) &#x1f618;博客制作不易…

Linux工具——gcc和gdb

&#x1f3c0;博主主页 &#x1f3c0;gitee主页 目录&#x1f3c0;Linux编译器-gcc⚽️gcc使用⚽️函数库&#x1f3c0;Linux调试器-gdb⚽️简介⚽️gdb使用&#x1f3c0;Linux项目自动化构建工具-make/Makefile⚽️简介⚽️依赖关系⚽️make/Makefile实现原理⚽️项目清理&…

证明电压电流相位差的余弦值和功率因数相等

证明&#xff1a;“电压电流相位差的余弦值”和“功率因数”相等。 电压电流相位差的余弦值和功率因数相等&#xff0c;这在《电路分析》中给出过结论&#xff0c;但没有给出详细的证明过程。其次&#xff0c;在电气工程师考试中&#xff0c;也会经常遇到。 电压电流相位差&am…

【Linux】虚拟机的克隆

【想要克隆虚拟机&#xff0c;被克隆的虚拟机必须是关机状态&#xff1b;】 一、克隆虚拟机 1、右击想要克隆的虚拟机 2、进入到这个页面后点击“下一步” 3、进入到这个页面后点击“下一步” 4、进入这个页面后选“创建完整克隆”&#xff0c;再点击下一步 5、最好将位置改成…

入门IC必读书目,你想知道的都在这里

在IC行业&#xff0c;技术和经验都很重要&#xff0c;为了更好的学习&#xff0c;现为大家整理了各岗位的学习书目。 通用基础类 《半导体物理学》 这本书被国内大部分高校都采用为半导体物理课程的教材。同时&#xff0c;也是部分高校推荐使用的微电子专业硕士生初试参考书。…

【cmake学习】搭建一个简单的cmake工程(优化版)

之前搭建了一个基本的cmake工程&#xff0c;仅使用了一个 CMakeLists.txt 文件来管理整个工程&#xff0c;实际上一个工程里可以包含多个 CMakeLists.txt 文件&#xff0c;这样做的目的是把引入所需文件、生成执行文件/库文件 这两个工作交由两个 CMakeLists.txt 分别实现。 【…

接口自动化【一】(抓取后台登录接口+postman请求通过+requests请求通过+json字典区别)

文章目录 前言一、requests库的使用二、json和字典的区别三、后端登录接口-请求数据生成四、接口自动化-对应电商项目中的功能五、来自postman的代码-后端登录总结前言 记录&#xff1a;json和字典的区别&#xff0c;json和字段的相互转化&#xff1b;postman发送请求与Python…

Python:清华ChatGLM-6B中文对话模型部署

1、简介 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级的显卡上进行本地部署&#xff08;INT4 量化级别下最低只需 6GB 显存&#xff0…

SpringBoot程序运行时动态修改主数据库配置(不需要改配置,不需要重启)

SpringBoot程序运行时修改主数据库配置&#xff08;不需要改配置&#xff0c;不需要重启&#xff09;搞事背景心路历程搞事背景 在面试某家单位的时候&#xff0c;碰到了一家单位线上考试&#xff0c;要求开发一个springboot后台。一眼看去都是正常的需求&#xff0c;突然我在…

Raft: 基于 Log 复制的共识算法

References Raft 演示 In Search of an Understandable Consensus Algorithm (Extended Version) 1. Raft 是什么 1.1 目标: 复制 Log 在讲解 Raft 协议的具体行为之前我们需要明白 Raft 的目标是什么&#xff1f;在一些情况下我们需要保证分布式集群中的机器拥有相同的数…

IOC容器——Bean

IOC容器——BeanBean配置name别名属性Bean作用范围scopeBean的实例化构造方法示例化静态工厂实例化实例工厂与FactoryBean实例工厂FactoryBeanbean的生命周期Bean配置 name别名属性 Bean ID 唯一&#xff0c;而关于Spring别名&#xff0c;我们可以在配置文件中使用name来定义&…

Google Play管理中心和ASO的重要性

Android Vitals 是我们应用优化的重要组成部分&#xff0c;能够显示应用的运行状况。一般来说&#xff0c;如果应用具有良好的体验&#xff0c;它会更容易在Google Play中被用户发现&#xff0c;从而获得更好的排名和更多的安装量。 从开发者的角度来看&#xff0c;Android Vi…

JAVA8新特性stream流收集为Map,value为null导致空指针的问题

jdk8 新特性stream深受喜爱&#xff0c;平时使用比较多&#xff0c;其中有&#xff1a; Map<String, String> collect2 list.stream().collect(Collectors.toMap(Book::getName, Book::getIdNO,(pre, after) -> pre)); 现象如下&#xff1a; package MainTest.str…

HTML5 <nav> 标签、HTML5 <noscript> 标签

HTML5 <nav> 标签 实例 HTML5 <nav>标签用于表示HTML页面中的导航&#xff0c;可以是页与页之间导航&#xff0c;也可以是页内的段与段之间导航。 一个导航链接实例&#xff1a; <nav> <a href"/html/">HTML</a> | <a href&qu…