- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
本文为从零开始写 Docker 系列第十八篇,利用 linux 下的 Veth、Bridge、iptables 等等相关技术,构建容器网络模型,为容器插上”网线“.
完整代码见:https://github.com/lixd/mydocker 欢迎 Star 。
推荐阅读以下文章对 docker 基本实现有一个大致认识:
开发环境如下:
root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
Codename: focal
root@mydocker:~# uname -r
5.4.0-74-generic
注意:需要使用 root 用户 。
前面两篇文章中已经实现了容器的基本功能,容器之间可以互相访问,同时容器可以访问外网,外部设备也可以通过宿主机端口访问到容器内部服务.
不过还缺少了资源清理逻辑,本篇主要实现在删除网络或者容器时对相关网络资源做一个清理工作.
创建网络会做以下事情:
那么删除时也需要做对应的清理 。
删除 iptables 规则 。
删除路由规则 。
删除 bridge 设备 。
// Delete 删除网络
func (d *BridgeNetworkDriver) Delete(network *Network) error {
// 清除路由规则
err := deleteIPRoute(network.Name, network.IPRange.IP.String())
if err != nil {
return errors.WithMessagef(err, "clean route rule failed after bridge [%s] deleted", network.Name)
}
// 清除 iptables 规则
err = deleteIPTables(network.Name, network.IPRange)
if err != nil {
return errors.WithMessagef(err, "clean snat iptables rule failed after bridge [%s] deleted", network.Name)
}
// 删除网桥
err = d.deleteBridge(network)
if err != nil {
return errors.WithMessagef(err, "delete bridge [%s] failed", network.Name)
}
return nil
}
使用 netlink.RouteDel 方法删除路由,具体如下:
// 删除路由,ip addr del xxx命令
func deleteIPRoute(name string, rawIP string) error {
retries := 2
var iface netlink.Link
var err error
for i := 0; i < retries; i++ {
// 通过LinkByName方法找到需要设置的网络接口
iface, err = netlink.LinkByName(name)
if err == nil {
break
}
log.Debugf("error retrieving new bridge netlink link [ %s ]... retrying", name)
time.Sleep(2 * time.Second)
}
if err != nil {
return errors.Wrap(err, "abandoning retrieving the new bridge link from netlink, Run [ ip link ] to troubleshoot")
}
// 查询对应设备的路由并全部删除
list, err := netlink.RouteList(iface, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, route := range list {
if route.Dst.String() == rawIP { // 根据子网进行匹配
err = netlink.RouteDel(&route)
if err != nil {
log.Errorf("route [%v] del failed,detail:%v", route, err)
continue
}
}
}
return nil
}
iptables 删除比较简单,就是把添加命令中的 -A 改成 -D即可,相比于按行号删除,这种方式不会删错.
对 configIPTables 方法 进行调整,把 action 作为配置,这样添加删除可以复用同一个方法.
func setupIPTables(bridgeName string, subnet *net.IPNet) error {
return configIPTables(bridgeName, subnet, false)
}
func deleteIPTables(bridgeName string, subnet *net.IPNet) error {
return configIPTables(bridgeName, subnet, true)
}
func configIPTables(bridgeName string, subnet *net.IPNet, isDelete bool) error {
action := "-A"
if isDelete {
action = "-D"
}
// 拼接命令
iptablesCmd := fmt.Sprintf("-t nat %s POSTROUTING -s %s ! -o %s -j MASQUERADE", action, subnet.String(), bridgeName)
cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
log.Infof("删除 SNAT cmd:%v", cmd.String())
// 执行该命令
output, err := cmd.Output()
if err != nil {
log.Errorf("iptables Output, %v", output)
}
return err
}
使用netlink.LinkDel 方法删除 bridge 设备.
// deleteBridge deletes the bridge
func (d *BridgeNetworkDriver) deleteBridge(n *Network) error {
bridgeName := n.Name
// get the link
l, err := netlink.LinkByName(bridgeName)
if err != nil {
return fmt.Errorf("getting link with name %s failed: %v", bridgeName, err)
}
// delete the link
if err = netlink.LinkDel(l); err != nil {
return fmt.Errorf("failed to remove bridge interface %s delete: %v", bridgeName, err)
}
return nil
}
容器启动时需要记录网络相关信息,便于后续查询或者删除时使用.
ContainerInfo 中新增网络相关字段 。
type Info struct {
NetworkName string `json:"networkName"` // 容器所在的网络
IP string `json:"ip"` // 容器IP
PortMapping []string `json:"portmapping"` // 端口映射
}
记录容器信息逻辑后移到绑定网络后,便于记录 IP 信息.
func Run(tty bool, comArray, envSlice []string, res *subsystems.ResourceConfig, volume, containerName, imageName string,
net string, portMapping []string) {
containerId := container.GenerateContainerID() // 生成 10 位容器 id
// 省略...
var containerIP string
// 如果指定了网络信息则进行配置
if net != "" {
// config container network
containerInfo := &container.Info{
Id: containerId,
Pid: strconv.Itoa(parent.Process.Pid),
Name: containerName,
PortMapping: portMapping,
}
ip, err := network.Connect(net, containerInfo)
if err != nil {
log.Errorf("Error Connect Network %v", err)
return
}
containerIP = ip.String()
}
// 在分配 IP 后在记录,便于存储网络相关信息
containerInfo, err := container.RecordContainerInfo(parent.Process.Pid, comArray, containerName, containerId,
volume, net, containerIP, portMapping)
if err != nil {
log.Errorf("Record container info error %v", err)
return
}
}
mydocker ps 命令增加 ip 信息展示 。
func ListContainers() {
// 读取存放容器信息目录下的所有文件
files, err := os.ReadDir(container.InfoLoc)
if err != nil {
log.Errorf("read dir %s error %v", container.InfoLoc, err)
return
}
containers := make([]*container.Info, 0, len(files))
for _, file := range files {
tmpContainer, err := getContainerInfo(file)
if err != nil {
log.Errorf("get container info error %v", err)
continue
}
containers = append(containers, tmpContainer)
}
// 使用tabwriter.NewWriter在控制台打印出容器信息
// tabwriter 是引用的text/tabwriter类库,用于在控制台打印对齐的表格
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
_, err = fmt.Fprint(w, "ID\tNAME\tPID\tIP\tSTATUS\tCOMMAND\tCREATED\n")
if err != nil {
log.Errorf("Fprint error %v", err)
}
for _, item := range containers {
_, err = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
item.Id,
item.Name,
item.IP,
item.Pid,
item.Status,
item.Command,
item.CreatedTime)
if err != nil {
log.Errorf("Fprint error %v", err)
}
}
if err = w.Flush(); err != nil {
log.Errorf("Flush error %v", err)
}
}
容器加入某个网络时需要做以下工作:
在容器退出后,需要做网络信息清理.
都是根据启动时存储的信息,调用 network.Disconnect 方法清理即可.
// Disconnect 将容器中指定网络中移除
func Disconnect(networkName string, info *container.Info) error {
networks, err := loadNetwork()
if err != nil {
return errors.WithMessage(err, "load network from file failed")
}
// 从networks字典中取到容器连接的网络的信息,networks字典中保存了当前己经创建的网络
network, ok := networks[networkName]
if !ok {
return fmt.Errorf("no Such Network: %s", networkName)
}
// veth 从 bridge 解绑并删除 veth-pair 设备对
drivers[network.Driver].Disconnect(fmt.Sprintf("%s-%s", info.Id, networkName))
// 清理端口映射添加的 iptables 规则
ep := &Endpoint{
ID: fmt.Sprintf("%s-%s", info.Id, networkName),
IPAddress: net.ParseIP(info.IP),
Network: network,
PortMapping: info.PortMapping,
}
return deletePortMapping(ep)
}
将 veth 设备从 bridge 解绑,并根据命名规则找到另一端的 veth 一起删除.
func (d *BridgeNetworkDriver) Disconnect(endpointID string) error {
// 根据名字找到对应的 Veth 设备
vethNme := endpointID[:5] // 由于 Linux 接口名的限制,取 endpointID 的前 5 位
veth, err := netlink.LinkByName(vethNme)
if err != nil {
return err
}
// 从网桥解绑
err = netlink.LinkSetNoMaster(veth)
if err != nil {
return errors.WithMessagef(err, "find veth [%s] failed", vethNme)
}
// 删除 veth-pair
// 一端为 xxx,另一端为 cif-xxx
err = netlink.LinkDel(veth)
if err != nil {
return errors.WithMessagef(err, "delete veth [%s] failed", vethNme)
}
veth2Name := "cif-" + vethNme
veth2, err := netlink.LinkByName(veth2Name)
if err != nil {
return errors.WithMessagef(err, "find veth [%s] failed", veth2Name)
}
err = netlink.LinkDel(veth2)
if err != nil {
return errors.WithMessagef(err, "delete veth [%s] failed", veth2Name)
}
return nil
}
iptables 清理和 前面 SNAT 清理类似,只需要控制 action 即可,把添加的 -A 替换为-D 即可.
func addPortMapping(ep *Endpoint) error {
return configPortMapping(ep, false)
}
func deletePortMapping(ep *Endpoint) error {
return configPortMapping(ep, true)
}
// configPortMapping 配置端口映射
func configPortMapping(ep *Endpoint, isDelete bool) error {
action := "-A"
if isDelete {
action = "-D"
}
var err error
// 遍历容器端口映射列表
for _, pm := range ep.PortMapping {
// 分割成宿主机的端口和容器的端口
portMapping := strings.Split(pm, ":")
if len(portMapping) != 2 {
logrus.Errorf("port mapping format error, %v", pm)
continue
}
// 由于iptables没有Go语言版本的实现,所以采用exec.Command的方式直接调用命令配置
// 在iptables的PREROUTING中添加DNAT规则
// 将宿主机的端口请求转发到容器的地址和端口上
// iptables -t nat -A PREROUTING ! -i testbridge -p tcp -m tcp --dport 8080 -j DNAT --to-destination 10.0.0.4:80
iptablesCmd := fmt.Sprintf("-t nat %s PREROUTING ! -i %s -p tcp -m tcp --dport %s -j DNAT --to-destination %s:%s",
action, ep.Network.Name, portMapping[0], ep.IPAddress.String(), portMapping[1])
cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
logrus.Infoln("配置端口映射 DNAT cmd:", cmd.String())
// 执行iptables命令,添加端口映射转发规则
output, err := cmd.Output()
if err != nil {
logrus.Errorf("iptables Output, %v", output)
continue
}
}
return err
}
根据前台容器和后台容器,需要在不同地方调用前面的清理逻辑.
前台容器直接在 Run 方法中清理即可.
func Run(tty bool, comArray, envSlice []string, res *subsystems.ResourceConfig, volume, containerName, imageName string,
net string, portMapping []string) {
containerId := container.GenerateContainerID() // 生成 10 位容器 id
// 省略...
if tty { // 如果是tty,那么父进程等待,就是前台运行,否则就是跳过,实现后台运行
_ = parent.Wait()
container.DeleteWorkSpace(containerId, volume)
container.DeleteContainerInfo(containerId)
if net != "" { // 如果指定了网络则在退出时做清理工作
network.Disconnect(net, containerInfo)
}
}
}
后台容器则在 mydocker stop 命令中做清理.
func removeContainer(containerId string, force bool) {
containerInfo, err := getInfoByContainerId(containerId)
if err != nil {
log.Errorf("Get container %s info error %v", containerId, err)
return
}
switch containerInfo.Status {
case container.STOP: // STOP 状态容器直接删除即可
// 先删除配置目录,再删除rootfs 目录
if err = container.DeleteContainerInfo(containerId); err != nil {
log.Errorf("Remove container [%s]'s config failed, detail: %v", containerId, err)
return
}
container.DeleteWorkSpace(containerId, containerInfo.Volume)
if containerInfo.NetworkName != "" { // 清理网络资源
if err = network.Disconnect(containerInfo.NetworkName, containerInfo); err != nil {
log.Errorf("Remove container [%s]'s config failed, detail: %v", containerId, err)
return
}
}
case container.RUNNING: // RUNNING 状态容器如果指定了 force 则先 stop 然后再删除
if !force {
log.Errorf("Couldn't remove running container [%s], Stop the container before attempting removal or"+
" force remove", containerId)
return
}
log.Infof("force delete running container [%s]", containerId)
stopContainer(containerId)
removeContainer(containerId, force)
default:
log.Errorf("Couldn't remove container,invalid status %s", containerInfo.Status)
return
}
}
测试以下几部分:
网络资源清理测试 。
容器信息记录测试 。
容器网络资源清理测试 。
测试网络删除后,对应的bridge、路由、iptables 等资源是否清理干净.
root@mydocker:~/feat-network-2/mydocker# go build .
root@mydocker:~/feat-network-2/mydocker# ./mydocker network create --driver bridge --subnet 10.10.10.1/24 testbr
root@mydocker:~/feat-network-3/mydocker# ./mydocker network list
NAME IpRange Driver
testbr1 10.10.0.1/24 bridge
查看对应设备 。
# brigde 设备
root@mydocker:~/feat-network-3/mydocker# ip link show type bridge
15: testbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 82:80:36:11:30:2f brd ff:ff:ff:ff:ff:ff
# 路由
root@mydocker:~/feat-network-3/mydocker# ip r
10.10.10.0/24 dev testbr proto kernel scope link src 10.10.10.1
# iptables
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 10.10.0.0/24 anywhere
接下来进行删除 。
root@mydocker:~/feat-network-3/mydocker# ./mydocker network remove testbr
分别检查路由、iptables、bridge 设备是否真的删除的.
# 网桥设备
root@mydocker:~/feat-network-3/mydocker# ip link show type bridge
# 路由规则
root@mydocker:~/feat-network-3/mydocker# ip r
# iptables
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
可以看到都删除了,说明网络资源清理是正常的.
测试容器信息是否能够正常记录和展示.
创建一个网络 。
root@mydocker:~/feat-network-2/mydocker# ./mydocker network create --driver bridge --subnet 10.10.10.1/24 testbr
root@mydocker:~/feat-network-3/mydocker# ./mydocker network list
NAME IpRange Driver
testbr1 10.10.0.1/24 bridge
然后启动容器指定使用该网络 。
./mydocker run -it -net testbr busybox sh
另一个终端查看容器信息 。
root@mydocker:~/feat-network-3/mydocker# ./mydocker ps
ID NAME PID IP STATUS COMMAND CREATED
3994300986 3994300986 10.10.10.2 103669 running sh 2024-03-07 13:24:34
退出前台容器 。
/ # exit
再次查看容器信息 。
root@mydocker:~/feat-network-3/mydocker# ./mydocker ps
ID NAME PID IP STATUS COMMAND CREATED
删除了,一切正常.
./mydocker run -it -p 8080:80 -net testbr busybox sh
查看容器中的网络设备 。
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
19: cif-09561@if20: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
link/ether a6:a3:16:22:c1:98 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.3/24 brd 10.10.10.255 scope global cif-09561
valid_lft forever preferred_lft forever
inet6 fe80::a4a3:16ff:fe22:c198/64 scope link
valid_lft forever preferred_lft forever
可以看到,其中 19 号设备叫做cif-09561@if20,这就是我们放入容器中的 veth 设备.
查看宿主机 。
root@mydocker:~/feat-network-3/mydocker# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
inet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3
valid_lft 61063sec preferred_lft 61063sec
inet6 fe80::f816:3eff:fe58:62ef/64 scope link
valid_lft forever preferred_lft forever
16: testbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 66:9e:1c:28:c2:40 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.1/24 brd 10.10.10.255 scope global testbr
valid_lft forever preferred_lft forever
inet6 fe80::74fa:43ff:feac:74f3/64 scope link
valid_lft forever preferred_lft forever
20: 09561@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master testbr state UP group default qlen 1000
link/ether 66:9e:1c:28:c2:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::649e:1cff:fe28:c240/64 scope link
valid_lft forever preferred_lft forever
testbr 为网桥,09561@if19 这个就是容器中 veth 的另一端.
查看 iptables 规则 。
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere anywhere tcp dpt:http-alt to:10.10.10.4:80
确实有一个 DNAT 规则用于处理端口映射.
测试停止容器后,这两个 veth 设备是否会删除.
退出容器 。
/ # exit
查看宿主机 。
root@mydocker:~/feat-network-3/mydocker# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
inet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3
valid_lft 60895sec preferred_lft 60895sec
inet6 fe80::f816:3eff:fe58:62ef/64 scope link
valid_lft forever preferred_lft forever
16: testbr: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.1/24 brd 10.10.10.255 scope global testbr
valid_lft forever preferred_lft forever
inet6 fe80::74fa:43ff:feac:74f3/64 scope link
valid_lft forever preferred_lft forever
09561@if19 被删除了.
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT 规则也清理了.
说明前台容器资源清理一切正常.
./mydocker run -d -p 8080:80 -net testbr busybox top
查看运行情况 。
root@mydocker:~/feat-network-3/mydocker# ./mydocker ps
ID NAME PID IP STATUS COMMAND CREATED
1891821312 1891821312 10.10.10.5 103828 running top 2024-03-07 13:32:19
同样的,查看 veth 设备和 DNAT 规则 。
root@mydocker:~/feat-network-3/mydocker# ip a
26: 18918@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master testbr state UP group default qlen 1000
link/ether ba:ab:aa:cb:21:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::b8ab:aaff:fecb:2170/64 scope link
valid_lft forever preferred_lft forever
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere anywhere tcp dpt:http-alt to:10.10.10.5:80
删除容器 。
root@mydocker:~/feat-network-3/mydocker# ./mydocker rm 1891821312 -f
查看 veth 设备和 DNAT 规则是否被清理 。
root@mydocker:~/feat-network-3/mydocker# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
inet 192.168.10.144/24 brd 192.168.10.255 scope global dynamic ens3
valid_lft 60557sec preferred_lft 60557sec
inet6 fe80::f816:3eff:fe58:62ef/64 scope link
valid_lft forever preferred_lft forever
16: testbr: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.1/24 brd 10.10.10.255 scope global testbr
valid_lft forever preferred_lft forever
inet6 fe80::74fa:43ff:feac:74f3/64 scope link
valid_lft forever preferred_lft forever
root@mydocker:~/feat-network-3/mydocker# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
都清理了,说明后台容器清理也是正常的.
【从零开始写 Docker 系列】持续更新中,搜索公众号【探索云原生】订阅,阅读更多文章.
本章主要处理了容器网络收尾工作,包括 veth、bridge、iptables、路由等网络资源的回收.
至此,整个容器网络就算是基本完成了.
最后再次推荐一下 Docker教程(十)---揭秘 Docker 网络:手动实现 Docker 桥接网络 。
完整代码见:https://github.com/lixd/mydocker 欢迎关注~ 。
相关代码见 feat-network-3 分支,测试脚本如下:
需要提前在 /var/lib/mydocker/image 目录准备好 busybox.tar 文件,具体见第四篇第二节.
# 克隆代码
git clone -b feat-network-3 https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试
./mydocker network create --driver bridge --subnet 10.10.10.1/24 testbr
./mydocker network list
最后此篇关于从零开始写Docker(十八)---容器网络实现(下):为容器插上”网线“的文章就讲到这里了,如果你想了解更多关于从零开始写Docker(十八)---容器网络实现(下):为容器插上”网线“的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要(我必须)将大量 float 写入 qdatastream 并且我只使用 4 个字节是必要的。setFloatingPointPrecision 或为 float 和 double 写入 4 或
我有一些 C 代码,我用 Python 对其进行了扩展。扩展的 C 代码有一个将一些结构附加到二进制文件的函数: void writefunction(const struct struct1* so
我正在用 C 语言开发一个小软件,用于在布告栏中读取和写入消息。每条消息都是一个以渐进数字命名的 .txt。 软件是多线程的,有很多用户可以并发操作。 用户可以进行的操作有: 阅读整个公告板(所有 .
我有 2 个线程同时访问同一个大文件 (.txt)。 第一个线程正在从文件中读取。第二个线程正在写入文件。 两个线程都访问同一个 block ,例如(开始:0, block 大小:10),但具有不同的
我做了很多谷歌搜索,但我仍然不确定如何继续。 Linux 下最常见的剪贴板读写方式是什么?我想要同时支持 Gnome 和 KDE 桌面。 更新:我是否认为没有简单的解决方案,必须将多个来源(gnome
1. 定义配置文件信息 有时候我们为了统一管理会把一些变量放到 yml 配置文件中 例如 图片 用 @ConfigurationProperties 代替 @Value 使用方法 定义对应字段的实体
在开始之前,我必须先声明我是 FORTRAN 的新手。我正在维护 1978 年的一段遗留代码。它的目的是从文件中读取一些数据值,处理这些值,然后将处理过的值输出到另一个文本文件。 给定以下 FORTR
我正在制作一个应用程序,我需要存储用户提供的一些信息。我尝试使用 .plist 文件来存储信息,我发现: NSString *filePath = @"/Users/Denis/Documents/X
在delphi类中声明属性时是否可能有不同类型的结果? 示例: 属性月份:字符串读取monthGet(字符串)写入monthSet(整数); 在示例中,我希望在属性(property)月份中,当我:读
我正在以二进制形式将文件加载到数组中,这似乎需要一段时间有没有更好更快更有效的方法来做到这一点。我正在使用类似的方法写回文件。 procedure openfile(fname:string); va
我想实现一个运行模拟的C#控制台应用程序。另外,我想给用户机会在控制台上按“+”或“-”来加速/减速模拟的速度。 有没有办法在编写控制台时读取控制台?我相信我可以为此使用多线程,但是我却不怎么做(我对
这是我的代码: use std::fs::File; use std::io::Write; fn main() { let f = File::create("").unwrap();
我有一个应用程序可以访问 csv 文本文件中的单词。由于它们通常不会更改,因此我将它们放置在 .jar 文件中,并使用 .getResourceAsStream 调用读取它们。我真的很喜欢这种方法,因
我使用kubeadm,docker 17.12.1-ce和法兰绒网络安装了Kubernetes 1.13.1集群 但是,我发现Kubernetes主服务器上有许多空文件,权限为666,该文件允许任何用
我的工作区中有一些 java 文件。现在我想编写一个java程序,它可以读取来自不同源的文本文件,一次一个,一行一行,并将这些行插入到工作区中各自的java文件中。 文本文件会告诉我将哪个文件插入到哪
用户A要求系统读取文件foo,同时用户B想要将他或她的数据保存到同一个文件中。在文件系统级别如何处理这种情况? 最佳答案 大多数文件系统(但不是全部)使用锁定来保护对同一文件的并发访问。锁可以是独占的
我对保护移动应用程序的 firebase 数据库有一些疑问。 例如,在反编译Android应用程序后,黑客可以获取firebase api key 然后访问firebase数据库,这是正确的吗? 假设
我想让文件从外部不可删除,并希望使用java从程序对该文件进行读/写操作。 S0,我使用以下代码使用java创建了不可删除的文件: Process pcs = Runtime.getRunti
当 Selector.select() 以阻塞模式等待读/写操作时,是否可以将写消息推送到客户端?如何将选择器从阻塞模式移至写入模式?触发器可以是一个后台线程,用于放置需要写入给定 channel 的
我目前正在学习在 Linux 环境中使用 C 进行套接字编程。作为一个项目,我正在尝试编写一个基本的聊天服务器和客户端。 目的是让服务器为每个连接的客户端派生一个进程。 我遇到的问题是读取一个 chi
我是一名优秀的程序员,十分优秀!