• 2008-01-10

    TCP/IP 指数增长和线性增长的编程实现

    Views: 20218 | 1 Comment

    在 TCP 的慢启动和拥塞控制中, 当发生拥塞时, TCP 进行慢启动, 当慢启动进行到一定阀值后进行拥塞避免. 正如 "TCP/IP 协议详解" 中提到的, "慢启动" 这个术语并不准确, 拥塞窗口在慢启动期间以指数速率增长. 在拥塞避免算法进行时, 拥塞窗口以线性速率增长, 每次加 1.

    这两个算法可以用编程语言来实现:

    using System;
    
    class Test
    {
        static int cwnd1 = 1;
        static int cwnd2 = 1;
        static int ssthresh = 64;
    
        public static void Main(){
            int step = 20;
            for(int i=0; i<step; i++){
                grow1();
                grow2();
                Console.WriteLine("{0,8}\t{1,8}", cwnd1, cwnd2);
            }
        }
    
        // 完全指数增长.
        static void grow1(){
            int num = cwnd1;
            for(int n=0; n<num; n++){
                cwnd1 += 1;
            }
        }
    
        // 先指数增长, 然后加性增长.
        static void grow2(){
            int wnd = cwnd2;
            int ssthresh_var = 0;
            for(int n=0; n<wnd; n++){
                if(cwnd2 < ssthresh){
                    cwnd2 += 1;
                }else{
                    if (++ssthresh_var == wnd) {
                        ssthresh_var = 0;
                        cwnd2 += 1;
                    }
                }
            }
        }
    }
    

    TCP/IP 源码的一个疑问:

    TCP/IP 详解 1 这样描述: 设置拥塞窗口 cwnd 为1, 发送 1 个报文段, 收到一个 ACK 后, cwnd 加 1, 然后发送 2 个, 收到 2 个 ACK 后, cwnd 加 2, 然后发送 4 个...

    这确实是指数增长, 但是, 因为延时 ACK 的存在, 发送的2个报文段可能只收到 1 个 ACK, 所以 cwnd 只增加 1 而不是增加 2. 在延时 ACK 影响的范围内, 这是线性增长.

    希望熟悉 TCP/IP 的朋友帮忙解惑, 十分感谢!

    Posted by ideawu at 2008-01-10 09:03:34
  • 2008-01-04

    生产者消费者模型中生产者的速度快于消费者时所产生的问题及其解决方法讨论

    Views: 14533 | No Comments

    在生产者消费者模型中, 如果生产者生产产品(在下文中, 和读者写者模型中的信息是同一个概念)的速度 P 快于消费者消费产品的速度 C, 那么当时间趋于无限时, 待处理的产品的数量也将是无限多. 在带有缓冲区的生产者消费者模型中, 这意味着缓冲区将被挤爆, 导致系统崩溃.

    下面两个条件可能导致系统的崩溃:

    1. 缓冲区是有限的.
    2. 缓冲区中的产品是有时效的.

    这两个条件是任何系统固有的, 不能消除. 所以为了避免系统崩溃, 可有以下的处理方法:

    1. 在系统可接受的范围内丢弃多出的产品, 使生产者的速度宏观上和消费者的速度相等.
    2. 实现一种抑制生产者生产速度的机制, 使生产者的速度和消费者的速度相等.
    3. 增加消费者的数量, 使其为至少 P/C.

    本文讨论的是以第 3 种为主要解决方法, 前两种为辅助解决方法时的情况. 消费者数量是有限制的, 因为消费者本身仍然需要消费其它资源.

    消费者有两个队列: 等待队列(WaitQueue)和工作队列(WorkQueue). 等待队列中包含当前系统中正在等待产品的消费者. 工作队列中包含当前系统中正在处理产品的消费者.

    用信号量来实现的伪代码:

    // 运行中的 BeginWait 的实例上限.
    int MAX_WAIT = 1;
    Semaphore waitSemaphore = new Semaphore(MAX_WAIT, MAX_WAIT);
    // 运行中的 DoWork 的实例上限.
    int MAX_WORK = 3;
    Semaphore workSemaphore = new Semaphore(MAX_WORK, MAX_WORK);
    
    void BeginWait(callback){
        // 该方法将回调函数注册到某个系统中, 一旦等待的信号出现, 这个系统将调用回调函数.
        // 例如, 可以利用操作系统的信号机制.
        // 无论如何, 该方法的实现必须是几乎"立即"返回的.
    }
    
    // 本方法的实例上限 = min(MAX_WORK * 2 - 1, MAX_WORK + MAX_WAIT - 1)
    void EndWait() {
        waitSemaphore.Release();    
        DoWork();
        if (waitSemaphore.WaitOne(0, false)) {
            BeginWait(this.EndWait);
        }
    }
    
    void DoWork(){
        workSemaphore.WaitOne();
        if (workSemaphore.WaitOne(0, false)) {
            if (waitSemaphore.WaitOne(0, false)) {
                BeginWait(this.EndWait);
            }
            workSemaphore.Release();
        }
        // 进行操作, 花费一些时间...
        workSemaphore.Release();
    }
    

    因为注册一个回调函数的花费可以忽略不计, 所以建议 MAX_WAIT 的值不要大于 1. 由于算法本身的实现, MAX_WAIT 大于 MAX_WORK 时和等于时的效果是相同的.

    完整的可运行的 C# 源代码

    下面的代码需要在 C# 编译器 8, 和 .Net 框架 2 下编译和运行. 运行它, 你可以观察到生产者和消费者的实例数目.

    using System;
    using System.Threading;
    
    class Test
    {
        public static void Main(){
            Class1 c = new  Class1();
            c.Run();
        }
    }
    
    class Class1
    {
        private Random rand = new Random();
        private const int MAX_WAIT = 1;
        private Semaphore waitSemaphore = new Semaphore(MAX_WAIT, MAX_WAIT);
        private const int MAX_WORK = 6;
        private Semaphore workSemaphore = new Semaphore(MAX_WORK, MAX_WORK);
        // 将 Interlocked.Increment(ref num) 放到一段代码的开始处,
        // 同时将 Interlocked.Decrement(ref num); 放到这段代码的结束处,
        // 你将可以看到这段代码在操作系统中的运行实例数目.        
        private int num = 0;
    
        public void Run() {
            waitSemaphore.WaitOne();
            BeginWait(this.EndWait);
            Console.WriteLine("Started");
            while(true){
                Thread.Sleep(500);
                Console.WriteLine(
                    "num={0,2}, working={1,3}. waiting={2,3}. maxWorkerId={3,3}, maxWaitId={4,3}",
                    num, maxWorkerId - minWorkerId, maxWaitId - minWaitId, maxWorkerId, maxWaitId);
            }
        }
    
        private void EndWait(IAsyncResult ar) {
            Interlocked.Increment(ref num);
            waitSemaphore.Release();
            DoWork();
            if (waitSemaphore.WaitOne(0, false)) {
                BeginWait(this.EndWait);
            }
            Interlocked.Decrement(ref num);
        }
    
        // 工作中的消费者的标识.
        private int maxWorkerId = 0;
        private int minWorkerId = 0;
    
        private void DoWork() {
            Interlocked.Increment(ref maxWorkerId);        
            workSemaphore.WaitOne();
            // 应该实现一种计数锁. SemaphoreLock?
            if (workSemaphore.WaitOne(0, false)) {
                if(waitSemaphore.WaitOne(0, false)){
                    BeginWait(this.EndWait);
                }
                workSemaphore.Release();
            }
            Thread.Sleep(rand.Next(2000) + 2000);
            workSemaphore.Release();
            Interlocked.Increment(ref minWorkerId);
        }
    
        delegate void WaitDelegate();
    
        private void BeginWait(AsyncCallback callback) {
            WaitDelegate wait = this.Wait;
            // 使用异步委托, 所以 BeginWait 应该会立即返回.
            wait.BeginInvoke(callback, null);
        }
    
        // 等待中的消费者标识.
        private int maxWaitId = 0;
        private int minWaitId = 0;
    
        private void Wait() {
            Interlocked.Increment(ref maxWaitId);
            Thread.Sleep(rand.Next(2000) + 200);
            Interlocked.Increment(ref minWaitId);
        }
    
    }
    

    2008-01-08 更新

    Posted by ideawu at 2008-01-04 11:04:23
  • 2007-12-18

    Google Talk 界面开发分析

    Views: 23933 | No Comments

    有分析文章表明, Google Talk 的界面很大部分使用了 MSHTML 控件. 所以, 它的开发方式如前面的文章(GUI 的架构设计)提到的: 结构描述语言 + 样式描述语言 + 结构操作语言 + 虚拟机(包含渲染器, 命令解释器).

    如果你做过 Web 界面开发, 你应该能看出 Google Talk 在哪方面使用了 MSHTML 控件. 例如在设置窗口里的界面选项:

    google talk

    在 C:\Documents and Settings\XXXXX\Local Settings\Application Data\Google\Google Talk\themes\system\chat 目录下你可以找到每一个界面对应的 HTML 模板和 CSS 样式表(main.css). 修改某个主题的 main.css 文件, 然后在设置窗口里选择该主题, 你就能看到界面的样式已经改变.

    Status.html 文件的内容是:

    <div class='system1st' style='color:%color%'>%message%</div>
    <div id="insert"></div>
    

    %message% 是一种简单的变量替换. main.css 里标签的名称仍然使用大写字母, 也许在 Web 版的 Google Talk 开发时, Google 的开发人员还没注意到 Web 标准.

    Posted by ideawu at 2007-12-18 09:29:28 Tags:
  • 2007-11-25

    GUI 的架构设计

    Views: 22466 | No Comments

    当前的 GPU 仍然拘泥于像素的计算, 它们应该建立更高的逻辑, 正如在某些领域, 硬件单元在整合原来的软件功能. 当前应用程序的 GUI 的特点是文本和图片堆砌.

    1. 建议的 GUI 架构

    应用程序的 GUI 应该和应用的计算是完全分离的. 当前的 GUI 在架构上和应用的计算在二进制代码层面是不区分的, 而且在编程语言层面也没有区分, 两者是高度耦合的.

    在编程语言层面, GUI 和应用的计算不应该通过变量引用耦合在一起, 而是应该通过单一的通信通道进行通信.

    在二进制代码层面, GUI 和应用的计算应该分离, 例如在计算机的体系结构上引入 GUI 处理器, 将 GUI 的渲染, 编程等交由 GUI 处理器处理. GUI 处理器在硬件级别上和 CPU 进行通信, 当然, 这种通信应该建立在某种较高的层面上.

    2. GUI 的特点

    在很多 GUI 应用中, 界面是稳定的, 结构化的, 组合性的.

    例如在一个文本编辑软件中, 虽然文本编辑区域不断增删文本, 但是, 菜单栏, 工具栏, 状态栏等的结构几乎不改变. 这种稳定性决定了可以使用描述性的文本来描述界面.

    继续上面的例子. 工具栏的按钮可能动态地(在运行时)增加或者减少几个, 但是, 这种变化是结构化的, 可以使用 DOM 方式进行操作.

    因为界面是结构化的, 所以可组合性比较高.

    3. GUI 的开发

    GUI 开发 = 结构描述语言 + 样式描述语言 + 结构操作语言 + 虚拟机(包含渲染器, 命令解释器). 一个例子是 HTML + CSS + JavaScript + Web Browser.

    3.1 使用声明式语言来描述界面的结构和外观

    当前的 GUI 开发往往使用 C, C++, Java 等命令式语言来描述界面的结构和外观, 但是, 声明式语言更适合这项工作.

    假设使用 XML 来描述界面的结构, 那么 XML 文本文档的结构很直观的反映了界面的结构. HTML/XHTML, GTK Glade, ASP.NET, Mozilla XUL, WPF 等使用 XML(或类 XML) 来描述界面.

    CSS 之类的样式语言可以用来描述界面的外观.

    外观和结构分离.

    3.2 使用命令式语言来改变界面的结构和外观

    命令式语言被用来改变界面的结构和外观, 可以使用 DOM 方式. 同时, 命令式语言用来同外界通信. 当然, 虚拟机可以内置部分的通信功能.

    3.3 要求

    上面提到的语言应该是解释型的.

    TODO: 某些概念有些模糊

    Posted by ideawu at 2007-11-25 10:58:48 Tags:
  • 2007-09-22

    使用流水线来实现并发

    Views: 10455 | No Comments

    如果下层实现了错误检测, 那么上层可以使用正面确认和重传(PAR, Positive Acknowledgment and Retransmission)机制来实现可靠传输. 因为"发送并等待一个确认"这个操作主要的时间消耗在数据传输的线路上, 所以 PAR 对线路的利用率和传输速率很低.

    提高传输速率的简单方法是使用多路传输, 同时采用多条 PAR 线路进行数据传输. 但是这样, 单条线路的利用率仍然很低. 另一种既提高线路利用率又提高传输速率的方法是对 PAR 进行流水线操作, 这样不需要增加传输线路.

    一个报文的 PAR 包括发送, 确认, 重传3个子操作, 它们的间距是传输时延. 下面不考虑重传.

    如果有多个报文要发送, 在第一个确认到来之前进行 snd_wnd 个报文的发送, 并且随着确认的到来, 进行后续的报文发送. 这样, 采用流水线方式发送, 可以总是保持线路在使用中, 传输速率为 v = snd_wnd/delay, 单位为报文.

    要采用流水线, 那么原来的操作必须是可分解的, 而且必须为单个操作进行标识, 比如采用序列号.

    流水线的实现方式:

    1. 为每一步子操作配备一个缓冲区, 当需要进行下一步子操作时, 将操作放入下一步缓冲.

    2. 对操作进行包装, 增加状态字段, 随着子操作的进行改变操作的状态.

    Posted by ideawu at 2007-09-22 06:55:02
  • 2007-09-21

    异步, 同步, 连接建立, 连接断开, 通信

    Views: 14535 | No Comments

    异步, 同步, 连接建立, 连接断开, 通信.

    流水线, 队列, 计时器.

    Posted by ideawu at 2007-09-21 21:34:08
|<<<5678910111213>>>| 12/13 Pages, 76 Results.