Shafeng`sblog

$cat/etc/redhat-releaseCentOSrelease6.6(Final)$uname-r2.6.32-504.30.3.el6.x86_64当前系统ruby版本是1.8.7

$ruby--versionruby1.8.7(2013-06-27patchlevel374)[x86_64-linux]ruby有自己的软件包管理器gem,就像python的pip,在当前环境使用gem会报错:

$geminstallinnodb_ruby-bash:gem:commandnotfound在这种情况下,我们需要在centos6上安装高版本的ruby。

添加CentOSSCLo软件集合存储库

$yum-yinstallcentos-release-scl-rhcentos-release-scl第二步-安装ruby2.4添加SCLo存储库后,我们可以使用以下命令安装Ruby2.4

$yuminstallrh-ruby24随着版本的更新,这个命令可能也会改变,如果提示没有rh-ruby24软件包,可以尝试rh-ruby25,rh-ruby26

在CentOS6上安装Ruby2.4之后,只需设置如下所述的环境变量

$sclenablerh-ruby24bash$ruby--versionruby2.4.6p354(2019-04-01revision67394)[x86_64-linux-gnu]现在就可以用gem安装ruby软件了:

总体的思路大概是上图这样,在部门10.0.17.0/24网段内的一台内网服务器10.0.17.48搭建openvpn服务,采用tap模式将内网ip地址分配给openvpn客户端,并通过外网端口转发将内网的服务映射到外网vps上

这个脚本提供的是tun模式的服务器,使用tun模式的话,openvpn客户端会得到另外一个内网网段的地址而不能与公司内网10.0.17.0/24直接交互,如需交互就要配置复杂的路由表,操作起来稍微有些不太友好。最初我使用了tun模式,10.0.17.48启动tun模式后会虚拟一张拥有10.8.0.1地址的网卡,并自动成为10.8.0.0/24网段的网关,openvpn客户端会得到10.8.0.0/24网段的ip地址,如果公司内的电脑想要访问10.8.0.0/24网段的机器,比如一个服务器开发人员远程通过openvpn连入公司内网,得到ip地址10.8.0.5,在自己的开发机上启动了一个服务器程序,处于公司内的测试人员想要访问该远程开发人员的服务器程序就需要在本地配置路由表:

linux:routeadd-net10.8.0.0netmask255.255.255.0gw10.0.17.48eth0(对应的删除命令routedel-net10.8.0.0netmask255.255.255.0,显示路由命令route)windows:outeadd10.8.0.0mask255.255.255.010.0.17.48(对应的删除命令routedelete10.8.0.0,显示路由命令routeprint)这样公司内网10.0.17.0/24网段的机器才知道如何找到10.8.0.0/24网段。但是我们有一些服务器没有在10.0.17.0/24网段,而是在10.0.30.0/24网段,尝试在10.0.30.0/24网段的机器上配置路由后发现不能双向通信,没有再接着深入研究,决定放弃tun模式使用tap模式,而使用tap模式需要首先创建网桥。

按照官方教程,我简单总结了一下使用方法:

首先找一台有两张网卡的服务器(一张网卡不知道行不行,没有深入研究),选其中一张做桥接。按照自己的网络情况修改bridge-start.sh中的参数。执行这些防火墙规则(不开防火墙不知道需不需要执行,反正我执行了,也许其实没啥用):iptables-AINPUT-itap0-jACCEPTiptables-AINPUT-ibr0-jACCEPTiptables-AFORWARD-ibr0-jACCEPT每次启动和关闭openvpn服务器都要按照这样的顺序:runbridge-startrunopenvpnstopopenvpnrunbridge-stop官方给出的创建网桥脚本(经过修改参数适用于我的网络环境):

sample-scripts/bridge-start

#!/bin/bash##################################SetupEthernetbridgeonLinux#Requires:bridge-utils##################################DefineBridgeInterfacebr="br0"#DefinelistofTAPinterfacestobebridged,#forexampletap="tap0tap1tap2".tap="tap0"#Definephysicalethernetinterfacetobebridged#withTAPinterface(s)above.eth="ens18"eth_ip="10.0.17.48"eth_netmask="255.255.255.0"eth_broadcast="10.0.17.255"fortin$tap;doopenvpn--mktun--dev$tdonebrctladdbr$brbrctladdif$br$ethfortin$tap;dobrctladdif$br$tdonefortin$tap;doifconfig$t0.0.0.0promiscupdoneifconfig$eth0.0.0.0promiscupifconfig$br$eth_ipnetmask$eth_netmaskbroadcast$eth_broadcast官方给出的停止网桥脚本:

sample-scripts/bridge-stop

#!/bin/bash#####################################TearDownEthernetbridgeonLinux#####################################DefineBridgeInterfacebr="br0"#DefinelistofTAPinterfacestobebridgedtogethertap="tap0"ifconfig$brdownbrctldelbr$brfortin$tap;doopenvpn--rmtun--dev$tdone通过外网端口转发将内网的服务映射到外网vps上/usr/sbin/openvpn–cd/etc/openvpn/–configserver.conf启动openvpn服务后,10.0.17.48就开始提供vpn服务了,但是不在公司内的外网工作人员并不能访问内网地址,就需要一个固定的外网地址进行转发,因为一直有使用ssh的端口转发功能,所以最先想到了ssh,由于ssh端口转发只支持tcp,所以只能使用tcp模式启动openvpn:

根据网络情况修改参数:ssh-fN-R19028:127.0.0.1:19028root@120.100.1.1-p22这样客户端通过连接120.100.1.1:19028就可以访问vpn服务了。

frps.ini:

[common]bind_port=19027kcp_bind_port=19027frpc.ini:

[common]server_addr=120.100.1.1server_port=19027protocol=kcp[openvpn]type=udplocal_ip=127.0.0.1local_port=19028remote_port=19028这样frp通过19027作为底层传输19028端口的内容,实现udp协议的转发。

最后把openvpn的配置贴一下备忘/etc/openvpn/server.conf:

port19028protoudpdevtap0usernobodygroupnobodypersist-keypersist-tunkeepalive10120topologysubnetserver-bridge10.0.17.48255.255.255.010.0.17.15010.0.17.253ifconfig-pool-persistipp.txtpush"dhcp-optionDNS114.114.114.114"push"dhcp-optionDNS114.114.114.115"push"route10.0.0.0255.255.0.0"client-to-clientdhnonexxxxxxxxx...客户端权限文件xxx.ovpn:

mysql数据库中的数据是以文件的形式存在磁盘上的,一张表主要对应着两个个文件,一个是frm存放表结构的,一个是ibd存放表数据和索引的。如果一张表的数据量太大的话,那么idb就会变的很大,查找数据就会变的很慢,这个时候我们可以利用mysql的分区功能,在物理上将这一张表对应的两个个文件,分割成许多个小块,这样呢,我们查找一条数据时,就不用全部查找了,只要知道这条数据在哪一块,然后在那一块找就行了。如果表的数据太大,可能一个磁盘放不下,这个时候,我们可以把数据分配到不同的磁盘里面去。

我首先根据mysql的表分区功能创建了两个实验表,结构一样,只是一个表分区,另一个不分区。

不分区的表tbl_test_nosub:

Altertabletbl_test_subpartitionbykey(fld_record_id)partitions24;//重建分区表大概花费了700秒然后尝试用python脚本分别对两张表查询10万次,tbl_test_sub耗时87s左右,而tbl_test_nosub耗时81s左右。分区后的表查询速度还是慢的。为了减少python本身对mysql效率的影响,我又改为用存储过程来测试查询效率:

1,表分区功能可以使一张表中存储的数据比单个磁盘或文件系统分区中存储的数据更多。2,通过删除仅包含该数据的一个或多个分区,通常可以轻松地从分区表中删除失去其用途的数据。相反,在某些情况下,通过添加一个或多个用于专门存储该数据的新分区,可以大大简化添加新数据的过程。3,满足以下条件的某些查询可以大大优化:满足给定WHERE子句的数据只能存储在一个或多个分区上,这会自动从搜索中排除任何剩余的分区。由于可以在创建分区表之后更改分区,因此您可以重新组织数据以增强在首次设置分区方案时可能不会经常使用的频繁查询。排除不匹配分区(以及因此包含的任何行)的能力通常称为分区修剪。关于查询效率的解释上,我的实验应该符合官方的说法,但是并没有提高查询效率,可能是数据文件拆分后从小块文件中查询得到的效率提升并没有定位分区消耗的效率大。据说一颗3层的B+树可以承载的数据是2000W,超过2000W应该会增加到4层,但是通过查询MySQL的数据我的实验数据表已经达到了5000W条,依然是3层:

#innodb_space-f./tbl_test_nosub.ibdspace-index-pages-summary|head-n10pageindexleveldatafreerecords31792329812860194418022100141221005179076808496152617901504710672937179015037107729281790150771037292918001528352189910180015283521899111790309159436#innodb_space-f./tbl_test_sub#P#p0.ibdspace-index-pages-summary|head-n10pageindexleveldatafreerecords3273228915959174274216816082852730160111253106273094246760182727301383223222678273094236749182927409010705453010274013685228580511273066259579128page_no为3和4的page_level是2,因为根level是0,所以这个树高度是3层;

安装percona-xtrabackup:

CHANGEMASTERTOMASTER_HOST='172.31.0.1',MASTER_PORT=4306,MASTER_USER='slave_user',MASTER_PASSWORD='slave_user',MASTER_LOG_FILE='mysql-bin.071479',MASTER_LOG_POS=202221999;启动从服务

startslave;查看从库状态

showslavestatus;看到两个yes表示主从已经正常了。

我们的项目线上运营了三年多,经历了很多次开新服,合服的过程,至此有一些服已经多次合服,并入了大量的数据,这些数据中大部分已经成为僵尸数据,已经影响了服务启动速度和普通玩家体验。现在运营要求将两个已经拥有海量数据的服合并,如果这种级别的两个库合并的话无疑会造成很多隐患,也许会无法支持业务,所以在跟运行协商后决定将库中一些只进行了新手阶段体验且没有进行充值的大量僵尸玩家删除,如果这些玩家回来,重新创建角色差别也不是很大。这不是最好的解决方法,但是鉴于项目整体条件和环境,也算是一个可行的方案。

人生要不断完善自己,项目也一样,最近用到了代码静态检查工具来检测了一下项目代码,用了两个工具,linux下的cppcheck和windows下的TscanCode,用法都很简单,就不细说了,最后得出的结果还是有一些区别的,大部分是真实存在的代码问题。

cppcheck的结果像这样:

[haizhan_src_2/ActivityTask/ActivityTask.cpp:48]:(warning)Membervariable'CActivityTask::tmExpire'isnotinitializedintheconstructor.[haizhan_src_2/ActivityTask/ActivityTask.cpp:48]:(warning)Membervariable'CActivityTask::bSendInsert'isnotinitializedintheconstructor.[haizhan_src_2/ActivityTask/ActivityTask.cpp:48]:(warning)Membervariable'CActivityTask::bDBInsert'isnotinitializedintheconstructor.[haizhan_src_2/Email/SystemEmailManager.cpp:407]->[haizhan_src_2/Email/SystemEmailManager.cpp:404]:(error)Iterator't_it'usedafterelementhasbeenerased.[haizhan_src_2/userinfo_export/userinfo_export_serviceService.cpp:379]:(error)Dereferencing'_pmapRoleInfos'afteritisdeallocated/released它会给出一些warning和error,包括未初始化的变量,数组越界使用,失效的迭代器,无效的函数等等等等。

TscanCode的结果:

两种工具各有千秋,不过个人认为还是TscanCode更好用,报告的内容也更全,腾讯还是做点好事的。。。

sar-B

pgpgin/s:每s从磁盘换入的页的大小(KB)pgpgout/s:每s换出到磁盘的页的大小(KB)fault/s:每s发生的缺页错误的次数,包括minorfault和majorfault。majflt/s:每s发生的majorfault的次数,majorfault会导致从磁盘载入内存页(即使用了swap分区)。pgfree/s:每s放入空闲列表中的页的个数。pgscank/s:每s被kswapd后台进程扫描的页的个数。pgscand/s:每s直接被扫描的页的个数。pgsteal/s:为了满足内存要求,每s从cache(pagecache和swapcache)回收的页的个数。%vmeff:等于pgsteal/pgscan,用于计算页回收(pagereclaim)的效率。

sar-q

runq-sz:处于运行或就绪的进程数量plist-sz:现在进程的总数(包括线程).ldavg-1:最近一分钟的负载.ldavg-5:最近五分钟的负载.ldavg-15:最近十分钟的负载.

sar-nDEV

IFACE:LAN接口rxpck/s:每秒钟接收的数据包txpck/s:每秒钟发送的数据包rxbyt/s:每秒钟接收的字节数txbyt/s:每秒钟发送的字节数rxcmp/s:每秒钟接收的压缩数据包txcmp/s:每秒钟发送的压缩数据包rxmcst/s:每秒钟接收的多播数据包

iptables配置文件路径/etc/sysconfig/iptables#修改后重启服务serviceiptablesrestartredis服务器上(codis)的iptables配置:

#Generatedbyiptables-savev1.4.7onSatMay713:57:072016*filter:INPUTACCEPT[0:0]:FORWARDACCEPT[0:0]:OUTPUTACCEPT[1:140]-AINPUT-s120.1.1.1/32-ptcp--dport19000-jACCEPT-AINPUT-s120.2.2.2/32-ptcp--dport19000-jACCEPT-AINPUT-s120.3.3.3/32-ptcp--dport19000-jACCEPT-AINPUT-ptcp--dport19000-jDROPCOMMIT#CompletedonSatMay713:57:072016这个配置的作用是,允许120.1.1.1,120.2.2.2,120.3.3.3这三台服务器访问19000端口,然后用-AINPUT-ptcp--dport19000-jDROP禁止其他所有地址对19000端口的访问,这样的话就只有这三台ip的机器可以访问redis的19000端口。为了实现这个需求走了一些弯路,一开始我的配置是这样的:

#Generatedbyiptables-savev1.4.7onSatMay713:57:072016*filter:INPUTACCEPT[0:0]:FORWARDACCEPT[0:0]:OUTPUTACCEPT[1:140]-IINPUT-s120.1.1.1/32-ptcp--dport19000-jACCEPT-IINPUT-s120.2.2.2/32-ptcp--dport19000-jACCEPT-IINPUT-s120.3.3.3/32-ptcp--dport19000-jACCEPT-IINPUT-ptcp--dport19000-jDROPCOMMIT#CompletedonSatMay713:57:072016这个配置始终“不生效”,没有达到我想要的结果,其实我并没有搞懂-A和-I的区别,-A是添加一条命令到防火墙规则的后面,类似stl中的push_back,而-I则类似stl中的push_front。而iptables是根据规则从前到后进行解释的,也就是说如果用-I的话相当于第一条就执行了-IINPUT-ptcp--dport19000-jDROP,这样的话后面不管写什么规则都没有用了。。。

记录一下出问题的函数以及解决后的函数:

用python连接codis服务器的测试程序,测试了一些简单的命令和简单排行榜。连接的ip地址是codis组件中proxy的地址,proxy接受的命令并不是全部是redis命令,具体情况参考官方文档

上线后codis的表现比较好,没有出现过什么问题,如果各个group的内存使用不太平均,还可以使用codis网页工具提供的功能对slot进行动态转移,把内存量使用大的group中的slot分配到其它group中。

管理页面的主页,显示了当前使用的内存量,连接数,redis中存储的key的数量和每秒请求数

在Proxy栏可看到我们已经启动的Proxy,但是Group栏为空,因为我们启动的codis-server并未加入到集群添加NEWGROUP,NEWGROUP行输入1,再点击NEWGROUP即可添加CodisServer

添加实例(即添加后端的redis服务器)AddServer行输入我们刚刚启动的codis-server地址,添加到我们刚新建的Group,然后再点击AddServer按钮即可

对slot初始化要分组,之后可以动态调整slot的归属

codis-proxy相当于redis,即连接codis-proxy和连接redis是没有任何区别的,codis-proxy无状态,不负责记录是否在哪保存,数据在zookeeper记录,即codisproxy向zookeeper查询key的记录位置,proxy将请求转发到一个组进行处理,一个组里面有一个master和一个或者多个slave组成,默认有1024个槽位,其把不同的槽位的内容放在不通的group。codis是基于go语言编写的,因此要安装go语言环境。每台服务器安装java环境和zookeeper,zookeeper集群最少需要3台服务器。

启动zookeeper

/data/service/zookeeper-3.4.11/bin/zkServer.sh/data/service/zookeeper-3.4.11/conf/zoo.cfg安装部署codis下载编译codis3.2版本我直接使用了编译好的一个版本

#####################################################Codis-Dashboard######################################################SetCoordinator,onlyaccept"zookeeper"&"etcd"&"filesystem".#forzookeeper/etcd,coorinator_authaccept"user:password"#QuickStart#coordinator_name="filesystem"#coordinator_addr="/tmp/codis"coordinator_name="zookeeper"coordinator_addr="172.31.0.84:19021"#coordinator_auth=""#SetCodisProductName/Auth.product_name="codis-warship"product_auth=""#Setbindaddressforadmin(rpc),tcponly.admin_addr="0.0.0.0:18080"#Setargumentsfordatamigration(onlyaccept'sync'&'semi-async').migration_method="semi-async"migration_parallel_slots=100migration_async_maxbulks=200migration_async_maxbytes="32mb"migration_async_numkeys=500migration_timeout="30s"#Setconfigsforredissentinel.sentinel_client_timeout="10s"sentinel_quorum=2sentinel_parallel_syncs=1sentinel_down_after="30s"sentinel_failover_timeout="5m"sentinel_notification_script=""sentinel_client_reconfig_script=""启动dashboard:

[root@node1codis]#nohup./bin/codis-proxy--ncpu=1--config=config/proxy.toml--log=proxy.log--log-level=WARN>>/var/log/codis_proxy.log&/codis/config/redis-6380.confredis的配置主要是改这几个

bind172.31.0.84port6380pidfile"/tmp/redis_6380.pid"maxmemory12Gdir"/data/service/codis"logfile"/tmp/redis_6380.log"通过codis-server指定redis.conf文件启动redis服务,不能通过redis命令启动redis服务,通过redis启动的redis服务加到codis集群无法正常使用:

[root@redis1codis]#./bin/codis-server../config/redis_6380.conf使用codis的网页服务器配置codis服务器启动codis-fe

nohup./bin/codis-fe--ncpu=1--log=fe.log--log-level=WARN--zookeeper=172.31.0.84:19021--listen=172.31.0.84:8060以上配置是我在实际使用中的配置,官方的教程在这里:

mysql-h127.0.0.1-P4306-uroot-p--execute="showengineinnodbstatus\G">./mysql.log查看mysql.log的内容发现有死锁内容:

更新之后确实也没有出现过类似的问题,但是这次死锁具体是不是因为这个顺序导致的,也不能完全确定,也许是某些不可知的问题,后面的工作中如果再遇到同类问题再来解决这个问题吧。

我们的项目本身有大量的云服务器,装机的时候用了统一的装机脚本,云服务器的密码都是一样的,因为早期只有几个核心程序员有登陆云服务器的权限,所以并没有对这方面做管理。直到有一个核心程序员离职,不得不管理一下手头的服务器

首先所有对服务器有操作权限的人员提供自己的ssh-key秘钥对,一般使用rsa秘钥,然后把这些秘钥对中的公钥(.pub)添加到服务器的/home/service/.ssh/authorized_keys文件中,新添加的公钥直接在该文件后面添加即可。

为了提升服务器的整体安全性,我们需要对ssh进行配置:

mkdir.sshchmod700.sshcd.sshtouchauthorized_keyschmod600authorized_keys#将公钥内容写入文件cat/data/service/Identity.pub>>authorized_keys这样如果有人离职,只需要把服务器上对应人员的公钥删除就可以了。

#123.206.76.219:4306是远端的ip和port,2009是远端的SSH端口,service是远端的ssh账号#44306:127.0.0.1是自己本地的port和ipssh-fN-L44306:127.0.0.1:4306service@123.206.76.219-p2009这个命令特别“绕”,不记录一下根本记不住…它实现的功能是ssh的端口映射,通过ssh将一个远端ip:port映射到本地,适用于非同一局域网内的机器。在我们的项目中因为腾讯的服务器必须使用腾讯云,跟阿里云不是在一个局域网内的,但是他们之间又要有mysql的访问需求,所以通过ssh做了这个端口映射,记录一下。

我们线上项目的分析报告已经被我删掉了。。。很遗憾没法记录当时的程序运行情况和每天优化后重新运行的分析情况。

#include#includeusingnamespacestd;voidfunc1(){for(inti=0;i<100000;++i){++i;char*p=(char*)malloc(1024);free(p);}}voidfunc2(){for(inti=0;i<200000;++i){char*p=(char*)malloc(1024);free(p);}}voidfunc3(){for(inti=0;i<100000;++i){func1();func2();}}intmain(){HeapProfilerStart("test.prof");//开启内存分析并指定所生成的profile文件名func3();HeapProfilerStop();//停止内存分析并输出结果(如果这行代码不运行,将无法输出任何数据,只有一个空文件)return0;}编译,运行并输出pdf(同CPU性能分析),不同之处在于内存分析每产生1G的内存使用就会生成一个文件,生成pdf的时候需要包含所有生成的文件/work/trunk/haizhan/Linux64_6.3.0_Debug/share_bin/pprof-pdftesttest.prof.*.heap>out.pdf

官方给出的建议是虽然gperftools对程序性能的影响不是非常大,也尽量不要在线上环境使用gperftools,但是经过我的使用发现gperftools大概会降低程序30%左右的效率,如果你的程序本身占用cpu并不高,而有些问题又必须在线上真实环境下才可能表现的出来,完全可以放到线上尝试一下。

我们的项目中,每个玩家在登陆时创建玩家对象CHZUser,同时通过SVR_BASE::co_create函数创建一个协程上下文对象(后面简称协程对象),协程对象的执行函数是该玩家的消息循环函数CHZUser::message_loop,

enumCoroutineStatus_t{emCoroutine_closed=0,///<关闭的emCoroutine_WaitRun,///<等待执行emCoroutine_Runing,///<正在执行emCoroutine_Message,///<等着消息emCoroutine_IOComplete///<等待IO完成};协程的五个状态

我们的项目在逻辑中的异步操作并不太多,而且使用异步本身是一件很麻烦的事情,mysql也没有封装异步的方法,但是后期一些项目上的功能需要使用异步sql查询数据库,为了适应项目的发展和需求,引入了协程。

从原理上看,协程保存了程序执行的当前位置,后续可以切换回来,即便是在一个完整函数的中间,也可以切走再切回来,像是一个用户态的线程,但是和线程不同的是,协程之间的切换需要我们自己实现。协程的好处是可以用符合思维习惯的同步写法,写出具有异步功能的高效代码,而不用像传统异步开发那样,设置各种回调函数把代码割离,弄的支离破碎很难理解。不过协程的切换虽然不会切到内核态,完全由用户来控制切换,但是协程的切换造成的开销,还是会比异步回调的方法开销大一些,但是总体来说,同步的写法实现异步这种好处还是盖过了切换带来的效率损耗。

因为c++并没有标准的协程库,所以各种实现协程的方法很多,当前比较知名的是仿go语言实现的c++库libgo,腾讯的libco,boost的coroutine,context组件,unix系统自带的ucontext等等。这些库我只看过libco和ucontext,腾讯的libco封装的太高级,其实用到自己项目中是不太灵活的,但是libco用汇编重写了切换协程的代码,在这方面的效率是比较高的,我们项目中用了unix系统的ucontext函数,实现了基本的协程功能,在实际使用中效率不太高,但是也足够用。记录一下ucontext的原理和使用。

先看一个简单的例子,来自维基百科:

#include#include#includeintmain(intargc,constchar*argv[]){ucontext_tcontext;getcontext(&context);puts("Helloworld");sleep(1);setcontext(&context);return0;}这个程序的运行结果是这样的:

[shafeng@localhostDesktop]$vimtest.cpp[shafeng@localhostDesktop]$g++test.cpp-otest[shafeng@localhostDesktop]$./testHelloworldHelloworldHelloworldHelloworldHelloworldHelloworldHelloworldHelloworldHelloworldHelloworldHelloworld^C[shafeng@localhostDesktop]$getcontext得到了当前的上下文,然后输出Helloworld,然后调用setcontext,又跳到了调用getcontext的位置,接着输出Helloworld,一直循环下去。。。。

ucontext很简洁,头文件,两个结构体mcontext_t和ucontext_t,四个函数getcontext(),setcontext(),makecontext(),swapcontext(),这些内容就可以实现协程。

mcontext_t应该是系统需要传递的参数,是用户无关的,用户只需要关心ucontext_t结构和getcontext(),setcontext(),makecontext(),swapcontext(),下面详细介绍一个结构体和四个函数:

typedefstructucontext{structucontext*uc_link;sigset_tuc_sigmask;stack_tuc_stack;mcontext_tuc_mcontext;...}ucontext_t;ucontext_t中uc_link指向后继上下文,当前上下文如果执行结束会跳转到uc_link指向的上下文中运行,如果uc_link为NULL那么当前线程直接退出。uc_stack设置当前上下文的堆栈大小和位置。其他参数暂时没用到,具体用到之后再补充

intgetcontext(ucontext_t*ucp);初始化ucp结构体,将当前的上下文保存到ucp中

intsetcontext(constucontext_t*ucp);设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得,如果调用成功则不返回。如果上下文是通过调用getcontext()取得,程序会继续执行这个调用。如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,如果func函数返回,则恢复makecontext第一个参数指向的上下文第一个参数指向的上下文context_t中指向的uc_link.如果uc_link为NULL,则线程退出。

voidmakecontext(ucontext_t*ucp,void(*func)(),intargc,...);makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link.

当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。

intswapcontext(ucontext_t*oucp,ucontext_t*ucp);保存当前上下文到oucp结构体中,然后激活ucp上下文。

如果执行成功,getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置相应的errno.

简单说来,getcontext获取当前上下文,setcontext设置当前上下文,swapcontext保存当前上下文并切换上下文,makecontext创建一个新的上下文。

#include#includevoidfunc1(void*arg){puts("func1");}voidcontext_test(){charstack[1024*128];ucontext_tchild,main;getcontext(&child);//获取当前上下文child.uc_stack.ss_sp=stack;//指定栈空间child.uc_stack.ss_size=sizeof(stack);//指定栈空间大小child.uc_stack.ss_flags=0;child.uc_link=&main;//设置后继上下文makecontext(&child,(void(*)(void))func1,0);//修改上下文指向func1函数swapcontext(&main,&child);//保存当前上下文到main,切换到child上下文puts("main");//如果设置了后继上下文,func1函数指向完后会返回此处}intmain(){context_test();return0;}这个例子中,getcontext首先获得了当前的上下文,child参数相当于一个输出,通过getcontext函数把当前上下文的信息写入到child中。然后修改了child上下文的栈空间和大小,以及后继上下文,然后调用makecontext,这时child相当于输入,将child上下文的执行位置指向func1,最后调用swapcontext,main和child都是输入,将当前的上下文保存到main中,然后切换到child上下文(此时的child上下文位置是func1函数的起点)。这样这个程序执行后的输出就是:

[shafeng@localhostDesktop]$vimtest.cpp[shafeng@localhostDesktop]$g++test.cpp-otest[shafeng@localhostDesktop]$./testfunc1main[shafeng@localhostDesktop]$可以看到当程序执行到swapcontext后,程序通过child上下文跳转到了func1,先执行了puts("func1");函数执行结束后跳转到child的后继上下文main,也就是调用swapcontext时保存起来的上下文,这样程序又回到了swapcontext执行结束的位置(因为swapcontext执行成功不返回,swapcontext已经执行成功,所以跳转回来之后程序的位置在swapcontext的下一句)并执行puts("main");

上面的程序在执行完child上下文后能够跳到main上下文是因为设置了child的后继上下文child.uc_link=&main,如果我们把这一句改为child.uc_link=NULL,编译执行后得到如下结果

[shafeng@localhostDesktop]$vimtest.cpp[shafeng@localhostDesktop]$g++test.cpp-otest[shafeng@localhostDesktop]$./testfunc1[shafeng@localhostDesktop]$可以看到child上下文在执行结束之后就直接退出了线程。所以上下文的后继上下文很重要,否则我们的程序可能直接退出。

以上就是ucontext的全部内容,非常简洁。我们项目中使用的就是ucontext,后面再记录一下项目中的实际使用情况。

最近项目上遇到一些问题,服务器程序在运行的时候cpu特别高,尤其是逻辑线程,一度达到99%。为了查找问题,使用了一些之前没有用过的命令,加上我之前用过的几个命令,写一篇记录gstackgstack线程id,例如:gstack2939,可以查看到当前线程的堆栈信息,如果程序的当前线程占cpu特别高,可能会出现死循环或者其他症状,我们可以通过使用gstack命令查看当前线程的堆栈信息来诊断问题.stracestrace-T-r-c-p线程id,例如:strace-T-r-c-p2939,可以得到当前线程的所有系统调用统计,停止统计的时候必须自己手动退出,会输出使用命令到停止统计期间的信息.gcoregcore进程id,例如:gcore12345,可以得到当前进程的所有线程的堆栈信息.一些总结,通过以上这些命令可以间接观察到程序当前的运行状态和运行位置.给予程序开发者更多的讯息以诊断程序当前可能出现的问题,有一个需要注意的地方,gstack和strace在运行期间不会影响到程序本身的运行,而gcore会将程序暂停,影响程序的正常工作.

[client]socket=/tmp/mysql.sock[mysqld_multi]log=/data/mysql_db/mysqld_multi.logmysqld=/usr/bin/mysqld_safemysqladmin=/usr/bin/mysqladminuser=shutdown_user###########################################################################################[mysqld4306]port=4306socket=/tmp/mysql_4306.sockpid-file=/tmp/mysql_4306.piddatadir=/data/mysql_db/4306default_storage_engine=InnoDBsql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLESskip-name-resolvemax_connections=1000innodb_flush_log_at_trx_commit=2innodb_flush_log_at_timeout=1innodb_buffer_pool_size=24080Minnodb_log_buffer_size=8Minnodb_log_file_size=256Mlog-bin=/data/mysql_binlog/4306/mysql-binbinlog_format=MIXEDbinlog_cache_size=1Mmax_binlog_size=512Mlog_warningsslow_query_loglong_query_time=2server-id=1expire_logs_days=5secure-file-priv=tmpdir=/data/mysql_tmpinnodb_buffer_pool_size这个选项调大可以增加innodb的内存大小,减少io操作

这个选项可以指定binlog保存的天数,binlog占用硬盘空间特别大,但是binlog又是恢复数据库时最宝贵的log,所以如何权衡这个问题还得结合自己项目的需要

这个选项可以让其他用户写入数据,项目在合服时无法写入数据,加入这个配置可以降低数据库的安全性检查

我们项目的服务器一直使用centos6.5,主要原因是当时各种云服务器厂商的主流稳定centos版本就是6.5,但是这个版本的系统自带的gcc版本是4.4.7,gcc版本过低导致很多gcc新版本的功能无法使用,比如c++11还有之前我介绍过的ASan。一开始我们自己做了妥协,把编译机从centos7降到了centos6.5,很多c++11的代码也做了修改。编译机和线上环境系统版本的统一保证了程序的稳定性。

但是随着项目的发展,一些gcc的新功能也确实特别诱人,为了升级gcc研究了一下这个问题。其实只要把编译机上的libstdc++.so文件与我们自己的程序一起发布就可以了,编译机上可以升级gcc到最新版本,程序编译发布时将升级过的libstdc++.so文件一同发布,让我们自己的程序在运行的时候动态链接编译机上的libstdc++.so(使用g++的-Wl,-rpath命令)

好像是gcc的某一个版本之前libstc++.so是依赖于libc.so的,也就是说当时如果只发布libstc++.so可能程序也跑不起来,之后某个版本又解除了这个依赖,不知道我说的对不对。。。记录一下。

总结一下,asan在检测程序内存方面的功能实在是强大,之前也用过valgrind来检查内存泄漏,但是valgrind对性能的影响实在是太大,完全不能放到线上环境这样真实的环境下测试,而且asan给出的问题报告相当详细,基本上看一次错误报告就能解决当前导致宕机的问题,而core文件的信息由于优化和其他程序上下文的差异会导致提供的信息基本上没什么作用.宕机十次,十个core文件也无法准确定位某些野指针错误的问题.对于asan,只有一个词能表达我的心情,那就是相见恨晚…asan你值得拥有,谁用谁知道.

记得项目还没上线的时候因为一次误操作清空了数据库,不是非常要命但是如果手动恢复的话还是很麻烦的,所以就用了mysql的binlog机制进行了数据库的恢复,因为没有上线,binlog并不大,也没有删除过binlog,所以很容易就恢复了,不过没有做记录,前几天另外一个项目组的数据库数据也因为误操作被清空了,正好又实践了一次…这次把mysqlbinlog的使用记录下来。首先mysql启动前要配置一些字段才能使binlog生效。

vi/usr/local/mysql/etc/my.cnf

log-bin=/data/mysql_binlog/4306/mysql-binbinlog_format=MIXEDbinlog_cache_size=1Mmax_binlog_size=512Mexpire_logs_days=5#我这里只记录了五天,因为binlog特别大,容易将硬盘占满,需要定时清理,但是如果binlog不全是没法恢复数据库的。binlog中保存了修改数据库的所有语句(增删改查中,除了selec语句的其他所有语句),完全按照顺序写入binlog,如果数据库不幸被删了,那么从binlog的第一条记录开始恢复就会恢复binlog保存的所有数据。使用binlog恢复数据的命令:

mysqlbinlogmysql-bin.000001--start-position=4--stop-position=7128197-ddb_launcher_hz|mysql-uroot-h127.0.0.1-P4306-Ddb_launcher_hz命令中指定了要恢复的binlog文件mysql-bin.000001,开始位置和结束位置,要恢复的数据库名字,用户名密码等等。开始位置和结束位置可以通过查看binlog内容来确定,查看binlog内容的命令:

mysqlbinlogmysql-bin.000001会输出binlog的内容:

.......SETTIMESTAMP=1519733350/*!*/;BEGIN/*!*/;#at261731#18022720:09:10serverid1end_log_pos262082CRC320x69ff7fd5Querythread_id=31923exec_time=0error_code=0SETTIMESTAMP=1519733350/*!*/;UPDATEtbl_activitySETfld_channel='',fld_status='1',fld_show_status='1',fld_start_time='2014-12-3116:00:00',fld_stop_time='2015-01-0116:00:00',fld_show_start_time='2014-12-3116:00:00',fld_show_stop_time='2015-01-0116:00:00'WHEREfld_id='7393'/*!*/;#at262082#18022720:09:10serverid1end_log_pos262184CRC320x243a0c9dQuerythread_id=31923exec_time=0error_code=0SETTIMESTAMP=1519733350/*!*/;COMMIT/*!*/;#at262184#18022720:09:10serverid1end_log_pos262285CRC320xad2078edQuerythread_id=31923exec_time=0error_code=0.......我们可以把binlog的内容输出到文本中寻找想要恢复的的起止位置。

两次通过binlog恢复数据库内容的操作都是在项目早期,数据量并不多,binlog没有删除过,所以恢复起来并没有什么压力,但是binlog占用的硬盘空间非常大,如何管理binlog以保证数据库数据的安全性是之后需要考虑的一个问题。

项目上线的时候有很多竞品压力,关于AI方面并没有做太多的规则,为了快速上线服务器只是实现了AI的移动和攻击两个行为,其移动规则只是随机寻点移动,攻击行为也只是攻击最近的敌人。随着项目的发展,策划对AI的智能程度提出了要求,所以我们对AI的功能进行了加强。我在之前的RPG项目中也做过AI,是基于状态机的,简单的RPG野外怪物使用状态机完全可以满足需求,但是目前的项目对AI行为的要求比较细致,所以使用了行为树。记录一下行为树的基本原理和使用情况

最简单的AI使用ifelse语句就可以实现,但是随着AI行为的复杂度越来越高,要提供给策划配置等等需求的出现,必须使用一种更为方便清晰的方法来实现AI,行为树就是一个不错的选择。要实现一个行为树,首先要把怪物的各种行为模块化,比如巡逻行为,追击行为,普通攻击行为,技能攻击行为,各种条件判断行为(自身血量是否不足30%,队友人数是否少于1个…)等等,之后利用行为树的原理将这些模块进行组合,以达到行为的多样性,这种多样性的组合是基于行为数量指数级增加的,所以提供给策划配置的灵活性非常好。一棵行为树表示一组AI逻辑,要执行这组AI逻辑,需要从根节点开始遍历执行整棵树,遍历执行的过程中,父节点根据其自身类别选择需要执行的子节点并执行,子节点执行完后将执行结果返回给父节点,一棵普通的行为树主要由四种节点组成,我把他们分为两类,枝干节点和叶子节点。枝干节点肯定不是行为树的末端,它们主要的功能是对自己枝干下的节点进行“管理”,如何执行自己枝干下的节点是枝干节点需要做的事情。叶子节点一定是行为树的末端,它们通常是一个动作,如攻击,移动等等。

–顺序节点(Sequence):枝干节点,顺序执行子节点,只要碰到一个子节点返回false,则返回false;否则返回true。

–选择节点(Selector):枝干节点,顺序执行子节点,只要碰到一个子节点返回true,则返回true;否则返回false。

–条件节点(Condition):叶子节点,执行条件判断,返回判断结果。

–动作节点(Action):叶子节点,执行设定的动作,一般返回true。

下面是一棵简单的普通行为树,这棵行为树的根节点是一个选择节点(Selector),根节点下有两个顺序节点的枝干,每个顺序节点下面是叶子节点的条件和动作。把这课行为树用语言描述一下就是,怪物开始行动,执行根节点,首先选择移动还是攻击,顺序执行移动(11)和攻击(12),当执行移动(11)的条件中战场内无敌人(111)为true,那么继续执行下面的条件自身范围内是否有友方(112),如果返回true那么就执行定点移动行为(113),执行113结束后整棵行为树执行结束,从根节点开始再次执行。如果111或者112返回false,那么执行攻击(12),如果视野范围内有敌人,那么就攻击距离最近的敌人(122),如果没有敌人,那么整棵行为树执行结束,从根节点再次开始执行。

项目中的代码实现:

掌握了行为树的基本原理就可以实现各种各样复杂的AI逻辑并且条理清晰,便于管理,不会因为逻辑的复杂而使代码混乱,下面的图片展示了一个比上面例子更复杂的逻辑,你完全可以根据自己项目的需要来配置一个更大的行为树,它会严格按照你的配置来执行。

1.查看是否安装vsftp

rpm-qa|grepvsftpd如果出现:

vsftpd-3.0.2-10.el7.x86_64说明已经安装了vsftpd,如果没有安装使用命令安装:

yum-yinstallvsftpd2,配置vsftpd

whereisvsftpd出现:

vsftpd:/usr/sbin/vsftpd/etc/vsftpd/usr/share/man/man8/vsftpd.8.gz到/etc/vsftpd目录下配置vsftpd.conf

pam_service_name=vsftpd//设置服务名字,防火墙设置里要用userlist_enable=YES//访问限制打开tcp_wrappers=YES//防火墙打开local_root=/haizhan//ftp的根目录listen_port=19008//修改ftp的默认端口21到19008listen=YESpasv_min_port=19010//pasv模式分配给客户端可以连接的端口范围pasv_max_port=19020pasv_promiscuous=YES//关闭pasv的安全检查pasv_enable=YES//使用pasv模式pasv_addr_resolve=YES//设置外网地址开关pasv_address=120.120.120.120//如果这台机器需要对外网提供ftp的pasv服务,需要在这里设置本机外网地址listen_ipv6=NO//关闭ipv6才能正常工作上面这些配置全部是必要配置,必须全部配置。

3,修改FTP系统默认端口修改/etc/services

ftp19008/tcpftp19008/udp4,配置访问限制1.修改/etc/hosts.deny加入:

vsftpd:ALL2.修改/etc/hosts.allow加入白名单ip,如:

vsftpd:192.168.0.1vsftpd:120.1.1.1vsftpd:192.168.0.0/255.255.255.0//限定网段的,没有试过。5,添加用户

只要是构造函数中只有一个可变参数的C++类,都要避免隐式调用。

首先回顾一下epoll使用中的几个函数.

epfd=epoll_create(...);//创建指定大小的epoll句柄epfd后文描述中的listen部分:

listen_fd=socket(...);//创建listen_fdbind(listen_fd,addr);//把listen_fd绑定到本地地址addrlisten(listen_fd,...);//监听listen_fd后文描述中的accept部分:

fd=accept(listen_fd,...);//从listen_fd接受一个新接进来的连接套接字fdepoll_ctl(epfd,op,fd,event);//对epfd做op操作,操作涉及监听fd的event事件//比如op是EPOLL_CTL_ADD,意思是把“对fd监听event事件”注册到epfd里面后文描述中的epoll_wait部分:

num=epoll_wait(epfd,events,...);//监听句柄epfd上的事件,并把事件通过event数组返回,num返回值是发生事件的个数各家不同的使用方法:

代表开源产品:redis

基本原理:这种模型基本就是教科书上的epoll使用方式:socket->bind->listen->epoll_wait->accept或者处理读写事件->epoll_wait……redis基本遵循这样循环处理网络事件和定时器事件

echoserver测试:10万QPS

优点:1)模型简单。这个模型是最简单的,代码实现方便,适合计算密集型应用2)不用考虑并发问题。模型本身是单线程的,使得服务的主逻辑也是单线程的,那么就不用考虑许多并发的问题,比如锁和同步3)适合短耗时服务。对于像redis这种每个事件基本都是查内存,是十分适合的,一来并发量可以接受,二来redis内部众多数据结构都是非常简单地实现

缺点:1)顺序执行影响后续事件。因为所有处理都是顺序执行的,所以如果面对长耗时的事件,会延迟后续的所有任务,特别对于io密集型的应用,是无法承受的

代表开源产品:thrift-nonblocking-server

基本原理:1)在这种模型中,有1+n个线程。2)有1个线程执行端口的listen并把listen_fd加入该线程的epoll_set,然后循环去做如下事情:1)epoll_wait监听新连接的到来,2)调用accept获得新到的fd,3)把fd放入队列,4)回到“1)”继续epoll_wait3)另外有n个工作线程,从队列里面获取文件描述符,然后执行:1)读取数据,2)执行任务

echoserver测试:6万QPS

优点:1)模型简单。这种模型的代码实现也是非常方便的2)并发能力强。对于任务耗时或者IO密集的服务,可以充分利用多核cpu的性能实现异步并发处理3)适合生产环境。虽然QPS没有特别高,但是对于目前大部分大型网站的吞吐量,这种网络处理能力也是足够了的,这也是为什么thriftnonblockingserver可以用这种模型的原因4)负载均衡。在这个模型,每个工作工作线程完成任务之后就会去队列里主动获取文件描述符,这个模型天然地就实现了负载均衡的功能。原因有2,一来是只有空闲的线程会拿到任务,二来是所有fd的事件监听都是由监听线程完成

缺点:1)队列是性能瓶颈。俗话说,不怕锁,只怕锁竞争。这个模型在运行过程中,n+1个线程竞争于队列之上,所以队列的访问是需要加锁的。对于echoserver这种每次任务耗时都极短的服务,每次处理完任务就很快就会回到队列锁的争抢行列。大量的锁竞争直接拖垮了QPS。不过好在常见的生产环境web服务都不是echoserver,每次请求都会是毫秒级的,不会在锁竞争上产生问题。

代表开源产品:memcached

基本原理:这种模型基本类似于上一种模型,区别在于把1个队列换成n个队列,每个工作线程绑定一个队列,每个工作线程从自己的队列消费数据,其他的保持一致

echoserver测试:20万QPS

优点:1)并发能力更强。相比于单队列的模型,多队列的好处是减少了队列的锁竞争。对于短耗时任务能得到比较多的提升,很适合缓存类应用

缺点:1)有可能导致负载不均。因为监听线程是不会去根据不同线程的处理速度决定把任务分配给哪个线程的,如果每个任务的耗时不均衡,那么就可能导致有些线程累死,有些线程饿死

memcached对该模型改进:memcached是多线程且是缓存类应用,非常适合这个模型。改进如下:1)工作线程拿到的fd,这个fd会加到本线程的epoll_set里面,这个fd的后续读写事件都由该线程处理2)工作线程和监听线程之间建立了管道,工作线程的管道fd也加入到工作线程的epoll_set里面,那么就可以让‘新fd到来’和‘旧fd可读写’这两种事件都由epoll_set监听,减少调度的复杂性3)因为memcached的任务基本是查内存的工作,耗时短而且相对均匀,所以对负载问题不是很敏感,可以使用该模型

代表开源产品:nginx

基本原理:(依据nginx的设计分析)1)master进程监听新连接的到来,并让其中一个worker进程accept。这里需要处理惊群效应问题,详见nginx的accept_mutex设计2)worker进程accept到fd之后,把fd注册到到本进程的epoll句柄里面,由本进程处理这个fd的后续读写事件3)worker进程根据自身负载情况,选择性地不去accept新fd,从而实现负载均衡

echoserver测试:后续补充

优点:1)进程挂掉不会影响这个服务2)和第二种模型一样,是由worker主动实现负载均衡的,这种负载均衡方式比由master来处理更简单

缺点:1)多进程模型编程比较复杂,进程间同步没有线程那么简单2)进程的开销比线程更多

对应开源产品:无

基本原理:1)1个线程监听端口并accept新fd,把fd的监听事件roundrobin地注册到n个worker线程的epoll句柄上2)如果worker线程是多个线程共享一个epoll句柄,那么事件需要设置EPOLLONESHOT,避免在读写fd的时候,事件在其他线程被触发3)worker线程epoll_wait获得读写事件并处理之

echoserver测试:200万QPS(因为资源有限,测试client和server放在同一个物理机上,实际能达到的上限应该还会更多)

优点:1)减少竞争。在第四种模型中,worker需要去争着完成accept,这里有竞争。而在这种模型中,就消除了这种竞争

缺点:1)负载均衡。这种模型的连接分配,又回到了由master分配的模式,依然会存在负载不均衡的问题。可以让若干个线程共享一个epoll句柄,从而把任务分配均衡到多个线程,实现全局更好的负载均衡效果

我们项目中的epoll模型基本上是第五种模型的方式,一线程负责listen+accept,accept到的fd通过负载情况分配到负载最低的n线程中的一个线程中去,在n线程初始化中调用epoll_create,所以n线程都有自己的epoll对象,n个worker线程自己用自己的epoll对象来epoll_wait分配给自己的fd,处理读写事件。最后n线程将整合好的消息结构push到主逻辑线程的队列中,主逻辑线程进行逻辑处理。因为游戏服务器都是长连接,基本上每个玩家造成的访问量基本上差别不大,所以在负载上基本上可以认为是均衡的,而且这种模型没有竞争,QPS高,非常适合我们的游戏项目。

1、除了第一个模型,其他模型都是多线程/多进程的,所以都采用了“1个master-n个worker”结构2、如果accept由master执行,那么master就需要执行分配fd的任务,这种设计会存在负载不均的问题;但是这种情况下,accept由谁执行不会存在竞争,性能更好3、如果accept让worker去执行,那么同一个listen_fd,这个时候由哪个worker来执行accept,便产生了竞争;产生了竞争就使得性能降低4、对于一般的逻辑性业务,选择由master去accept并分配任务更合适一些。因为大部分业务请求耗时都较长(毫秒级别),而且请求的耗时不尽相同,所以对负载均衡要求也较高;另外一般的业务,也不可能到单机10万QPS的量级5、这几个模型的变化,无非就是三个问题的选择:1)多进程,多线程,还是单线程?2)每个worker自己管理事件,还是由master统一管理?3)accept由master还是worker执行?6、在系统设计中,需要根据实际情况和需求,选择合适的网络事件方案

epoll作为目前linux处理网络io的“唯一”选择,几乎在所有的互联网服务器项目中被使用,各种不同的知名项目对epoll模型的设计都有一些区别,我们的项目也是epoll,记录一下,加深记忆。首先epoll的基本操作很简单,只有几个函数和一个结构体:

头文件:#include

intepoll_create(intsize);

epoll_create()可以创建一个epoll实例。在linux内核版本大于2.6.8后,这个size参数就被弃用了,但是为了兼容老版本,传入的值必须大于0,在epoll_create()的最初实现版本时,size参数的作用是创建epoll实例时候告诉内核需要使用多少个文件描述符。内核会使用size的大小去申请对应的内存(如果在使用的时候超过了给定的size,内核会申请更多的空间)。epoll_create()会返回新的epoll对象的文件描述符。这个文件描述符用于后续的epoll操作。如果不需要使用这个描述符,请使用close关闭。

intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);

epoll_ctl()可以控制通过epoll_create()创建的epfd,通过op增删改文件描述符(比如socket)fd上的事件。

op值:EPOLL_CTL_ADD在epfd中注册指定的fd文件描述符并能把event和fd关联起来。EPOLL_CTL_MOD改变fd和event之间的联系。EPOLL_CTL_DEL从指定的epfd中删除fd文件描述符。在这种操作中event是被忽略的,并且为可以等于NULL。

event这个参数是用于关联制定的fd文件描述符的。它的定义如下:

typedefunionepoll_data{void*ptr;intfd;uint32_tu32;uint64_tu64;}epoll_data_t;structepoll_event{uint32_tevents;/*Epollevents*/epoll_data_tdata;/*Userdatavariable*/};events是传入参数,是一个字节的掩码构成的。下面是可以用的事件:

`EPOLLIN`-当关联的文件可以执行read()操作时。`EPOLLOUT`-当关联的文件可以执行write()操作时。`EPOLLET`-设置指定的文件描述符模式为边缘触发,默认的模式是水平触发。LT(level-triggered)水平触发是缺省的工作方式,并且同时支持block和no-blocksocket。在这种模式下,内核告诉你一个fd是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你,直到这个fd的数据被读完。

ET(edge-triggered)边缘触发是高速工作方式,只支持no-blocksocket。在这种模式下,当fd从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(直到你读取了全部数据或者fd遇到错误)。

上面这些事件是经常用到的,下面这几个事件目前理解还不够深,先放着吧。。。

EPOLLRDHUP-(从linux2.6.17开始)当socket关闭的时候,或者半关闭写段的(当使用边缘触发的时候,这个标识在写一些测试代码去检测关闭的时候特别好用)EPOLLPRI-当read()能够读取紧急数据的时候。EPOLLERR-当关联的文件发生错误的时候,epoll_wait()总是会等待这个事件,并不是需要必须设置的标识。EPOLLHUP-当指定的文件描述符被挂起的时候。epoll_wait()总是会等待这个事件,并不是需要必须设置的标识。当socket从某一个地方读取数据的时候(管道或者socket),这个事件只是标识出这个已经读取到最后了(EOF)。所有的有效数据已经被读取完毕了,之后任何的读取都会返回0(EOF)。EPOLLONESHOT-(从linux2.6.17开始)设置指定文件描述符为单次模式。这意味着,在设置后只会有一次从epoll_wait()中捕获到事件,之后你必须要重新调用epoll_ctl()重新设置。data是传入参数,主要使用intfd;来标记文件描述符,也可以使用void*ptr;标记自定义的数据格式,包含文件描述符和自定义的数据。

intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);

epoll_wait()这个系统调用是用来等待epfd中的事件。events是传入参数,一组空的epoll_event对象,用来接收epoll操作返回数据。maxevents是epoll_event数组的大小,必须要大于0(这里的epoll_event数组大小可以理解为一次epoll_wait调用操作的最大fd数量)。

timeout这个参数是用来制定epoll_wait会阻塞多少毫秒,会一直阻塞到下面几种情况:

1.一个文件描述符触发了事件。2.被一个信号处理函数打断,或者timeout超时。

当timeout等于-1的时候这个函数会无限期的阻塞下去,当timeout等于0的时候,就算没有任何事件,也会立刻返回。

一个精简版的epoll伪代码:

下面是一个完整可运行的简单epoll例子:

天气太热写不下去代码,就翻柜子,找到c++primer翻了一下,好久不看书,记一下翻到的内容,稍微巩固下…给定下面的类层次,从VMI类内部可以限定地访问哪些继承成员?哪些继承成员需要限定?解释你的推理。

classBase{public:bar(int);protected:intival;};classDerived1:virtualpublicBase{public:bar(char);foo(char);protected:charcval;};classDerived2:virtualpublicBase{public:foo(int);protected:intival;charcval;};classVMI:publicDerived1,publicDerived2{};从这个继承层次看,VMI类内部访问哪些没有二义性,哪些成员有二义性呢?从VMI类内部可以不加限定地访问继承成员bar和ival:bar在共享基类Base和派生类Derived1中都存在,但特定派生类实例的优先级高于共享基类实例,所以在VMI类内部不加限定地访问bar,则访问到的是Derived1中的bar实例。ival在共享基类Base和派生类Derived2中都存在,同理,在VMI类中不加限定地访问ival,访问到的是Derived2中的ival实例。继承成员foo和cval需要限定:二者在Derived1和Derived2中都存在,Derived1和Derived2均为Base的派生类,访问优先级相同,所以,如果在VMI类内不加限定地访问foo和cval,则会出现二义性。

最近在做的项目中有一个需求,我负责向客户端推送一组数据,这些数据需要另一个服务器程序员填充,我需要调用他的一个函数,并把我的填充函数传递给他,然后在他的函数中填充这些数据.

首先我用普通函数试验了一下,因为c++11中的bind和function并没有使用过太多.```c++

