东风草堂博客

公众号:开发者来风

TCP链接:
conn, err := net.Dial(“tcp”, “192.168.0.10:2100”)
UDP链接:
conn, err := net.Dial(“udp”, “192.168.0.12:975”)
ICMP链接(使用协议名称):
conn, err := net.Dial(“ip4:icmp”, “www.baidu.com“)
ICMP链接(使用协议编号):
conn, err := net.Dial(“ip4:1”, “10.0.0.3”)

在成功建立连接后,我们就可以进行数据的发送和接收。发送数据时,使用conn的Write()成员方法,接收数据时使用Read()方法。

Dial()函数是对DialTCP()、DialUDP()、DialIP()和DialUnix()的封装.
验证IP地址有效性的代码如下: func net.ParseIP()
创建子网掩码的代码如下:
func IPv4Mask(a, b, c, d byte) IPMask
获取默认子网掩码的代码如下:
func (ip IP) DefaultMask() IPMask 根据域名查找IP的代码如下:
func ResolveIPAddr(net, addr string) (*IPAddr, error)
func LookupHost(name string) (cname string, addrs []string, err error);

net/http包的Client类型提供了如下几个方法,让我们可以用最简洁的方式实现HTTP请求:
func (c *Client) Get(url string) (r *Response, err error)
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err
error)
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) func (c *Client) Head(url string) (r *Response, err error)
func (c *Client) Do(req *Request) (resp *Response, err error)

http.Get()方法,等价于http.DefaultClient.Get()。

阅读全文 »

在工程上,有两种最常见的并发通信模型:共享数据和消息。共享数据是指多个并发单元分别保持对同一个数据的引用,实现对该数据的共享。被共享的数据可能有多种形式,比如内存数据块、磁盘文件、网络数据等。在实际工程应用中最常见的无疑是内存了,也就是常说的共享内存。Go语言提供的是另一种通信模型,即以消息机制而非共享内存作为通信方式。

消息机制认为每个并发单元是自包含的、独立的个体,并且都有自己的变量,但在不同并发单元间这些变量不共享。每个并发单元的输入和输出只有一种,那就是消息。这有点类似于进程的概念,每个进程不会被其他进程打扰,它只做好自己的工作就可以了。不同进程间靠消息来通信,它们不会共享内存。

channel是Go语言在语言级别提供的goroutine间的通信方式。如果对Unix管道有所了解的话,就不难理解channel,可以将其认为是一种类型安全的管道。

我们通过ch <- 1语句向对应的channel中写入一个数据。在这个channel被读取前,这个操作是阻塞的。在所有的goroutine启动完成后,我们通过<-ch语句从10个channel中依次读取数据。在对应的channel写入数据前,这个操作也是阻塞的。这样,我们就用channel实现了类似锁的功能,进而保证了所有goroutine完成后主函数才返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func Count(ch chan int) {
ch <- 1
fmt.Println("Counting")
}
func main() {
chs := make([]chan int10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i]) }
for _, ch := range(chs) {
<-ch // 阻塞等待,不然退出了
}
}
阅读全文 »

在Go语言中,你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}

func main() {
var a Integer = 1
if a.Less(2) {
fmt.Println(a, "Less 2")
}
}

func (a Integer) Less(b Integer) bool { // 面向对象 return a < b
}
func Integer_Less(a Integer, b Integer) bool { // 面向过程 return a < b
}
a.Less(2) // 面向对象的用法
Integer_Less(a, 2) // 面向过程的用法

C++语言的面向对象之所以让有些人迷惑的一大原因就在于其隐藏的this指针。一旦把隐藏的this指针显露出来,大家看到的就是一个面向过程编程。如果读者了解Python语法,就会知道Python的成员方法中会有一个self参数,它和this指针的作用是完全一样的。

1
2
3
4
5
struct Integer { int val;
};
bool Integer_Less(Integer* this, Integer* b) { // 不隐藏的写法
return this->val < b->val;
}

