20155202张旭 Linux下IPC机制

IPC机制定义

  • 在linux下的多个进程间的通信机制叫做IPC(Inter-Process Communication),它是多个进程之间相互沟通的一种方法。在linux下有多种进程间通信的方法:半双工管道、命名管道、消息队列、信号、信号量、共享内存、内存映射文件,套接字等等。使用这些机制可以为linux下的网络服务器开发提供灵活而又坚固的框架。

共享内存

  • 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间(这里的地址空间具体是哪个地方?)中。其他进程可以将同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是malloc分配的一样。如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到。

  • 共享内存是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换。共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅映射到各进程的地址不同而已,因此不需要进行复制,可以直接使用此段空间。

  • 注意:共享内存本身并没有同步机制,需要程序员自己控制。

共享内存的实例
  1. int shmget(key_t key,size_t size,int shmflg); //shmget函数用来创建一个新的共享内存段, 或者访问一个现有的共享内存段(不同进程只要key值相同即可访问同一共享内存段)。第一个参数key是ftok生成的键值,第二个参数size为共享内存的大小,第三个参数sem_flags是打开共享内存的方式。
  2. eg.int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);//第三个参数参考消息队列int msgget(key_t key,int msgflag);
  3. void *shmat(int shm_id,const void *shm_addr,int shmflg); //shmat函数通过shm_id将共享内存连接到进程的地址空间中。第二个参数可以由用户指定共享内存映射到进程空间的地址,shm_addr如果为0,则由内核试着查找一个未映射的区域。返回值为共享内存映射的地址。
  4. eg.char *shms = (char *)shmat(shmid, 0, 0);//shmid由shmget获得
  5. int shmdt(const void *shm_addr); //shmdt函数将共享内存从当前进程中分离。 参数为共享内存映射的地址。
  6. eg.shmdt(shms);
  7. int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//shmctl函数是控制函数,使用方法和消息队列msgctl()函数调用完全类似。参数一shm_id是共享内存的句柄,cmd是向共享内存发送的命令,最后一个参数buf是向共享内存发送命令的参数。

代码练习:

  1. #include <stdio.h>
  2. #include <sys/ipc.h>
  3. #include <sys/shm.h>
  4. #include <sys/types.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. typedef struct{
  8. char name[8];
  9. int age;
  10. } people;
  11. int main(int argc, char** argv)
  12. {
  13. int shm_id,i;
  14. key_t key;
  15. char temp[8];
  16. people *p_map;
  17. char pathname[30] ;
  18. strcpy(pathname,"/tmp") ;
  19. key = ftok(pathname,0x03);
  20. if(key==-1)
  21. {
  22. perror("ftok error");
  23. return -1;
  24. }
  25. printf("key=%d\n",key) ;
  26. shm_id=shmget(key,4096,IPC_CREAT|IPC_EXCL|0600);
  27. if(shm_id==-1)
  28. {
  29. perror("shmget error");
  30. return -1;
  31. }
  32. printf("shm_id=%d\n", shm_id) ;
  33. p_map=(people*)shmat(shm_id,NULL,0);
  34. memset(temp, 0x00, sizeof(temp)) ;
  35. strcpy(temp,"test") ;
  36. temp[4]='0';
  37. for(i = 0;i<3;i++)
  38. {
  39. temp[4]+=1;
  40. strncpy((p_map+i)->name,temp,5);
  41. (p_map+i)->age=0+i;
  42. }
  43. shmdt(p_map) ;
  44. return 0 ;
  45. }
  46. #include <stdio.h>
  47. #include <string.h>
  48. #include <sys/ipc.h>
  49. #include <sys/shm.h>
  50. #include <sys/types.h>
  51. #include <unistd.h>
  52. typedef struct{
  53. char name[8];
  54. int age;
  55. } people;
  56. int main(int argc, char** argv)
  57. {
  58. int shm_id,i;
  59. key_t key;
  60. people *p_map;
  61. char pathname[30] ;
  62. strcpy(pathname,"/tmp") ;
  63. key = ftok(pathname,0x03);
  64. if(key == -1)
  65. {
  66. perror("ftok error");
  67. return -1;
  68. }
  69. printf("key=%d\n", key) ;
  70. shm_id = shmget(key,0, 0);
  71. if(shm_id == -1)
  72. {
  73. perror("shmget error");
  74. return -1;
  75. }
  76. printf("shm_id=%d\n", shm_id) ;
  77. p_map = (people*)shmat(shm_id,NULL,0);
  78. for(i = 0;i<3;i++)
  79. {
  80. printf( "name:%s\n",(*(p_map+i)).name );
  81. printf( "age %d\n",(*(p_map+i)).age );
  82. }
  83. if(shmdt(p_map) == -1)
  84. {
  85. perror("detach error");
  86. return -1;
  87. }
  88. return 0 ;
  89. }