#include#includestructInfo;typedefstd::functionNewInfo;typedefstd::functionPropExecute;usingnamespacestd::placeholders;structInfo{intkey;intvalue;};voidtest(PropExecutefunc){func(1,1);}voidprop(intkey,intvalue,NewInfof){Info*info=f();info->key=key;info->value=value;}Info*new_info(){returnnewInfo;}intmain(){test(bind(prop,_1,_2,new_info));return1;}这样写就基本实现了该功能,Info和PropExecute函数类型由我公开给服务器同事,同事提供test函数给我调用,并在该函数内使用我传递过去的函数将数据填充(这里我用1,1填充).

好了,现在修改一下,把所有的普通函数都修改为类成员函数,因为项目中这些函数都是类成员函数.

#include#includestructInfo;typedefstd::functionNewInfo;typedefstd::functionPropExecute;usingnamespacestd::placeholders;classAttr{public:Attr():key(1),value(1){}~Attr(){};voidget_attr(PropExecutefunc){func(key,value);}intkey;intvalue;};classInfo{public:Info():key(0),value(0){};~Info(){};Info*new_info(){returnnewInfo;}intkey;intvalue;};classMyClass{public:MyClass(){}~MyClass(){}voidset_prop(intkey,intvalue,NewInfof){Info*info=f();info->key=key;info->value=value;}};intmain(){Infoinfo;Attrattr;MyClassmyclass;NewInfonewinfo=std::bind(&Info::new_info,&info);attr.get_attr(bind(&MyClass::set_prop,&myclass,_1,_2,newinfo));return1;}修改为类成员函数后就是这样了,这里遇到一个问题,main函数中调用get_attr时分成了两条代码,因为写成这样编译报错attr.get_attr(bind(&MyClass::set_prop,&myclass,_1,_2,std::bind(&Info::new_info,&info)));我估计可能是类型不匹配之类的错误.总之分成两条代码就OK了.