go传递指针而不是值的写法:

阅读全文 »

Go语言中提供了C/C++程序员期盼多年的多重赋值功能,比如下面这个交换i和j变量的语句:
i, j = j, i
在不支持多重赋值的语言中,交互两个变量的内容需要引入一个中间变量:
t = i; i = j; j = t;

Go的常量定义const Pi float64 = 3.14159265358979323846可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它 与字面常量一样,是无类型常量const zero = 0.0。
由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达式,比如试图以如下方式定义常量就会导致编译错误:const Home = os.GetEnv(“HOME”)

iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const (
a = 1 << iota // a == 1 (iota在每个const开头被重设为0)
b = 1 << iota // b == 2
c = 1 << iota // c == 4
)

// 如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式
const ( // iota被重设为0
c0 = iota // c0 == 0
c1 // c1 == 1
c2 // c2 == 2
)

// 枚举的写法
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays // 这个常量没有导出
)

布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。

阅读全文 »

所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。

1
2
3
4
5
6
// c语言内存回收的考验
int* p = new int;
p += 10; // 对指针进行了偏移,因此那块内存不再被引用
// ...... 这里可能会发生针对这块int内存的垃圾收集 ......
p -= 10; // 咦,居然又偏移到原来的位置
*p = 10; // 如果有垃圾收集,这里就无法保证可以正常运行了

Go语言还内置了一个对于其他静态类型语言通常用库方式支持的字典类型(map)。可以认为数组切片是一种可动态增长的数组,数组切片的功能与C++标准库中的vector非常类似。

1
2
3
4
5
6
7
8
9
10
11
12
// 多返回值
func getName()(firstName, middleName, lastName, nickName string){ return "May", "M", "Chen", "Babe"
}
// 可以返回空值
func getName()(firstName, middleName, lastName, nickName string){ firstName = "May"
middleName = "M"
lastName = "Chen"
nickName = "Babe"
return
// 调用过程
fn, mn, ln, nn := getName()
}

c语言在实现一个接口之前必须先定义该接口,并且将类型和接口紧密绑定,即接口的修改会影响到所有实现了该接口的类型,而Go语言的接口体系则避免了这类问题。

阅读全文 »

分布式锁

在分布式系统中,如何保证多个进程或线程之间对共享资源的访问互斥性和并发性?
在分布式锁的情况下,还是需要单机锁的,可以减少对同时加锁的并发量,减轻中间件的压力。
没有抢到锁怎么处理?自旋,性能差。事件通知回调最好。

如何保证多个进程或线程之间的互斥性和并发性?

在一个分布式系统中,多个进程或线程需要对共享资源进行读写操作,如果不加限制,可能会导致数据不一致或者出现竞态条件。因此,需要引入分布式锁来控制对共享资源的访问。

  1. MySQL实现分布式锁的一种常见方式是使用InnoDB的行级锁和SELECT … FOR UPDATE语句。简单,使用方便,不需要引入Redis、zookeeper等中间件。但不适合高并发的场景,db操作性能较差,没有设置超时时间,有锁表的风险。具体步骤如下:
阅读全文 »

如何确认光猫是桥接还是路由模式?

当数据包进入桥接器时,桥接器会查看源MAC地址,并将其存储在一个表格中。然后,桥接器会检查目的MAC地址,并将其与该表中的所有MAC地址进行比较。如果该目的MAC地址已保存在表格中,桥接器就将数据包切换到与目的MAC地址相应的端口上。如果目的MAC地址不在表格中,则桥接器广播数据包到所有端口。

方法一: 我们用一台电脑的网口使用网线直接连接光猫的LAN口(也就是上网口),若是电脑连上之后能直接上网,那么就说明,光猫为:路由模式。 反之,若是电脑不能直接上网,那么说明光猫是桥接模式,需要电脑拨号,输入上网宽带账号、密码才能拨号上网。
方法二: 我们还可以直接登录光猫的后台管理界面查看,查看网络-网络设置里面的连接模式是路由还是桥接。如下:

常见组网模式

方法一:跑量设备,直接接入光猫LAN口,光猫开启DMZ或者UNPN(推荐);
方法二:光猫连接模式改成桥接、路由器上网模式改成拨号上网。

阅读全文 »

自主部署代理

不用搭环境,自己有境外的vps就行,最好是openai支持的地区的vps,下载bin里面的执行文件直接就能跑,最简单的api proxy方式,最重要的是支持SSE,让客户端请求时响应得更加迅速,也提供了golang的源码,需要定制的可以自行完善。

1
./api_proxy -daemon -port 9000 # 最好开启daemon守护进程模式

docker的部署方式,直接拉镜像部署,不用自己配置环境,这种自主部署的代理可以支持sse,使用nodejs的可以参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
sudo yum remove docker
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/
sed -i 's+https://download.docker.com+https://mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker # 这个不要忘了
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
yum install libxcrypt-compat # 报错dlopen: libcrypt.so.1: cannot open shared object file: No such file or directory

# 代理配置,参考https://docs.docker.com/engine/daemon/proxy/#httphttps-proxy,HTTPS_PROXY配成一样的就行
mkdir -p /etc/systemd/system/docker.service.d
touch /etc/systemd/system/docker.service.d/proxy.conf
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80"
Environment="HTTPS_PROXY=https://proxy.example.com:443"
systemctl daemon-reload
systemctl restart docker

docker pull python:3-slim # 可以用来跑python脚本
docker run -dit --name proxy -p 9000:9000 easychen/ai.level06.com:latest # Proxy地址为 http://${IP}:9000
# 可用环境变量
# PORT: 服务端口
# PROXY_KEY: 代理访问KEY,用于限制访问
# TIMEOUT:请求超时时间,默认5秒

# 新方法
wget https://github.com/Yidadaa/ChatGPT-Next-Web/archive/refs/tags/v1.7.1.tar.gz
tar -xzvf v1.7.1.tar.gz
cd ChatGPT-Next-Web-1.7.1
docker build .
docker tag IMAGEID(镜像id) REPOSITORY:TAG(仓库:标签)
docker login
docker push nephen2023/chatgpt-next-web
阅读全文 »

参考

消息检查是否违规:https://platform.openai.com/docs/guides/moderation/quickstart
怎么开启流式传输:https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream

1
2
stream boolean Optional Defaults to false
Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message.

怎么做到连续上下文:https://platform.openai.com/docs/guides/chat/introduction,可以用https://platform.openai.com/playground?mode=chat 进行测试,入下图:刚开始问的时候assistant内容是为空的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 当用户指令引用先前的消息时,包括对话历史记录会有所帮助。
messages=[
# 主要的输入是消息参数。消息必须是一个消息对象的数组,其中每个对象都有一个角色("系统"、"用户 "或 "助手")和内容(消息的内容)。对话可以短到1条消息,也可以写满很多页。
# 通常,对话首先使用系统消息进行格式化,然后是交替的用户和助理消息。
{"role": "system", "content": "You are a helpful assistant."}, # 系统信息有助于设置助手的行为
{"role": "user", "content": "Who won the world series in 2020?"}, # 用户消息有助于指导助手。它们可以由应用程序的最终用户生成,或由开发人员设置为指令
{"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
{"role": "user", "content": "Where was it played?"} # 用户消息有助于指导助手。它们可以由应用程序的最终用户生成,或由开发人员设置为指令
]

completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=messages
)

print(completion.choices[0].message)
阅读全文 »

在 Zeabur 上部署

推荐使用 Zeabur,具体操作如下

  1. Fork 这个仓库为你自己的仓库
fork 2. 在 [Zeabur](https://zeabur.com) 部署你的仓库 3. 在 [Zeabur](https://zeabur.com) 控制台新增一个服务 步骤 1
阅读全文 »
0%