管道 (PIPE)

  • 管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。

    管道的特点:

  1. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  2. 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。比如fork或exec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程。当父进程与使用fork创建的子进程直接通信时,发送数据的进程关闭读端,接受数据的进程关闭写端。
  3. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
  4. 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

代码练习:

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<string.h>
  5. #include<wait.h>
  6. #include<sys/types.h>
  7. int main(int argc ,char *argv[])
  8. {
  9. int pipefd[2],result;
  10. char buf[1024];
  11. int flag=0;
  12. pid_t pid;
  13. result= pipe(pipefd);//创建一个管道
  14. if(result==-1){
  15. perror("pipe error:");
  16. exit(EXIT_FAILURE);
  17. }
  18. pid=fork();//创建一个子进程
  19. if(pid==-1)
  20. {
  21. perror("fork error:");
  22. exit(EXIT_FAILURE);
  23. }
  24. else if(pid==0){
  25. if((close(pipefd[1]))==-1)//close write only read
  26. {
  27. perror("close write error:");
  28. exit(EXIT_FAILURE);
  29. }
  30. while(1){ //循环读取数据
  31. read(pipefd[0],buf,1024);//最多读取1024个字节
  32. printf("read from pipe : %s\n",buf);
  33. if(strcmp(buf,"quit")==0){// if 读取到的字符串是exit 这是
  34. //父进程会接受到一个终止进程的信号,父进程会回收子进程的资 // 源等
  35. exit(EXIT_SUCCESS);
  36. }
  37. }
  38. }else{
  39. //close read only write
  40. if((close(pipefd[0]))==-1){
  41. perror("close read error:");
  42. exit(EXIT_FAILURE);
  43. }
  44. while(1)//循环写入内容
  45. {
  46. waitpid(pid,NULL,WNOHANG);//等待子进程退出
  47. if(flag==1)
  48. exit(0);
  49. scanf("%s",buf);
  50. write(pipefd[1],buf,strlen(buf)+1);//具体写多少个字节
  51. if(strcmp(buf,"quit")==0){
  52. flag=1;
  53. sleep(1);//让子进程完全退出。
  54. }
  55. }
  56. }
  57. return 1;
  58. }
管道实例:
  1. #include <unistd.h>
  2. int pipe(int file_descriptor[2]);//建立管道,该函数在数组上填上两个新的文件描述符后返回0,失败返回-1。
  3. eg.int fd[2]
  4. int result = pipe(fd);

命名管道(FIFO)

  • 命名管道是一种特殊类型的文件,它在系统中以文件形式存在。这样克服了管道的弊端,他可以允许没有亲缘关系的进程间通信。具体操作方法只要创建了一个命名管道然后就可以使用open、read、write等系统调用来操作。创建可以手工创建或者程序中创建。