最近在项目里遇到一个问题,关于数据库读取大量数据的,这部分代码项目里的思路是,对数据表做索引,然后依据索引排序,取出最上面的max_size(5000)条数据,然后基于后面的数据继续排序,取出最上面max_size(5000)条,依此循环直到取出最后一条数据.开始我觉得这样可能效率会比较低,因为每次操作都要进行一次排序,我觉得用limit0,max_size.limit0+max_size,max_size+max_size这样的方法要快一些,后来想了一下问题的关键应该是在每次取第一条数据的时候,比如先排序再去取的话,因为建立了索引,所以排序是很快的,而且第一条马上就找到了,但是用limit0,max_size这样的方法的话,每次查找第一条数据都要顺序数到位置,所以先排序再取数据的方法是稍微好一点的.

这是一个简单的person类.//person.hxx

#includeclassperson{public:person(conststd::string&first,conststd::string&last,unsignedshortage);conststd::string&first()const;conststd::string&last()const;unsignedshortage()const;voidage(unsignedshort);private:std::stringfirst_;std::stringlast_;unsignedshortage_;};使用ODB后修改如下:

//person.hxx

#include#include//(1)#pragmadbobject//(2)classperson{...private:person(){}//(3)friendclassodb::access;//(4)#pragmadbidauto//(5)unsignedlongid_;//(5)std::stringfirst_;std::stringlast_;unsignedshortage_;};使用这个sql文件建立数据表//ThisfilewasgeneratedbyODB,object-relationalmapping(ORM)compilerforC++.

