1 链码结构

1.1 链码接口

链码启动必须通过调用 shim 包中的 Start 函数,传递一个类型为 Chaincode 的参数,该参数是一个接口类型,有两个重要的函数 Init 与 Invoke 。

type Chaincode interface{
Init(stub ChaincodeStubInterface) peer.Response
Invoke(stub ChaincodeStubInterface) peer.Response
}
  • Init:在链码实例化或升级时被调用, 完成初始化数据的工作
  • Invoke:更新或查询帐本数据状态时被调用, 需要在此方法中实现响应调用或查询的业务逻辑

实际开发中, 开发人员可以自行定义一个结构体,重写 Chaincode 接口的两个方法,并将两个方法指定为自定义结构体的成员方法。

1.2 链码结构

package main

// 引入必要的包
import(
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
) // 声明一个结构体
type SimpleChaincode struct { } // 为结构体添加Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{
// 在该方法中实现链码初始化或升级时的处理逻辑
// 编写时可灵活使用stub中的API
} // 为结构体添加Invoke方法
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{
// 在该方法中实现链码运行中被调用或查询时的处理逻辑
// 编写时可灵活使用stub中的API
} // 主函数,需要调用shim.Start()方法
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
  • shim: 用来访问/操作数据状态、事务上下文和调用其他链代码的 API, 链码通过 shim.ChaincodeStub 提供的方法来读取和修改账本的状态
  • peer: 提供了链码执行后的响应信息的 API,peer.Response 封装了响应信息

2 链码相关的 API

shim 包提供了如下几种类型的接口:

  • 参数解析 API:调用链码时需要给被调用的目标函数/方法传递参数,该 API 提供解析这些参数的方法
  • 账本状态数据操作 API:该 API 提供了对账本数据状态进行操作的方法,包括对状态数据的查询及事务处理等
  • 交易信息获取 API:获取提交的交易信息的相关 API
  • 对 PrivateData 操作的 API: Hyperledger Fabric 在 1.2.0 版本中新增的对私有数据操作的相关 API
  • 其他 API:其他的 API,包括事件设置、调用其他链码操作

2.1 参数解析 API

// 返回调用链码时指定提供的参数列表(以字符串数组形式返回)
GetStringArgs() []string // 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回)
GetFunctionAndParameters() (function string, params []string) // 返回提交交易提案时提供的参数列表(以字节串数组形式返回)
GetArgsSlice() ([]byte, error) // 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回)
GetArgs() [][]byte

一般使用 GetFunctionAndParameters() 及 GetStringArgs() 。

2.2 账本数据状态操作 API

// 查询账本,返回指定键对应的值
GetState(key string) ([]byte, error) // 尝试添加/更新账本中的一对键值
// 这一对键值会被添加到写集合中,等待 Committer 进一步确认,验证通过后才会真正写入到账本
PutState(key string, value []byte) error // 尝试删除账本中的一对键值
// 同样,对该对键值删除会添加到写集合中,等待 Committer 进一步确认,验证通过后才会真正写入到账本
DelState(key string) error // 查询指定范围的键值,startKey 和 endkey 分别指定开始(包括)和终止(不包括),当为空时默认是最大范围
// 返回结果是一个迭代器结构,可以按照字典序迭代每个键值对,最后需要调用 Close() 方法关闭
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) // 返回指定键的所有历史值。该方法的使用需要节点配置中打开历史数据库特性(ledger.history.enableHistoryDatabase=true)
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) // 给定一组属性(attributes),将这些属性组合起来构造返回一个复合键
// 例如:CreateComositeKey("name-age",[]string{"Alice", "12"});
CreateCompositeKey(objectType string, attributes []string) (string, error) // 将指定的复合键进行分割,拆分成构造复合键时所用的属性
SplitCompositeKey(compositeKey string) (string, []string, error) // 根据局部的复合键(前缀)返回所有匹配的键值,即与账本中的键进行前缀匹配
// 返回结果是一个迭代器结构,可以按照字典序迭代每个键值对,最后需要调用 Close() 方法关闭
GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) // 对(支持富查询功能的)状态数据库进行富查询,返回结果是一个迭代器结构,目前只支持 CouchDB
// 注意该方法不会被 Committer 重新执行进行验证,所以不能用于更新账本状态的交易中
GetQueryResult(query string) (StateQueryIteratorInterface, error)