命名管道实例:
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <fcntl.h>
  4. #include <limits.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. int main()
  10. {
  11. const char *fifo_name = "/tmp/my_fifo";
  12. int pipe_fd = -1;
  13. int data_fd = -1;
  14. int res = 0;
  15. const int open_mode = O_WRONLY;
  16. int bytes_sent = 0;
  17. char buffer[PIPE_BUF + 1];
  18. if(access(fifo_name, F_OK) == -1)
  19. {
  20. //管道文件不存在
  21. //创建命名管道
  22. res = mkfifo(fifo_name, 0777);
  23. if(res != 0)
  24. {
  25. fprintf(stderr, "Could not create fifo %s\n", fifo_name);
  26. exit(EXIT_FAILURE);
  27. }
  28. }
  29. printf("Process %d opening FIFO O_WRONLY\n", getpid());
  30. //以只写阻塞方式打开FIFO文件,以只读方式打开数据文件
  31. pipe_fd = open(fifo_name, open_mode);
  32. data_fd = open("Data.txt", O_RDONLY);
  33. printf("Process %d result %d\n", getpid(), pipe_fd);
  34. if(pipe_fd != -1)
  35. {
  36. int bytes_read = 0;
  37. //向数据文件读取数据
  38. bytes_read = read(data_fd, buffer, PIPE_BUF);
  39. buffer[bytes_read] = '\0';
  40. while(bytes_read > 0)
  41. {
  42. //向FIFO文件写数据
  43. res = write(pipe_fd, buffer, bytes_read);
  44. if(res == -1)
  45. {
  46. fprintf(stderr, "Write error on pipe\n");
  47. exit(EXIT_FAILURE);
  48. }
  49. //累加写的字节数,并继续读取数据
  50. bytes_sent += res;
  51. bytes_read = read(data_fd, buffer, PIPE_BUF);
  52. buffer[bytes_read] = '\0';
  53. }
  54. close(pipe_fd);
  55. close(data_fd);
  56. }
  57. else
  58. exit(EXIT_FAILURE);
  59. printf("Process %d finished\n", getpid());
  60. exit(EXIT_SUCCESS);
  61. }
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. int mkfifo(const char *filename,mode_t mode); //建立一个名字为filename的命名管道,参数mode为该文件的权限(mode%~umask),若成功则返回0,否则返回-1,错误原因存于errno中。
  4. eg.mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
  1. int mknod(const char *path, mode_t mode, dev_t dev); //第一个参数表示你要创建的文件的名称,第二个参数表示文件类型,第三个参数表示该文件对应的设备文件的设备号。只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。
  2. eg.mknod(FIFO_FILE,S_IFIFO|0666,0);

管道和命名管道的区别:

  • 对于命名管道FIFO来说,IO操作和普通管道IO操作基本一样,但是两者有一个主要的区别,在命名管道中,管道可以是事先已经创建好的,比如我们在命令行下执行
  1. mkfifo myfifo
  1. #include <unistd.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <limits.h>
  8. #include <string.h>
  9. int main()
  10. {
  11. const char *fifo_name = "/tmp/my_fifo";
  12. int pipe_fd = -1;
  13. int data_fd = -1;
  14. int res = 0;
  15. int open_mode = O_RDONLY;
  16. char buffer[PIPE_BUF + 1];
  17. int bytes_read = 0;
  18. int bytes_write = 0;
  19. //清空缓冲数组
  20. memset(buffer, '\0', sizeof(buffer));
  21. printf("Process %d opening FIFO O_RDONLY\n", getpid());
  22. //以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名
  23. pipe_fd = open(fifo_name, open_mode);
  24. //以只写方式创建保存数据的文件
  25. data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);
  26. printf("Process %d result %d\n",getpid(), pipe_fd);
  27. if(pipe_fd != -1)
  28. {
  29. do
  30. {
  31. //读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中
  32. res = read(pipe_fd, buffer, PIPE_BUF);
  33. bytes_write = write(data_fd, buffer, res);
  34. bytes_read += res;
  35. }while(res > 0);
  36. close(pipe_fd);
  37. close(data_fd);
  38. }
  39. else
  40. exit(EXIT_FAILURE);
  41. printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
  42. exit(EXIT_SUCCESS);
  43. }

就是创建一个命名通道,我们必须用open函数来显示地建立连接到管道的通道,而在管道中,管道已经在主进程里创建好了,然后在fork时直接复制相关数据或者是用exec创建的新进程时把管道的文件描述符当参数传递进去。

  • 一般来说FIFO和PIPE一样总是处于阻塞状态。也就是说如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并向管道写入数据。这个阻塞动作反过来也是成立的。如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK标志,以关闭默认的阻塞操作。

信号 (signal)

  • 信号机制是unix系统中最为古老的进程之间的通信机制,用于一个或几个进程之间传递异步信号。信号可以有各种异步事件产生,比如键盘中断等。shell也可以使用信号将作业控制命令传递给它的子进程。
信号实例

int kill(pid_t pid,int sig); //kill函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig。

int raise(int sig);//向当前进程中自举一个信号sig, 即向当前进程发送信号。

include <unistd.h>

unsigned int alarm(unsigned int seconds); //alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。使用alarm函数的时候要注意alarm函数的覆盖性,即在一个进程中采用一次alarm函数则该进程之前的alarm函数将失效。

int pause(void); //使调用进程(或线程)睡眠状态,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。

##### 代码:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. int main()
  6. {
  7. char buffer[100];
  8. struct sigaction act;
  9. if(sigaction(SIGINT,&act, NULL) == -1)
  10. {
  11. printf("sigaction error exit now\n");
  12. exit(0);
  13. }
  14. while(1)
  15. {
  16. fgets(buffer,sizeof(buffer),stdin);
  17. printf("%s\n",buffer);
  18. }
  19. return 0;
  20. }

