Linux编程---进程通信
Linux的通信方式主要有分类有以下几种:
-匿名管道和FIFO有名管道
-消息队列,信号量和共享存储
-套接字
对于套接字的进程通信,我就留在套接字的文章中再写了.
一.管道
管道是最古老的进程通信机制了.提供进程间的单向通信.
1.创建管道
int pipe(int fdes[2]);
实际上管道通过參数返回读和写的两个文件描写叙述符.相当于是打开了两个文件吧.可是这个文件是特殊的pipe文件.fdes[0]表示的是输入,fdes[2]表示的是输出.注意,这个函数仅仅创建一个文件,而不是创建两个文件一个用来读,一个用来写.
管道创建调用成功,那么就返回0,否则返回-1,并设置errno错误条件.
假设向写文件描写叙述符进行读,那么会生成一个SIGPIPE信号,而且当信号被堵塞是将以EPIPE错误失败.管道文件不同意文件定位,读和写操作都是顺序的,读从文件的開始处读,写则写至文件尾.
这么看似乎好像没有和别的进程关联起来.实际上,管道单独使用的话仅仅是创建了两个文件而已,想要使用管道还得使用fork,exec,dup等函数来创建一个进程才干使用.
简答来说,对于创建的进程来说,其读写的文件是相反的.而且对于管道来说,其方向仅仅能是单向的,所以假设你创建了新进程,两个进程至少要关闭一个文件描写叙述符.
2.popen和pclose函数
这两个函数是C标准库中的两个函数.而且创建的方式也比系统调用方便.
FILE *popen(const char *command,const char *mode);
int pclose(FILE * stream);
第一个函数通过第一个參数,解析其命令建立一个新进程.过程就是先建立一个管道,然后派生一个子进程,调用exec启动一个shell程序运行command给出的命令.并马上返回一个管道相应的流指针.
mode的方式仅仅能是”r”和”w”两种方式之中的一个,可是不能两个一起,所以这和一般的文件模式是不同的.
这么来看,这个函数就比用上面的pipe系统调用方便得多.封装了包含fork,exec,dup等一系列调用~当然,这个函数相对自己调用pipe+fork+exec+close来说效率就要差一些了.
管道IO的原子性
实际上管道就是一片内存缓冲区.(我记得原来学的时候创建一个管道,然后在图形界面下把管道文件给删了,可是管道仍然正常使用).所以假设写入管道的数据块大小超过这片缓冲区的大小时,内核就要分几次写入了.假设是分批写的话,那么就非常可能造成混乱了.试想假设A进程须要分3次写,在写完第二次的时候时间片到了,切换到另外一个进程也要往这个管道写内容,那么就可能导致内容产生混乱.(能够fork出多个子进程向同一个管道进行写操作给父进程数据).假设是一对一的关系就无所谓了,可是对于一对多就须要注意了.
管道缓冲区的大小由定义在头文件<limits.h>中的宏PIPE_BUF给出.在Linux系统中其值为4096.而且一旦写入PIPE_BUF个字节至管道后,进一步写管道的操作将堵塞直到有些字节已读出.
二.FIFO特别文件
因为管道仅仅能用于存在父子关系的进程之间的通信,为了满足非父子进程关系下的通信.能够用FIFO特别文件.
FIFO特别文件类似于管道,也是半双工方式,而且数据也是按先进先出顺序传送,因此的名FIFO.可是创建的方法与管道不同,管道式匿名通信通道,而FIFO特别文件通过调用mknod()被等级到文件系统,因此FIFO有时也成为有名管道.
与管道的主要不同有:
--FIFO作为特别文件存在于文件系统中
--不同祖先的进程能够通过FIFO特别文件共享数据
--当共享进程完毕了全部IO操作后,除非unlink删除它,否则FIFO特别文件将存在文件系统中,而且能够留待下一次使用.
创建了FIFO文件之后,对于不论什么进程,仅仅要知道了其名字而且有适当的訪问权限,就能依照正常文件同样的方法打开它读或者写.
在某个进程读之前,必须有进程表示以写的方式打开这个文件,否则读进程则会一直堵塞.
同理,在写之前也一定要有进程打开了读,否则一样堵塞.
1.创建FIFO文件
int mkfifo(const char *path,mode_t mode);
int mknod(const char *path,mode_t mode,dev_t dev);
mkfifo创建一个FIFO特别文件,用參数path来指定其路径和文件名称.mode參数则用来设置文件的权限.取值和open函数一样.
mknod创建一个给定文件类型的新文件,path同mkfifo.文件类型由mode參数给出,可选的值例如以下:
S_IFIFO 这个就是特别文件
S_IFCHR 字符特别文件(不可移植)
S_IFDIR 文件夹文件(不可移植)
S_IFBLK 块特别文件(不可移植)
S_IFREG 普通文件(不可移植)
除开这些个參数,mode还须要另外附加上文件的訪问方式,用获得方式组合宏定义.mknod似乎是比mkdir之类函数更底层的函数.
而且这个mode还受umask的修正,对于mkfifo则不受其影响.
因为刚打开时可能造成堵塞(读方式打开没有写进程,写操作打开没有读进程),而且打开文件的时候能够用O_NONBLOCK标志.第一次操作这个文件,而且打开方式使用了O_NONBLOCK的时候,open函数会返回-1,而且设置errno为错误值.
2.读写FIFO文件
和管道不同,因为打开FIFO文件能够使用O_NONBLOCK标志.所以要复杂一些.
没有O_NONBLOCK的时候读,假设没有能够读的,那么就会堵塞.有O_NONBLOCK的话,那么就直接返回-1,而且设置errno为EAGAIN了.
正常情况下假设文件写满了再调用write,那么会一直堵塞,知道信息被读走.完毕操作时,返回写入的字节数.当用了O_NONBLOCK打开的时候,那么不能写则会返回-1,表示没有写进不论什么数据.errno也设置为EAGAIN.
原子性和管道一致.对于多个写一个读的时候,最好保证write的进程是不没有使用O_NONBLOCK的.而且每次写的时候,写入的数据小于PIPE_BUF的大小.保证写入数据的完整性.
三.系统V 的IPC
这个主要有三种进程通信方式:消息队列,共享存储,信号量
每个IPC资源有两个唯一的标志与之相连:keyword和标识.
1.keyword
类型是一个key_t的长整型.用来命名要使用的IPC资源,以便能够被多个进程引用.进程通过指定一个keyword调用xxxget()函数可获得一个IPC资源的标识,就像指定文件名称调用open获得文件描写叙述字一样.
其取值能够为:
IPC_PRIVATE:其值一般为0.表示总是创建一个新的IPC资源.能够用过xxxget()函数来获得.而且其它进程不可能通过xxxget来获得一样的keyword.但能够通过其它途径获得其标识来訪问它.另外,对于进程的子进程是共享这个标志的.
非零值: 表示这个队列能够被多个进程使用.
为了方便应用能像文件一样用路径名来表示IPC资源,C标准库中提供了一个函数ftok能够将文件名称转换wei keyword.
key_t ftok(const char *path,int id);
ftok将依据path和id返回一个类型为key_t的keyword,该keyword能够作为msgget,semget,shmget函数的IPC资源參数.
path參数必须是一个已经存在的文件路径名.id仅仅有低8位有效.假设id的低8位为0,那么ftok的行为不确定.
ftok返回的keyword是依据文件的inode号来确定的.假设文件在删除后又又一次创建,那么ftok返回的keyword也会改变,虽然參数一样.
2.标识
每个独立的消息队列,信号量集合和共享存储段都由一个唯一的正整数所标识.假设说keyword类似于文件名称,那么表示就类似于文件描写叙述字.
标识对于不同类型是独立的.也就是消息队列和信号量的都有一个标识,那么这两个类型标识值能够同样.
3.IPC资源描写叙述结构与成员ipc_perm
每一个IPC的属主在创建这个资源之后,这个资源能够被属主自己或root用户调用IPC控制函数xxxctl将这个IPC资源的全部权转让给其它进程.新的进程将成为这个IPC的当前属主.但创建者仍然保持不变.
每一个IPC资源被创建时,内核会在系统内部创建它的标识和一个类型为xxxid_ds的数据结构与之相连,并存储它的用户,用户组以及其它人的读写全县信息于xxxid_ds的成员xxx_perm中.xxxid_ds为IPC资源描写叙述,与消息队列,共享存储,信号量表示相连的IPC资源描写叙述各自是msgid_ds,shmind_ds,semid_ds.
ipc_perm结构体中有一下一些成员
uid_t uid IPC资源当前全部者的用户ID
gid_t gid IPC资源当前全部者的组
cuid_t cuid IPC资源创建者的用户ID
cgid_t cgid IPC资源创建者的组ID
mode_t mode 读写许可权限.
mode和文件的权限类似.包含自己,组和其它人的一些权限.但和文件不同的是运行权限一般为0.由于对于IPC来说没有意义.详细有哪些宏能够百度mode_t这个类型.
因为资源能够在进程结束后仍然存在.所以也能够通过命令来查询和删除资源
ipcs和ipcrm两个shell命令就是用来查询资源和删除资源.
两个命令用-q,-m,-s来指定类型.
四.消息队列
消息队列类似FIFO.也是半双工的,仅仅能单向.可是和FIFO不同,对于消息队列其信息是离散的,一段一段的信息.这些信息能够有不同的结构和优先级.这样通信的方式也更灵活了.实际上消息队列是内核地址空间的内部链表.
对于消息队列而言,内核维护一个msqid_ds数据结构,记录了消息队列的特征和当前状态.虽说是内核空间的东西,可是对于应用而言会经常訪问它.结构中的成员例如以下:
struct ipc_perm msg_perm 操作许可权限数据结构指针
struct msg *msg_first 指向队列中第一条消息的指针
struct msg *msg_last 指向队列中最后一条消息的指针
pid_t msg_lspid 最后一个发送消息的进程ID
pid_t msg_lrpid 最后一个接受消息的进程ID
time_t msg_stime 最后一条消息的发送时间
time_t msg_rtime 最后一条消息的接受时间
time_t msg_ctime 最后的改变时间
unsigned short msg_qnum 队列中当前消息个数
unsigned short msg_qbytes 队列中所同意的最大字节个数
unsigned short msg_cbytes 队列中当前字节数
这些结构体信息能够用msgctl来查看.
消息正文存放在用户分配的消息缓冲区中被发送和接受.消息缓冲区是一个结构,系统除了要求它的第一个成员必须是long int类型且值大于0外,对其它成员不做限定.第一个成员给出消息类型.通常,我们将消息缓冲区十位一个msgbuf类型的结构,并把它看作是全部消息的模版,例如以下所看到的:
struct msgbuf{
long int mtype; 消息类型
char mtext[1]; 消息正文
}
1.创建和获得消息队列
int msgget(key_t key,int flags);
參数key就是队列的keyword;參数flags除开文件权限的宏外,还有下面两种宏
IPC_CREAT: 假设不存在与key相连的IPC资源,创建它:若未指定IPC_CREATE且系统中不存在同样key值的IPC资源,shmget()将失败返回
IPC_EXCL: 若已经存在与key相连的IPC资源,调用失败.
假设是调用来创建一个新的消息队列,那么内核会创建一个msqid_ds结构.msqid_ds结构成员msg_perm包括属主信息和訪问权限.
将key值指定为IPC_PRIVATE,msgget将总是创建一个消息队列.假设在flags中设置IPC_CREAT标志.相同也会创建新队列.单独设置IPC_EXCL没有作用,它与IPC_CREAT一起用来保证不会打开一个已经存在的队列.假设仅仅指定IPC_CREAT,msgget要么返回新创建的消息队列ID,要么返回具有相同key值的消息队列ID.
当flags为0时,能够用来得到与key相关的进程ID.
2.消息队列的查询,设置和删除
int msgctl(int msgid,int cmd,struct msqid_ds *buf);
msgctl对消息队列msqid运行參数cmd要求的控制操作.cmd能够取下面值:
IPC_STAT: 复制消息队列的内核数据机构msqid_ds到buf所指用户区.此命令用于查看消息队列的状态
IPC_SET: 用buf结构中给出的值设置消息队列msqid内核数据结构中成员msg_perm的uid,gid,mode,qbytes的值.此命令用于改变消息队列状态.
IPC_RMID: 删除msqid指定的消息队列
这些命令中,对于STAT来说,必须具有消息队列的读权限.运行IPC_SET和IPC_RMID命令的进程仅仅能是消息队列的创建者,拥有或特权进程.此外,仅仅有特权进程才干够增大消息队列的字节数.
假设msgctl调用成功返回0,否则返回-1并设置errno.
3.发送和接收消息
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long int msgtyp, int msgflg);
两个函数中參数msqid必须为消息队列ID.第二个參数msgp是指向用户消息缓冲区的指针.消息缓冲区应当指定为上面说的msgbuf类型的结构体(我认为既然已经确定按某种形式来分析消息了,为什么还要用void*...).而且消息类型实际上就是优先级.第三个參数就是正文的大小了,而不是整个结构体的大小必须大于0且小于系统限制值MSGMAX..
1)发送消息
msgsnd向消息队列msqid发送一条消息.要发送的消息存放在msgp所指用户消息缓冲区中.
当在下面情况时,msgsnd会堵塞
--消息队列中的总字节数已等于msg_qbytes()
--系统中全部消息队列中的消息总数已达到系统施加的限制值.
从堵塞返回也有下面几种情况:
--消息能够被发送了...
--消息队列已经从系统中删除.此时会设置errno为EIDRM而且返回-1.
--调用进程收到一个信号.调用被中断,设置errno为EINTR并返回-1.
和一般IO一样,能够通过msgflg来设置为不堵塞的状态IPC_NOWAIT.当发生堵塞时,消息不被发送,调用将设置errno为EAGAIN并马上返回.
2)接收消息
这个函数就是msgsnd的反过程.主要说明一下第四个和第五个參数
第四个參数指明要接受的消息类型.
=0 接受队列中的第一个消息.即顺序接受消息
>0 接受类型等于msgtyp的第一个消息.仅仅用于接受某种特定类型的消息.感觉和=0的情况几乎相同吧.
<0 接收类型小于或等于msgtyp绝对值的第一个最低类型的消息.用于接收优先消息.也就是说消息中的第一个long int的值越小优先级越高.
第五个參数msgflg指明所系统的消息不在队列时,以及接受消息大小大于msgsz给定值时採取的动作.它有两个宏:
MSG_NOERROR: 表示消息过大的时候自己主动截断.否则未使用时将设置errno为E2BIG
MSG_NOWAIT: 表示接收消息并不堵塞.
堵塞的状况下错误和msgsnd的形式一样.
五.共享存储段
共享存储段是通过内存来共享信息内容.这个方式是最快的,可是其本身并不提供不论什么同步,所以写代码会麻烦一些.能够用后面的信号量一起使用.
1.创建和获得共享存储段
int shmget(key_t key,size_t size,int shmflg);
key和消息队列一样.用IPC_PRIVATE表示创建一个私有共享存储段,其它情况函数的动作还取决于shmflg中的IPC_CREAT和IPC_EXCL..
类似的shmflg能够设置除IPC_CREAT和IPC_EXCL外,还能够设置权限.
调用成功的话,返回与key相关的存储段ID.同一时候key相连的数据机构shmind_ds结构成员例如以下:
struct ipc_perm shm_perm 操作稀客权限数据结构指针
size_t shm_segsz 共享存储段的大小,单位为字节
pid_t shm_lpid 最后对共享存储进行操作的进程pid
pid_t shm_cpid 创建进程的ID
shmatt_t shm_nattch 当前连接数number of current attaches
time_t shm_atime 最后调用shmat的时间
time_t shm_dtime 最后调用shmdt的时间
time_t shm_ctime 最后调shmctl的改变时间
2.共享存储段的查询,设置和删除
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
shmid即共享段的标志.參数cmd指定操作,与消息队列的情况类似,cmd的取值能够是IPC_STAT,IP_SET和IPC_RMID.相应查询,设置和删除.对于查询要具备读权限.对于SET而言,并不影响已经连接了该共享存储段的进程读写权限.对于RMID来说,调用之后不同意进程再与其连接,共享段仍然存在,直到最后一个连接进程与其分离之后才从系统中删除.这时假设有新进程连接这个段,那么会又一次创建一个段.
调用成功返回0,否则返回-1并设置errno.
3.共享存储段的连接和分离
创建共享段时,我们并不知道其共享段的地址.实际上在创建之后还涉及一个连接和分离的过程.
void *shmat(int shmid,const void *shmaddr,int shmflg);
int shmdt(const void *shmaddr);
前一个函数是连接,后一个是分离.
对于第一个函数来说,shmid是其共享段ID.shmaddr则是用来指定共享段地址,能够是0让系统分配,也能够是一个地址值.通常就填写为0了.假设是地址值,通常要是页面的整数值.
shmflg能够设置两个宏:
SHM_RND: 设置这个标致之后,对于shmaddr为非0的时候,实际的地址会变成shmaddr-(shmaddr%SHMLBA)这个值.SHMLAB是系统定义的共享存储最小对齐边界大小.
SHM_RDNOLY: 存储段是仅仅读的.
对于第二个分离函数来说.分离并不一定会导致共享段被删除.调用了shmctl并设置IPC_RMID之后而且连接数为0才会删除共享段.实际上当进程结束的时候,系统也会自己主动的分离共享段.
当连接之后,进程就能够通过地址,把其当作一个大数组来用了~
六.信号量
信号量就是能够多次计数的一种同步方式.而相互排斥量则是仅仅有0和1.
semid_ds结构成员例如以下:
struct ipc_perm sem_perm; 操作许可权限数据结构指针
struct sem *sem_base; 指向信号量集合的指针
unsigned short int sem_nsems; 集合中信号量的个数
time_t sem_otime; 最后一次操作的时间
time_t sem_ctime; 最后一次改变此结构的时间
sem结构成员
unsigned short semval; 信号量当前值
pid_t sempid; 最后操作该信号量的进程ID
unsigned short semncnt; 等待对该信号量运行P操作的进程数
unsigned short semzcnt; 等待semval=0的近成熟
1)创建和获得信号量标识
int semget(key_t key,int nsems,int semflg);
key与semflg和上面的共享内存一样,.这里主要写一下nsems指明信号量集合中信号量的个数.(注意是集合,而不是单个信号量!!!)而且这个值是有限制的.必须大于0且小于SEMMSL.Linux中默认值为250.当key所指定的信号量集合已经存在时,nsems必须为0,或者大于集合原来的信号量数.否则调用失败.一般取0就能够了.
返回值为信号量的ID,即标识.
2)信号量的查询,设置和删除
int semctl(int semid,int semnum,int cmd,[union semun arg]);
第一个參数指定标识ID,第二个则是信号集合中的第几个信号.cmd则是操作.第四个參数比較特别,它是可选的.结构例如以下
union semun{
int val; 用于SETVAL命令,指明要设置的信号量值
struct semid_ds *buf; 用于IPC_STAT/IPC_SET命令,指向存放信号量集合数据结构的缓冲区
unsigned short *array; 用于GETALL/SETALL命令,存放所获得的或要设置的信号量集合中全部信号的值.
}
cmd命令有下面一些选项:
SETVAL 设置单个信号的值 用arg.val
GETALL 返回信号量集合中全部信号量的值 arg.arry
SETALL 设置信号量集合中全部信号量的值 arg.arry
IPC_STAT 放置与信号量集合相连的semid_ds结构的当前值于arg.buf指定的缓冲区
IPC_SET 用arg.buf指定结构值替代与信号量集合相连的semid_ds结构值
GETVAL 返回单个信号量的值
GETPID 返回最后一个操作该信号量集合的进程ID
GETNCNT 返回semncnt的值
GETZCNT 返回semzcnt的值
IPC_RMID 删除指定的信号量集合
通经常常使用的就SETVAL和IPC_RMID.对于其它的全然能够写个函数封装起来...
调用这个函数要注意两点:
---对于不同命令,semctl要求不同的參数,当中有的參数可能是没用的,应当指定參数值为0.
---联合semun的成员buf和array均为指针,在使用之前一定要指向一个可用空间.
3)信号量操作
int semop(int semid,struct sembuf *sops,size_t nsops);
第一个參数即信号集合标识,第二个參数sembuf结构的成员例如以下:
unsigned short int sem_num; 信号量编号
short int sem_op; 信号量操作
short int sem_flg; 操作标志
op有下面取值
<0 降低一个信号量额值.降低量为其绝对值.当不够减时,则堵塞,直到足够减.
=0 等待信号量变为0.一直堵塞直到为0.
>0 添加一个信号量的值.添加量就是其大小.
sem_flg有下面标志:
IPC_NOWAIT: 表示不堵塞
SEM_UNDO: 当进程退出时,运行信号量解除操作.对于指定了这个宏的信号量,内部有一个semadj变量与之相连,它的作用是在进程死亡时调整信号量的值.也就是假设一个信号量的值降低了x,那么它就加上semadj;反之则降低semadj.设置了这个宏之后,对这个信号量的反操作也要设置SEM_UNDO.这样才干保证最后信号量能恢复到0.
最后第三个參数则是指定第二个參数有多少个struct sembuf元素.取值范围为0~SEMOP.当中SEMOP是一个可配置的系统參数,规定了单个semop调用同意的最大操作个数.
仅仅有当集合中全部信号量操作都能成功时才成功返回,且返回值为0.假设集合中某个信号量操作不能完毕,则全部操作都不会运行.假设操作没有设置IPC_NOWAIT则会一直堵塞.直到下面一种情况发生:
--全部操作完毕.
--进程收到一个信号
--信号量集合被删除.
内核会保证不论什么时刻仅仅有一个进程可以操纵信号量集合.假设多个进程发送操作请求,那么内核会一个个的响应,可是顺序是随机的.
Linux编程---进程通信的更多相关文章
- Linux之进程通信20160720
好久没更新了,今天主要说一下Linux的进程通信,后续Linux方面的更新应该会变缓,因为最近在看Java和安卓方面的知识,后续会根据学习成果不断分享更新Java和安卓的方面的知识~ Linux进程通 ...
- Linux下进程通信的八种方法
Linux下进程通信的八种方法:管道(pipe),命名管道(FIFO),内存映射(mapped memeory),消息队列(message queue),共享内存(shared memory),信号量 ...
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
- Linux下进程通信之管道
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把 ...
- Linux:进程通信之消息队列Message实例
/*send.c*/ /*send.c*/ #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h&g ...
- linux之间进程通信
进程间通信方式: 同主机进程间数据交换机制: pipe(无名管道) / fifo(有名管道)/ message queue(消息队列)和共享内存. 必备基础: f ...
- [置顶] 简单解析linux下进程通信方法
linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的.而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间 ...
- Golang并发编程进程通信channel了解及简单使用
概念及作用 channel是一个数据类型,用于实现同步,用于两个协程之间交换数据.goroutine奉行通过通信来共享内存,而不是共享内存来通信.引用类型channel是CSP模式的具体实现,用于多个 ...
- 进程以及进程通信(IPC)类型
这里用我有限的知识来解释同时参考了一些其他博主的子类,希望能给与一部分入门的朋友一个清晰的理解,有问题之处还请指出 首先简单谈一下什么是进程? 答:进程是装入内存运行的程序段,是许多的系统对象拥有权的 ...
随机推荐
- JMeter学习笔记21-如何添加思考时间
本文来介绍,JMeter如何插入思考时间.前面介绍过一个真实的性能测试场景,是需要加入思考时间,来模拟真实用户行为.本文就来介绍,如何在三个请求之间添加思考时间. 1. 在Test Plan下新建一个 ...
- Java 正则表达式详解---https://www.jb51.net/article/16829.htm
一.正则表达式基础知识 我们先从简单的开始.假设你要搜索一个包含字符“cat”的字符串,搜索用的正则表达式就是“cat”.如果搜索对大小写不敏感,单词“catalog”.“Catherine”.“so ...
- tomcat的管理(manager)报错403
管理tomcat的时候遇到了以下问题: 1.刚开始需要用户名密码,不知道用户名和密码是什么,但是输入什么都不正确. 解决办法: 自己在tomcat-users.xml中按格式添加用户 conf文件夹里 ...
- 计算几何 I. 极角
参考资料 hankcs.com: POJ 1981 Circle and Points 题解 aswmtjdsj: POJ 1981 Circle and Points [定长圆覆盖最多点问题] zx ...
- [BZOJ3611] [Heoi2014]大工程(DP + 虚树)
传送门 $dp[i][0]$表示节点i到子树中的所有点的距离之和 $dp[i][1]$表示节点i到子树中最近距离的点的距离 $dp[i][2]$表示节点i到子树中最远距离的点的距离 建好虚树后dp即可 ...
- 转 Linux文件管理
Linux文件管理 http://www.cnblogs.com/vamei/archive/2012/09/09/2676792.html 作者:Vamei 出处:http://www.cnblog ...
- flask-script插件
首先在启动Flask项目时,我们可以传不同的参数作为运行参数.但是我们只能在入口app.run()传参.这样十分的不方便.Flask-Script 是一个 Flask 扩展,为 Flask 程序添加了 ...
- docker使用 命令
Dockerfile FROM golang:alpine3. AS build-stage WORKDIR /go/src/mypro.exportReport COPY . . RUN go bu ...
- hdu3987,最小割时求最少割边数
题意:求最小割时候割边最少的数量.算法:先求dinic一遍,跑出残网络,再把该网络中满流量(残量为0)的边 残量改为1,其他边残量改为无穷,则再跑一次最大流,所得即为答案.(思,最小割有喝多组,但是要 ...
- HUNAN -11566 Graduation Examination(找规律)
http://acm.hunnu.edu.cn/online/?action=problem&type=show&id=11566&courseid=0 输入n,求出第n个fi ...