注意: 通过 put 写入的数据状态不能立刻 get 到,因为 put 只是链码执行的模拟交易(防止重复提交攻击),并不会真正将状态保存到账本中,必须经过 Orderer 达成共识之后,将数据状态保存在区块中,然后保存在各 peer 节点的账本中。

2.3 交易信息相关 API

// 返回交易提案中指定的交易 ID。
// 一般情况下,交易 ID 是客户端提交提案时由 Nonce 随机串和签名者身份信息哈希产生的数字摘要
GetTxID() string // 返回交易提案中指定的 Channel ID
GetChannelID() string // 返回交易被创建时的客户端打上的的时间戳
// 这个时间戳是直接从交易 ChannnelHeader 中提取的,所以在所以背书节点处看到的值都相同
GetTxTimestamp() (*timestamp.Timestamp, error) // 返回交易的 binding 信息
// 交易的 binding 信息是将交提案的 nonse、Creator、epoch 等信息组合起来哈希得到数字摘要
GetBinding() ([]byte, error) // 返回该 stub 的 SignedProposal 结构,包括了跟交易提案相关的所有数据
GetSignedProposal() (*pb.SignedProposal, error) // 返回该交易提交者的身份信息(用户证书)
// 从 SignedProposal 中的 SignatureHeader.Creator 提取
GetCreator() ([]byte, error) // 返回交易中带有的一些临时信息
// 从 ChaincodeProposalPayload.transient 提取,可以存放与应用相关的保密信息,该信息不会被写入到账本
GetTransient() (map[string][]byte, error)

2.4 对 PrivateData 操作的 API

// 根据指定的 key,从指定的私有数据集中查询对应的私有数据
GetPrivateData(collection, key string) ([]byte, error) // 将指定的 key 与 value 保存到私有数据集中
PutPrivateData(collection string, key string, value []byte) error // 根据指定的 key 从私有数据集中删除相应的数据
DelPrivateData(collection, key string) error // 根据指定的开始与结束 key 查询范围(不包含结束key)内的私有数据
GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) // 根据给定的部分组合键的集合,查询给定的私有状态
GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error) // 根据指定的查询字符串执行富查询 (只支持支持富查询的 CouchDB)
GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)

2.5 其他 API

// 设定当这个交易在 Committer 处被认证通过,写入到区块时发送的事件(event),一般由 Client 监听
SetEvent(name string, payload []byte) error // 调用另外一个链码的 Invoke 方法
// 如果被调用链码在同一个通道内,则添加其读写集合信息到调用交易;否则执行调用但不影响读写集合信息
// 如果 channel 为空,则默认为当前通道。目前仅限读操作,同时不会生成新的交易
InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response

3 链码开发

3.1 账户转账

package main