消息队列

  • 消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个独立的链表。

    消息队列克服了信号承载信息量少,管道只能承载无格式字符流。
消息队列的本质
  • Linux的消息队列(queue)实质上是一个链表,它有消息队列标识符(queue ID)。 msgget创建一个新队列或打开一个存在的队列;msgsnd向队列末端添加一条新消息;msgrcv从队列中取消息, 取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息。

消息队列与命名管道的比较

  • 消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。
  • 与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
  • 消息队列头文件:
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <sys/msg.h>
  • 1、消息缓冲区结构:
  1. struct msgbuf{
  2. long mtype;
  3. char mtext[1];//柔性数组
  4. }
  • 在结构中有两个成员,mtype为消息类型,用户可以给某个消息设定一个类型,可以在消息队列中正确地发送和接受自己的消息。mtext为消息数据,采用柔性数组,用户可以重新定义msgbuf结构。例如:
  1. struct msgbuf{
  2. long mtype;
  3. char mtext[1];//柔性数组
  4. }
  • 当然用户不可随意定义msgbuf结构,因为在linux中消息的大小是有限制的,在linux/msg.h中定义如下:

define MSGMAX 8192

消息总的大小不能超过8192个字节,包括mtype成员(4个字节)。

  • 2、ipc_perm内核数据结构:结构体ipc_perm保存着消息队列的一些重要的信息,比如说消息队列关联的键值,消息队列的用户id组id等。它定义在头文件linux/ipc.h中。
  1. struct ipc_perm{
  2. key_t key;
  3. uid_t uid;
  4. gid_t gid;
  5. .......
  6. };
代码:
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<sys/msg.h>
  4. #include<string.h>
  5. #include<unistd.h>
  6. #define MAX_TEXT 1024
  7. #define MSG_SIZE 512
  8. struct msg_st{
  9. long int msg_type; //消息类型
  10. char text[MAX_TEXT];//消息内容
  11. };
  12. int main()
  13. {
  14. struct msg_st data;
  15. char buf[MSG_SIZE];
  16. int msgid=msgget((key_t)2456,0666|IPC_CREAT);
  17. if(msgid==-1){
  18. perror("msgget");
  19. exit(1);
  20. }
  21. while(1){
  22. printf("接收:");
  23. if(msgrcv(msgid,(void*)&data,MAX_TEXT,1,0)==-1){
  24. perror("msgrcv");
  25. exit(2);
  26. }
  27. printf("%s\n",data.text);
  28. }
  29. return 0;
  30. }
  1. #include<stdio.h>
  2. #include<sys/ipc.h>
  3. #include<stdlib.h>
  4. #include<sys/types.h>
  5. #include<sys/msg.h>
  6. #include<unistd.h>
  7. #include<string.h>
  8. #define MAX_TEXT 512
  9. #define MSG_SIZE 512
  10. struct msg_st
  11. {
  12. long int msg_type; //消息类型
  13. char text[MAX_TEXT];//消息内容
  14. };
  15. int main()
  16. {
  17. struct msg_st data;
  18. char buf[MSG_SIZE];
  19. //创建消息队列
  20. int msgid=msgget((key_t)2456,0666|IPC_CREAT);
  21. if(msgid==-1)
  22. {
  23. perror("msgget");
  24. exit(1);
  25. }
  26. printf("消息队列创建成功\n");
  27. //发送消息
  28. while(1)
  29. {
  30. //从键盘输入发送的消息
  31. printf("发送:");
  32. fgets(buf,MSG_SIZE,stdin);
  33. data.msg_type=1;
  34. strcpy(data.text,buf);
  35. //将消息发送到消息队列
  36. if(msgsnd(msgid,(void*)&data,MAX_TEXT,0)==-1){
  37. perror("msgsnd");
  38. exit(1);
  39. }
  40. }
  41. return 0;
  42. }

消息队列、信号量以及共享内存的相似之处:

  • 它们被统称为XSI IPC,它们在内核中有相似的IPC结构(消息队列的msgid_ds,信号量的semid_ds,共享内存的shmid_ds),而且都用一个非负整数的标识符加以引用(消息队列的msg_id,信号量的sem_id,共享内存的shm_id,分别通过msgget、semget以及shmget获得),标志符是IPC对象的内部名,每个IPC对象都有一个键(key_t key)相关联,将这个键作为该对象的外部名。

  • 参考博客:linux基础——linux进程间通信(IPC)机制总结

