`

Unix 网络编程_阅读笔记四 线程、客户/服务器程序设计范式

 
阅读更多

Unix 网络编程_阅读笔记 四 (Socket高级篇之线程、客户/服务器程序设计范式)

--Unix Network Programming

 王宇 原创并发布


本文代码,在以下环境下编译通过

  • CentOS 6.4
  • Kernal version: 2.6.32
  • GCC version: 4.4.7

一、 线程

父进程accept一个连接,fork一个子进程,该子进程处理与该连接对端的客户之间的通信,这种范式多少年来一直用的挺好,fork调用却存在一些问题:

  • fork是昂贵的。
  • fork返回之后父子进程之间信息的传递需要进程间通信(IPC)机制。调用fork之前父进程向尚未存在的子进程传递信息相当容易,因为子进程将从父进程数据空间及所有描述符的一个副本开始运行。然而从子进程往父进程返回信息却比较费力。

线程有助于解决这两个问题。线程有时称为轻权进程(lightweight process)。同一进程内的所有线程共享相同的全局内存。这使得线程之间易于共享信息,然而伴随这种容易性而来的却是同步问题。

线程除了共享全局变量外还共享:

  • 进程指令
  • 大多数数据
  • 打开的文件(即描述符)
  • 信号处理函数和信号处置
  • 当前工作目录
  • 用户ID和组ID

不过每个线程有各自的:

  • 线程ID
  • 寄存器集合,包括程序计数器和栈指针
  • 栈(用于存放局部变量和返回地址)
  • errno
  • 信号掩码
  • 优先级

1、基本线程函数:创建和终止

  • pthread_create 函数 创建并启动一个线程
  • pthread_join 函数 等待给定线程终止
  • pthread_self 函数 获得线程自身线程ID
  • pthread_detach 函数 转变为脱离状态(detached)
  • pthread_exit 函数 终止线程

2、使用线程的str_cli函数

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<pthread.h>
#include<errno.h>

#define SERV_PORT 51000
#define MAXLINE 4096 
#define SA struct sockaddr

char* Ip_address = "192.168.153.130";

static int socket_fd;
static FILE *fp;


size_t readline(int fd, void *vptr, size_t maxlen)
{
    size_t n, rc;
    char c, *ptr;

    ptr = vptr;

    for(n = 1; n < maxlen; n++)
    {
again:
        if( (rc = read(fd, &c, 1)) == 1)
        {
            *ptr++ = c;     
            if(c == '\n')
            {
                break;
            }
        }
        else if(rc == 0)
        {
            return(n - 1);
        }
        else
        {
            if(errno == EINTR)
            {
                goto again; 
            }     
            return (-1);
        }

    }

    return(n);
}

ssize_t writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;
    while(nleft > 0)
    {
        if((nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if(nwritten < 0 && errno == EINTR)
            {
                nwritten = 0;
            }
            else
            {
                return(-1);
            }

        }

        nleft -= nwritten;
        ptr += nwritten;    
    }

    return(n);
}

void* copyto(void *arg)
{
    char sendline[MAXLINE];

    while(fgets(sendline, MAXLINE, fp) != NULL )
    {
        if(writen(socket_fd, sendline, strlen(sendline)) == -1)
        {
            perror("Error: write socket!\n");
        }

    }
    if(shutdown(socket_fd, SHUT_WR) == -1)
    {
        perror("Error: shutdown!\n");
    }

    return(NULL);
}


void str_cli(FILE *fp_arg, int socket_fd_arg)
{
    char recvline[MAXLINE];
    pthread_t tid;
    socket_fd = socket_fd_arg;
    fp = fp_arg;

    if(pthread_create(&tid, NULL, copyto, NULL) != 0)
    {
        perror("Error: pthread_create!\n");
    }

    while(readline(socket_fd, recvline, MAXLINE) > 0)
    {
        fputs(recvline, stdout);
    }
}


int main()
{
    int socket_fd, connect_rt;
    struct sockaddr_in servaddr;
    char err_message[MAXLINE];

    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    if(socket_fd == -1)
    {
        printf("Error: created socket!\n");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, Ip_address, &servaddr.sin_addr);

    connect_rt = connect(socket_fd, (SA *)&servaddr, sizeof(servaddr));

    if(connect_rt != 0)
    {
        perror(err_message);    
        printf("Error: connect socket!\n");
        printf("%s\n", err_message);
        exit(1);
    }

    str_cli(stdin, socket_fd);


    exit(0);
}

 

 

3、使用线程的TCP回射服务程序

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<errno.h>
#include<pthread.h>

#define SA struct sockaddr
#define SERV_PORT 51000
#define MAXLINE 4096 
#define LISTENQ 1024


void str_echo(int socket_fd)
{
    ssize_t n;
    char buf[MAXLINE];

again:
    while( (n = read(socket_fd, buf, sizeof(buf))) > 0)
    {
        if(write(socket_fd, buf, n) == -1 )
        {
            perror("Error:write.\n");
        }
    }

    if( n < 0 && errno == EINTR)
    {
        goto again;
    }
    else if(n < 0)
    {
        perror("ERROR: str_echo\n");
    }

}


static void* doit(void *arg)
{
    int conn_fd;
    conn_fd = *((int *)arg);
    free(arg);

    pthread_detach(pthread_self());
    str_echo(conn_fd);

    close(conn_fd);
    return (NULL);
}


int main()
{
    int socket_fd, *connect_fd; 
    struct sockaddr_in servaddr, clientaddr;
    socklen_t client_len;
    pid_t child_pid;
    pthread_t tid;

    socket_fd = socket(AF_INET, SOCK_STREAM, 0);

    if(socket_fd == -1)
    {
        perror("Error: created socket!\n");
        exit(1);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    if((bind(socket_fd, (SA *)&servaddr, sizeof(servaddr))) == -1)
    {
        perror("Error: Bind port!\n");
        exit(1);
    }

    if((listen(socket_fd, LISTENQ)) == -1)
    {
        perror("Error: Listen!\n");
        exit(1);
    }

    for(;;)
    {
        client_len = sizeof(clientaddr);
        connect_fd = (int*)malloc(sizeof(int));

        if(connect_fd == NULL)
        {
            perror("Error: malloc!\n");
            exit(1);
        }

        *connect_fd = accept(socket_fd, (SA *)&clientaddr, &client_len);

        if(pthread_create(&tid, NULL, &doit, connect_fd) == -1)
        {
            perror("Error: pthread!\n");
            exit(1);
        }

    }

    exit(0);
}

 

 

4、线程特定数据

把一个未线程化的程序转换成使用线程的版本时,有时会碰到因其中有函数使用静态变量而引起的一个常见编程错误。 有以下几种解决方案:

  • 使用线程特定数据。这个办法并不简单,而且转换成了只能在支持线程的系统上工作的函数。本办法的优点是调用顺序无需变动,所有变动都体现在库函数中而非调用这些函数的应用程序中。
  • 改变调用顺序
  • 改变接口的结构,避免使用静态变量,这样函数就可以是线程安全的。

每个系统支持有限数量的线程特定数据元素。POSIX要求这个限制不小于128(每个进程)

5、互斥锁

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
Both return: 0 if OK, positive Exxx value on error
 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

#define NLOOP 200
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;


void* doit(void *vptr)
{
    int i, value;

    for(i = 0; i < NLOOP; i++)
    {
        if(pthread_mutex_lock(&counter_mutex) !=0)  
        {
            perror("Error: lock\n");
        }

        value = counter;
        printf("%d: %d\n", pthread_self(), value + 1);
        counter = value + 1;

        if(pthread_mutex_unlock(&counter_mutex) !=0)    
        {
            perror("Error: unlock\n");
        }
    }

    return(NULL);
}


int main()
{

    pthread_t t_id_A, t_id_B;

    pthread_create(&t_id_A, NULL, &doit, NULL);
    pthread_create(&t_id_B, NULL, &doit, NULL);

    pthread_join(t_id_A, NULL);
    pthread_join(t_id_B, NULL);

    exit(0);
}
 

6、条件变量

修改以上代码,使counter变量每增加10,则打印一个分隔线:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

#define NLOOP 100
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* doit(void *vptr)
{
    int i, value;

    for(i = 0; i < NLOOP; i++)
    {
        if(pthread_mutex_lock(&counter_mutex) !=0)  
        {
            perror("Error: lock\n");
        }

        value = counter;
        printf("%d: %d\n", pthread_self(), value + 1);
        counter = value + 1;

        if((counter%10) == 0)
        {
            pthread_cond_signal(&cond);
        }

        if(pthread_mutex_unlock(&counter_mutex) !=0)    
        {
            perror("Error: unlock\n");
        }

        sleep(1);
    }

    return(NULL);
}

void* do_split(void *vptr)
{
    for(;;)
    {
        if(pthread_mutex_lock(&counter_mutex) !=0)  
        {
            perror("Error: lock\n");
        }

        pthread_cond_wait(&cond, &counter_mutex);

        if(counter == (NLOOP * 2))
        {
            pthread_exit(0);
        }

        printf("%d: ----------\n", pthread_self());

        if(pthread_mutex_unlock(&counter_mutex) !=0)    
        {
            perror("Error: unlock\n");
        }
    }
}


int main()
{

    pthread_t t_id_A, t_id_B, t_id_C;

    pthread_create(&t_id_A, NULL, &do_split, NULL);
    pthread_create(&t_id_B, NULL, &doit, NULL);
    pthread_create(&t_id_C, NULL, &doit, NULL);

    pthread_join(t_id_A, NULL);
    pthread_join(t_id_B, NULL);
    pthread_join(t_id_C, NULL);

    exit(0);
}

 


二、客户/服务器程序设计范式

1、服务器程序设计范式:

  • 1、迭代服务器(无进程控制,用作测量基准)
  • 2、并发服务器,每个客户请求fork一个子进程
  • 3、预先派生子进程,每个子进程无保护地调用accept
  • 4、预先派生子进程,使用文件上锁保护accept
  • 5、预先派生子进程,使用线程互斥锁上锁保护accept
  • 6、预先派生子进程,父进程向子进程传递套接字描述符
  • 7、并发服务器,每个客户请求创建一个线程
  • 8、预先创建线程服务器,使用互斥锁保护accept
  • 9、预先创建线程服务器,由主线程调用accept

2、总结:

  • 当系统负载较轻时,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了。这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接受。我们的其他意见是就重负荷运行的服务器而言的,譬如Web服务器。
  • 相比传统的每个客户fork一次设计范式,预先创建一个子进程池或一个线程池的设计范式能够把进程控制CPU时间降低10倍或以上。编写这些范式的程序并不复杂,不过需超越本章所给例子的是:监视闲置子进程个数,随着所服务客户数的动态变化而增加或减少这个数目
  • 某些实现允许多个子进程或线程阻塞在同一个accept调用中,另一些实现却要求包绕accept调用安置某种类型的锁加以保护。文件锁或Pthread互斥锁都可以使用。
  • 让所有子进程或线程自行调用accept通常比让父进程或主线程独自调用accept并把描述符传递给子进程或线程来的简单而快速。
  • 由于潜在select冲突的原因,让所有子进程或线程阻塞在同一个accept调用中比让它们阻塞在同一个select调用中更可取。
  • 使用线程通常远快于使用进程。不过选择每个客户一个子进程还是每个客户一个线程取决于操作系统提供什么支持,还可能取决于为服务每个客户需激活其他什么程序(若有其他程序需激活的话)举例子说,如果accept客户连接的服务器调用fork和exec,那么fork一个单线程的进程可能快于fork一个多线程的进程
分享到:
评论

相关推荐

    《Python核心编程》读书笔记

    Python的设计简洁而强大,拥有丰富的标准库,支持正则表达式,提供系统调用接口,支持多线程,具有垃圾回收机制,支持交互式编程和多种编程范式。Python的语法允许程序员用更少的代码行表达概念,其代码密度可以是...

    关于Python的个人学习笔记

    随着技能的提升,可以涉及更高级的主题,如异常处理、多线程、网络编程和数据库操作。Python丰富的生态系统,如数据分析(NumPy、Pandas)、科学计算(SciPy)、Web开发(Django、Flask)和人工智能(TensorFlow、...

    mybatis-3.0.5.jar中文-英文对照文档.zip

    # 压缩文件中包含: 中文-英文对照文档 jar包下载地址 Maven依赖 Gradle依赖 源代码下载地址 # 本文件关键字: jar中文-英文对照文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

    SQLite3的使用+API接口的调用

    SQLite3的使用+API接口的调用(c/c++、Qt)

    aws-java-sdk-s3-1.12.246.jar中文文档.zip

    # 压缩文件中包含: 中文文档 jar包下载地址 Maven依赖 Gradle依赖 源代码下载地址 # 本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

    基于混沌-高斯变异-麻雀搜索算法的BP神经网络优化(CGSSA-BP)在电厂运行数据回归预测中的应用(含优化前后对比)MATLAB代码

    内容概要:本文介绍了如何使用混沌-高斯变异-麻雀搜索算法(CGSSA)优化BP神经网络来进行电力行业的回归预测。主要内容包括数据准备、BP神经网络构建、CGSSA优化过程、以及优化前后效果对比。通过MATLAB代码实现,展示了如何读取EXCEL数据并进行训练和测试,最终通过图表和误差指标对比优化前后的预测效果。 适用人群:适用于具有一定MATLAB编程基础和技术背景的研究人员、工程师,特别是从事电力数据分析和机器学习领域的专业人士。 使用场景及目标:① 对电厂运行数据进行精准回归预测,辅助电厂运维规划和能源分配优化;② 提供详细的代码实现和优化方法,帮助用户理解和应用CGSSA优化BP神经网络的技术。 其他说明:文中提供了完整的代码示例,包括主程序、数据划分、BP神经网络预测、CGSSA-BP神经网络预测和结果对比等功能模块。此外,还讨论了一些优化技巧和注意事项,如数据归一化、隐藏层节点选择等。

    第三方浏览器下载包.apk

    第三方浏览器下载包.apk

    嵌入式系统开发_ARM11架构_Linux操作系统_QT48开发环境_QTCreator工具_人脸识别算法_商业保密技术_QML界面开发_视频嵌入技术_跨平台移植_基于上海高校.zip

    嵌入式系统开发_ARM11架构_Linux操作系统_QT48开发环境_QTCreator工具_人脸识别算法_商业保密技术_QML界面开发_视频嵌入技术_跨平台移植_基于上海高校

    宠物领养救助系统 - Java课程设计/毕业设计项目 - SpringBoot+Vue+MySQL宠物领养平台

    宠物领养救助系统是基于SpringBoot+MyBatisPlus+Vue+MySQL开发的Java项目,适合作为Java课程设计、毕业设计或期末大作业。技术涵盖前后端开发,帮助初学者快速掌握Java全栈技能。采用IDEA开发,代码规范,易于二次扩展,是Java学习者的理想实战项目!

    winmm钢琴程序代码QZQ.txt

    winmm钢琴程序代码QZQ

    scratch少儿编程逻辑思维游戏源码-狗狗变形者.zip

    scratch少儿编程逻辑思维游戏源码-狗狗变形者.zip

    thymeleaf-2.0.8.jar中文文档.zip

    # 压缩文件中包含: 中文文档 jar包下载地址 Maven依赖 Gradle依赖 源代码下载地址 # 本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

    基于回声状态网络(ESN)的数据分类预测算法实现与Matlab代码实践

    内容概要:本文详细介绍了基于回声状态网络(ESN)的数据分类预测方法及其Matlab实现。首先简述了ESN的基本概念,强调其作为特殊递归神经网络的特点,即储备池连接权值固定不变。接着逐步展示了从数据准备、ESN模型构建、训练到预测的具体步骤,包括参数设置、状态更新规则、输出层训练方法等。文中还探讨了各部分代码的作用及意义,并提供了调参建议,如谱半径、泄漏率等参数的选择依据。此外,作者分享了自己在实验过程中的一些经验和心得,指出ESN在处理时间序列分类任务方面的优势。 适合人群:对递归神经网络特别是ESN感兴趣的科研工作者、学生以及有一定编程基础并想深入了解ESN机制的研究人员。 使用场景及目标:适用于需要进行时间序列数据分析和分类的应用场合,如金融趋势预测、语音识别等领域。通过学习本文提供的完整流程,读者可以掌握如何利用ESN解决实际问题,并能够根据自身需求调整模型参数以获得更好的性能。 其他说明:文中不仅给出了完整的Matlab代码示例,而且针对每一环节进行了详细的解释,帮助读者更好地理解ESN的工作原理和技术细节。同时提醒读者注意某些关键参数的调节范围,以便于在实际项目中取得理想的效果。

    威纶通触摸屏一机多屏程序

    内容概要:本文深入介绍了威纶通触摸屏一机多屏程序及其与FX3U系列PLC和MODBUS通讯的集成应用。首先,文章阐述了系统的硬件架构,即一个FX3U系列PLC搭配四个MT6051ip触摸屏的工作原理。接着,详细解析了威纶通模板的特点,特别是梯形图的详尽注释,便于理解和维护。此外,文章还探讨了PLC与上位机的MODBUS通讯设置,包括波特率、数据位、停止位等参数的具体配置方法。最后,强调了该系统的学习意义和借鉴价值,适用于新手和资深工程师。 适合人群:从事自动化控制领域的工程师和技术人员,尤其是那些希望深入了解PLC、触摸屏和MODBUS通讯的人群。 使用场景及目标:①帮助工程师快速掌握威纶通触摸屏的一机多屏配置;②提高PLC与触摸屏、上位机之间的通讯效率;③优化中小型企业生产线的监控系统,提升生产效率和稳定性。 其他说明:文中提供的实例和代码片段有助于读者更好地理解和实践相关技术,同时附带了一些实用的小技巧,如心跳检测和双看门狗设计,增强了系统的可靠性和容错能力。

    无人机航测_大疆航线规划_KMZ文件生成与解析_基于JavaXStream注解的DJIPilot2兼容航线文件处理工具_支持航点飞行建图航拍等多种任务模板_包含航线高度速度航向角失.zip

    无人机航测_大疆航线规划_KMZ文件生成与解析_基于JavaXStream注解的DJIPilot2兼容航线文件处理工具_支持航点飞行建图航拍等多种任务模板_包含航线高度速度航向角失

    Matlab蒙特卡洛模拟求解复杂模型可靠度近似解

    内容概要:本文详细介绍了蒙特卡洛方法在工程可靠度计算中的应用,特别是在处理涉及多种概率分布参数的情况下。首先展示了基本的Matlab实现,如生成正态分布和极值分布的随机样本,并通过极限状态函数判断结构的安全性。接着讨论了处理相关变量的有效方法——拉丁超立方抽样,以及进一步提高计算效率的重要抽样法。此外,还探讨了并行计算和置信区间的计算,确保结果的准确性。最后强调了蒙特卡洛方法在解决复杂可靠度问题中的优越性和实用性。 适合人群:从事工程可靠度分析的研究人员和技术人员,以及对数值模拟感兴趣的工程师。 使用场景及目标:适用于需要评估结构或其他系统的可靠性的场合,尤其是在无法获得解析解的情况下。目标是提供一种简单有效的数值方法来估算失效概率和可靠度。 其他说明:文中提供了大量具体的Matlab代码示例,帮助读者更好地理解和应用蒙特卡洛方法。同时提醒使用者注意计算资源的合理分配,以平衡精度和效率。

    人力资源管理系统 - Java课程设计 - 毕业设计 - 期末大作业 - SpringBoot+Vue项目 - 初学者实战

    基于SpringBoot+MyBatisPlus+Vue+MySQL的人力资源管理系统,专为Java课程设计、毕业设计及期末大作业打造。采用主流技术栈(Idea开发),适合Java初学者快速上手,提供完整源码与文档,助力高效完成学习任务!

    Screenshot_2025-01-26-01-50-08-41.jpg

    Screenshot_2025-01-26-01-50-08-41.jpg

    基于FPGA的DDS多波形信号发生器:支持多种波形生成与仿真,采用Quarter与Modesim联合仿真,Verilog HDL编程实现

    内容概要:本文详细介绍了如何利用FPGA和Verilog HDL语言实现一个多功能DDS(直接数字频率合成)信号发生器。该发生器不仅可以生成常见的正弦波、方波、锯齿波和三角波,还可以实现2PSK、2ASK和AM调制。文章首先解释了DDS的基本原理,即通过相位累加器和查找表(LUT)生成波形。接着分别给出了各个波形的具体实现代码,并讨论了调制技术的实现方法。最后,文章提供了仿真的步骤以及一些实用的调试技巧,确保设计方案能够在实际硬件环境中正常工作。 适合人群:具备一定FPGA和Verilog编程基础的电子工程师、研究人员和技术爱好者。 使用场景及目标:适用于需要精确控制频率和波形的应用场合,如通信系统、音频设备、测试仪器等。目标是掌握DDS技术的工作原理及其在FPGA上的具体实现方法,同时提高对复杂数字系统的理解和设计能力。 其他说明:文中提供的代码片段可以直接用于实验环境,有助于快速搭建原型并进行验证。此外,作者还分享了一些优化建议和常见问题解决方案,使读者能够更好地应对实际项目中的挑战。

Global site tag (gtag.js) - Google Analytics