import (
"fmt"
"strconv" "github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
) type SimpleChaincode struct {
} // 初始化数据状态,实例化/升级链码时被自动调用
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
// println 函数的输出信息会出现在链码容器的日志中
fmt.Println("ex02 Init") // 获取用户传递给调用链码的所需参数
_, args := stub.GetFunctionAndParameters() var A, B string // 两个账户
var Aval, Bval int // 两个账户的余额
var err error // 检查合法性, 检查参数数量是否为 4 个, 如果不是, 则返回错误信息
if len(args) != 4 {
return shim.Error("Incorrect number of arguments. Expecting 4")
} A = args[0] // 账户 A 用户名
Aval, err = strconv.Atoi(args[1]) // 账户 A 余额
if err != nil {
return shim.Error("Expecting integer value for asset holding")
} B = args[2] // 账户 B 用户名
Bval, err = strconv.Atoi(args[3]) // 账户 B 余额
if err != nil {
return shim.Error("Expecting integer value for asset holding")
} fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) // 将账户 A 的状态写入账本中
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
} // 将账户 B 的状态写入账本中
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
} // 一切成功,返回 nil(shim.Success)
return shim.Success(nil)
} // 对账本数据进行操作时(query, invoke)被自动调用
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
fmt.Println("ex02 Invoke")
// 获取用户传递给调用链码的函数名称及参数
function, args := stub.GetFunctionAndParameters() // 对获取到的函数名称进行判断
if function == "invoke" {
// 调用 invoke 函数实现转账操作
return t.invoke(stub, args)
} else if function == "delete" {
// 调用 delete 函数实现账户注销
return t.delete(stub, args)
} else if function == "query" {
// 调用 query 实现账户查询操作
return t.query(stub, args)
}
// 传递的函数名出错,返回 shim.Error()
return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
} // 账户间转钱
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var A, B string // 账户 A 和 B
var Aval, Bval int // 账户余额
var X int // 转账金额
var err error if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
} A = args[0] // 账户 A 用户名
B = args[1] // 账户 B 用户名 // 从账本中获取 A 的余额
Avalbytes, err := stub.GetState(A)
if err != nil {
return shim.Error("Failed to get state")
}
if Avalbytes == nil {
return shim.Error("Entity not found")
}
Aval, _ = strconv.Atoi(string(Avalbytes)) // 从账本中获取 B 的余额
Bvalbytes, err := stub.GetState(B)
if err != nil {
return shim.Error("Failed to get state")
}
if Bvalbytes == nil {
return shim.Error("Entity not found")
}
Bval, _ = strconv.Atoi(string(Bvalbytes)) // X 为 转账金额
X, err = strconv.Atoi(args[2])
if err != nil {
return shim.Error("Invalid transaction amount, expecting a integer value")
}
// 转账
Aval = Aval - X
Bval = Bval + X
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) // 更新转账后账本中 A 余额
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
} // 更新转账后账本中 B 余额
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
} return shim.Success(nil)
} // 账户注销
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
} A := args[0] // 账户用户名 // 从账本中删除该账户状态
err := stub.DelState(A)
if err != nil {
return shim.Error("Failed to delete state")
} return shim.Success(nil)
} // 账户查询
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var A string
var err error if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
} A = args[0] // 账户用户名 // 从账本中获取该账户余额
Avalbytes, err := stub.GetState(A)
if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
return shim.Error(jsonResp)
} if Avalbytes == nil {
jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
return shim.Error(jsonResp)
} jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
fmt.Printf("Query Response:%s\n", jsonResp)
// 返回转账金额
return shim.Success(Avalbytes)
} func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}

该链码位于 ./fabric-samples/chaincode/chaincode_example02,我们启动 dev 网络对其进行测试:

$ cd ./fabric-samples/chaincode-docker-devmode/
$ docker-compose -f docker-compose-simple.yaml up -d

进入链码容器,对链码进行编译:

$ docker exec -it chaincode bash
# cd chaincode_example02/go/
# go build
# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go

打开一个新的终端,进入 cli 容器,安装并示例化链码:

$ docker exec -it cli bash
# peer chaincode install -p chaincodedev/chaincode/chaincode_example02/go -n test -v 0
# peer chaincode instantiate -n test -v 0 -c '{"Args":["init","a", "100", "b","200"]}' -C myc

查询账户 a 的余额,返回结果为 100:

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

从账户 a 转账 10 给 b:

# peer chaincode invoke -n test -c '{"Args":["invoke","a","b","10"]}' -C myc

再次查询账户 b 的余额,返回结果为 90:

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

可以在 chaincode 容器中查看到运行的日志:

ex02 Init
Aval = 100, Bval = 200
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210
ex02 Invoke
Query Response:{"Name":"a","Amount":"90"}

关闭网络:

$ docker-compose -f docker-compose-simple.yaml down

3.2 汽车信息记录

package main

