从获取服务的服务的角度讲,一个进程可以直接调用函数,也可以通过和另个一个进程互动
获取相关的服务。从进程间通信的角度讲,socket是一种不同物理机器上进程间通信的方式。
本机间的进程间通信的方式有匿名管道,命名管道(mkfifo), Unix domain socket, 通过
相同的文件通信,共享内存。不同物理机器之间的进程见通信一般用socket。
匿名管道
匿名管道的一般用法是,在父进程中创建管道,fork出子进程,这样子进程也继承了父进程
中的管道的读文件描述符和写文件描述符,如果是父进程往子进程写数据,那么父进程需要
需要关闭读读描述符,子进程需要关闭写描述符,反之亦然,通信的时候父进程向写文件
描述符写入信息,子进程读读文件描述符读出信息。
命名管道
命名管道的方式,需要首先使用mkfifo创建管道文件,然后写进程向管道里写入数据,读
进程从管道里读出数据。命名管道可以使用在独立的进程之间,而匿名管道必须在父子进程
之间使用。(如何查看系统里的管道?)
文件通信
文件通信需要使用文件锁保证一致性
共享内存
使用ipcs -m可以查看系统里现在的共享内存,共享内存是用户态的一块内存,两个进程可以
通过一块共享内存交换信息。使用共享内存会有同步问题,需要对共享内存加锁,做到各个
进程对共享内存访问的互斥,这个可以用信号量机制,信号量是一个系统机制。
产生数据的进程:
1 | seg_id = shmget(TIME_MEM_KEY, SEG_SIZE, IPC_CREAT | 0777); |
消耗数据的进程:
1 | seg_id = shmget(TIME_MEM_KEY, SEG_SIZE, 0777); |
socket
关于socket通信,这几章介绍了基于TCP的socket,数据报socket(基于UDP), 和Unix domain
socket。可以使用select和poll响应多个阻塞的文件。
流式socket
client端连接建立的流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32struct sockaddr_in servadd; /* the number to call */
struct hostent *hp; /* used to get number */
int sock_id, sock_fd; /* the socket and fd */
/*
* AF_INET表示Internet域,SOCK_STREAM表示流式socket
*/
sock_id = socket(AF_INET, SOCK_STREAM, 0); /* get a line */
if (sock_id == -1)
oops( "socket" );
/* servadd用来描述server的地址, 地址由IP和port组成 */
bzero(&servadd, sizeof(servadd)); /* zero the address */
/*
* 这个函数返回host对应的IP地址,入参是host name或者域名。测试返现,
* 如果是host name,得到的是/etc/hosts下对应的IP; 如果是域名,会得到对应
× 的IP。
*/
hp = gethostbyname(av[1]); /* lookup host's ip # */
if (hp == NULL)
oops(av[1]); /* or die */
bcopy(hp->h_addr, (struct sockaddr *)&servadd.sin_addr, hp->h_length);
servadd.sin_port = htons(atoi(av[2])); /* fill in port number */
servadd.sin_family = AF_INET ; /* fill in socket type */
/* now dial */
if (connect(sock_id,(struct sockaddr *)&servadd, sizeof(servadd)) != 0)
oops( "connect" );server端(省略了一些错误处理):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39struct sockaddr_in saddr; /* build our address here */
struct hostent *hp; /* this is part of our */
char hostname[HOSTLEN]; /* address */
int sock_id,sock_fd; /* line id, file desc */
FILE *sock_fp; /* use socket as stream */
sock_id = socket(PF_INET, SOCK_STREAM, 0); /* get a socket */
bzero((void *)&saddr, sizeof(saddr)); /* clear out struct */
gethostname(hostname, HOSTLEN); /* where am I ? */
hp = gethostbyname(hostname); /* get info about host */
/* fill in host part */
bcopy((void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
saddr.sin_port = htons(PORTNUM); /* fill in socket port */
saddr.sin_family = AF_INET ; /* fill in addr family */
if (bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
oops("bind");
if (listen(sock_id, 1) != 0)
oops("listen");
while (1) {
sock_fd = accept(sock_id, NULL, NULL); /* wait for call */
if (sock_fd == -1)
oops("accept"); /* error getting calls */
/* fdopen把一个文件fd和一个文件流建立联系 */
sock_fp = fdopen(sock_fd,"w"); /* we'll write to the */
if (sock_fp == NULL) /* socket as a stream */
oops("fdopen"); /* unless we can't */
thetime = time(NULL); /* get time */
/* and convert to strng */
fprintf(sock_fp, "The time here is ..");
fprintf(sock_fp, "%s", ctime(&thetime));
fclose(sock_fp); /* release connection */
}数据报socket
数据报socket不需要提前建立链路,所以流式socket里的connect,listen,accept都
没有。数据报socket用sendto和recvfrom来发送和接收数据, recvfrom可以得到发送
者的地址,这样就可以用sendto给发送者发送信息。(数据报可以直接对socket fd做read操作么?)
Unix domain socket
和上面的使用IP加端口号的地址表示方式不同,Uninx domain socket用主机上的一个
文件表示地址。client:
1
2
3
4
5
6
7
8
9
10
11int sock;
struct sockaddr_un addr;
sock = socket(PF_UNIX, SOCK_DGRAM, 0);
addr.sun_family = AF_UNIX;
/* sockname是一个文件名字 */
strcpy(addr.sun_path, sockname);
addrlen = strlen(sockname) + sizeof(addr.sun_family);
sendto(sock, msg, strlen(msg), 0, (struct sockaddr *)&addr, addrlen);server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20int sock; /* read messages here */
struct sockaddr_un addr; /* this is its address */
/* build an address */
addr.sun_family = AF_UNIX; /* note AF_UNIX */
strcpy(addr.sun_path, sockname); /* filename is address */
addrlen = strlen(sockname) + sizeof(addr.sun_family);
sock = socket(PF_UNIX, SOCK_DGRAM, 0); /* note PF_UNIX */
/* bind the address */
bind(sock, (struct sockaddr *) &addr, addrlen);
while (1) {
/* 直接read socket fd */
l = read(sock, msg, MSGLEN); /* read works for DGRAM */
msg[l] = '\0'; /* make it a string */
/* do your job... */
}select poll