20155202 张旭 课下作业: Linux下IPC机制的更多相关文章

  1. 请问下./在Linux下是什么意思

    请问下./在Linux下是什么意思 http://zhidao.baidu.com/link?url=1f-80KN7cdi-7XECpwXLUn6Ps4reMBL2zB6eiDk7JliwDgW6k ...

  2. Windows10下配置Linux下C语言开发环境

    今天为大家介绍如在Windows10下配置Linux下C语言开发环境,首先安装linux子系统:启用开发者模式 1.打开设置 2.点击更新和安全3.点击开发者选项 4.启用开发人员模式 5.更改系统功 ...

  3. linux各种IPC机制(进程通信)

    linux各种IPC机制 (2011-07-08 16:58:35)      原文地址:linux各种IPC机制(转)作者:jianpengliu 原帖发表在IBM的developerworks网站 ...

  4. [转帖]linux各种IPC机制

    linux各种IPC机制 docker中的资源隔离,一种就是IPC的隔离.IPC是进程间通信. 下面的文章转载自https://blog.csdn.net/yyq_9623/article/detai ...

  5. windows下与linux下安装redis及redis扩展

    1.        Redis的介绍 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起 ...

  6. Windows下与Linux下编写socket程序的区别 《转载》

     原文网址:http://blog.chinaunix.net/uid-2270658-id-308160.html [[Windows]] [Windows: 头文件的区别] #include< ...

  7. python安装MySQLdb:在windows下或linux下(以及eclipse中pydev使用msqldb的配置方法)

    写的非常好,可以解决问题: windows下:http://blog.csdn.net/wklken/article/details/7253245 linux下:http://blog.csdn.n ...

  8. windows下类似Linux下的grep命令

    今天要查看windws下代理服务器有哪些IP连接过来,但使用 netstat -na 后出现很多连接会话,不方便查看. 想到Linux下的grep非常方便,于是网络上搜寻,还是有类似的命令findst ...

  9. linux各种IPC机制

    docker中的资源隔离,一种就是IPC的隔离.IPC是进程间通信. 下面的文章转载自https://blog.csdn.net/yyq_9623/article/details/78794775 原 ...

随机推荐

  1. responsebody和requestbody的使用

    Controller的方法上加了一个@ResponseBody,那么他的作用是什么呢?/** * 新增或修改一条对象. */ @RequestMapping("/save_field&quo ...

  2. viedo formats vs file formats

    web的视频世界,有两个概念非常容易搞混淆,即:视频文件的格式,比如.mp4,.flv,.ogv等等,以及视频本身的格式,就是指的codec算法名称,比如h.264,mpeg-4等. http://w ...

  3. 深入理解SVG坐标体系和transformations- viewport, viewBox,preserveAspectRatio

    本文翻译自blog: https://www.sarasoueidan.com/blog/svg-coordinate-systems/ SVG元素不像其他HTML元素一样受css盒子模型所制约.这个 ...

  4. Oracle EBS 查询物料报错

  5. [CENTOS7] 修改机器名:hostnamectl set-hostname host.domain

    # hostnamectl set-hostname host.domain

  6. python已写内容中可能的报错及解决办法

    理论上我发的每个短文,直接复制放到py里面,python xx.py是可以执行的,不过因为版本,编码什么的问题会有报错,详见这里 报错: SyntaxError: Non-ASCII characte ...

  7. [翻译] INSSearchBar

    INSSearchBar 效果: An animating search bar. 一个带动画效果的search bar. Originally developed for ShopNow v2. ( ...

  8. wxpython 编程触发菜单或按钮事件

    最近逐步熟悉wxpython,编写了几个小小功能的GUI程序,GUI中免不了会有在代码中触发控件事件的业务需求.在其他Gui界面的语言中有postevent.triggerevent 调用事件名称的函 ...

  9. MacOS(苹果电脑&苹果系统)连接京瓷300i 打印机

    前往京瓷官网下载打印机驱动: http://www.kyoceradocumentsolutions.com.cn/support/mfp/download/taskalfa300i.html 驱动安 ...

  10. mysql数据库配置文件

    一.数据库配置文件 数据库配置文件是很一个很强大的功能,这是数据库管理员经常需要关注的配置文件. my.ini  #这是在windows下的配置文件名称. my.conf  #这是在linux下的配置 ...