import (
"bytes"
"encoding/json"
"fmt"
"strconv" "github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
) type SmartContract struct {
} type Car struct {
Make string `json:"make"` // 产商
Model string `json:"model"` // 型号
Colour string `json:"colour"` // 颜色
Owner string `json:"owner"` // 拥有者
} // 在链码初始化过程中调用 Init 来数据,此处不做任何操作
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
} // query 和 invoke 时被自动调用
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response { // 解析用户调用链码传递的函数名及参数
function, args := APIstub.GetFunctionAndParameters() // 调用不同的函数
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
} return shim.Error("Invalid Smart Contract function name.")
} // 初始化账本数据
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
} i := 0
for i < len(cars) {
fmt.Println("i is ", i)
carAsBytes, _ := json.Marshal(cars[i])
// key 为编号 CARi,value 为 Car 结构体的 json 串
APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
fmt.Println("Added", cars[i])
i = i + 1
} return shim.Success(nil)
} // 根据编号查询汽车
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
} carAsBytes, _ := APIstub.GetState(args[0])
return shim.Success(carAsBytes)
} // 创建一辆新的汽车数据
func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 5 {
return shim.Error("Incorrect number of arguments. Expecting 5")
} var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]} carAsBytes, _ := json.Marshal(car)
APIstub.PutState(args[0], carAsBytes) return shim.Success(nil)
} // 查询全部的汽车
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { // 查询 startKey(包括)到 endKey(不包括)间的值
startKey := "CAR0"
endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close() // 延迟关闭迭代器 // 将查询结果以 json 字符串的形式写入 buffer
var buffer bytes.Buffer
buffer.WriteString("[") bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
} if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"") buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]") fmt.Printf("- queryAllCars:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes())
} // 根据汽车编号改变车的拥有者
func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
} carAsBytes, _ := APIstub.GetState(args[0])
car := Car{} json.Unmarshal(carAsBytes, &car)
car.Owner = args[1] // 更改汽车拥有者 carAsBytes, _ = json.Marshal(car)
APIstub.PutState(args[0], carAsBytes) // 更新账本 return shim.Success(nil)
} func main() {
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}

该链码位于 ./fabric-samples/chaincode/fabcar,我们启动 dev 网络对其进行测试:

$ cd ./fabric-samples/chaincode-docker-devmode/
$ docker-compose -f docker-compose-simple.yaml up -d

进入链码容器,对链码进行编译:

$ docker exec -it chaincode bash
# cd fabcar/go/
# go build
# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go

打开一个新的终端,进入 cli 容器,安装并示例化链码:

$ docker exec -it cli bash
# peer chaincode install -p chaincodedev/chaincode/fabcar/go -n test -v 0
# peer chaincode instantiate -n test -v 0 -c '{"Args":[]}' -C myc

初始化账本数据:

# peer chaincode invoke -n test -c '{"Args":["initLedger"]}' -C myc

查询账本全部汽车的信息:

# peer chaincode query -n test -c '{"Args":["queryAllCars"]}' -C myc
[{"Key":"CAR0", "Record":{"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},{"Key":"CAR2", "Record":{"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},{"Key":"CAR4", "Record":{"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},{"Key":"CAR5", "Record":{"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},{"Key":"CAR6", "Record":{"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},{"Key":"CAR7", "Record":{"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},{"Key":"CAR8", "Record":{"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},{"Key":"CAR9", "Record":{"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]

创建一个新的汽车信息写入账本:

# peer chaincode invoke -n test -c '{"Args":["createCar","CAR10","Toyota","Prius","blue","233"]}' -C myc

查询编号为 CAR10 的汽车信息:

# peer chaincode query -n test -c '{"Args":["queryCar","CAR10"]}' -C myc
{"make":"Toyota","model":"Prius","colour":"blue","owner":"233"}

改变编号为 CAR10 的汽车的拥有者:

# peer chaincode invoke -n test -c '{"Args":["changeCarOwner","CAR10","hehe"]}' -C myc

再次查询编号为 CAR10 的汽车信息:

# peer chaincode query -n test -c '{"Args":["queryCar","CAR10"]}' -C myc
{"make":"Toyota","model":"Prius","colour":"blue","owner":"hehe"}

关闭网络:

$ docker-compose -f docker-compose-simple.yaml down

参考

  1. 《Hyperledger Fabric 菜鸟进阶攻略》
  2. https://www.cnblogs.com/preminem/p/7754811.html

Fabric1.4:Go 链码开发与编写的更多相关文章

  1. Fabric1.4 链码开发,开发模式下的测试

    关闭之前已启动的网络环境 sudo docker-compose -f docker-compose-cli.yaml down 进入devmode目录:  cd ~/go/src/github.co ...

  2. Fabric1.4:链码管理与测试

    1 链码介绍 智能合约在 Hyperledger Fabric 中称为链码(chaincode),是提供分布式账本的状态处理逻辑.链码被部署在fabric 的网络节点中,能够独立运行在具有安全特性的受 ...

  3. JMeter二次开发(2)-编写 JSON Assertion 插件

    本篇文章主要介绍如何对JMeter进行二次开发,添加自己所需的功能.这里以Json验证为例进行说明.在web接口测试过程中,JSON的应用已经非常普遍,但原声的JMeter并没有提供Json及Json ...

  4. 基于JZ2440开发板编写bootloader总结(一)

    凡走过必留下痕迹,学点什么都会有用的. 本系列博文总结了自己在学习嵌入式Linux编程过程中的收获,若有错误,恳请指正,谢谢! --参考教材韦东山系列教材 bootloader 是一个用于启动linu ...

  5. 如何为开发项目编写规范的README文件(windows),此文详解

    为什么要写这篇博客? 其实我是一个入坑已经半年的程序员,因为不是计算机专业,只能自己摸索,所以我深知博客的重要性.每次我的学习笔记啊,项目的,面试题啊,有的,只要有时间,我肯定上传上来,一方面自己可以 ...

  6. 全志A33 lichee 搭建Qt App开发环境编写helloworld

    开发平台 * 芯灵思SinlinxA33开发板 淘宝店铺: https://sinlinx.taobao.com/ 嵌入式linux 开发板交流 QQ:641395230 Step 1 在虚拟机(Ce ...

  7. Struts2_day01--Struts2的核心配置文件_常量配置_分模块开发_Action编写方式

    Struts2的核心配置文件 1 名称和位置固定的 2 在配置文件中主要三个标签 package.action.result,标签里面的属性 标签package 1 类似于代码包,区别不同的actio ...

  8. Unity 开发游戏编写代码的技巧

    在平时开发游戏过程中,遇到一些编写代码很繁琐的问题. 我发现我团队中每个人都会遇到,就算打写出来分享下经验. 条件断点 利用IDE提供的工具, 右键断点的时候 输入条件, 当条件达成的时候,断点才能命 ...

  9. 【Linux开发】编写属于你的第一个Linux内核模块

    曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引你走向前方-- 内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了.Linux内核和它的用户空间是大 ...

随机推荐

  1. 2005年NOIP普及组复赛题解

    题目涉及算法: 陶陶摘苹果:入门题: 校门外的树:简单模拟: 采药:01背包: 循环:模拟.高精度. 陶陶摘苹果 题目链接:https://www.luogu.org/problem/P1046 循环 ...

  2. 2013-10-6 datagridview实现换行并自动设置行高

    datagridview设置换行,如下,文本设置\r\n即可换行 dv4.DefaultCellStyle.WrapMode = DataGridViewTriState.True; dv4.Auto ...

  3. 机器学习降维方法概括, LASSO参数缩减、主成分分析PCA、小波分析、线性判别LDA、拉普拉斯映射、深度学习SparseAutoEncoder、矩阵奇异值分解SVD、LLE局部线性嵌入、Isomap等距映射

    机器学习降维方法概括   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u014772862/article/details/52335970 最近 ...

  4. Django入门2--Django的应用和开发第一个Template

    Django创建应用的命令: 应用的目录: 开发第一个Template:

  5. caffe学习(1):多平台下安装配置caffe

    如何在 centos 7.3 上安装 caffe 深度学习工具   有好多朋友在安装 caffe 时遇到不少问题.(看文章的朋友希望关心一下我的创业项目趣智思成) 今天测试并整理一下安装过程.我是在阿 ...

  6. Vue v-if和v-show的使用.区别

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 如何让in/exists 子查询(半连接)作为驱动表?

    一哥们问我,怎么才能让子查询作为驱动表? SQL如下: select rowid rid from its_car_pass7 v where 1 = 1 and pass_datetime > ...

  8. linux 一个写缓存例子

    我们已经几次提及 shortprint 驱动; 现在是时候真正看看. 这个模块为并口实现一个非 常简单, 面向输出的驱动; 它是足够的, 但是, 来使能文件打印. 如果你选择来测试这个 驱动, 但是, ...

  9. js求1到任意数之间的所有质数

    何为质数: 只能被1 和 自身 整除的数; 方法: 利用js中求模, 看是否有余数. ---> 3%2 = 1; 5%2 = 3......... 代码如下: function test (n) ...

  10. 【62.89%】【BZOJ 1072】[SCOI2007]排列perm

    Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 1862  Solved: 1171 [Submit][Status][Discuss] Descri ...