Linux 进程间通信
2018-10-24 08:41:17来源:博客园 阅读 ()
1,
进程通信:在用户空间实现通信时不可能的,通过linux内核通信
线程间通信: 可以在用户空间实现,可通过全局变量通信
2,通信方式
管道通信:无名管道,有名管道(文件系统中有名)
信号通信:信号(通知)通信包括:信号的发送,信号的接受和信号的处理
IPC通信:共享内存,消息队列,和信号灯
以上是的单机模式下的进程通信(只有一个linux内核)
Socket通信:存在于一个网络中两个进程间的通信(两个LINUX内核)
3,思路: 每种通信方式都是基于文件IO 的思想
open
read
write
close
__________________________________________________________________________________________________
二:无名管道
内核的管道是由队列实现(入队和出队)
在文件IO中创建文件是由open 函数来实现,但它只能创建普通文件,管道文件需要用pipe函数来创建
int pipe (int fd[2]); 创建管道,微系统调用
参数: 就是得到的 文件描述符。可见有两个文件 描述符:fd[0],fd[1]; 管道有一个读端fd[0] 和一个写端fd[1] ,不能改变
返回值: 成功是0,出错时-1;
注意:
1)管道是创建在内存中的,进程结束,空间释放,管道就不存在了。
2)管道中的东西,读完就删除了
3)如果管道中没有东西可读,就会读阻塞。
4)无名管道不能实现非父子进程(只能是内核中的同一管道)
#include "unistd.h" #include "stdio.h" #include "stdlib.h" int main() { int fd[2]; int ret; char write_buf[] = "hello linux"; char read_buf[128] = {0}; ret = pipe(fd); if(ret <0) { printf("creat process failure\n"); return -1; } printf("creat process success ,fd[0]=%d,fd[1]=%d\n",fd[0],fd[1]); write(fd[1],write_buf,sizeof(write_buf)); read(fd[0],read_buf,128); printf("read:%s\n",read_buf); close(fd[0]); close(fd[1]); return 0; } 结果: creat process success ,fd[0]=3,fd[1]=4 read:hello linux
父子进程:fork();
pid = fork();
if (pid>0) {}; 父进程
if (pid ==0) {}; 子进程
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
getpid();获取当前进程ID
getppid();获取当前进程的父进程ID
三:有名管道
通过管道实现无亲缘关系进程间通信,
1,创建这个文件节点,不可以通过open 函数,open 函数只能创建普通文件,不能创建特殊文件(管道 -mkdifo ,套接字 -socket ,字符设备文件 -mknod ,块设备文件 -mknod , 符号链接文件 -ln -s, 目录文件 mkdir )
2,管道文件只有inode 号,不占磁盘块空间,和套接字、字符设备文件、块设备文件一样。 普通文件和符号链接文件及目录文件,不仅有inode 号,还占磁盘空间。
3,mkfifo 用来创建管道文件的节点,没有在内核中创建管道
只有通过open函数打开这个文件是才会在内核空间创建管道
mkfifo
int mkfifo (const char *filename,mode_t mode); 创建管道文件
参数:管道文件名,权限(仍与umask有关),
返回值:创建成功返回0,失败返回-1
四:信号通信
信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发送信号,用户空间进程不能发送信号。
信号通信的框架:
信号的发送:(发送信号进程) kill raise alarm
信号的处理:(接受信号进程) pause() sleep(睡眠) while(1)
信号的发送:(发送信号进程) signal
1,信号的发送()
kill :
头文件:#include <signal.h>
#inlcude <sys/types.h>
函数: int kill (pid_t pid,int sig);
参数:pid : 1,正数:要接受信号的进程的进程号 2,0:信号被发送到所有和PID进程在同一个进程组的进程 3,-1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig : 信号
返回值:成功 :0; 出错:-1;
此时kill为函数,kill命令为结束进程 ,kill函数可以实现命令功能
raise : 发信号给自己 == kill (getpid(),sig)
- 头文件:#include <signal.h>
#include <sys/types.h>
- 函数: int raise (int sig);
- 参数:sig : 信号
- 返回值:成功 :0; 出错:-1;
alarm : 闹钟信号 定时,只能发送给当前进程
- 头文件:#include "unsitd.h"
- 函数: unsigned int alarm (insigned int seconds);
- 参数: seconds 指定秒数
- 返回值: 成功:如果调用alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟的剩余时间,负责返回0 ; 出错:-1;
2,信号的接收
要想使接收到进程的能接收到信号,进程不能结束;
- sleep;
- pause: 进程状态为 S(睡眠状态), 与sleep 不同的是,sleep 可以是进程睡眠一段时间,而pause 如果不唤醒,它可以一直睡眠;
- while(1);
pause:
- 头文件:#inlclude "unistd.h"
- 函数: int pause (void);
- 返回值: 成功 : 0 ;出错 : -1;
3,信号的处理
(1)进程的默认处理方式(内核为用户进程设置的默认处理方式)
- 忽略
- 终止进程
- 暂停
(2)自己的处理方式
自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用自己的处理方式
signal
头文件: #include "signal.h"
函数: void (*signal (int signum, void (*handler) (int))) (int);
参数:
signum : 指定信号
hangler:
SIG_IGN : 忽略该信号
SIG_DFL : 采用系统默认方式处理信号 (终止)
自定义的信号处理函数指针
返回值: 成功 :设置之前的信号处理方式 ; 出错 : -1 ;
自我感觉就像是中断???
1 #include "sys/types.h" 2 #include "signal.h" 3 #include "stdlib.h" 4 5 void myfun() 6 { 7 int i; 8 for(i=0;i<10;i++) 9 printf("second , i=%d\n",i); 10 return; 11 12 } 13 int main() 14 { 15 int i; 16 i=0; 17 signal(14,myfun); 18 printf("alarm before\n"); 19 alarm(4); 20 printf("alarm after\n"); 21 22 while(i<10) 23 { 24 i++; 25 sleep(1); 26 printf("main,i=%d\n",i); 27 28 } 29 return 0; 30 } 31 ~ 32 33 结果: 34 alarm before 35 alarm after 36 main,i=1 37 main,i=2 38 main,i=3 39 second , i=0 40 second , i=1 41 second , i=2 42 second , i=3 43 second , i=4 44 second , i=5 45 second , i=6 46 second , i=7 47 second , i=8 48 second , i=9 49 main,i=4 50 main,i=5 51 main,i=6 52 main,i=7 53 main,i=8 54 main,i=9 55 main,i=10 56
IPC通信————》IPC对象 {共享内存,消息队列,信号灯};
文件IO | IPC |
open |
Msg_get 打开或创建消息队列 Shm_get 创建共享内存 Sem_get 创建信号灯 |
read write |
msgsnd msgrecv (读 写) shmat shmdt semop |
close |
msgctrl shmctrl semctrl |
五,共享内存
共享内存就是一块缓存,类似于用户空间的数组,或malloc函数分配的空间一样
1,Shm_get 打开或创建共享内存
头文件: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
函数: int shmget (key_t key, int size ,int shmflg);
参数: key : IPC_PRIVATE 或 ftok 的返回值,,,,(在使用返回值时,需要加宏)( shmid = shmget (key,128,IPC_CREAT | 0777); )
size : 共享内存的大小
shmflg: 同open 函数的权限位,也可用8进制表示法
返回值: 成功 共享内存标识符——ID——文件描述符 ; 出错 : -1 ;
查看IPC对象 : ipcs -m(查看内核空间共享内存对象) -q (消息队列) -s (信号灯)
删除IPC对象 : ipcrm -m id
返回值: 共享内存段标识符 IPC 的ID 号
1 #include "signal.h" 2 #include "unistd.h" 3 #include "stdio.h" 4 #include "stdlib.h" 5 #include "sys/types.h" 6 #include "sys/shm.h" 7 8 int main() 9 { 10 int shmid; 11 int pid; 12 13 pid = ftok("./home/xiaozhao/2.c",'a'); 14 shmid = shmget(pid,128,IPC_CREAT | 0777); 15 16 if(shmid<0) 17 { 18 printf("creat share memory failure\n"); 19 return -1; 20 } 21 22 printf("craet share meory sucess\n"); //创建共享内存 23 system("ipcs -m"); 24 return 0; 25 26 } 27 28 29 30 31 结果: 32 craet share meory sucess 33 34 ------------ 共享内存段 -------------- 35 键 shmid 拥有者 权限 字节 连接数 状态 36 0x00000000 1179648 xiaozhao 600 524288 2 目标 37 0x00000000 393217 xiaozhao 600 524288 2 目标 38 0x00000000 425986 xiaozhao 600 524288 2 目标 39 0x00000000 688131 xiaozhao 600 524288 2 目标 40 0x00000000 720900 xiaozhao 600 16777216 2 41 0x00000000 819205 xiaozhao 600 524288 2 目标 42 0x00000000 1081350 xiaozhao 600 524288 2 目标 43 0x00000000 950279 xiaozhao 600 67108864 2 目标 44 0x00000000 1212424 xiaozhao 600 1048576 2 目标 45 0x00000000 1376265 xiaozhao 600 524288 2 目标 46 0x00000000 1736714 root 777 128 0 47 0xffffffff 1769483 root 777 128 0
{
key 的区别
1) 使用宏创建的key ,,,始终为0
2) ftok : 创建key 的值
char ftok (const char *path ,char key)
参数: 第一个: 文件路径和文件名 ; 第二个 : 一个字符;
返回值: 正确返回一个key的值, 错误返回-1;
IPC_PRIVATE 操作时,共享内存的key 值是0;所以使用 ftok 开创建KEY值。 只要KEY 的值是一样的,用户空间的进程通过这个函数打开,则会对内核的同一个IPC对象操作
}
3,shmat 将共享内存映射到用户空间中
为了方便用户空间对共享内存的操作,使用地址映射的方式
void *shmat (int shmit ,const void *shmaddr , int shmflg);
参数: shmat : ID 号
shmaddr: 映射到的地址,NULL为系统自动完成的映射
shmflg: SHM_RDONLY 共享内存只读 ; 默认是0 , 表示共享内存可读写 ;
返回值: 成功 : 映射后的地址 ; 出错 : NULL;
shmdt : 将用户空间进程里的地址映射删除。
int shmdt (const void *shmaddr);
参数: shmaddr 共享内存映射后的地址;(就是映射后的返回值)
返回值: 成功 : 0 ; 失败 : -1 ;
共享内存的特点:
1) 共享内存创建之后,一直存在于内核之中,直到被删除或系统关闭
2)共享内存和管道不一样,读取后,内容仍在其共享内存中。
shmctl : 删除共享内存对象
函数: int shmctl (int shmid , int cmd , struct shmid_ds *buf);
参数: shmid : 要操作的共享内存标识符
cmd : IPC_STAT (获取对象属性) —————— 实现了 ipcs -m
IPC_SET (设置对象属性)
IPC_RMID (删除对象) ——————实现了 ipcrm -m
buf : 指定IPC_STAT/IPC_SET 时用以保存/设置属性
返回值: 成功 :0 ; 出错 : -1;
当删除共享内存时,不需要第三个参数,可以设置为NULL
1 include "signal.h" 2 #include "unistd.h" 3 #include "stdio.h" 4 #include "stdlib.h" 5 #include "sys/types.h" 6 #include "sys/shm.h" 7 8 int main() 9 { 10 int shmid; 11 char *p; 12 13 shmid = shmget(IPC_PRIVATE,128,0777); 14 15 if(shmid<0) 16 { 17 printf("creat share memory failure\n"); 18 return -1; 19 } 20 21 printf("craet share meory sucess\n"); //创建共享内存 22 system("ipcs -m"); 23 24 p = (char *)shmat(shmid,NULL,0); //映射到用户空间 25 if(p == NULL) 26 { 27 printf("shmat function failure\n"); 28 return -2; 29 } 30 printf("shmat function sucess\n"); 31 32 //写内容,从键盘输入 33 fgets(p,128,stdin); 34 35 //读 36 printf("read : p=%s\n",p); 37 return 0; 38 39 } 40 41 42 43 结果: 44 craet share meory sucess 45 46 ------------ 共享内存段 -------------- 47 键 shmid 拥有者 权限 字节 连接数 状态 48 0x00000000 1179648 xiaozhao 600 524288 2 目标 49 0x00000000 393217 xiaozhao 600 524288 2 目标 50 0x00000000 425986 xiaozhao 600 524288 2 目标 51 0x00000000 688131 xiaozhao 600 524288 2 目标 52 0x00000000 720900 xiaozhao 600 16777216 2 53 0x00000000 819205 xiaozhao 600 524288 2 目标 54 0x00000000 1081350 xiaozhao 600 524288 2 目标 55 0x00000000 950279 xiaozhao 600 67108864 2 目标 56 0x00000000 1212424 xiaozhao 600 1048576 2 目标 57 0x00000000 1376265 xiaozhao 600 524288 2 目标 58 0x00000000 1736714 root 777 128 0 59 60 shmat function sucess 61 1231few 62 read : p=1231few
1 #include "signal.h" 2 #include "unistd.h" 3 #include "stdio.h" 4 #include "stdlib.h" 5 #include "sys/types.h" 6 #include "sys/shm.h" 7 #include "string.h" 8 9 void fun ( int signum) 10 { 11 return ; 12 13 } 14 int main() 15 { 16 int shmid; 17 int key,pid; 18 char *p; 19 20 key=ftok("/home/xiaozhao/2.c",'a'); //获取key值 ,在单向通讯中,可以直接使用宏 21 if(key<0) 22 { 23 printf("creat key failure\n"); 24 return -2; 25 } 26 printf("creat key sucess\n"); 27 28 29 shmid = shmget(pid,128,IPC_CREAT | 0777); // 创建共享内存 30 if(shmid<0) 31 { 32 printf("creat share memory failure\n"); 33 return -1; 34 } 35 printf("share meory sucess\n"); 36 37 pid=fork(); 38 if(pid>0) 39 { 40 signal(SIGUSR2,fun); 41 p = (char *)shmat(shmid,NULL,0); //父进程映射 42 if(p==NULL) 43 { 44 printf("parent process:shmat function failuer\n"); 45 return -3; 46 } 47 while(1) 48 { 49 printf("parent process start write share memory:\n"); 50 fgets(p,128,stdin); 51 kill(pid,SIGUSR1); //child process read data 52 pause(); 53 } 54 } 55 56 57 if(pid==0) 58 { 59 signal(SIGUSR1,fun); 60 p = (char *)shmat(shmid,NULL,0); 61 while(1) 62 { 63 pause(); 64 printf("share memory data:%s",p); 65 kill(getppid(),SIGUSR2); 66 } 67 } 68 69 shmdt(p); 70 shmctl(shmid,IPC_RMID,NULL); 71 system("ipcs -m"); 72 73 74 return 0; 75 76 } 77 78 79 结果: 80 creat key sucess 81 share meory sucess 82 parent process start write share memory: 83 123 84 share memory data:123 85 parent process start write share memory: 86 123 87 share memory data:123 88 parent process start write share memory:
fork() 放在 Shmget 之后,可以保证父子进程在同一共享内存中进行。
六,消息队列
管道为顺序队列,而消息队列为链式队列
1,msgget 打开创建消息队列
头文件: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg,h>
函数: int msgget (key_t key, int flag);
参数: key : 和消息队列关联的KEY值
flag : 消息队列的访问权限
返回值: 成功 :消息队列ID ; 出错 : -1;
2,msgctl 删除消息队列
头文件: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg,h>
函数: int msgctl (int msgqid, int cmd,struct msqid_ds *buf);
参数: msqid : 消息队列ID
cmd : IPC_STAT : 读取消息队列的属性,并将其保存在BUF指向的缓存中
IPC_SET : 设置消息队列的属性,这个参数取自BUF
IPC_RMID : 从系统中删除消息队列
buf : 消息队列缓存区
返回值: 成功 0 ; 出错 -1;
当删除消息队列时,不需要第三个参数,可以设置为NULL
3,msgsnd 发送消息
函数: int msgsnd (int msqid, const void *msgp,size_t size,int flag);
参数: msqid : 消息队列ID
msgp : 指向消息队的指针。常用消息结构msgbuf 如下:
struct msgbuf
{
long mtype; //消息类型
char mtext[N]; // 消息正文
}
size : 发送消息正文的字节数
flag : IPC_NOWAIT 消息没有发送完成函数也会立即返回
0 直到消息发送完成才返回
返回值: 成功 0 ; 出错 -1;
4,msgrcv 接收消息
函数: int msgrcv (int msgid, void *msgp , size_t size , long msgtype , int flag);
参数: msqid : 消息队列 ID
msgp : 接收消息的缓冲区
size : 要接收的消息的字节数
msgtype: 0 接收消息队列中的第一个消息
大于0 接收消息队列中第一个类型为 msgtyp 的消息
小于0 接收消息队列中类型值不大于msgtyp 的绝对值且类型又最小的消息
flag : 0 若无消息函数会一直阻塞
IPC_NOWAIT 若没有消息,进程会立即返回ENOMSG
返回值: 成功 :接收到的消息长度 ; 出错 -1;
读就是删除,写就是插入。
1 #include "sys/types.h" 2 #include "sys/msg.h" 3 #include "signal.h" 4 #include "unistd.h" 5 #include "stdio.h" 6 #include "stdlib.h" 7 #include "string.h" 8 struct msgbuf 9 { 10 long type; 11 char buf[124]; 12 }; 13 14 int main() 15 { 16 int msgid; 17 struct msgbuf sendbuf,readbuf; 18 msgid = msgget(IPC_PRIVATE,0777); 19 if(msgid<0) 20 { 21 printf ("creat message queue failure\n"); 22 return -1; 23 } 24 printf("creat message queue sucess , msgid=%d\n",msgid); 25 26 system("ipcs -q"); 27 // init sengbuf 28 sendbuf.type=100; 29 printf("please input message :\n"); 30 fgets(sendbuf.buf,124,stdin); 31 msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.buf),0); 32 33 memset(readbuf.buf,0,124); 34 msgrcv(msgid,(void *)&readbuf,124,100,0); 35 printf("read :%s\n",readbuf.buf); 36 37 // msgctl(msgid,IPC_RMID,NULL); 38 system("ipcs -q"); 39 40 41 42 return 0; 43 44 } 45 46 结果: 47 creat message queue sucess , msgid=98305 48 49 --------- 消息队列 ----------- 50 键 msqid 拥有者 权限 已用字节数 消息 51 0x00000000 65536 root 777 0 0 52 0x00000000 98305 root 777 0 0 53 54 please input message : 55 12321313 56 read :12321313 57 58 59 --------- 消息队列 ----------- 60 键 msqid 拥有者 权限 已用字节数 消息 61 0x00000000 65536 root 777 0 0 62 0x00000000 98305 root 777 0 0
1 write: 2 #include "sys/types.h" 3 #include "sys/msg.h" 4 #include "signal.h" 5 #include "unistd.h" 6 #include "stdio.h" 7 #include "stdlib.h" 8 #include "string.h" 9 struct msgbuf 10 { 11 long type; 12 char buf[124]; 13 }; 14 15 int main() 16 { 17 int msgid; 18 int key; 19 struct msgbuf sendbuf; 20 key=ftok("/home/xiaozhao/b.c",'a'); 21 if(key<0) 22 { printf(" creat key failure\n"); 23 return -2;} 24 printf("creat key sucess\n"); 25 msgid = msgget(key, IPC_CREAT | 0777); 26 if(msgid<0) 27 { 28 printf ("creat message queue failure\n"); 29 return -1; 30 } 31 printf("creat message queue sucess , msgid=%d\n",msgid); 32 33 system("ipcs -q"); 34 // init sengbuf 35 sendbuf.type=100; 36 while(1) 37 { 38 printf("please input message :\n"); 39 fgets(sendbuf.buf,124,stdin); 40 msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.buf),0); 41 42 // memset(readbuf.buf,0,124); 43 // msgrcv(msgid,(void *)&readbuf,124,100,0); 44 // printf("read :%s\n",readbuf.buf); 45 } 46 msgctl(msgid,IPC_RMID,NULL); 47 system("ipcs -q"); 48 return 0; 49 } 50 51 52 53 54 read: 55 #include "sys/types.h" 56 #include "sys/msg.h" 57 #include "signal.h" 58 #include "unistd.h" 59 #include "stdio.h" 60 #include "stdlib.h" 61 #include "string.h" 62 struct msgbuf 63 { 64 long type; 65 char buf[124]; 66 }; 67 68 int main() 69 { 70 int msgid; 71 int key; 72 struct msgbuf readbuf; 73 key=ftok("/home/xiaozhao/b.c",'a'); 74 if(key<0) 75 { printf(" creat key failure\n"); 76 return -2;} 77 printf("creat key sucess\n"); 78 msgid = msgget(key, IPC_CREAT | 0777); 79 if(msgid<0) 80 { 81 printf ("creat message queue failure\n"); 82 return -1; 83 } 84 printf("creat message queue sucess , msgid=%d\n",msgid); 85 86 system("ipcs -q"); 87 // init sengbuf 88 // sendbuf.type=100; 89 while(1) 90 { 91 // printf("please input message :\n"); 92 // fgets(sendbuf.buf,124,stdin); 93 // msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.buf),0); 94 95 memset(readbuf.buf,0,124); 96 msgrcv(msgid,(void *)&readbuf,124,100,0); 97 printf("read :%s\n",readbuf.buf); 98 } 99 msgctl(msgid,IPC_RMID,NULL); 100 system("ipcs -q"); 101 return 0; 102 } 103
消息队列双向通信必须保证收发数据类型不同,用父子进程可以实现
七,信号灯
信号量的集合
1,semget 创建信号灯
头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys.sem.h>
函数: int semget (key_t key ,int nsems ,int semfig);
参数: key : 和信号灯集关联的key 值
nsems: 信号灯集合包含的信号灯数目
semfig : 信号灯集的访问权限
返回值: 成功 : 信号灯集ID 出错 : -1
2, semctl 删除信号灯
头文件: #include <sys/types.h>
#inlcude <sys/ipc.h>
#include <sys/sem.h>
函数: int semctl ( int semid , int semnum , int cmd ,...union semun arg);
参数: semid : 信号灯集ID
semnum : 要修改的信号灯编号
cmd : GETVAL : 获取信号灯的值
SETVAL : 设置信号灯的值
IPC_RMID : 从系统中删除信号灯集合
(删除时) 第四个参数可以NULL,也可以不写。
返回值 ; 成功 :0 ; 出错 -1
设置信号灯时用到第四个参数(联合体)
union semun
{
int val;
// SETVAL : 设置信号灯的值
struct semid_ds *buf;
// IPC_STAT (获取对象属性)
//IPC_SET (设置对象属性)
unsigned short *array ; /* Array for GETALL,SETALL */
struct seminfo *_buf ; /*Buffer for IPC_INFO (Linux-specific) */
};
3,semop 信号灯P操作
头文件 : #include <sys/types.h>
#inlcude <sys/ipc.h>
#include <sys/sem.h>
函数 ; int semop ( int semid ,struct sembuf *opsptr , size_t nops);
参数 : semid : 信号灯集ID
struct sembuf {
short sem_num; //要操作的信号灯的编号
short sem_op ; // 0 : 等待 , 直到信号灯的值变成0
1 : 释放资源,V操作
-1 : 分配资源,P操作
short sem_fig ; // 0(阻塞),IPC_NOWAIT(非阻塞), SEM_UNDO()
}
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:Linux中FTP的一点理解
下一篇:Linux 获取帮助
- Linux系统如何设置开机自动运行脚本? 2020-06-11
- Linux指令和shell脚本 2020-06-11
- 适合开发者的最佳Linux发行版 2020-06-11
- RAID 1 软件实现(Linux 系统) 2020-06-10
- linux各级目录 2020-06-08
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash