2024年2月

1、简介

Socket.D 是基于"事件"和"语义消息""流"的网络应用层协议。底层可以依赖 TCP、UDP、KCP、WebSocket 等传输层协议。其开发背后的动机是用开销更少的协议取代超文本传输协议(HTTP),HTTP 协议对于许多任务(如微服务通信)来说效率低下。

2、Socket.D 的集群应用

在 Socket.D 的集群故事里,会有三个角色:

  • 请求者
  • 经理人(或者中间人)
  • 响应者

客户端通过 url 连接经理人后,就会成为集群的一部分。它可以是请求者,也可以是响应者。

sd:tcp://127.0.0.1:8602?@=demoapp

url 会包括:

  • 协议头(sd 表示 socket.d 协议,tcp 表示传输方案)
  • 地址与端口
  • 路径
  • 还有
    @
    参数,这个参数会申明自己的应用名字。连接经理人时,也相当于完成身份注册了。

在集群内部,相互间通过
At
进行发起向“响应者”的请求。就像:

session.send("test", new StringEntity("hello").at("demoapp"));

3、集群的四种转发

在集群的活动中,请求者会发消息给经理人,经理人根据 at name 再转发给相应的响应者。

  • 四种转发方式(单播,单播!,组播,广播):
at 描述 备注
demoapp 单播 给叫这个名的其中一个会话发(使用 平均轮询 “负载均衡”策略)
demoapp! 单播! 给叫这个名的其中一个会话发(使用 ip_hash “负载均衡”策略)
demoapp* 组播 给叫这个名的整组会话发(如果自己也叫这个名,则自己除外)
* 广播 给集群里的全部会话发(自己除外)
  • 通过at方式进行转发,示例:
session.send("test", new StringEntity("hello").at("demoapp"));
session.send("test", new StringEntity("hello").at("demoapp!"));
session.send("test", new StringEntity("hello").at("demoapp*"));
session.send("test", new StringEntity("hello").at("*"));

4、演示

假设经理人(或者中间人) 的服务地址为:127.0.0.1:8602。下面以 Java 语言展示效果:

  • 创建经理人
public class BrokerDemo {
    public static void main(String[] args) throws Exception {
        SocketD.createServer("sd:tcp")
                .config(c -> c.port(8602).fragmentHandler(new BrokerFragmentHandler()))
                .listen(new BrokerListener())
                .start();
    }
}
  • 创建响应者(自己不需要端口启动,连接经理人后即可提供服务)
public class ResponderDemo {
    public static void main(String[] args) throws Exception {
        //连接到 broker ,并给自己命名为:demoapp
        ClientSession session = SocketD.createClient("sd:tcp://127.0.0.1:8602?@=demoapp")
                .listen(new EventListener().doOn("/hello", (s,m)->{
                    //监听 "/hello" 事件,如果是请求则答复
                    if(m.isRequest()){
                        s.reply(m, new StringEntity("me too!"));
                    }
                }))
                .open();
    }
}
  • 创建请求者
public class RequesterDemo {
    public static void main(String[] args) throws Exception {
        //连接到 broker ,并给自己命名为:demotester
        ClientSession session = SocketD.createClient("sd:tcp://127.0.0.1:8602?@=demotester")
                .open();

        //发送消息,并要求转发给 "demoapp"
        session.sendAndRequest("/hello", new StringEntity("").at("demoapp")).thenReply(r->{
            //收到答复后,打印结果
            print(r.dataAsString());
        });
    }
}

原生的渐变方法

在SVG中提供的原生渐变方法有两种,分别为线性渐变
linearGradient
和径向渐变
radialGradient
。我们以一个稍微复杂的路径来作为模板,为其添加两种渐变效果:

<svg width="800" height="300">
    <defs>
        <linearGradient id="linear-grad">
            <stop offset="0" stop-color="#f7ff00" />
            <stop offset="1" stop-color="#db36a4" />
        </linearGradient>
        <radialGradient id="radial-grad">
            <stop offset="0" stop-color="#f7ff00" />
            <stop offset="1" stop-color="#db36a4" />
        </radialGradient>

        <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200"/>
    </defs>

    <use xlink:href="#grad-path" stroke="black" fill="none" stroke-width="3" />
    <use xlink:href="#grad-path" stroke="url(#linear-grad)" fill="none" stroke-width="9" x="250" />
    <use xlink:href="#grad-path" stroke="url(#radial-grad)" fill="none" stroke-width="9" x="500" />
</svg>

展示效果如下:

虽然这两种渐变类型还有许多其他的属性可以设置,但它们的渐变颜色始终是只能沿着某一方向直线分布的,无法满足我们希望颜色可以沿着任意路径作渐变的需求。

对于某些特殊的路径如
<circle>
可以通过一些比较hack的方式来仅依靠CSS就能实现沿路径颜色渐变:例如这个
示例
。但我们需要的是足够通用的办法,可以做到任意路径上的颜色渐变;这就需要借助JavaScript来实现。

模拟渐变

既然SVG没有原生提供我们想要的渐变效果,就需要想想其他办法来“曲线救国”。

我们首先考虑下在Canvas画布中要实现沿路径渐变颜色是如何实现的:Canvas的2D上下文同样是只提供了线性渐变
createLinearGradient()
和径向渐变
createRadialGradient()
两种方法。但不同之处在于,Canvas画布是可以
逐像素绘制
颜色的。在绘制路径时根据渐变颜色做插值计算,得出每个像素应用的颜色值即可。

将这种思考迁移到SVG中来。尽管SVG是矢量图绘制,不支持像素级操作的;但通过方法
getTotalLength()

getPointAtLength()
可以获得路径的总长度及组成路径的这些离散点在SVG视图内的坐标。再利用
<circle>
图形来模拟“像素点”,依次摆放到前述的点坐标上,再设置对应的渐变颜色:

这些“像素点”越密集呈现的效果就越好。出于对页面性能的考虑,事先选定一个
合适的密度
很重要。

<svg width="800" height="300">
    <defs>
        <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200" />
    </defs>
    
    <g id="dots-container">
        <!-- 放置circle的容器 -->
    </g>
</svg>

<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="./createDots.js"></script>

这里引入
d3.js
库只是为了方便做颜色的插值计算。

const strokeWidth = 9
const startColor = '#f7ff00', endColor = '#db36a4'
const gradientPath = document.querySelector('#grad-path')
const dotsContainer = document.querySelector('#dots-container')
// 选择合适的点间距来控制密度
//const dotsDensity = strokeWidth * 1.0
const dotsDensity = strokeWidth * 0.2
const numberOfDots = Math.ceil(gradientPath.getTotalLength() / dotsDensity)

createDots()


function createDots() {
    for(let i = 0; i < numberOfDots; ++i) {
        const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
        dotsContainer.appendChild(circle)

        const steps = i / numberOfDots
        // 计算坐标点
        const pos = gradientPath.getPointAtLength(steps * gradientPath.getTotalLength())
        circle.setAttribute('cx', pos.x)
        circle.setAttribute('cy', pos.y)
        circle.setAttribute('r', strokeWidth / 2)
        // 计算颜色插值
        const interpolator = d3.interpolate(startColor, endColor)
        const curColor = interpolator(steps)
        circle.setAttribute('fill', curColor)
    }
}

动画

接下来我们再为其加入动画,使渐变颜色沿着路径移动来产生动效。

<svg width="800" height="300">
    <defs>
        <path id="grad-path" d="M 50,50 H 200 V 150 L 50,100 V 200 H 200" />
    </defs>
    
    <use xlink:href="#grad-path" stroke="#868e96" fill="none" stroke-width="1" />
    <g id="dots-container">
        <!-- 放置circle的容器 -->
    </g>
</svg>

<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="./test.js"></script>

除了渐变色外,定义动效的宽度、高度及时长:

const startColor = '#f7ff00', endColor = '#db36a4'
const gradientPath = document.querySelector('#grad-path')
const dotsContainer = document.querySelector('#dots-container')
// 动效的宽高
const strokeWidth = 9
const effectLength = 100
// 选择合适的点间距来控制密度
const dotsDensity = strokeWidth * 0.4
const numberOfDots = Math.ceil(effectLength / dotsDensity)
// 动画时长
const duration = 1500

根据配置创建所有的像素点,初始化颜色与半径等属性后将其引用存储起来:

// 初始化所有点
const dots = []
createDots()

function createDots() {
    for(let i = 0; i < numberOfDots; ++i) {
        const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
        dotsContainer.appendChild(circle)
        
        const color = d3.interpolate(startColor, endColor)(i / numberOfDots)
        circle.setAttribute('fill', color)
        circle.setAttribute('display', 'none')
        circle.setAttribute('r', strokeWidth / 2)

        dots.push(circle)
    }
}

执行动画。由
window.requestAnimationFrame
决定动画的帧数。传入每帧的回调函数,从动效的尾部开始处理,由
dotsDensity
做像素点的间隔向前依次更新位置,对于不在路径范围内的点做隐藏处理:

window.requestAnimationFrame(animationTask)
let startTime = -1
function animationTask(timestamp) {
    if(startTime === -1) {
        startTime = timestamp
    }

    const process = (timestamp - startTime) / duration
    // 动效的终点坐标
    const tailIdx = process * gradientPath.getTotalLength()
    for(let i = numberOfDots - 1; i >= 0; --i) {
        const posIdx = tailIdx - (numberOfDots - i) * dotsDensity
        const hide = posIdx < 0 || posIdx > gradientPath.getTotalLength()

        const pos = gradientPath.getPointAtLength(posIdx)
        
        updateDot(i, pos, hide)
    }

    window.requestAnimationFrame(animationTask)
}

function updateDot(idx, pos, isHide) {
    const circle = dots[idx]

    if(isHide) {
        circle.setAttribute('display', 'none')

    } else {
        circle.setAttribute('display', 'inline')
        circle.setAttribute('cx', pos.x)
        circle.setAttribute('cy', pos.y)
    }
}


最终效果如下~

    internal class Program
    {
        static List<string> list=new List<string>() { "A","B","C","D","A","B","C","D" };
        static string hiddenEle1 = string.Empty;//第一次藏起来的牌
        static void Main(string[] args)
        {
            Console.WriteLine($"初始牌组:{string.Join(',',list)}");
            //第一趴 名字有几个字
            Console.WriteLine("请输入你的名字有几个字");
            var nameCount = Convert.ToInt32(Console.ReadLine());
            Fun1(nameCount);
            Console.WriteLine($"变化之后:{string.Join(',', list)}");
            //第二趴 拿掉三张牌
            Fun2(3);
            Console.WriteLine($"插入三张牌后:{string.Join(',', list)}");
            //第三趴 把最上面一张牌藏起来
            Fun3();
            Console.WriteLine($"藏起来第一张牌后:{string.Join(',', list)}");
            //第四趴南方人北方人
            Console.WriteLine("北方-1 南方-2 不南不北-3,请输入对应数字");
            var directCount=Convert.ToInt32(Console.ReadLine());
            Fun2(directCount);
            Console.WriteLine($"南北方人后:{string.Join(',', list)}");
            //第五趴 男女生
            Console.WriteLine("男-1 女-2,请输入对应数字");
            var sexCount = Convert.ToInt32(Console.ReadLine());
            Fun4(sexCount);
            Console.WriteLine($"男女生后:{string.Join(',', list)}");
            //第六趴 见证奇迹的时刻
            Fun5();
            Console.WriteLine($"见证奇迹后:{string.Join(',', list)}");
            //第七趴 好运留下来烦恼丢出去
            Console.WriteLine("开始好运留下了 烦恼丢出去");
            var hiddenEle2 = Fun6(sexCount);

            Console.WriteLine($"牌1:{hiddenEle1} 牌2:{hiddenEle2}");
        }
        private static void Fun1(int nameCount)
        {
            if (nameCount >= list.Count)
            {
                // 如果 nameCount 大于或等于列表长度,则不需要进行操作
                return;
            }

            for (int i = 0; i < nameCount; i++)
            {
                string element = list[0];  // 获取第0个元素
                list.RemoveAt(0);          // 移除第0个元素
                list.Add(element);         // 将第0个元素添加到最后
            }
        }
        private static void Fun2(int forCount)
        {
            //被删除掉的三张牌
            var removeElement = new List<string>();
            for (int i = 0; i < forCount; i++)
            {
                removeElement.Add(list[0]);
                list.RemoveAt(0);
            }

            //把拿到的三张牌插到中间
            var random = new Random();
            int insertIndex = random.Next(1, list.Count - 1); // 生成一个介于第一个和倒数第二个位置之间的随机插入位置

            for (int i = removeElement.Count - 1; i >= 0; i--)
            {
                list.Insert(insertIndex, removeElement[i]);
            }
        }
        private static void Fun3()
        {
            //把第一张牌藏起来
            hiddenEle1 = list[0];
            list.RemoveAt(0);
        }

        private static void Fun4(int count)
        {
            for (int i = 0; i < count; i++)
            {
                list.RemoveAt(0);
            }
        }

        private static void Fun5()
        {
            for (int i = 0; i < 7; i++)
            {
                string element = list[0];
                list.RemoveAt(0);
                list.Add(element);
            }
        }

        private static string Fun6(int sexCount)
        {
            int forCount = 0;
            if (sexCount == 1) forCount = 5;//男生要5次
            else forCount = 4;
            for (int i = 0; i < forCount; i++)
            {
                // 把第一张牌放到数组最后面 (好运留下来)
                string element = list[0];
                list.RemoveAt(0);
                list.Add(element);

                //烦恼丢出去
                list.RemoveAt(0);
            }
            return list[0];
        }
    }

1.前言:

文件输入输出是个很有用的东西,有时比赛时要有:要求使用文件输入输出,还有时候……

遇到这种时间限制非常恶心的题目:手动测试会有误差……

文件输入输出
是个很好的选择!

2.写法:

C

C语言的写法有点复杂,涉及文件指针,本文不多介绍。下面给出示范写法:

#include <stdio.h>
int main() {
    FILE *file = fopen("文件名", "文件模式:r,w");
    if(file!=NULL){//若打开失败,file值为NULL
		fprintf(file, "整数:%d\n",8/*如同printf*/);
	}
    fclose(file);//记得关闭
    return 0;
}

C++

我们可以将 cin,cout重定向到文件,或者使用:
ifstream

ofstream


其中,ifstream是文件输入,ofstream是文件输出。

我们可以创建文件对象,在使用时就可以像cin,cout一样了
(不要忘记关闭文件!!!)

ifstream in("infile.in");
ofstream out("outfile.out");
int a;
in>>a;
out<<a;
in.colse();
out.close();

或者在创建对象后再打开。

ifstream in;
ofstream out;
in.open("infile.in");
out.open("outfile.out");
int a;
in>>a;
out<<a;
in.colse();
out.close();

3.完整代码:

#include<bits/stdc++.h>
using namespace std;
#define infile test.in
#define outfile test.out
#define cin_cout_f
//#define speedup
#ifdef cin_cout_f
	#define cin in
	#define cout out
	ifstream in("infile");
	ofstream out("outfile");
	//文件输入输出
#else
	#ifdef speedup
		void spup(){
			ios::sync_with_stdio(false);
			cin.tie(0);
		}
		//cin,cout加速,别用printf&scanf
	#endif
#endif
int main(){
	int a;
	cin>>a;
	cout<<a;
}

4.后续:

是不是很简单,点个赞吧。

前言

知乎上有一个提问:你的编程能力从什么时候开始突飞猛进的?
↓↓↓

今天,我们就这个话题一起来做个讨论。

我的回答

话说这个话题着实有点泛、难以回答,这里简单跟大家分享一下我对于这个问题的一些看法,希望大家喜欢。
我的观点认为,一个程序员但凡编程能力突飞猛进之后,会在如下6个能力方面有所体现:
1、编程语 言熟练度 :良好的编程能力要求掌握一到多门编程语言,能够熟练运用这些语言进行软件开发。
对相关语言的API语法、框架、工具包非常熟悉与掌握,能够娴熟的在合适场景下进行选择性的应用。
2、问题解决能力 :编程能力包括对问题的分析和解决能力,能够有效地定位和解决在软件开发过程中遇到的各种问题。
这主要体现在解决线上bug的能力。相关开发同学能不能第一时间快速止血问题(通过数据订正、回滚等手段快速阻断问题源,避免事态进一步恶化),然后线下快速定位到问题(查阅线上日志再结合项目源码),继而快速修复问题(发bugfix版)的能力。

3、代码质量和规范
:良好的编程能力表现在编写高质量、易读、可维护的代码,遵循编程规范和最佳实践。

我之前写过一些列所谓的“屎山”代码的文章。描述什么是“屎山”代码?它有什么特征?以及如何规避产生这种”屎山“代码?
有几个链接供大家做个参考:
为什么祖传代码被称为“屎山”?
Bug是如何产生的?
4、软件设计与架构 :良好的编程能力涉及对软件设计和架构的理解,能够设计出具有良好扩展性和灵活性的系统。
这主要体现在用一些画图工具(processOn、diagrams.net、Excalidraw、PPT等)画一些UML图或业务架构图、系统架构图等,抽象化你的具体业务需求内容,以详细设计文档的形式体现出来。
关于UML画图工具,我曾写过一篇文章,这个工具我自己一直在用,画图效果也确实不错,比较专业和美观,大家不妨可以看看: 这款uml画图工具,阿里字节都用疯了,你还不知道?
5、调试和优化 :良好的编程能力包括对代码进行调试和性能优化的能力,使得软件能够更高效、更稳定地运行。
关于调优,如果流量小,世界注定一片祥和,但如果有一天流量暴增,就会出现各种性能问题(比如响应缓慢啊、OOM、甚至程序直接崩溃),所以程序员是需要花时间去进行调优的。

通常调优的手段有:服务水平扩容、利用缓存减轻数据库“读”的压力、通过mq对流量进行削峰填谷、数据库分库分表减轻数据库写的压力与存储压力等

6、测试与质量保证
:编程能力要求对测试和质量保证有基本的理解,能够编写有效的测试用例和进行单元测试等。

虽然编程界有句笑话,程序员写代码写的就是BUG。但无可厚非体现一个程序员好的编程能力其能编写出高质量、不易出错的代码往往是是最低要求。很难想象一个一天到晚写BUG的程序员能力能好到哪里去。
关于在实际编程中,如何才能写出不易出错质量高的代码,这里我贴一篇我在早些时候发表过的关于此话题的一篇文章: 代码重构前vs重构后 ,大家可以参考看看。

终上所述,上述我总结的这6个编程能力是我认为可以体现一个程序员编程突飞猛进后的标志,当然实际操作过程中,具体的要求可能会因具体的职位、项目需求和行业特点而有所不同。

OK,今天的分享到处接近尾声,接下来,分享一则我们可爱的知友关于这个问题的精彩答复,灰常精彩,一定看到最后哦!

知友作答

写到最后


感谢您一路陪伴着我,探索编程的奇妙世界。如果您对
程序员编程技巧、计算机原理、职场进阶、认知成长
等充满兴趣,那么不要错过未来我为大家奉上的精彩内容!点击
关注
,让您的程序员之旅更加丰富多彩,我们一同成长,一同前行!