mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-02-25 00:50:15 +08:00
Merge remote-tracking branch 'LCTT/master'
This commit is contained in:
commit
ecb7770f2e
@ -1,18 +1,20 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-10895-1.html)
|
||||
[#]: subject: (Check your spelling at the command line with Ispell)
|
||||
[#]: via: (https://opensource.com/article/19/5/spelling-command-line-ispell)
|
||||
[#]: author: (Scott Nesbitt https://opensource.com/users/scottnesbitt)
|
||||
|
||||
使用 Ispell 在命令行中检查拼写
|
||||
======
|
||||
Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。
|
||||
![Command line prompt][1]
|
||||
|
||||
好的拼写是一种技巧。它是一项需要时间学习和掌握的技能。也就是说,有些人从来没有完全掌握这种技能,我知道有两三个出色的作家无法完全掌握拼写。
|
||||
> Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。
|
||||
|
||||

|
||||
|
||||
好的拼写是一种技巧。它是一项需要时间学习和掌握的技能。也就是说,有些人从来没有完全掌握这种技能,我知道有两三个出色的作家就无法完全掌握拼写。
|
||||
|
||||
即使你拼写得很好,偶尔也会输入错字。特别是在最后期限前如果你快速敲击键盘,那就更是如此。无论你的拼写的是什么,通过拼写检查器检查你所写的内容总是一个好主意。
|
||||
|
||||
@ -20,9 +22,9 @@ Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。
|
||||
|
||||
### 入门
|
||||
|
||||
自 1971 年以来,Ispell 以各种形式出现过。不要被它的年龄欺骗。Ispell 仍然是一个可以在 21 世纪高效使用的应用。
|
||||
自 1971 年以来,Ispell 就以各种形式出现过。不要被它的年龄欺骗。Ispell 仍然是一个可以在 21 世纪高效使用的应用。
|
||||
|
||||
在开始之前,请打开终端窗口并输入**which ispell** 来检查计算机上是否安装了 Ispell。如果未安装,请打开发行版的软件包管理器并从那里安装 Ispell。
|
||||
在开始之前,请打开终端窗口并输入 `which ispell` 来检查计算机上是否安装了 Ispell。如果未安装,请打开发行版的软件包管理器并从那里安装 Ispell。
|
||||
|
||||
不要忘记为你使用的语言安装词典。我唯一使用的语言是英语,所以我只需下载美国和英国英语字典。你可以不局限于我的(也是唯一的)母语。Ispell 有[超过 50 种语言的词典][5]。
|
||||
|
||||
@ -32,25 +34,25 @@ Ispell 可以帮助你在纯文本中消除超过 50 种语言的拼写错误。
|
||||
|
||||
如果你还没有猜到,Ispell 只能用在文本文件。这包括用 HTML、LaTeX 和 [nroff 或 troff][7] 标记的文档。之后会有更多相关内容。
|
||||
|
||||
要开始使用,请打开终端窗口并进入包含要运行拼写检查的文件的目录。输入 **ispell** 后跟文件名,然后按回车键。
|
||||
要开始使用,请打开终端窗口并进入包含要运行拼写检查的文件的目录。输入 `ispell` 后跟文件名,然后按回车键。
|
||||
|
||||
![Checking spelling with Ispell][8]
|
||||
|
||||
Ispell 高亮了它无法识别的第一个词。如果单词拼写错误,Ispell 通常会提供一个或多个备选方案。按下 **R**,然后按下正确选择旁边的数字。在上面的截图中,我按了 **R** 和 **0** 来修复错误。
|
||||
Ispell 高亮了它无法识别的第一个词。如果单词拼写错误,Ispell 通常会提供一个或多个备选方案。按下 `R`,然后按下正确选择旁边的数字。在上面的截图中,我按了 `R` 和 `0` 来修复错误。
|
||||
|
||||
另一方面,如果单词拼写正确,请按下 **A** 然后移动到下一个拼写错误的单词。
|
||||
另一方面,如果单词拼写正确,请按下 `A` 然后移动到下一个拼写错误的单词。
|
||||
|
||||
继续这样做直到到达文件的末尾。Ispell 会保存你的更改,创建你刚检查的文件的备份(扩展名为 _.bak_),然后关闭。
|
||||
继续这样做直到到达文件的末尾。Ispell 会保存你的更改,创建你刚检查的文件的备份(扩展名为 `.bak`),然后关闭。
|
||||
|
||||
### 其他几个选项
|
||||
|
||||
此示例说明了 Ispell 的基本用法。这个程序有[很多选项][9],有些你_可能_会用到,而另一些你_可能永远_不会使用。让我们快速看下我经常使用的一些。
|
||||
此示例说明了 Ispell 的基本用法。这个程序有[很多选项][9],有些你*可能*会用到,而另一些你*可能永远*不会使用。让我们快速看下我经常使用的一些。
|
||||
|
||||
之前我提到过 Ispell 可以用于某些标记语言。你需要告诉它文件的格式。启动 Ispell 时,为 TeX 或 LaTeX 文件添加 **-t**,为 HTML 文件添加 **-H**,对于 groff 或 troff 文件添加 **-n**。例如,如果输入 **ispell -t myReport.tex**,Ispell 将忽略所有标记。
|
||||
之前我提到过 Ispell 可以用于某些标记语言。你需要告诉它文件的格式。启动 Ispell 时,为 TeX 或 LaTeX 文件添加 `-t`,为 HTML 文件添加 `-H`,对于 groff 或 troff 文件添加 `-n`。例如,如果输入 `ispell -t myReport.tex`,Ispell 将忽略所有标记。
|
||||
|
||||
如果你不想在检查文件后创建备份文件,请将 **-x** 添加到命令行。例如,**ispell -x myFile.txt**。
|
||||
如果你不想在检查文件后创建备份文件,请将 `-x` 添加到命令行。例如,`ispell -x myFile.txt`。
|
||||
|
||||
如果 Ispell 遇到拼写正确但不在其字典中的单词,像是名字,会发生什么?你可以按 **I** 将该单词添加到个人单词列表中。这会将单词保存到 _/home_ 家目录下的 _.ispell_default_ 的文件中。
|
||||
如果 Ispell 遇到拼写正确但不在其字典中的单词,比如名字,会发生什么?你可以按 `I` 将该单词添加到个人单词列表中。这会将单词保存到 `/home` 目录下的 `.ispell_default` 的文件中。
|
||||
|
||||
这些是我在使用 Ispell 时最有用的选项,但请查看 [Ispell 的手册页][9]以了解其所有选项。
|
||||
|
||||
@ -63,7 +65,7 @@ via: https://opensource.com/article/19/5/spelling-command-line-ispell
|
||||
作者:[Scott Nesbitt][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,3 +1,5 @@
|
||||
Northurland Translating
|
||||
|
||||
What Did Ada Lovelace's Program Actually Do?
|
||||
======
|
||||
The story of Microsoft’s founding is one of the most famous episodes in computing history. In 1975, Paul Allen flew out to Albuquerque to demonstrate the BASIC interpreter that he and Bill Gates had written for the Altair microcomputer. Because neither of them had a working Altair, Allen and Gates tested their interpreter using an emulator that they wrote and ran on Harvard’s computer system. The emulator was based on nothing more than the published specifications for the Intel 8080 processor. When Allen finally ran their interpreter on a real Altair—in front of the person he and Gates hoped would buy their software—he had no idea if it would work. But it did. The next month, Allen and Gates officially founded their new company.
|
||||
|
@ -1,388 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (FSSlc)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Inter-process communication in Linux: Sockets and signals)
|
||||
[#]: via: (https://opensource.com/article/19/4/interprocess-communication-linux-networking)
|
||||
[#]: author: (Marty Kalin https://opensource.com/users/mkalindepauledu)
|
||||
|
||||
Inter-process communication in Linux: Sockets and signals
|
||||
======
|
||||
|
||||
Learn how processes synchronize with each other in Linux.
|
||||
|
||||

|
||||
|
||||
This is the third and final article in a series about [interprocess communication][1] (IPC) in Linux. The [first article][2] focused on IPC through shared storage (files and memory segments), and the [second article][3] does the same for basic channels: pipes (named and unnamed) and message queues. This article moves from IPC at the high end (sockets) to IPC at the low end (signals). Code examples flesh out the details.
|
||||
|
||||
### Sockets
|
||||
|
||||
Just as pipes come in two flavors (named and unnamed), so do sockets. IPC sockets (aka Unix domain sockets) enable channel-based communication for processes on the same physical device (host), whereas network sockets enable this kind of IPC for processes that can run on different hosts, thereby bringing networking into play. Network sockets need support from an underlying protocol such as TCP (Transmission Control Protocol) or the lower-level UDP (User Datagram Protocol).
|
||||
|
||||
By contrast, IPC sockets rely upon the local system kernel to support communication; in particular, IPC sockets communicate using a local file as a socket address. Despite these implementation differences, the IPC socket and network socket APIs are the same in the essentials. The forthcoming example covers network sockets, but the sample server and client programs can run on the same machine because the server uses network address localhost (127.0.0.1), the address for the local machine on the local machine.
|
||||
|
||||
Sockets configured as streams (discussed below) are bidirectional, and control follows a client/server pattern: the client initiates the conversation by trying to connect to a server, which tries to accept the connection. If everything works, requests from the client and responses from the server then can flow through the channel until this is closed on either end, thereby breaking the connection.
|
||||
|
||||
An iterative server, which is suited for development only, handles connected clients one at a time to completion: the first client is handled from start to finish, then the second, and so on. The downside is that the handling of a particular client may hang, which then starves all the clients waiting behind. A production-grade server would be concurrent, typically using some mix of multi-processing and multi-threading. For example, the Nginx web server on my desktop machine has a pool of four worker processes that can handle client requests concurrently. The following code example keeps the clutter to a minimum by using an iterative server; the focus thus remains on the basic API, not on concurrency.
|
||||
|
||||
Finally, the socket API has evolved significantly over time as various POSIX refinements have emerged. The current sample code for server and client is deliberately simple but underscores the bidirectional aspect of a stream-based socket connection. Here's a summary of the flow of control, with the server started in a terminal then the client started in a separate terminal:
|
||||
|
||||
* The server awaits client connections and, given a successful connection, reads the bytes from the client.
|
||||
|
||||
* To underscore the two-way conversation, the server echoes back to the client the bytes received from the client. These bytes are ASCII character codes, which make up book titles.
|
||||
|
||||
* The client writes book titles to the server process and then reads the same titles echoed from the server. Both the server and the client print the titles to the screen. Here is the server's output, essentially the same as the client's:
|
||||
|
||||
```
|
||||
Listening on port 9876 for clients...
|
||||
War and Peace
|
||||
Pride and Prejudice
|
||||
The Sound and the Fury
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
#### Example 1. The socket server
|
||||
|
||||
```
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "sock.h"
|
||||
|
||||
void report(const char* msg, int terminate) {
|
||||
perror(msg);
|
||||
if (terminate) exit(-1); /* failure */
|
||||
}
|
||||
|
||||
int main() {
|
||||
int fd = socket(AF_INET, /* network versus AF_LOCAL */
|
||||
SOCK_STREAM, /* reliable, bidirectional, arbitrary payload size */
|
||||
0); /* system picks underlying protocol (TCP) */
|
||||
if (fd < 0) report("socket", 1); /* terminate */
|
||||
|
||||
/* bind the server's local address in memory */
|
||||
struct sockaddr_in saddr;
|
||||
memset(&saddr, 0, sizeof(saddr)); /* clear the bytes */
|
||||
saddr.sin_family = AF_INET; /* versus AF_LOCAL */
|
||||
saddr.sin_addr.s_addr = htonl(INADDR_ANY); /* host-to-network endian */
|
||||
saddr.sin_port = htons(PortNumber); /* for listening */
|
||||
|
||||
if (bind(fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0)
|
||||
report("bind", 1); /* terminate */
|
||||
|
||||
/* listen to the socket */
|
||||
if (listen(fd, MaxConnects) < 0) /* listen for clients, up to MaxConnects */
|
||||
report("listen", 1); /* terminate */
|
||||
|
||||
fprintf(stderr, "Listening on port %i for clients...\n", PortNumber);
|
||||
/* a server traditionally listens indefinitely */
|
||||
while (1) {
|
||||
struct sockaddr_in caddr; /* client address */
|
||||
int len = sizeof(caddr); /* address length could change */
|
||||
|
||||
int client_fd = accept(fd, (struct sockaddr*) &caddr, &len); /* accept blocks */
|
||||
if (client_fd < 0) {
|
||||
report("accept", 0); /* don't terminate, though there's a problem */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read from client */
|
||||
int i;
|
||||
for (i = 0; i < ConversationLen; i++) {
|
||||
char buffer[BuffSize + 1];
|
||||
memset(buffer, '\0', sizeof(buffer));
|
||||
int count = read(client_fd, buffer, sizeof(buffer));
|
||||
if (count > 0) {
|
||||
puts(buffer);
|
||||
write(client_fd, buffer, sizeof(buffer)); /* echo as confirmation */
|
||||
}
|
||||
}
|
||||
close(client_fd); /* break connection */
|
||||
} /* while(1) */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The server program above performs the classic four-step to ready itself for client requests and then to accept individual requests. Each step is named after a system function that the server calls:
|
||||
|
||||
1. **socket(…)** : get a file descriptor for the socket connection
|
||||
2. **bind(…)** : bind the socket to an address on the server's host
|
||||
3. **listen(…)** : listen for client requests
|
||||
4. **accept(…)** : accept a particular client request
|
||||
|
||||
|
||||
|
||||
The **socket** call in full is:
|
||||
|
||||
```
|
||||
int sockfd = socket(AF_INET, /* versus AF_LOCAL */
|
||||
SOCK_STREAM, /* reliable, bidirectional */
|
||||
0); /* system picks protocol (TCP) */
|
||||
```
|
||||
|
||||
The first argument specifies a network socket as opposed to an IPC socket. There are several options for the second argument, but **SOCK_STREAM** and **SOCK_DGRAM** (datagram) are likely the most used. A stream-based socket supports a reliable channel in which lost or altered messages are reported; the channel is bidirectional, and the payloads from one side to the other can be arbitrary in size. By contrast, a datagram-based socket is unreliable (best try), unidirectional, and requires fixed-sized payloads. The third argument to **socket** specifies the protocol. For the stream-based socket in play here, there is a single choice, which the zero represents: TCP. Because a successful call to **socket** returns the familiar file descriptor, a socket is written and read with the same syntax as, for example, a local file.
|
||||
|
||||
The **bind** call is the most complicated, as it reflects various refinements in the socket API. The point of interest is that this call binds the socket to a memory address on the server machine. However, the **listen** call is straightforward:
|
||||
|
||||
```
|
||||
if (listen(fd, MaxConnects) < 0)
|
||||
```
|
||||
|
||||
The first argument is the socket's file descriptor and the second specifies how many client connections can be accommodated before the server issues a connection refused error on an attempted connection. ( **MaxConnects** is set to 8 in the header file sock.h.)
|
||||
|
||||
The **accept** call defaults to a blocking wait: the server does nothing until a client attempts to connect and then proceeds. The **accept** function returns **-1** to indicate an error. If the call succeeds, it returns another file descriptor—for a read/write socket in contrast to the accepting socket referenced by the first argument in the **accept** call. The server uses the read/write socket to read requests from the client and to write responses back. The accepting socket is used only to accept client connections.
|
||||
|
||||
By design, a server runs indefinitely. Accordingly, the server can be terminated with a **Ctrl+C** from the command line.
|
||||
|
||||
#### Example 2. The socket client
|
||||
|
||||
```
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
#include "sock.h"
|
||||
|
||||
const char* books[] = {"War and Peace",
|
||||
"Pride and Prejudice",
|
||||
"The Sound and the Fury"};
|
||||
|
||||
void report(const char* msg, int terminate) {
|
||||
perror(msg);
|
||||
if (terminate) exit(-1); /* failure */
|
||||
}
|
||||
|
||||
int main() {
|
||||
/* fd for the socket */
|
||||
int sockfd = socket(AF_INET, /* versus AF_LOCAL */
|
||||
SOCK_STREAM, /* reliable, bidirectional */
|
||||
0); /* system picks protocol (TCP) */
|
||||
if (sockfd < 0) report("socket", 1); /* terminate */
|
||||
|
||||
/* get the address of the host */
|
||||
struct hostent* hptr = gethostbyname(Host); /* localhost: 127.0.0.1 */
|
||||
if (!hptr) report("gethostbyname", 1); /* is hptr NULL? */
|
||||
if (hptr->h_addrtype != AF_INET) /* versus AF_LOCAL */
|
||||
report("bad address family", 1);
|
||||
|
||||
/* connect to the server: configure server's address 1st */
|
||||
struct sockaddr_in saddr;
|
||||
memset(&saddr, 0, sizeof(saddr));
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr =
|
||||
((struct in_addr*) hptr->h_addr_list[0])->s_addr;
|
||||
saddr.sin_port = htons(PortNumber); /* port number in big-endian */
|
||||
|
||||
if (connect(sockfd, (struct sockaddr*) &saddr, sizeof(saddr)) < 0)
|
||||
report("connect", 1);
|
||||
|
||||
/* Write some stuff and read the echoes. */
|
||||
puts("Connect to server, about to write some stuff...");
|
||||
int i;
|
||||
for (i = 0; i < ConversationLen; i++) {
|
||||
if (write(sockfd, books[i], strlen(books[i])) > 0) {
|
||||
/* get confirmation echoed from server and print */
|
||||
char buffer[BuffSize + 1];
|
||||
memset(buffer, '\0', sizeof(buffer));
|
||||
if (read(sockfd, buffer, sizeof(buffer)) > 0)
|
||||
puts(buffer);
|
||||
}
|
||||
}
|
||||
puts("Client done, about to exit...");
|
||||
close(sockfd); /* close the connection */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The client program's setup code is similar to the server's. The principal difference between the two is that the client neither listens nor accepts, but instead connects:
|
||||
|
||||
```
|
||||
if (connect(sockfd, (struct sockaddr*) &saddr, sizeof(saddr)) < 0)
|
||||
```
|
||||
|
||||
The **connect** call might fail for several reasons; for example, the client has the wrong server address or too many clients are already connected to the server. If the **connect** operation succeeds, the client writes requests and then reads the echoed responses in a **for** loop. After the conversation, both the server and the client **close** the read/write socket, although a close operation on either side is sufficient to close the connection. The client exits thereafter but, as noted earlier, the server remains open for business.
|
||||
|
||||
The socket example, with request messages echoed back to the client, hints at the possibilities of arbitrarily rich conversations between the server and the client. Perhaps this is the chief appeal of sockets. It is common on modern systems for client applications (e.g., a database client) to communicate with a server through a socket. As noted earlier, local IPC sockets and network sockets differ only in a few implementation details; in general, IPC sockets have lower overhead and better performance. The communication API is essentially the same for both.
|
||||
|
||||
### Signals
|
||||
|
||||
A signal interrupts an executing program and, in this sense, communicates with it. Most signals can be either ignored (blocked) or handled (through designated code), with **SIGSTOP** (pause) and **SIGKILL** (terminate immediately) as the two notable exceptions. Symbolic constants such as **SIGKILL** have integer values, in this case, 9.
|
||||
|
||||
Signals can arise in user interaction. For example, a user hits **Ctrl+C** from the command line to terminate a program started from the command-line; **Ctrl+C** generates a **SIGTERM** signal. **SIGTERM** for terminate, unlike **SIGKILL** , can be either blocked or handled. One process also can signal another, thereby making signals an IPC mechanism.
|
||||
|
||||
Consider how a multi-processing application such as the Nginx web server might be shut down gracefully from another process. The **kill** function:
|
||||
|
||||
```
|
||||
int kill(pid_t pid, int signum); /* declaration */
|
||||
```
|
||||
|
||||
can be used by one process to terminate another process or group of processes. If the first argument to function **kill** is greater than zero, this argument is treated as the pid (process ID) of the targeted process; if the argument is zero, the argument identifies the group of processes to which the signal sender belongs.
|
||||
|
||||
The second argument to **kill** is either a standard signal number (e.g., **SIGTERM** or **SIGKILL** ) or 0, which makes the call to **signal** a query about whether the pid in the first argument is indeed valid. The graceful shutdown of a multi-processing application thus could be accomplished by sending a terminate signal—a call to the **kill** function with **SIGTERM** as the second argument—to the group of processes that make up the application. (The Nginx master process could terminate the worker processes with a call to **kill** and then exit itself.) The **kill** function, like so many library functions, houses power and flexibility in a simple invocation syntax.
|
||||
|
||||
#### Example 3. The graceful shutdown of a multi-processing system
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
void graceful(int signum) {
|
||||
printf("\tChild confirming received signal: %i\n", signum);
|
||||
puts("\tChild about to terminate gracefully...");
|
||||
sleep(1);
|
||||
puts("\tChild terminating now...");
|
||||
_exit(0); /* fast-track notification of parent */
|
||||
}
|
||||
|
||||
void set_handler() {
|
||||
struct sigaction current;
|
||||
sigemptyset(¤t.sa_mask); /* clear the signal set */
|
||||
current.sa_flags = 0; /* enables setting sa_handler, not sa_action */
|
||||
current.sa_handler = graceful; /* specify a handler */
|
||||
sigaction(SIGTERM, ¤t, NULL); /* register the handler */
|
||||
}
|
||||
|
||||
void child_code() {
|
||||
set_handler();
|
||||
|
||||
while (1) { /** loop until interrupted **/
|
||||
sleep(1);
|
||||
puts("\tChild just woke up, but going back to sleep.");
|
||||
}
|
||||
}
|
||||
|
||||
void parent_code(pid_t cpid) {
|
||||
puts("Parent sleeping for a time...");
|
||||
sleep(5);
|
||||
|
||||
/* Try to terminate child. */
|
||||
if (-1 == kill(cpid, SIGTERM)) {
|
||||
perror("kill");
|
||||
exit(-1);
|
||||
}
|
||||
wait(NULL); /** wait for child to terminate **/
|
||||
puts("My child terminated, about to exit myself...");
|
||||
}
|
||||
|
||||
int main() {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
perror("fork");
|
||||
return -1; /* error */
|
||||
}
|
||||
if (0 == pid)
|
||||
child_code();
|
||||
else
|
||||
parent_code(pid);
|
||||
return 0; /* normal */
|
||||
}
|
||||
```
|
||||
|
||||
The shutdown program above simulates the graceful shutdown of a multi-processing system, in this case, a simple one consisting of a parent process and a single child process. The simulation works as follows:
|
||||
|
||||
* The parent process tries to fork a child. If the fork succeeds, each process executes its own code: the child executes the function **child_code** , and the parent executes the function **parent_code**.
|
||||
* The child process goes into a potentially infinite loop in which the child sleeps for a second, prints a message, goes back to sleep, and so on. It is precisely a **SIGTERM** signal from the parent that causes the child to execute the signal-handling callback function **graceful**. The signal thus breaks the child process out of its loop and sets up the graceful termination of both the child and the parent. The child prints a message before terminating.
|
||||
* The parent process, after forking the child, sleeps for five seconds so that the child can execute for a while; of course, the child mostly sleeps in this simulation. The parent then calls the **kill** function with **SIGTERM** as the second argument, waits for the child to terminate, and then exits.
|
||||
|
||||
|
||||
|
||||
Here is the output from a sample run:
|
||||
|
||||
```
|
||||
% ./shutdown
|
||||
Parent sleeping for a time...
|
||||
Child just woke up, but going back to sleep.
|
||||
Child just woke up, but going back to sleep.
|
||||
Child just woke up, but going back to sleep.
|
||||
Child just woke up, but going back to sleep.
|
||||
Child confirming received signal: 15 ## SIGTERM is 15
|
||||
Child about to terminate gracefully...
|
||||
Child terminating now...
|
||||
My child terminated, about to exit myself...
|
||||
```
|
||||
|
||||
For the signal handling, the example uses the **sigaction** library function (POSIX recommended) rather than the legacy **signal** function, which has portability issues. Here are the code segments of chief interest:
|
||||
|
||||
* If the call to **fork** succeeds, the parent executes the **parent_code** function and the child executes the **child_code** function. The parent waits for five seconds before signaling the child:
|
||||
|
||||
```
|
||||
puts("Parent sleeping for a time...");
|
||||
sleep(5);
|
||||
if (-1 == kill(cpid, SIGTERM)) {
|
||||
...sleepkillcpidSIGTERM...
|
||||
```
|
||||
|
||||
If the **kill** call succeeds, the parent does a **wait** on the child's termination to prevent the child from becoming a permanent zombie; after the wait, the parent exits.
|
||||
|
||||
* The **child_code** function first calls **set_handler** and then goes into its potentially infinite sleeping loop. Here is the **set_handler** function for review:
|
||||
|
||||
```
|
||||
void set_handler() {
|
||||
struct sigaction current; /* current setup */
|
||||
sigemptyset(¤t.sa_mask); /* clear the signal set */
|
||||
current.sa_flags = 0; /* for setting sa_handler, not sa_action */
|
||||
current.sa_handler = graceful; /* specify a handler */
|
||||
sigaction(SIGTERM, ¤t, NULL); /* register the handler */
|
||||
}
|
||||
```
|
||||
|
||||
The first three lines are preparation. The fourth statement sets the handler to the function **graceful** , which prints some messages before calling **_exit** to terminate. The fifth and last statement then registers the handler with the system through the call to **sigaction**. The first argument to **sigaction** is **SIGTERM** for terminate, the second is the current **sigaction** setup, and the last argument ( **NULL** in this case) can be used to save a previous **sigaction** setup, perhaps for later use.
|
||||
|
||||
|
||||
|
||||
|
||||
Using signals for IPC is indeed a minimalist approach, but a tried-and-true one at that. IPC through signals clearly belongs in the IPC toolbox.
|
||||
|
||||
### Wrapping up this series
|
||||
|
||||
These three articles on IPC have covered the following mechanisms through code examples:
|
||||
|
||||
* Shared files
|
||||
* Shared memory (with semaphores)
|
||||
* Pipes (named and unnamed)
|
||||
* Message queues
|
||||
* Sockets
|
||||
* Signals
|
||||
|
||||
|
||||
|
||||
Even today, when thread-centric languages such as Java, C#, and Go have become so popular, IPC remains appealing because concurrency through multi-processing has an obvious advantage over multi-threading: every process, by default, has its own address space, which rules out memory-based race conditions in multi-processing unless the IPC mechanism of shared memory is brought into play. (Shared memory must be locked in both multi-processing and multi-threading for safe concurrency.) Anyone who has written even an elementary multi-threading program with communication via shared variables knows how challenging it can be to write thread-safe yet clear, efficient code. Multi-processing with single-threaded processes remains a viable—indeed, quite appealing—way to take advantage of today's multi-processor machines without the inherent risk of memory-based race conditions.
|
||||
|
||||
There is no simple answer, of course, to the question of which among the IPC mechanisms is the best. Each involves a trade-off typical in programming: simplicity versus functionality. Signals, for example, are a relatively simple IPC mechanism but do not support rich conversations among processes. If such a conversion is needed, then one of the other choices is more appropriate. Shared files with locking is reasonably straightforward, but shared files may not perform well enough if processes need to share massive data streams; pipes or even sockets, with more complicated APIs, might be a better choice. Let the problem at hand guide the choice.
|
||||
|
||||
Although the sample code ([available on my website][4]) is all in C, other programming languages often provide thin wrappers around these IPC mechanisms. The code examples are short and simple enough, I hope, to encourage you to experiment.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/19/4/interprocess-communication-linux-networking
|
||||
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkalindepauledu
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://en.wikipedia.org/wiki/Inter-process_communication
|
||||
[2]: https://opensource.com/article/19/4/interprocess-communication-ipc-linux-part-1
|
||||
[3]: https://opensource.com/article/19/4/interprocess-communication-ipc-linux-part-2
|
||||
[4]: http://condor.depaul.edu/mkalin
|
@ -7,33 +7,33 @@
|
||||
[#]: via: (https://opensource.com/article/18/7/put-platforms-python-game)
|
||||
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
|
||||
|
||||
Put platforms in a Python game with Pygame
|
||||
放置舞台到一个使用 Pygame 的 Python 游戏中
|
||||
======
|
||||
In part six of this series on building a Python game from scratch, create some platforms for your characters to travel.
|
||||
在这系列的第六部分中,在从零构建一个 Python 游戏时,为你的角色创建一些舞台来旅行。
|
||||

|
||||
|
||||
This is part 6 in an ongoing series about creating video games in Python 3 using the Pygame module. Previous articles are:
|
||||
这是关于使用 Pygame 模块来在 Python 3 中创建电脑游戏的仍在进行的一系列的文章的第六部分。先前的文章是:
|
||||
|
||||
+ [Learn how to program in Python by building a simple dice game][24]
|
||||
+ [Build a game framework with Python using the Pygame module][25]
|
||||
+ [How to add a player to your Python game][26]
|
||||
+ [Using Pygame to move your game character around][27]
|
||||
+ [What's a hero without a villain? How to add one to your Python game][28]
|
||||
+ [通过构建一个简单的骰子游戏来学习如何用 Python 编程][24]
|
||||
+ [使用 Python 和 Pygame 模块构建一个游戏框架][25]
|
||||
+ [如何添加一个玩家到你的 Python 游戏][26]
|
||||
+ [使用 Pygame 来在周围移动你的游戏角色][27]
|
||||
+ [没有一个坏蛋的一个英雄是什么?如何添加一个坏蛋到你的 Python 游戏][28]
|
||||
|
||||
|
||||
A platformer game needs platforms.
|
||||
一个舞台游戏需要舞台。
|
||||
|
||||
In [Pygame][1], the platforms themselves are sprites, just like your playable sprite. That's important because having platforms that are objects makes it a lot easier for your player sprite to interact with them.
|
||||
在 [Pygame][1] 中,舞台本身是小精灵,正像你的可玩的小精灵。这一点是重要的,因为有对象的舞台,使你的玩家小精灵很简单地与舞台一起作用。.
|
||||
|
||||
There are two major steps in creating platforms. First, you must code the objects, and then you must map out where you want the objects to appear.
|
||||
创建舞台有两个主要步骤。首先,你必须编码对象,然后,你必须设计你希望对象来出现的位置。
|
||||
|
||||
### Coding platform objects
|
||||
### 编码舞台对象
|
||||
|
||||
To build a platform object, you create a class called `Platform`. It's a sprite, just like your [`Player`][2] [sprite][2], with many of the same properties.
|
||||
为构建一个舞台对象,你创建一个称为`Platform`的类。它是一个小精灵,正像你的[`玩家`][2] [小精灵][2],带有很多相同的属性。
|
||||
|
||||
Your `Platform` class needs to know a lot of information about what kind of platform you want, where it should appear in the game world, and what image it should contain. A lot of that information might not even exist yet, depending on how much you have planned out your game, but that's all right. Just as you didn't tell your Player sprite how fast to move until the end of the [Movement article][3], you don't have to tell `Platform` everything upfront.
|
||||
你的`舞台`类需要知道很多你想要的舞台的类型的信息 ,它应该出现在游戏世界的哪里,和它应该包含的什么图片。它们中很多信息可能还尚不存在,依赖于你计划了多少游戏,但是,没有关系。正像直到[移到文章][3]的结尾时,你不告诉你的玩家小精灵多快速度移到,你没有必要告诉`Platform`预交的每一件事。
|
||||
|
||||
Near the top of the script you've been writing in this series, create a new class. The first three lines in this code sample are for context, so add the code below the comment:
|
||||
在你所写的这系列中脚本的顶部附近,创建一个新的类。在这代码示例中前三行是用于上下文,因此在注释的下面添加代码:
|
||||
|
||||
```
|
||||
import pygame
|
||||
@ -53,55 +53,55 @@ def __init__(self,xloc,yloc,imgw,imgh,img):
|
||||
self.rect.x = xloc
|
||||
```
|
||||
|
||||
When called, this class creates an object onscreen in some X and Y location, with some width and height, using some image file for texture. It's very similar to how players or enemies are drawn onscreen.
|
||||
当被调用时,这个类在一些 X 和 Y 位置上创建一个对象 onscreen, 带有一些宽度和高度,对于纹理使用一些图片文件。它非常类似于如何玩家或敌人绘制onscreen。
|
||||
|
||||
### Types of platforms
|
||||
### 舞台的类型
|
||||
|
||||
The next step is to map out where all your platforms need to appear.
|
||||
下一步是设计你所有舞台需要出现的地方。
|
||||
|
||||
#### The tile method
|
||||
#### 瓷砖方法
|
||||
|
||||
There are a few different ways to implement a platform game world. In the original side-scroller games, such as Mario Super Bros. and Sonic the Hedgehog, the technique was to use "tiles," meaning that there were a few blocks to represent the ground and various platforms, and these blocks were used and reused to make a level. You have only eight or 12 different kinds of blocks, and you line them up onscreen to create the ground, floating platforms, and whatever else your game needs. Some people find this the easier way to make a game since you just have to make (or download) a small set of level assets to create many different levels. The code, however, requires a little more math.
|
||||
这里有几个不同的方法来实施一个舞台游戏世界。在最初的侧面滚动游戏,例如,马里奥超级兄弟和刺猬索尼克,这个技巧是来使用"瓷砖",意味着这里有几个块“瓷砖”来代表地面和各种各样的舞台,并且这些块被使用和重复使用来制作一个层次。你仅有8或12种不同的块,你排列它们在屏幕上来创建地面,浮动的舞台,和你游戏需要的其它的一切事物。一些人找到这最容易的方法来制作一个游戏,尽管你不得不制作(或下载)一小组价值相等的有用的事物来创建很多不同的有用的事物。然而,代码需要一点更多的数学。
|
||||
|
||||
![Supertux, a tile-based video game][5]
|
||||
|
||||
[SuperTux][6], a tile-based video game.
|
||||
[SuperTux][6] ,一个基于瓷砖的电脑游戏。
|
||||
|
||||
#### The hand-painted method
|
||||
#### 手工绘制方法
|
||||
|
||||
Another method is to make each and every asset as one whole image. If you enjoy creating assets for your game world, this is a great excuse to spend time in a graphics application, building each and every part of your game world. This method requires less math, because all the platforms are whole, complete objects, and you tell [Python][7] where to place them onscreen.
|
||||
另一个方法是来使各个和每一个有用的事物作为一整个图像。如果你享受为你的游戏世界创建有用的事物,在一个图形应用程序中花费时间来构建你的游戏世界的各个和每一部件是一个极好的理由。这个方法需要较少的数学,因为所有的舞台是完整的对象,并且你告诉 [Python][7] 在屏幕上放置它们的位置。
|
||||
|
||||
Each method has advantages and disadvantages, and the code you must use is slightly different depending on the method you choose. I'll cover both so you can use one or the other, or even a mix of both, in your project.
|
||||
每种方法都有优势和劣势,并且依赖于你的选择使用的代码是稍微不同的.我将覆盖这两方面,所以你可以在你的工程中使用一个或另一个,甚至两者的混合。
|
||||
|
||||
### Level mapping
|
||||
### 层次映射
|
||||
|
||||
Mapping out your game world is a vital part of level design and game programming in general. It does involve math, but nothing too difficult, and Python is good at math so it can help some.
|
||||
总的来说,映射出你的游戏世界是层次设计和游戏程序的一个重要的部分。这需要数学,但是没有什么太难的,而且 Python 擅长数学,因此它可以帮助一些。
|
||||
|
||||
You might find it helpful to design on paper first. Get a sheet of paper and draw a box to represent your game window. Draw platforms in the box, labeling each with its X and Y coordinates, as well as its intended width and height. The actual positions in the box don't have to be exact, as long as you keep the numbers realistic. For instance, if your screen is 720 pixels wide, then you can't fit eight platforms at 100 pixels each all on one screen.
|
||||
你可以发现先在纸张上设计是有益的。获取纸张的一个表格,并绘制一个方框来代表你的游戏窗体。在方框中绘制舞台,用 X 和 Y 坐标标记每一个,以及它的意欲达到的宽度和高度。在方框中的实际位置没有必要是精确的,只要你保持实际的数字。譬如,假如你的屏幕是 720 像素宽,那么你不能在一个屏幕上以 100 像素处容纳8块舞台。
|
||||
|
||||
Of course, not all platforms in your game have to fit in one screen-sized box, because your game will scroll as your player walks through it. So keep drawing your game world to the right of the first screen until the end of the level.
|
||||
当然,在你的游戏中不是所有的舞台不得不容纳在一个屏幕大小的方框,因为你的游戏将随着你的玩家行走而滚动。所以保持绘制你的游戏世界到第一屏幕的右侧,直到层次的右侧。
|
||||
|
||||
If you prefer a little more precision, you can use graph paper. This is especially helpful when designing a game with tiles because each grid square can represent one tile.
|
||||
如果你更喜欢精确一点,你可以使用方格纸。当设计一个带有瓷砖的游戏时,这是特别有用的,因为每个方格可以代表一个瓷砖。
|
||||
|
||||
![Example of a level map][9]
|
||||
|
||||
Example of a level map.
|
||||
一个平面地图示例。
|
||||
|
||||
#### Coordinates
|
||||
#### 坐标系
|
||||
|
||||
You may have learned in school about the [Cartesian coordinate system][10]. What you learned applies to Pygame, except that in Pygame, your game world's coordinates place `0,0` in the top-left corner of your screen instead of in the middle, which is probably what you're used to from Geometry class.
|
||||
你可能已经在学校中学习[笛卡尔坐标系][10]。你学习的东西应用到 Pygame,除了在 Pygame 中,你的游戏世界的坐标系放置 `0,0` 在你的屏幕的左上角而不是在中间,中间可能是你which is probably what you're used to from Geometry class.
|
||||
|
||||
![Example of coordinates in Pygame][12]
|
||||
|
||||
Example of coordinates in Pygame.
|
||||
在 Pygame 中的坐标示例。
|
||||
|
||||
The X axis starts at 0 on the far left and increases infinitely to the right. The Y axis starts at 0 at the top of the screen and extends down.
|
||||
X 轴起始于最左边的 0 ,无限地向右增加。Y 轴起始于屏幕顶部的 0 ,向下延伸。
|
||||
|
||||
#### Image sizes
|
||||
#### 图片大小
|
||||
|
||||
Mapping out a game world is meaningless if you don't know how big your players, enemies, and platforms are. You can find the dimensions of your platforms or tiles in a graphics program. In [Krita][13], for example, click on the **Image** menu and select **Properties**. You can find the dimensions at the very top of the **Properties** window.
|
||||
映射出一个游戏世界不是毫无意义的,如果你不知道你的玩家,敌人,舞台是多大的。你可以找到你的舞台的尺寸或在一个图形程序中的标题。在 [Krita][13] 中,例如,单击**图形**菜单,并选择**属性**。你可以在**属性**窗口的非常顶部处找到尺寸。
|
||||
|
||||
Alternately, you can create a simple Python script to tell you the dimensions of an image. Open a new text file and type this code into it:
|
||||
可选地,你可以创建一个简单点的 Python 脚本来告诉你的一个图形的尺寸。打开一个新的文本文件,并输入这些代码到其中:
|
||||
|
||||
```
|
||||
#!/usr/bin/env python3
|
||||
@ -123,44 +123,44 @@ Y = dim.size[1]
|
||||
print(X,Y)
|
||||
```
|
||||
|
||||
Save the text file as `identify.py`.
|
||||
保存文本文件为 `identify.py` 。
|
||||
|
||||
To set up this script, you must install an extra set of Python modules that contain the new keywords used in the script:
|
||||
为安装这个脚本,你必需安装安装一组额外的 Python 模块,它们包含使用在脚本中新的关键字:
|
||||
|
||||
```
|
||||
$ pip3 install Pillow --user
|
||||
```
|
||||
|
||||
Once that is installed, run your script from within your game project directory:
|
||||
一旦这些被安装,在你游戏工程目录中运行你的脚本:
|
||||
|
||||
```
|
||||
$ python3 ./identify.py images/ground.png
|
||||
(1080, 97)
|
||||
```
|
||||
|
||||
The image size of the ground platform in this example is 1080 pixels wide and 97 high.
|
||||
在这个示例中的地面舞台的图形的大小是1080像素宽和97像素高。
|
||||
|
||||
### Platform blocks
|
||||
### 舞台块
|
||||
|
||||
If you choose to draw each asset individually, you must create several platforms and any other elements you want to insert into your game world, each within its own file. In other words, you should have one file per asset, like this:
|
||||
如果你选择单独地绘制每个有用的事物,你必需创建一些舞台和一些你希望插入到你的游戏世界中其它的元素,每个元素都在它自己的文件中。换句话说,你应该每个有用的事物都有一个文件,像这:
|
||||
|
||||
![One image file per object][15]
|
||||
|
||||
One image file per object.
|
||||
每个对象一个图形文件。
|
||||
|
||||
You can reuse each platform as many times as you want, just make sure that each file only contains one platform. You cannot use a file that contains everything, like this:
|
||||
你可以按照你希望的次数重复使用每个舞台,只要确保每个文件仅包含一个舞台。你不能使用一个包含每一件事物的文件,像这:
|
||||
|
||||
![Your level cannot be one image file][17]
|
||||
|
||||
Your level cannot be one image file.
|
||||
你的层次不能是一个图形。
|
||||
|
||||
You might want your game to look like that when you've finished, but if you create your level in one big file, there is no way to distinguish a platform from the background, so either paint your objects in their own file or crop them from a large file and save individual copies.
|
||||
当你完成时,你可能希望你的游戏看起来像这样,但是如果你在一个大文件中创建你的层次,没有方法从背景中区分一个舞台,因此,要么在它们拥有的文件中绘制你的对象,要么从一个大规模文件中复制它们,并单独地保存副本。
|
||||
|
||||
**Note:** As with your other assets, you can use [GIMP][18], Krita, [MyPaint][19], or [Inkscape][20] to create your game assets.
|
||||
**注意:** 如同你的其它的有用的事物,你可以使用[GIMP][18],Krita,[MyPaint][19],或[Inkscape][20] 来创建你的游戏的有用的事物。
|
||||
|
||||
Platforms appear on the screen at the start of each level, so you must add a `platform` function in your `Level` class. The special case here is the ground platform, which is important enough to be treated as its own platform group. By treating the ground as its own special kind of platform, you can choose whether it scrolls or whether it stands still while other platforms float over the top of it. It's up to you.
|
||||
舞台出现在每个层次开始的屏幕上,因此你必需在你的`Level`类中添加一个`platform`函数。在这里特殊的情况是地面舞台,作为它自身拥有的舞台组来对待是足够重要的。通过把地面看作它自身拥有的特殊类型的舞台,你可以选择它是否滚动,或在其它舞台漂浮在它上面期间是否仍然站立。它取决于你。
|
||||
|
||||
Add these two functions to your `Level` class:
|
||||
添加这两个函数到你的`Level`类:
|
||||
|
||||
```
|
||||
def ground(lvl,x,y,w,h):
|
||||
@ -187,15 +187,15 @@ def platform( lvl ):
|
||||
return plat_list
|
||||
```
|
||||
|
||||
The `ground` function requires an X and Y location so Pygame knows where to place the ground platform. It also requires the width and height of the platform so Pygame knows how far the ground extends in each direction. The function uses your `Platform` class to generate an object onscreen, and then adds that object to the `ground_list` group.
|
||||
`ground` 函数需要一个 X 和 Y 位置,以便 Pygame 知道在哪里放置地面舞台。它也需要舞台的宽度和高度,这样 Pygame 知道地面延伸到每个方向有多远。该函数使用你的 `Platform` 来来生成一个对象 onscreen ,然后他就这个对象到 `ground_list` 组。
|
||||
|
||||
The `platform` function is essentially the same, except that there are more platforms to list. In this example, there are only two, but you can have as many as you like. After entering one platform, you must add it to the `plat_list` before listing another. If you don't add a platform to the group, then it won't appear in your game.
|
||||
`platform` 函数本质上是相同的,除了其有更多的舞台来列出。在这个示例中,仅有两个,但是你可以想多少就多少。在进入一个舞台后,在列出另一个前,你必需添加它到 `plat_list` 中。如果你不添加一个舞台到组中,那么它将不出现在你的游戏中。
|
||||
|
||||
> **Tip:** It can be difficult to think of your game world with 0 at the top, since the opposite is what happens in the real world; when figuring out how tall you are, you don't measure yourself from the sky down, you measure yourself from your feet to the top of your head.
|
||||
> **提示:** 很难想象你的游戏世界的0在顶部,因为在真实世界中发生的情况是相反的;当估计你多高时,你不要从天空下面测量你自己,从脚到头的顶部来测量。
|
||||
>
|
||||
> If it's easier for you to build your game world from the "ground" up, it might help to express Y-axis values as negatives. For instance, you know that the bottom of your game world is the value of `worldy`. So `worldy` minus the height of the ground (97, in this example) is where your player is normally standing. If your character is 64 pixels tall, then the ground minus 128 is exactly twice as tall as your player. Effectively, a platform placed at 128 pixels is about two stories tall, relative to your player. A platform at -320 is three more stories. And so on.
|
||||
> 如果对你来说从“地面”上来构建你的游戏世界更容易,它可能有助于表示 Y 轴值为负数。例如,你知道你的游戏世界的底部是 `worldy` 的值。因此 `worldy` 减去地面(97,在这个示例中)的高度是你的玩家正常站立的位置。如果你的角色是64像素高,那么地面减去128正好是你的玩家的两倍。事实上,一个放置在128像素处舞台大约是两层楼高度,相对于你的玩家。一个舞台在-320处是三层楼高。等等
|
||||
|
||||
As you probably know by now, none of your classes and functions are worth much if you don't use them. Add this code to your setup section (the first line is just for context, so add the last two lines):
|
||||
正像你现在可能所知的,如果你不使用它们,你的类和函数是没有有价值的。添加这些代码到你的 setup 部分(第一行只是上下文,所以添加最后两行):
|
||||
|
||||
```
|
||||
enemy_list = Level.bad( 1, eloc )
|
||||
@ -203,7 +203,7 @@ ground_list = Level.ground( 1,0,worldy-97,1080,97 )
|
||||
plat_list = Level.platform( 1 )
|
||||
```
|
||||
|
||||
And add these lines to your main loop (again, the first line is just for context):
|
||||
并提交这些行到你的主循环(再一次,第一行仅用于上下文):
|
||||
|
||||
```
|
||||
enemy_list.draw(world) # refresh enemies
|
||||
@ -211,24 +211,24 @@ ground_list.draw(world) # refresh ground
|
||||
plat_list.draw(world) # refresh platforms
|
||||
```
|
||||
|
||||
### Tiled platforms
|
||||
### 瓷砖舞台
|
||||
|
||||
Tiled game worlds are considered easier to make because you just have to draw a few blocks upfront and can use them over and over to create every platform in the game. There are even sets of tiles for you to use on sites like [OpenGameArt.org][21].
|
||||
瓷砖游戏世界被认为更容易制作,因为你只需要绘制一些在前面的块,就能在游戏中反反复复创建每一个舞台。在网站上甚至有一组供你来使用的瓷砖,像 [OpenGameArt.org][21]。
|
||||
|
||||
The `Platform` class is the same as the one provided in the previous sections.
|
||||
`Platform` 类与在前面部分中的类是相同的。
|
||||
|
||||
The `ground` and `platform` in the `Level` class, however, must use loops to calculate how many blocks to use to create each platform.
|
||||
在 `Level` 类中的 `ground` 和 `platform` , 然而,必需使用循环来计算使用多少块来创建每个舞台。
|
||||
|
||||
If you intend to have one solid ground in your game world, the ground is simple. You just "clone" your ground tile across the whole window. For instance, you could create a list of X and Y values to dictate where each tile should be placed, and then use a loop to take each value and draw one tile. This is just an example, so don't add this to your code:
|
||||
如果你打算在你的游戏世界中有一个坚固的地面,地面是简单的。你仅从整个窗口一边到另一边"克隆"你的地面瓷砖。例如,你可以创建一个 X 和 Y 值的列表来规定每个瓷砖应该放置的位置,然后使用一个循环来获取每个值和绘制一个瓷砖。这仅是一个示例,所以不要添加这到你的代码:
|
||||
|
||||
```
|
||||
# Do not add this to your code
|
||||
gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656]
|
||||
```
|
||||
|
||||
If you look carefully, though, you can see all the Y values are always the same, and the X values increase steadily in increments of 64, which is the size of the tiles. That kind of repetition is exactly what computers are good at, so you can use a little bit of math logic to have the computer do all the calculations for you:
|
||||
如果你仔细看,不过,你也可以看到所有的 Y 值是相同的,X 值以64的增量不断地增加,这是瓷砖的东西。这种类型的重复是精确地,是计算机擅长的,因此你可以使用一点数学逻辑来让计算机为你做所有的计算:
|
||||
|
||||
Add this to the setup part of your script:
|
||||
添加这代你的脚本的 setup 部分:
|
||||
|
||||
```
|
||||
gloc = []
|
||||
@ -243,9 +243,9 @@ while i <= (worldx/tx)+tx:
|
||||
ground_list = Level.ground( 1,gloc,tx,ty )
|
||||
```
|
||||
|
||||
Now, regardless of the size of your window, Python divides the width of the game world by the width of the tile and creates an array listing each X value. This doesn't calculate the Y value, but that never changes on flat ground anyway.
|
||||
现在,不管你的窗口的大小,Python 通过瓷砖的宽度 分割游戏世界的宽度,并创建一个数组列表列出每个 X 值。这不计算 Y 值,但是无论如何,从不在平的地面上更改。
|
||||
|
||||
To use the array in a function, use a `while` loop that looks at each entry and adds a ground tile at the appropriate location:
|
||||
为在一个函数中使用数组,使用一个`while`循环,查看每个条目并在适当的位置添加一个地面瓷砖:
|
||||
|
||||
```
|
||||
def ground(lvl,gloc,tx,ty):
|
||||
@ -263,13 +263,13 @@ def ground(lvl,gloc,tx,ty):
|
||||
return ground_list
|
||||
```
|
||||
|
||||
This is nearly the same code as the `ground` function for the block-style platformer, provided in a previous section above, aside from the `while` loop.
|
||||
除了 `while` 循环,这几乎与在上面一部分中提供的块样式平台游戏 `ground` 函数的代码相同。
|
||||
|
||||
For moving platforms, the principle is similar, but there are some tricks you can use to make your life easier.
|
||||
对于移到舞台,原理是相似的,但是这里有一些你可以使用的技巧来使你的生活更简单。
|
||||
|
||||
Rather than mapping every platform by pixels, you can define a platform by its starting pixel (its X value), the height from the ground (its Y value), and how many tiles to draw. That way, you don't have to worry about the width and height of every platform.
|
||||
而不通过像素映射每个舞台,你可以通过它的起始像素(它的 X 值),从地面(它的 Y 值)的高度,绘制多少瓷砖来定义一个舞台。用那种方法,你不必担心每个舞台的宽度和高度。
|
||||
|
||||
The logic for this trick is a little more complex, so copy this code carefully. There is a `while` loop inside of another `while` loop because this function must look at all three values within each array entry to successfully construct a full platform. In this example, there are only three platforms defined as `ploc.append` statements, but your game probably needs more, so define as many as you need. Of course, some won't appear yet because they're far offscreen, but they'll come into view once you implement scrolling.
|
||||
这个技巧的逻辑有一点更复杂,因此仔细复制这些代码。有一个 `while` 循环在另一个 `while` 循环的内部,因为这个函数必需考虑在每个数组入口处的所有三个值来成功地建造一个完整的舞台。在这个示例中,这里仅有三个舞台被定义为 `ploc.append` 语句,但是你的游戏可能需要更多,因此你需要多少就定义多少。当然,一些也将不出现,因为它们远在屏幕外,但是一旦你实施滚动,它们将呈现眼前。
|
||||
|
||||
```
|
||||
def platform(lvl,tx,ty):
|
||||
@ -295,7 +295,7 @@ def platform(lvl,tx,ty):
|
||||
return plat_list
|
||||
```
|
||||
|
||||
To get the platforms to appear in your game world, they must be in your main loop. If you haven't already done so, add these lines to your main loop (again, the first line is just for context):
|
||||
为获取舞台,使其出现在你的游戏世界,它们必需在你的主循环中。如果你还没有这样做,添加这些行到你的主循环(再一次,第一行仅被用于上下文)中:
|
||||
|
||||
```
|
||||
enemy_list.draw(world) # refresh enemies
|
||||
@ -303,16 +303,16 @@ To get the platforms to appear in your game world, they must be in your main loo
|
||||
plat_list.draw(world) # refresh platforms
|
||||
```
|
||||
|
||||
Launch your game, and adjust the placement of your platforms as needed. Don't worry that you can't see the platforms that are spawned offscreen; you'll fix that soon.
|
||||
启动你的游戏,根据需要调整你的舞台的放置位置。不要担心,你不能看见在屏幕外面产生的舞台;你将不久后修复。
|
||||
|
||||
Here is the game so far in a picture and in code:
|
||||
到目前为止,这是在一个图片和在代码中游戏:
|
||||
|
||||
![Pygame game][23]
|
||||
|
||||
Our Pygame platformer so far.
|
||||
到目前为止,我们的 Pygame 舞台。
|
||||
|
||||
```
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python3
|
||||
# draw a world
|
||||
# add a player and player control
|
||||
# add player movement
|
||||
@ -552,7 +552,7 @@ via: https://opensource.com/article/18/7/put-platforms-python-game
|
||||
|
||||
作者:[Seth Kenlon][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
译者:[robsan](https://github.com/robsean)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
@ -0,0 +1,372 @@
|
||||
[#]: collector: "lujun9972"
|
||||
[#]: translator: "FSSlc"
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
[#]: subject: "Inter-process communication in Linux: Sockets and signals"
|
||||
[#]: via: "https://opensource.com/article/19/4/interprocess-communication-linux-networking"
|
||||
[#]: author: "Marty Kalin https://opensource.com/users/mkalindepauledu"
|
||||
|
||||
Linux 下的进程间通信:套接字和信号
|
||||
======
|
||||
|
||||
学习在 Linux 中进程是如何与其他进程进行同步的。
|
||||
|
||||

|
||||
|
||||
本篇是 Linux 下[进程间通信][1](IPC)系列的第三篇同时也是最后一篇文章。[第一篇文章][2]聚焦在通过共享存储(文件和共享内存段)来进行 IPC,[第二篇文章][3]则通过管道(无名的或者有名的)及消息队列来达到相同的目的。这篇文章将目光从高处(套接字)然后到低处(信号)来关注 IPC。代码示例将用力地充实下面的解释细节。
|
||||
|
||||
### 套接字
|
||||
|
||||
正如管道有两种类型(有名和无名)一样,套接字也有两种类型。IPC 套接字(即 Unix domain socket)给予进程在相同设备(主机)上基于通道的通信能力;而网络套接字给予进程运行在不同主机的能力,因此也带来了网络通信的能力。网络套接字需要底层协议的支持,例如 TCP(传输控制协议)或 UDP(用户数据报协议)。
|
||||
|
||||
与之相反,IPC 套接字依赖于本地系统内核的支持来进行通信;特别的,IPC 通信使用一个本地的文件作为套接字地址。尽管这两种套接字的实现有所不同,但在本质上,IPC 套接字和网络套接字的 API 是一致的。接下来的例子将包含网络套接字的内容,但示例服务器和客户端程序可以在相同的机器上运行,因为服务器使用了 localhost(127.0.0.1)这个网络地址,该地址表示的是本地机器上的本地机器的地址。
|
||||
|
||||
套接字以流的形式(下面将会讨论到)被配置为双向的,并且其控制遵循 C/S(客户端/服务器端)模式:客户端通过尝试连接一个服务器来初始化对话,而服务器端将尝试接受该连接。假如万事顺利,来自客户端的请求和来自服务器端的响应将通过管道进行传输,直到其中任意一方关闭该通道,从而断开这个连接。
|
||||
|
||||
一个`迭代服务器`(只适用于开发)将一直和连接它的客户端打交道:从最开始服务第一个客户端,然后到这个连接关闭,然后服务第二个客户端,循环往复。这种方式的一个缺点是处理一个特定的客户端可能会一直持续下去,使得其他的客户端一直在后面等待。生产级别的服务器将是并发的,通常使用了多进程或者多线程的混合。例如,我台式机上的 Nginx 网络服务器有一个 4 个 worker 的进程池,它们可以并发地处理客户端的请求。在下面的代码示例中,我们将使用迭代服务器,使得我们将要处理的问题达到一个很小的规模,只关注基本的 API,而不去关心并发的问题。
|
||||
|
||||
最后,随着各种 POSIX 改进的出现,套接字 API 随着时间的推移而发生了显著的变化。当前针对服务器端和客户端的示例代码特意写的比较简单,但是它着重强调了基于流的套接字中连接的双方。下面是关于流控制的一个总结,其中服务器端在一个终端中开启,而客户端在另一个不同的终端中开启:
|
||||
|
||||
* 服务器端等待客户端的连接,对于给定的一个成功连接,它就读取来自客户端的数据。
|
||||
* 为了强调是双方的会话,服务器端会对接收自客户端的数据做回应。这些数据都是 ASCII 字符代码,它们组成了一些书的标题。
|
||||
* 客户端将书的标题写给服务器端的进程,并从服务器端的回应中读取到相同的标题。然后客户端和服务器端都在屏幕上打印出标题。下面是服务器端的输出,客户端的输出也和它完全一样:
|
||||
|
||||
```
|
||||
Listening on port 9876 for clients...
|
||||
War and Peace
|
||||
Pride and Prejudice
|
||||
The Sound and the Fury
|
||||
```
|
||||
|
||||
#### 示例 1. 使用套接字的客户端程序
|
||||
|
||||
```c
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "sock.h"
|
||||
|
||||
void report(const char* msg, int terminate) {
|
||||
perror(msg);
|
||||
if (terminate) exit(-1); /* failure */
|
||||
}
|
||||
|
||||
int main() {
|
||||
int fd = socket(AF_INET, /* network versus AF_LOCAL */
|
||||
SOCK_STREAM, /* reliable, bidirectional: TCP */
|
||||
0); /* system picks underlying protocol */
|
||||
if (fd < 0) report("socket", 1); /* terminate */
|
||||
|
||||
/* bind the server's local address in memory */
|
||||
struct sockaddr_in saddr;
|
||||
memset(&saddr, 0, sizeof(saddr)); /* clear the bytes */
|
||||
saddr.sin_family = AF_INET; /* versus AF_LOCAL */
|
||||
saddr.sin_addr.s_addr = htonl(INADDR_ANY); /* host-to-network endian */
|
||||
saddr.sin_port = htons(PortNumber); /* for listening */
|
||||
|
||||
if (bind(fd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0)
|
||||
report("bind", 1); /* terminate */
|
||||
|
||||
/* listen to the socket */
|
||||
if (listen(fd, MaxConnects) < 0) /* listen for clients, up to MaxConnects */
|
||||
report("listen", 1); /* terminate */
|
||||
|
||||
fprintf(stderr, "Listening on port %i for clients...\n", PortNumber);
|
||||
/* a server traditionally listens indefinitely */
|
||||
while (1) {
|
||||
struct sockaddr_in caddr; /* client address */
|
||||
int len = sizeof(caddr); /* address length could change */
|
||||
|
||||
int client_fd = accept(fd, (struct sockaddr*) &caddr, &len); /* accept blocks */
|
||||
if (client_fd < 0) {
|
||||
report("accept", 0); /* don't terminated, though there's a problem */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read from client */
|
||||
int i;
|
||||
for (i = 0; i < ConversationLen; i++) {
|
||||
char buffer[BuffSize + 1];
|
||||
memset(buffer, '\0', sizeof(buffer));
|
||||
int count = read(client_fd, buffer, sizeof(buffer));
|
||||
if (count > 0) {
|
||||
puts(buffer);
|
||||
write(client_fd, buffer, sizeof(buffer)); /* echo as confirmation */
|
||||
}
|
||||
}
|
||||
close(client_fd); /* break connection */
|
||||
} /* while(1) */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
上面的服务器端程序执行典型的 4 个步骤来准备回应客户端的请求,然后接受其他的独立请求。这里每一个步骤都以服务器端程序调用的系统函数来命名。
|
||||
|
||||
1. `socket(…)` : 为套接字连接获取一个文件描述符
|
||||
2. `bind(…)` : 将套接字和服务器主机上的一个地址进行绑定
|
||||
3. `listen(…)` : 监听客户端请求
|
||||
4. `accept(…)` :接受一个特定的客户端请求
|
||||
|
||||
上面的 `socket` 调用的完整形式为:
|
||||
|
||||
```
|
||||
int sockfd = socket(AF_INET, /* versus AF_LOCAL */
|
||||
SOCK_STREAM, /* reliable, bidirectional */
|
||||
0); /* system picks protocol (TCP) */
|
||||
```
|
||||
|
||||
第一个参数特别指定了使用的是一个网络套接字,而不是 IPC 套接字。对于第二个参数有多种选项,但 `SOCK_STREAM` 和 `SOCK_DGRAM`(数据报)是最为常用的。基于流的套接字支持可信通道,在这种通道中如果发生了信息的丢失或者更改,都将会被报告。这种通道是双向的,并且从一端到另外一端的有效载荷在大小上可以是任意的。相反的,基于数据报的套接字大多是不可信的,没有方向性,并且需要固定大小的载荷。`socket` 的第三个参数特别指定了协议。对于这里展示的基于流的套接字,只有一种协议选择:TCP,在这里表示的 `0`;。因为对 `socket` 的一次成功调用将返回相似的文件描述符,一个套接字将会被读写,对应的语法和读写一个本地文件是类似的。
|
||||
|
||||
对 `bind` 的调用是最为复杂的,因为它反映出了在套接字 API 方面上的各种改进。我们感兴趣的点是这个调用将一个套接字和服务器端所在机器中的一个内存地址进行绑定。但对 `listen` 的调用就非常直接了:
|
||||
|
||||
```
|
||||
if (listen(fd, MaxConnects) < 0)
|
||||
```
|
||||
|
||||
第一个参数是套接字的文件描述符,第二个参数则指定了在服务器端处理一个拒绝连接错误之前,有多少个客户端连接被允许连接。(在头文件 `sock.h` 中 `MaxConnects` 的值被设置为 `8`。)
|
||||
|
||||
`accept` 调用默认将是一个拥塞等待:服务器端将不做任何事情直到一个客户端尝试连接它,然后进行处理。`accept` 函数返回的值如果是 `-1` 则暗示有错误发生。假如这个调用是成功的,则它将返回另一个文件描述符,这个文件描述符被用来指代另一个可读可写的套接字,它与 `accept` 调用中的第一个参数对应的接收套接字有所不同。服务器端使用这个可读可写的套接字来从客户端读取请求然后写回它的回应。接收套接字只被用于接受客户端的连接。
|
||||
|
||||
在设计上,一个服务器端可以一直运行下去。当然服务器端可以通过在命令行中使用 `Ctrl+C` 来终止它。
|
||||
|
||||
#### 示例 2. 使用套接字的客户端
|
||||
|
||||
```c
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
#include "sock.h"
|
||||
|
||||
const char* books[] = {"War and Peace",
|
||||
"Pride and Prejudice",
|
||||
"The Sound and the Fury"};
|
||||
|
||||
void report(const char* msg, int terminate) {
|
||||
perror(msg);
|
||||
if (terminate) exit(-1); /* failure */
|
||||
}
|
||||
|
||||
int main() {
|
||||
/* fd for the socket */
|
||||
int sockfd = socket(AF_INET, /* versus AF_LOCAL */
|
||||
SOCK_STREAM, /* reliable, bidirectional */
|
||||
0); /* system picks protocol (TCP) */
|
||||
if (sockfd < 0) report("socket", 1); /* terminate */
|
||||
|
||||
/* get the address of the host */
|
||||
struct hostent* hptr = gethostbyname(Host); /* localhost: 127.0.0.1 */
|
||||
if (!hptr) report("gethostbyname", 1); /* is hptr NULL? */
|
||||
if (hptr->h_addrtype != AF_INET) /* versus AF_LOCAL */
|
||||
report("bad address family", 1);
|
||||
|
||||
/* connect to the server: configure server's address 1st */
|
||||
struct sockaddr_in saddr;
|
||||
memset(&saddr, 0, sizeof(saddr));
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr =
|
||||
((struct in_addr*) hptr->h_addr_list[0])->s_addr;
|
||||
saddr.sin_port = htons(PortNumber); /* port number in big-endian */
|
||||
|
||||
if (connect(sockfd, (struct sockaddr*) &saddr, sizeof(saddr)) < 0)
|
||||
report("connect", 1);
|
||||
|
||||
/* Write some stuff and read the echoes. */
|
||||
puts("Connect to server, about to write some stuff...");
|
||||
int i;
|
||||
for (i = 0; i < ConversationLen; i++) {
|
||||
if (write(sockfd, books[i], strlen(books[i])) > 0) {
|
||||
/* get confirmation echoed from server and print */
|
||||
char buffer[BuffSize + 1];
|
||||
memset(buffer, '\0', sizeof(buffer));
|
||||
if (read(sockfd, buffer, sizeof(buffer)) > 0)
|
||||
puts(buffer);
|
||||
}
|
||||
}
|
||||
puts("Client done, about to exit...");
|
||||
close(sockfd); /* close the connection */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
客户端程序的设置代码和服务器端类似。两者主要的区别既不是在于监听也不在于接收,而是连接:
|
||||
|
||||
```
|
||||
if (connect(sockfd, (struct sockaddr*) &saddr, sizeof(saddr)) < 0)
|
||||
```
|
||||
|
||||
对 `connect` 的调用可能因为多种原因而导致失败,例如客户端拥有错误的服务器端地址或者已经有太多的客户端连接上了服务器端。假如 `connect` 操作成功,客户端将在一个 `for` 循环中,写入它的响应然后读取返回的响应。在经过会话后,服务器端和客户端都将调用 `close` 去关闭可读可写套接字,尽管其中一个关闭操作已经足以关闭他们之间的连接,但此时客户端可能就此关闭,但正如前面提到的那样,服务器端将一直保持开放以处理其他事务。
|
||||
|
||||
从上面的套接示例中,我们看到了请求信息被返回给客户端,这使得客户端和服务器端之间拥有进行丰富对话的可能性。也许这就是套接字的主要魅力。在现代系统中,客户端应用(例如一个数据库客户端)和服务器端通过套接字进行通信非常常见。正如先前提及的那样,本地 IPC 套接字和网络套接字只在某些实现细节上面有所不同,一般来说,IPC 套接字有着更低的消耗和更好的性能。它们的通信 API 基本是一样的。
|
||||
|
||||
### 信号
|
||||
|
||||
一个信号中断了一个正在执行的程序,在这种意义下,就是用信号与这个程序进行通信。大多数的信号要么可以被忽略(阻塞)或者被处理(通过特别设计的代码)。`SIGSTOP` (暂停)和 `SIGKILL`(立即停止)是最应该提及的两种信号。符号常数拥有整数类型的值,例如 `SIGKILL` 对应的值为 `9`。
|
||||
|
||||
信号可以在与用户交互的情况下发生。例如,一个用户从命令行中敲了 `Ctrl+C` 来从命令行中终止一个程序;`Ctrl+C` 将产生一个 `SIGTERM` 信号。针对终止,`SIGTERM` 信号可以被阻塞或者被处理,而不像 `SIGKILL` 信号那样。一个进程也可以通过信号和另一个进程通信,这样使得信号也可以作为一种 IPC 机制。
|
||||
|
||||
考虑一下一个多进程应用,例如 Nginx 网络服务器是如何被另一个进程优雅地关闭的。`kill` 函数:
|
||||
|
||||
```
|
||||
int kill(pid_t pid, int signum); /* declaration */
|
||||
```
|
||||
bei
|
||||
可以被一个进程用来终止另一个进程或者一组进程。假如 `kill` 函数的第一个参数是大于 `0` 的,那么这个参数将会被认为是目标进程的 pid(进程 ID),假如这个参数是 `0`,则这个参数将会被识别为信号发送者所属的那组进程。
|
||||
|
||||
`kill` 的第二个参数要么是一个标准的信号数字(例如 `SIGTERM` 或 `SIGKILL`),要么是 `0` ,这将会对信号做一次询问,确认第一个参数中的 pid 是否是有效的。这样将一个多进程应用的优雅地关闭就可以通过向组成该应用的一组进程发送一个终止信号来完成,具体来说就是调用一个 `kill` 函数,使得这个调用的第二个参数是 `SIGTERM` 。(Nginx 主进程可以通过调用 `kill` 函数来终止其他 worker 进程,然后再停止自己。)就像许多库函数一样,`kill` 函数通过一个简单的可变语法拥有更多的能力和灵活性。
|
||||
|
||||
#### 示例 3. 一个多进程系统的优雅停止
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
void graceful(int signum) {
|
||||
printf("\tChild confirming received signal: %i\n", signum);
|
||||
puts("\tChild about to terminate gracefully...");
|
||||
sleep(1);
|
||||
puts("\tChild terminating now...");
|
||||
_exit(0); /* fast-track notification of parent */
|
||||
}
|
||||
|
||||
void set_handler() {
|
||||
struct sigaction current;
|
||||
sigemptyset(¤t.sa_mask); /* clear the signal set */
|
||||
current.sa_flags = 0; /* enables setting sa_handler, not sa_action */
|
||||
current.sa_handler = graceful; /* specify a handler */
|
||||
sigaction(SIGTERM, ¤t, NULL); /* register the handler */
|
||||
}
|
||||
|
||||
void child_code() {
|
||||
set_handler();
|
||||
|
||||
while (1) { /` loop until interrupted `/
|
||||
sleep(1);
|
||||
puts("\tChild just woke up, but going back to sleep.");
|
||||
}
|
||||
}
|
||||
|
||||
void parent_code(pid_t cpid) {
|
||||
puts("Parent sleeping for a time...");
|
||||
sleep(5);
|
||||
|
||||
/* Try to terminate child. */
|
||||
if (-1 == kill(cpid, SIGTERM)) {
|
||||
perror("kill");
|
||||
exit(-1);
|
||||
}
|
||||
wait(NULL); /` wait for child to terminate `/
|
||||
puts("My child terminated, about to exit myself...");
|
||||
}
|
||||
|
||||
int main() {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
perror("fork");
|
||||
return -1; /* error */
|
||||
}
|
||||
if (0 == pid)
|
||||
child_code();
|
||||
else
|
||||
parent_code(pid);
|
||||
return 0; /* normal */
|
||||
}
|
||||
```
|
||||
|
||||
上面的停止程序模拟了一个多进程系统的优雅退出,在这个例子中,这个系统由一个父进程和一个子进程组成。这次模拟的工作流程如下:
|
||||
|
||||
* 父进程尝试去 fork 一个子进程。假如这个 fork 操作成功了,每个进程就执行它自己的代码:子进程就执行函数 `child_code`,而父进程就执行函数 `parent_code`。
|
||||
* 子进程将会进入一个潜在的无限循环,在这个循环中子进程将睡眠一秒,然后打印一个信息,接着再次进入睡眠状态,以此循环往复。来自父进程的一个 `SIGTERM` 信号将引起子进程去执行一个信号处理回调函数 `graceful`。这样这个信号就使得子进程可以跳出循环,然后进行子进程和父进程之间的优雅终止。在终止之前子,进程将打印一个信息。
|
||||
* 在 fork 一个子进程后,父进程将睡眠 5 秒,使得子进程可以执行一会儿;当然在这个模拟中,子进程大多数时间都在睡眠。然后父进程调用 `SIGTERM` 作为第二个参数的 `kill` 函数,等待子进程的终止,然后自己再终止。
|
||||
|
||||
下面是一次运行的输出:
|
||||
|
||||
```
|
||||
% ./shutdown
|
||||
Parent sleeping for a time...
|
||||
Child just woke up, but going back to sleep.
|
||||
Child just woke up, but going back to sleep.
|
||||
Child just woke up, but going back to sleep.
|
||||
Child just woke up, but going back to sleep.
|
||||
Child confirming received signal: 15 ## SIGTERM is 15
|
||||
Child about to terminate gracefully...
|
||||
Child terminating now...
|
||||
My child terminated, about to exit myself...
|
||||
```
|
||||
|
||||
对于信号的处理,上面的示例使用了 `sigaction` 库函数(POSIX 推荐的用法)而不是传统的 `signal` 函数,`signal` 函数有轻便性问题。下面是我们主要关心的代码片段:
|
||||
|
||||
* 假如对 `fork` 的调用成功了,父进程将执行 `parent_code` 函数,而子进程将执行 `child_code` 函数。在给子进程发送信号之前,父进程将会等待 5 秒:
|
||||
|
||||
```
|
||||
puts("Parent sleeping for a time...");
|
||||
sleep(5);
|
||||
if (-1 == kill(cpid, SIGTERM)) {
|
||||
...sleepkillcpidSIGTERM...
|
||||
```
|
||||
|
||||
假如 `kill` 调用成功了,父进程将在子进程终止时做等待,使得子进程不会变成一个僵尸进程。在等待完成后,父进程再退出。
|
||||
* `child_code` 函数首先调用 `set_handler` 然后进入它的可能永久睡眠的循环。下面是我们将要查看的 `set_handler` 函数:
|
||||
|
||||
```
|
||||
void set_handler() {
|
||||
struct sigaction current; /* current setup */
|
||||
sigemptyset(¤t.sa_mask); /* clear the signal set */
|
||||
current.sa_flags = 0; /* for setting sa_handler, not sa_action */
|
||||
current.sa_handler = graceful; /* specify a handler */
|
||||
sigaction(SIGTERM, ¤t, NULL); /* register the handler */
|
||||
}
|
||||
```
|
||||
|
||||
上面代码的前三行在做相关的准备。第四个语句将为 `graceful` 设定 handler ,它将在调用 `_exit` 来停止之前打印一些信息。第 5 行和最后一行的语句将通过调用 `sigaction` 来向系统注册上面的 handler。`sigaction` 的第一个参数是 `SIGTERM` ,用作终止;第二个参数是当前的 `sigaction` 设定,而最后的参数(在这个例子中是 `NULL` )可被用来保存前面的 `sigaction` 设定,以备后面的可能使用。
|
||||
|
||||
使用信号来作为 IPC 的确是一个很轻量的方法,但确实值得尝试。通过信号来做 IPC 显然可以被归入 IPC 工具箱中。
|
||||
|
||||
### 这个系列的总结
|
||||
|
||||
在这个系列中,我们通过三篇有关 IPC 的文章,用示例代码介绍了如下机制:
|
||||
* 共享文件
|
||||
* 共享内存(通过信号量)
|
||||
* 管道(有名和无名)
|
||||
* 消息队列
|
||||
* 套接字
|
||||
* 信号
|
||||
|
||||
甚至在今天,在以线程为中心的语言,例如 Java、C# 和 Go 等变得越来越流行的情况下,IPC 仍然很受欢迎,因为相比于使用多线程,通过多进程来实现并发有着一个明显的优势:默认情况下,每个进程都有它自己的地址空间,除非使用了基于共享内存的 IPC 机制(为了达到安全的并发,竞争条件在多线程和多进程的时候必须被加上锁。),在多进程中可以排除掉基于内存的竞争条件。对于任何一个写过甚至是通过共享变量来通信的基本多线程程序的人来说,TA 都会知道想要写一个清晰、高效、线程安全的代码是多么具有挑战性。使用单线程的多进程的确是很有吸引力的,这是一个切实可行的方式,使用它可以利用好今天多处理器的机器,而不需要面临基于内存的竞争条件的风险。
|
||||
|
||||
当然,没有一个简单的答案能够回答上述 IPC 机制中的哪一个更好。在编程中每一种 IPC 机制都会涉及到一个取舍问题:是追求简洁,还是追求功能强大。以信号来举例,它是一个相对简单的 IPC 机制,但并不支持多个进程之间的丰富对话。假如确实需要这样的对话,另外的选择可能会更合适一些。带有锁的共享文件则相对直接,但是当要处理大量共享的数据流时,共享文件并不能很高效地工作。管道,甚至是套接字,有着更复杂的 API,可能是更好的选择。让具体的问题去指导我们的选择吧。
|
||||
|
||||
尽管所有的示例代码(可以在[我的网站][4]上获取到)都是使用 C 写的,其他的编程语言也经常提供这些 IPC 机制的轻量包装。这些代码示例都足够短小简单,希望这样能够鼓励你去进行实验。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/19/4/interprocess-communication-linux-networking
|
||||
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[FSSlc](https://github.com/FSSlc)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/mkalindepauledu
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://en.wikipedia.org/wiki/Inter-process_communication
|
||||
[2]: https://opensource.com/article/19/4/interprocess-communication-ipc-linux-part-1
|
||||
[3]: https://opensource.com/article/19/4/interprocess-communication-ipc-linux-part-2
|
||||
[4]: http://condor.depaul.edu/mkalin
|
Loading…
Reference in New Issue
Block a user