DROPTABLEIFEXISTS`person`;CREATETABLE`person`(`id`BIGINTUNSIGNEDNOTNULLPRIMARYKEYAUTO_INCREMENT,`first`TEXTNOTNULL,`last`TEXTNOTNULL,`age`SMALLINTUNSIGNEDNOTNULL)ENGINE=InnoDB;测试例子如下,执行commit函数后三个person类对象的数据就写入了数据库中,真的非常方便简洁.//driver.cxx

打开找到相应的VC版本工程用VS打开,我用的是VS2012,相应的版本是VC11.

一开始编译出错,因为这个版本里包含了ssh和ssl的第三方库,我也用不着所以在预编译宏里把关于ssl和ssh的都去掉.

然后继续编译lib静态库成功了,新建测试工程加入头文件和生成的附加库libcurld.lib,发现有无法识别的外部符号错误,网上找了半天解决方法.

最后的解决方法是.libcurl的预编译设定为WIN32_DEBUGBUILDING_LIBCURL测试项目的预编译设置为BUILDING_LIBCURL并且在测试项目头文件中添加以下内容#defineCURL_STATICLIB#ifdefined(_DEBUG)#pragmacomment(lib,"libcurld.lib")#else#pragmacomment(lib,"libcurl.lib")#endif#pragmacomment(lib,"winmm.lib")#pragmacomment(lib,"ws2_32.lib")#pragmacomment(lib,"wldap32.lib")

再次编译,搞定.

cdyourDir

./configuremakemakechecksudomakeinstall

然后编译,会出现这个错误”#errorHostarchitecturewasnotdetectedassupportedbyprotobuf”

解决这个问题的方法是:

Inplatform_macros.h,Replace#else#errorHostarchitecturewasnotdetectedassupportedbyprotobuf#endifBy:#elifdefined(__aarch64__)#defineGOOGLE_PROTOBUF_ARCH_ARM1#defineGOOGLE_PROTOBUF_ARCH_64_BIT1#else#errorHostarchitecturewasnotdetectedassupportedbyprotobuf#endif最后再编译就OK了!

回答:

我呢,6年策划经验,换了5家公司,跟过9个项目,上线5个,大成有2;做过执行,拼过关卡,搭过系统,算过数值,客串过两场主策;踩过潮流的节点,也曾逆流而动;页游入行,转回端游,又跳进手游;待过大公司,见过土老板,窝过小外资,曾在风投热捧的大佬手下干活,还和人鼓捣过创业计划……以上这些吹牛全不重要,关键是,有一天我突然想明白了,所以,现在我已经不做游戏了。(至于原因我会在下面的生涯理想中提到)

翻过上面46个回答,没看见有跳出行业的人来现身说法,作为离局者,我提供自己几年的思见作为一个参考视角,这里我对业内的情况不多做细说,只谈一些共通性的问题,希望能有所帮助。

我能领会题主所说的那种痛苦与彷徨,因为我也曾这样一路走来。题目中的疑问,我们分开来说,先说工作,再谈生活。

那些年加班伤不起的工作

刚入行的那会,我总是埋头干活,各色需求总是来者不拒,一心想着都是尽快的自我提升,7*12的日子也没少过,直到把生活压榨到极限,才发现想跟上事务的累积速度是永远不可能完成的任务。这时候,我开始学会明确岗位的职责,定位自己的位置,规划工作的事项,降低沟通的成本,改进工作方式,甚至学会适当拒绝。当你从一个混乱无序的状态,到分清轻重缓急之后,工作强度和生活追求之间的对立,也会随之慢慢转为在你个人意志支配之下的共处。

一般说到职业规划时,我们谈的总是在当前岗位上的上行通道。这条路,大家都很明晰了,不需赘言。稍微提上两句的就是:当你想往上升的时候,不要只看到职位以及所能带来的权益。而应该尝试使用目标职位的思路,来考虑对待你当下所从事的工作,以及对全局走势的一个看法。多想一些再与实际相印证总不会有坏处。很多事情其实是想的时候指点江山容易,真轮到你干也许更加坑爹。比如在看到全线飘红红的加班安排时,你有没有看到项目层的压力,以及公司态度的微妙转变,当你觉得通过某个改动很傻逼的时候,有没有算过其中的风险系数。当然你觉得某项决定真心傻逼时,你有没有考虑过可能会造成的影响,以及是不是该预备两手补救措施。如果只着眼手上的事务,那么成长将变得缓慢。

到这里,我想返回头说下有关规划的个人观点:我认为规划实际上是一个在学习中进步,让能力匹配上目标的过程。规划的重点在于建立、明确、完善一套属于自己的方法论和世界观,而非仅是简单职业的成长。当你手中不仅是握着实操的具体经验,而是一套行之有效的处理问题的方式方法与学习能力之后,我想,就算跳到一个十万八千里外的行业中,也能很快成长起来。我一直相信一句话:凡所经历,皆是加持。这里加持的并非具体的事件,而是通过事件总结出的经验教训。以此为基础产生的认识,对未来的期许,才算得上是规划。

每个人入行时,都对着工作抱有着憧憬。这些理想是入行的动力,也是维系工作的热情。就算最不受待见的公务员,也是希望能过上朝九晚五喝茶看报的舒坦日子。但理想和现实总存在着出入,有时候差异可能很大。这种落差从工作内容到工作目标上都无时无刻地在消蚀你的热情。这时候,就需要重塑和纠正最初的理想。

我不敢说每个入行的游戏从业者都是抱着对游戏一腔热血而入行的,比如我就见过啥也不懂游戏都不玩,光听说游戏业赚钱就想进来当策划混口饭吃的90后;也见过许多美术和程序漠不关心项目,只看分配到自己工作。但是我还一直抱有一个理想,希望有朝一日能带出一款自己满意、开发团队喜欢、玩家热爱的产品;如果还有可能,我希望通过游戏传递快乐的同时,能带给玩家一个相对客观、正向的价值导向;我的野望,或者说狂妄也就到此为止了。

但是现在,我却已不再做游戏了。我一直认为,策划的本质就是将快乐的方法通过游戏传递出去。而现在,我看到的和做过的大多游戏传递而出的却是一种赤裸裸的逐利意识。我不反对赚钱,我只是本能反感这种简单粗暴毫无美感的圈钱方式。刚开始的时候,我曾因为宝箱的机制和人吵过,为耐久的扣点和人吵过,为加星的爆率和人吵过,为了各自匪夷所思的收费陷阱和人理论……随着后来的整个游戏圈的约定俗成,我逐步妥协,却还是觉得应该坚持一个不作恶的底线。当我看到,越来越多的所谓玩点其实就是收钱的坑点的时候,玩游戏的玩家越花钱越不开心,我感到我的工作也越来越不开心。当我的开心依附于玩家砸入用于购买开心的钱化入公司的户头再转为个人工资的时候,我感到这份工作已经失却了最初的理想。

曾待过一个公司,会让策划定期去听客服录音,有天晚上便听到一个小姑娘质问:为什么我花了六千块钱还洗不出一个完美的宠物宝宝!关于这个问题的内部回答是什么呢?呵,理论上这是两万块钱才会出现的概率哦。嗯,两万软妹币换得200%的成长模板和两个紫红耀眼的大字。是的,这钱不偷、不抢、不蒙、不骗、不犯法,但是对于三观懵懂的小盆友来说,你导出这样的价值取向给他真的大丈夫吗?而那些我被人砍了要花多少钱买什么才能报仇这样的客服问答我就不提了。关于这些,我不喜欢,真的不喜欢。

于是,我又重新思考了下自己的职业理想,赚大钱并不在我的首位,我更想做的是些好玩有趣,为人们提供欢乐,便利人们生活,不作恶、正能量的互联网科技项目。最后,我就离开了。

当在离职原因一栏写下:“不爱了”三个字与游戏圈作别时,心底其实涌过一阵轻松:我终于敢放下这个我且算精通,将要出头,却早已麻木的工作。《FightClub》里说“It’sonlyafterwe’velosteverytingthatwe’refreetodoanyting.”我觉得我正逼近这个状态。

总之呢,道不同不相为谋。如果不放弃眼下,便无法收获未来。如果你不去尝试,永远不知道自己适合什么。窄门未必能上天堂,但自己走得舒坦就成。当你对工作感到彷徨、失意和痛苦的时候,就想想最初的理想,可忍耐的忍耐,该离开时离开。这些都是我的个人观点,也许我生来就是一个不安分的人,觉得折腾也并非是坏事。

谁不想有个明媚的生活

关于特别提出的女朋友。我想先问一句:你是真想找个携手到老的伴侣呢,还是仅仅想享受女朋友所带来的好处。我认识的单身死宅们,大都停留在后一个的幻想阶段。在一地狼藉的屋子里光着膀子打着dota,一边喊着为什么我没有女盆友这种人活该孤单到老。没想法、没追求、没行动,总幻想有一天有个白来的妹子,呵呵呵呵,祝你成功。对于真心想找妹子的小伙伴呢,就送9个字啦:“高筑墙、广积粮、缓称王”。翻译过来就是:自我提升、扩大圈子、别急着确立关系(这里说的不是屯备胎和玩暧昧,是说对待感情要慎重啦,不能仅仅是因为寂寞就在一起,因为长得好就在一起,因为有车有房就在一起……不然,出来混迟早要还的。)至于哪里找,怎么追,如何相处都是后话。首先你得摆正态度。大家都是做项目的,自然知道执行才是关键,守株待兔、怨天尤人是从来没有结果的。别说前面没路,当你想走时,自然就有路了。

via:知乎

一般来说,GDB主要帮忙你完成下面四个方面的功能:

1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。

2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)

3、当程序被停住时,可以检查此时你的程序中所发生的事。

4、动态的改变你程序的执行环境。

(命令行的调试工具却有着图形化工具所不能完成的功能)

下面介绍gdb常用的命令

(gdb)list显示当前运行位置的前5行和后5行代码

(gdb)listfunction(gdb)listfilename:function显示所指向的本文件的函数或者其他文件内函数的前2行和后8行代码

(gdb)list-往前显示代码

(gdb)list+往后显示代码

这些命令默认都是显示10行,可以用setlistsize20修改显示的行数为20行,用showlistsize查看当前设置的显示数量.

list命令还有下面的用法:

(gdb)list,显示从first行到last行之间的源代码。

(gdb)list,显示从当前行到last行之间的源代码。

在你调试程序时,当程序被停住时,你可以使用print命令(简写命令为p)print命令的格式是:(gdb)printn

打印出变量n的值,可以按照指定的进制查看变量的值,比如intn=5可以使用print/xn命令得到$26=000000101结果(gdb)whatisptype=int*显示某个变量的类型

(gdb)printFindfuc(1,0)对程序中函数的调用

(gdb)print*pCTable$8={e=reference=’/000’,location=0x0,next=0x0}数据结构和其他复杂对象

(gdb)printh@10$13=(-1,345,23,-234,0,0,0,98,345,10)查看内存中在变量h后面的10个整数,一个动态数组的语法如下所示:base@length

还没怎么接触过,留空.

打印当前的函数调用栈的所有信息。如:(gdb)bt#0func(n=250)attst.c:6#10x08048524inmain(argc=1,argv=0xbffff674)attst.c:30#20x400409edin__libc_start_main()from/lib/libc.so.6从上可以看出函数的调用栈信息:__libc_start_main-->main()-->func()

切换栈(gdb)frame1切换到bt显示出来的栈序号为1的栈中去.比如:frame0,表示栈顶,上面的例子中就是func所在栈,一般当前程序执行的位置的栈就是栈顶,frame1,表示栈的第二层,即main函数所在栈

(gdb)upn表示向栈的上面移动n层,可以不打n,表示向上移动一层。

(gdb)downn表示向栈的下面移动n层,可以不打n,表示向下移动一层。

break命令(可以简写为b)可以用来在调试的程序中设置断点.(gdb)breakfilename:line-number在指定文件的指定行上设置一个断点

(gdb)breakfilename:function-name在指定文件的指定函数入口处设置一个断点

(gdb)breakline-or-functionifexpr在指定行号或者指定函数内设置一个断点,当expr条件成立时断住程序,如:break46ifdwCount==100

(gdb)infobreakNumTypeDispEnbAddressWhat1breakpointkeepy0x000028bcinCApp:InitatCApp.cpp:1552breakpointkeepy0x0000291cinmainatGameApp.cpp:168显示断点信息

(gdb)deletebreakpoint1删除编号为1的断点

(gdb)deletebreakpoint删除所有断点

设置观察点,还没怎么用过,留空

设置捕捉点,还没怎么用过,留空

程序执行

(gdb)setargs可指定运行时参数。(如:setargs1020304050)

(gdb)showargs查看设置好的运行参数

(gdb)run执行程序

(gdb)continue执行到下一个断点相当于vs里的F5快捷键

(gdb)next执行下一句代码相当于vs里的F10快捷键

(gdb)step执行下一句代码,如果下一句代码调用了函数,那么进入该函数,相当于vs里的F11快捷键

(gdb)finish跳出当前函数,相当于vs里的shift+F11快捷键

(gdb)until跳出当前while或者for循环(这个貌似在vs里是没有的,求大牛指点)

跳转执行,还没怎么用过,留空

强制调用函数,留空

还有一些关于”信号”,”搜索”等没怎么接触过的命令和用法,有待以后工作和学习中接触到之后再填补.

THE END
1.Linux:开发工具(1)安装命令:sudo yum remove lrzsz 无论是安装还是卸载,都会询问你是否确定,如果你不希望他询问,可以加一个-y 1.3.3 一些好玩的软件(可以自己查) sl cowsay 二、Linux开发工具vim vim是Linux下常用的一款多模式的文本编辑器(可以用他来写代码),每个Linux账户都独有一个vim编辑器。 https://atigger.cn/archives/434.html
2.SRE就业考试题3、升级 centos 内核版本为最新版本的内核 rpm 包完成? 备份重要数据 查看当前内核版本 uname -r 安装官方仓库的最新的稳定版内核 yum install kernel -y 设置默认引导项 #查看可用内核列表,awk-F\''$1=="menuentry " {print i++ " : " $2}'/etc/grub2.cfg#设置默认内核grub2-set-default0#更新GRUBhttps://blog.csdn.net/huierwe007/article/details/144389331
3.服务器程序开发,哪种编程语言是最佳选择?服务器程序可以使用多种编程语言编写,包括但不限于 Python、Java、C++、Go、Node.js 等。 服务器程序用什么语言? 在当今的数字化时代,服务器程序扮演着至关重要的角色,它们负责处理客户端请求、执行业务逻辑、管理数据存储等关键任务,选择合适的编程语言来开发服务器程序是至关重要的,因为它直接影响到程序的性能、可https://www.kdun.com/ask/1403197.html
4.吊打面试官的java面试神器!全网最全Java面试题总结2.7 垃圾回收的优点和原理。并考虑2种回收机制。 2.8 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收? 2.9 Java中会存在内存泄漏吗,请简单描述。 2.10 深拷贝和浅拷贝 2.11 System.gc() 和 Runtime.gc() 会做什么事情? https://www.bilibili.com/opus/1004118467146678279
5.Linux期末大考来袭:你准备好了吗?期末试题解析与备考攻略3.系统管理与维护:涉及用户管理(添加、删除用户,修改密码等)、软件包管理(使用apt、yum等工具安装、卸载软件)、系统监控(使用top、htop、vmstat等工具)、日志分析等内容 4.网络配置与服务管理:考察网络配置(如IP地址设置、DNS解析)、防火墙规则设置(iptables或firewalld)、常见服务(如Apache、Nginx、MySQL、SSH)的安http://www.iis8.com/iis8_com/html/2024/12/422610.html
6.阿里后端优化这么恐怖?看完这20W字Java性能实战经验手册,最少P7编程许多人在解决问题的时候,只能看到表面,然而性能问题都藏得很深,就不能仅凭感觉入手。总之,一旦遇到“性能优化”问题,很少人能够由点及面逆向分析,最终找到瓶颈点和优化方法,而性能优化是软件工程的深水区,也是衡量一个程序员能力高低的标准。俗话说:“授人以鱼不如授人以渔”。在这分享这份java性能调优实战手册给https://www.163.com/dy/article/JJB0TT410553TKEZ.html
7.rpm与yum使用及优缺点是什么–PingCode优点: 自动解决依赖关系:可以自动查找和安装所需的依赖项。 易于使用:命令结构简单,适合初学者。 缺点: 灵活性较低:与rpm相比,可选配置较少。 速度慢于rpm:自动解决依赖关系可能会增加操作时间。 常见问答 Q1:我应该使用rpm还是yum? A1:如果需要精确控制和灵活性,请使用rpm;如果优先考虑易用性和自动解决依赖关系https://docs.pingcode.com/ask/48741.html
8.Linux系统中rpm与yum的区别是什么?老男孩Linux运维云计算课程凡是接触过Linux系统的用户,绝大部分人都知道rpm和yum,而且很多用户经常把rpm和yum混为一谈,因为它们名字相似、功能相同,都可以用于软件的安装。那么Linux系统中rpm与yum的区别是什么?本文为大家详细讲解一下,希望对你们有用。 凡是接触过Linux系统的用户,绝大部分人都知道rpm和yum,而且很多用户经常把rpm和yum混为一https://www.oldboyedu.com/blog/3708.html
9.Linux下rpmyum和源码三种安装方式详细介绍Linux程序会提供很多RPM包的格式,用户根据系统情况选择适合的RPM包直接安装,而源码包相当于通用型,可以是用于多个系统中,所以需要运行configure脚本来检测环境,生成对应的安装信息。1.1 源码安装优点 1、文档齐全2、因为可以定位到代码,所以debug方便3、本机兼容性最好(由于是本机编译的,只要编译通过,就没有各种库的依赖的https://www.jb51.net/article/175923.htm
10.openeuler与centos学哪个lanhy的技术博客CentOS使用YUM(Yellowdog Updater, Modified)作为其包管理系统。YUM使得安装、更新和删除软件包变得相对简单。例如,要安装Nginx服务器,您可以运行以下命令: sudo yum install nginx 1. Ubuntu使用APT Ubuntu使用APT(Advanced Package Tool)作为其包管理系统。与YUM类似,APT也提供了易于使用的方式来管理软件包。以下是在https://blog.51cto.com/u_12959/12802086
11.什么是yum源?如何对其进行配置?腾讯云开发者社区3)yum源:可以理解为rpm软件包管理的升级版 优点:能够解决软件包之间的依赖关系,提高运维人员的工作效率。 注:yum源底层还是基于RPM进行软件安装操作的,所以yum安装软件后,也可以通过rpm -qa |grep 软件名称来查询软件包是否已安装。 3、yum源分类 3.1 本地yum源 https://cloud.tencent.com/developer/article/2431523
12.MySQL高可用架构(MHA)与Atlas读写分离wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo # 安装manager 依赖包 yum install -y perl-Config-Tiny epel-release perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes # 安装manager管理软件 rpm -ivh mha4mysql-manager-0.56-0.el6.noarch.rpm https://www.imooc.com/article/313246
13.字节上岸成功,整理一波测试开发岗的基础知识,含答案本科非科班,春招找非技术岗工作失败(无法通过群面)于是秋招转码了。谁又能想到秋招形势严峻比春招还严峻…. 太难了!!! 8月末开始投简历,9月份开始面了tplink、字节、美团、广立微电子这四家公司,然后这边最近秋招结束,所以来发下自己整理的一些知识点来 分享,https://maimai.cn/article/detail?fid=1679089730&efid=YLyF0ejo0qEtjVB1UUE-yw
14.Linux操作系统(Centos7)零基础入门,超详细!3-U #升级rpm软件包服务 rpm -Uvh zsh-5.0.2-7.el7.x86_64.rpm(注意升级的软件包一定要比当前软件包版本高) (9)rpm包卸载 -e #卸载 rpm -e zsh(注意这里要写软件名,不要写包名) 4.2.Linux安装服务软件yum方式 (1)什么是yum安装? 基于C/S架构,yum安装称之为傻瓜式安装 (2)yum安装优点 方便快捷,https://developer.aliyun.com/article/1222674
15.RPM打包指南RedHatProductDocumentationRPM 软件包管理器(RPM)是运行在 RHEL、CentOS 和 Fedora 上的软件包管理系统。您可以使用 RPM 为上述任何所述操作系统分发、管理和更新创建的软件。 1.2. RPM 优点 复制链接 与传统存档文件中软件分发相比,RPM 软件包管理系统带来了一些优势。 RPM 可让您: 使用标准软件包管理工具(如 Yum 或 PackageKit)安https://access.redhat.com/documentation/zhcn/red_hat_enterprise_linux/7/html-single/rpm_packaging_guide/index
16.下载rpm包与本地yum仓库的制作前面两种是利用厂商发布的iso镜像文件作为yum仓库。当我们需要安装一些第三方提供的软件的时候我们需要到网上下载rpm的安装包,但是每次都下载多麻烦,我们可以将较常使用的rpm安装包归到一个文件里面制作成一个可以被系统识别的yum仓库,通过配置yum仓库指向文件可以将它设置成本地的yum源也可以是通过http发布的共享yum源。https://www.jianshu.com/p/286fcef565ab