0%

code.visualstudio.com

Using C++ and WSL in VS Code

In this tutorial, you will configure Visual Studio Code to use the GCC C++ compiler (g++) and GDB debugger on Ubuntu in the Windows Subsystem for Linux (WSL). GCC stands for GNU Compiler Collection; GDB is the GNU debugger. WSL is a Linux environment within Windows that runs directly on the machine hardware, not in a virtual machine.

Note: Much of this tutorial is applicable to working with C++ and VS Code directly on a Linux machine.

Visual Studio Code has support for working directly in WSL with the Remote - WSL extension. We recommend this mode of WSL development, where all your source code files, in addition to the compiler, are hosted on the Linux distro. For more background, see VS Code Remote Development.

After completing this tutorial, you will be ready to create and configure your own C++ project, and to explore the VS Code documentation for further information about its many features. This tutorial does not teach you about GCC or Linux or the C++ language. For those subjects, there are many good resources available on the Web.

If you have any problems, feel free to file an issue for this tutorial in the VS Code documentation repository.

Prerequisites#

To successfully complete this tutorial, you must do the following steps:

  1. Install Visual Studio Code.
  2. Install the Remote - WSL extension.
  3. Install Windows Subsystem for Linux and then use the links on that same page to install your Linux distribution of choice. This tutorial uses Ubuntu. During installation, remember your Linux user password because you'll need it to install additional software.

Set up your Linux environment#

  1. Open the Bash shell for WSL. If you installed an Ubuntu distro, type "Ubuntu" in the Windows search box and then click on it in the result list. For Debian, type "Debian", and so on.

    Ubuntu in Start Menu

    The shell appears with a command prompt that by default consists of your user name and computer name, and puts you in your home directory. For Ubuntu it looks like this:

    Bash Shell
  2. Make a directory called projects and then subdirectory under that called helloworld:

    1
    2
    3
    mkdir projects
    cd projects
    mkdir helloworld

  3. Although you will be using VS Code to edit your source code, you'll be compiling the source code on Linux using the g++ compiler. You'll also debug on Linux using GDB. These tools are not installed by default on Ubuntu, so you have to install them. Fortunately, that task is quite easy!

  4. From the WSL command prompt, first run apt-get update to update the Ubuntu package lists. An out-of-date distro can sometimes interfere with attempts to install new packages.

    1
    sudo apt-get update

    If you like, you can run sudo apt-get update && sudo apt-get dist-upgrade to also download the latest versions of the system packages, but this can take significantly longer depending on your connection speed.

  5. From the command prompt, install the GNU compiler tools and the GDB debugger by typing:

    1
    sudo apt-get install build-essential gdb

image-20210419161354074
  1. Verify that the install succeeded by locating g++ and gdb. If the filenames are not returned from the whereis command, try running the update command again.
1
2
whereis g++
whereis gdb

Note: The setup steps for installing the g++ compiler and GDB debugger apply if you are working directly on a Linux machine rather than in WSL. Running VS Code in your helloworld project, as well as the editing, building, and debugging steps are the same.

Run VS Code in WSL#

Navigate to your helloworld project folder and launch VS Code from the WSL terminal with code .:

1
2
cd $HOME/projects/helloworld
code .

You'll see a message about "Installing VS Code Server". VS Code is downloading and installing a small server on the Linux side that the desktop VS Code will then talk to.

image-20210419155740210

VS Code will then start and open the helloWorld folder. The File Explorer shows that VS Code is now running in the context of WSL with the title bar [WSL: Ubuntu].

File Explorer in WSL

You can also tell the remote context from the Status bar.

Remote context in the Status bar

If you click on the Remote Status bar item, you will see a dropdown of Remote commands appropriate for the session.

image-20210419155444952

For example, if you want to end your session running in WSL, you can select the Close Remote Connection command from the dropdown.

Running code . from your WSL command prompt will restart VS Code running in WSL.

The code . command opened VS Code in the current working folder, which becomes your "workspace". As you go through the tutorial, you will see three files created in a .vscode folder in the workspace:

  • c_cpp_properties.json (compiler path and IntelliSense settings)
  • tasks.json (build instructions)
  • launch.json (debugger settings)

Add a source code file#

In the File Explorer title bar, select the New File button and name the file helloworld.cpp.

New File title bar button

Install the C/C++ extension#

Once you create the file and VS Code detects it is a C++ language file, you may be prompted to install the Microsoft C/C++ extension if you don't already have it installed.

C++ extension notification

Choose Install and then Reload Required when the button is displayed in the Extensions view to complete installing the C/C++ extension.

If you already have C/C++ language extensions installed locally in VS Code, you'll need to go to the Extensions view (Ctrl+Shift+X) and install those extensions into WSL. Locally installed extensions can be installed into WSL by selecting the Install in WSL button and then Reload Required.

Install in WSL button

Add hello world source code#

Now paste in this source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main()
{
vector<string> msg {"Hello", "C++", "World", "from", "VS Code", "and the C++ extension!"};

for (const string& word : msg)
{
cout << word << " ";
}
cout << endl;
}

Now press Ctrl+S to save the file. Notice how the file you just added appears in the File Explorer view (Ctrl+Shift+E) in the side bar of VS Code:

File Explorer

You can also enable Auto Save to automatically save your file changes, by checking Auto Save in the main File menu.

The Activity Bar on the far left lets you open different views such as Search, Source Control, and Run. You'll look at the Run view later in this tutorial. You can find out more about the other views in the VS Code User Interface documentation.

若在此处遇到未找到头文件的问题,请参照一下方法。

image-20210419160838596

Explore IntelliSense#

In your new helloworld.cpp file, hover over vector or string to see type information. After the declaration of the msg variable, start typing msg. as you would when calling a member function. You should immediately see a completion list that shows all the member functions, and a window that shows the type information for the msg object:

Statement completion IntelliSense

You can press the Tab key to insert the selected member; then, when you add the opening parenthesis, you will see information about any arguments that the function requires.您可以按Tab键插入所选的成员;然后,当添加左括号时,您将看到关于函数所需的任何参数的信息。

Build helloworld.cpp#

Next, you will create a tasks.json file to tell VS Code how to build (compile) the program. This task will invoke the g++ compiler on WSL to create an executable file based on the source code.

From the main menu, choose Terminal > Configure Default Build Task. In the dropdown, which will display a tasks dropdown listing various predefined build tasks for C++ compilers. Choose g++ build active file, which will build the file that is currently displayed (active) in the editor.

Tasks C++ build dropdown

This will create a tasks.json file in a .vscode folder and open it in the editor.

Your new tasks.json file should look similar to the JSON below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "g++ build active file",
"command": "/usr/bin/g++",
"args": ["-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}"],
"options": {
"cwd": "/usr/bin"
},
"problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

The command setting specifies the program to run; in this case that is g++. The args array specifies the command-line arguments that will be passed to g++. These arguments must be specified in the order expected by the compiler. This task tells g++ to take the active file (${file}), compile it, and create an executable file in the current directory (${fileDirname}) with the same name as the active file but without an extension (${fileBasenameNoExtension}), resulting in helloworld for our example.

Note: You can learn more about tasks.json variables in the variables reference.

The label value is what you will see in the tasks list; you can name this whatever you like.

The "isDefault": true value in the group object specifies that this task will be run when you press Ctrl+Shift+B. This property is for convenience only; if you set it to false, you can still run it from the Terminal menu with Tasks: Run Build Task.

Running the build#

  1. Go back to helloworld.cpp. Your task builds the active file and you want to build helloworld.cpp.

  2. To run the build task defined in tasks.json, press Ctrl+Shift+B or from the Terminal main menu choose Tasks: Run Build Task.

  3. When the task starts, you should see the Integrated Terminal panel appear below the source code editor. After the task completes, the terminal shows output from the compiler that indicates whether the build succeeded or failed. For a successful g++ build, the output looks something like this:

    G++ build output in terminal
  4. Create a new terminal using the + button and you'll have a bash terminal running in the context of WSL with the helloworld folder as the working directory. Run ls and you should now see the executable helloworld (no file extension).

    WSL bash terminal
  5. You can run helloworld in the terminal by typing ./helloworld.

Modifying tasks.json#

You can modify your tasks.json to build multiple C++ files by using an argument like "${workspaceFolder}/*.cpp" instead of ${file}. You can also modify the output filename by replacing "${fileDirname}/${fileBasenameNoExtension}" with a hard-coded filename (for example 'helloworld.out').

Debug helloworld.cpp#

Next, you'll create a launch.json file to configure VS Code to launch the GDB debugger when you press F5 to debug the program. From the main menu, choose Run > Add Configuration... and then choose C++ (GDB/LLDB).

You'll then see a dropdown for various predefined debugging configurations. Choose g++ build and debug active file.

C++ debug configuration dropdown

VS Code creates a launch.json file, opens it in the editor, and builds and runs 'helloworld'.

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
{
"version": "0.2.0",
"configurations": [
{
"name": "g++ build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "g++ build active file",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}

The program setting specifies the program you want to debug. Here it is set to the active file folder ${fileDirname} and active filename without an extension ${fileBasenameNoExtension}, which if helloworld.cpp is the active file will be helloworld.

By default, the C++ extension won't add any breakpoints to your source code and the stopAtEntry value is set to false. Change the stopAtEntry value to true to cause the debugger to stop on the main method when you start debugging.

The remaining steps are provided as an optional exercise to help you get familiar with the editing and debugging experience.

Start a debugging session#

  1. Go back to helloworld.cpp so that it is the active file.
  2. Press F5 or from the main menu choose Run > Start Debugging. Before you start stepping through the code, let's take a moment to notice several changes in the user interface:
  • The Integrated Terminal appears at the bottom of the source code editor. In the Debug Output tab, you see output that indicates the debugger is up and running.

  • The editor highlights the first statement in the main method. This is a breakpoint that the C++ extension automatically sets for you:

    Initial breakpoint
  • The Run view on the left shows debugging information. You'll see an example later in the tutorial.

  • At the top of the code editor, a debugging control panel appears. You can move this around the screen by grabbing the dots on the left side.

    Debugging controls

Step through the code#

Now you're ready to start stepping through the code.

  1. Click or press the Step over icon in the debugging control panel.

    Step over button

    This will advance program execution to the first line of the for loop, and skip over all the internal function calls within the vector and string classes that are invoked when the msg variable is created and initialized. Notice the change in the Variables window on the left.

    Debugging windows

    In this case, the errors are expected because, although the variable names for the loop are now visible to the debugger, the statement has not executed yet, so there is nothing to read at this point. The contents of msg are visible, however, because that statement has completed.

  2. Press Step over again to advance to the next statement in this program (skipping over all the internal code that is executed to initialize the loop). Now, the Variables window shows information about the loop variables.

  3. Press Step over again to execute the cout statement. (Note that as of the March 2019 release, the C++ extension does not print any output to the Debug Console until the loop exits.)

  4. If you like, you can keep pressing Step over until all the words in the vector have been printed to the console. But if you are curious, try pressing the Step Into button to step through source code in the C++ standard library!

    Breakpoint in gcc standard library header

    To return to your own code, one way is to keep pressing Step over. Another way is to set a breakpoint in your code by switching to the helloworld.cpp tab in the code editor, putting the insertion point somewhere on the cout statement inside the loop, and pressing F9. A red dot appears in the gutter on the left to indicate that a breakpoint has been set on this line.

    Breakpoint in main

    Then press F5 to start execution from the current line in the standard library header. Execution will break on cout. If you like, you can press F9 again to toggle off the breakpoint.

    When the loop has completed, you can see the output in the Debug Console tab of the integrated terminal, along with some other diagnostic information that is output by GDB.

    Debug console display

Set a watch#

Sometimes you might want to keep track of the value of a variable as your program executes. You can do this by setting a watch on the variable.

  1. Place the insertion point inside the loop. In the Watch window, click the plus sign and in the text box, type word, which is the name of the loop variable. Now view the Watch window as you step through the loop.

    Watch window
  2. Add another watch by adding this statement before the loop: int i = 0;. Then, inside the loop, add this statement: ++i;. Now add a watch for i as you did in the previous step.

  3. To quickly view the value of any variable while execution is paused on a breakpoint, you can hover over it with the mouse pointer.

    Mouse hover

C/C++ configurations#

If you want more control over the C/C++ extension, you can create a c_cpp_properties.json file, which will allow you to change settings such as the path to the compiler, include paths, C++ standard (default is C++17), and more.

You can view the C/C++ configuration UI by running the command C/C++: Edit Configurations (UI) from the Command Palette (Ctrl+Shift+P).

Command Palette

This opens the C/C++ Configurations page. When you make changes here, VS Code writes them to a file called c_cpp_properties.json in the .vscode folder.

Command Palette

You only need to modify the Include path setting if your program includes header files that are not in your workspace or in the standard library path.

Visual Studio Code places these settings in .vscode/c_cpp_properties.json. If you open that file directly, it should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"configurations": [
{
"name": "Linux",
"includePath": ["${workspaceFolder}/**"],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}

Closing the WSL session#

When you are done working in WSL, you can close your remote session with the Close Remote Connection command available in the main File menu and the Command Palette (Ctrl+Shift+P). This will restart VS Code running locally. You can easily reopen your WSL session from the File > Open Recent list by selecting folders with the [WSL] suffix.

Next steps#

11/22/2019

learningaboutelectronics.com

What is a Bypass Capacitor?

Bypass capacitor

A bypass capacitor is a capacitor that shorts AC signals to ground, so that any AC noise that may be present on a DC signal is removed, producing a much cleaner and pure DC signal.

A bypass capacitor essentially bypasses AC noise that may be on a DC signal, filtering out the AC, so that a clean, pure DC signal goes through without any AC ripple.

For example, you may want a pure DC signal from a power source.

Below is a transistor circuit. A transistor is an active device, so in order to work, it needs DC power. This power source is VCC. In this case, VCC equals 15 volts.

DC Power Source for Transistor

This 15 volts provides power to the transistor so that the transistor can amplify signals. We want this signal to be as purely DC as possible. Although we obtain our DC voltage, VCC, from a DC power source such as a power supply, the voltage isn't always purely DC. In fact, many times the voltage is very noisy and contains a lot of AC ripple on it, especially at the 60Hz frequency because this is the frequency at which AC signals run in many countries.

So although we want a pure DC signal, such as below:

Pure DC Voltage

Many times, we get a noisy signal that looks like:

Noisy DC Voltage Signal

A DC signal such as this is actually very common. This is undesired because it adds noise to the transistor circuit. Therefore, this noisy DC signal will be imposed on the AC signal. So the AC signal which may have music or some type of recording will now have much more noise.

This noise which is on the signal is AC ripple. Many times when using a DC power supply connected to an AC power outlet, it will have some of the AC noise transfer to the DC power voltage. AC ripple can also appear from other sources, so even batteries can produce noise.

To eliminate this AC ripple, we use a bypass capacitor. So our transistor circuit above will have a bypass capacitor added to it:

Bypass Capacitor for a Transistor Circuit

A capacitor is a device that offers a tremendously high resistance for signals of low frequencies. Therefore, signals at low frequencies will not go through them. This is because signals (current) always takes the path of least resistance. Therefore, they will instead go through the resistor, RE. Remember, again, this is for low frequency signals, which is basically DC signals.

However, capacitors offer much less resistance at higher frequencies (AC signals). So AC signals will go through the capacitor and then to gorund. Therefore, DC signals will go through the resistor, RE, while AC signals will go through the capacitor, getting shunted to ground. So AC signals get shunted to ground. This is how we have a clean DC signal across our circuit, while AC noise imposed on it is bypassed to ground.

How a Bypass Capacitor Works

So a bypass capacitor blocks the DC from entering it by the great resistance it offers to the signal but accepts the AC noise that may be on the DC line and shunts or bypasses it to ground. This is how bypass capacitors work.

How to Choose the Value of the Bypass Capacitor

Now that you know conceptually what a bypass capacitor is, the next step is to know how to select the value of the bypass capacitor.

And selecting the value is pretty straightforward.

The value of the bypass capacitor should be at least 1/10th of the resistance across the emitter resistance, RE at the lowest frequency intended to be bypassed.

Because capacitors are reactive devices, they have different resistances to signals based on the signal's frequency. This is referred to as the capacitor's reactance, which can be seen as the resistance it offers. We want the capacitor to have 1/10th of the resistance to the flow of current than what the resistor offers for the frequency signal that we want to bypass.

If you visualize the current moving through the transistor, it can take one of 2 paths once it passes the collector and moves through the emitter. Current can either go the resistor, RE or current can flow through the bypass capacitor. Current always takes the path of least resistance. Therefore, current will take the path of the lower resistance. This is why you want the value of the resistance of the bypass capacitor to be at least 1/10th the value of the emitter resistor or, even better, less than one-tenth. We want the AC current to flow through the least resistance path, which is the bypass capacitor if the correct value is chosen.

However, DC signals do not see it as AC. To DC, the capacitor has infinite resistance. So DC will automatically go through the RE resistor, which offers lower resistance by far to the infinite resistance of the capacitor.

AC, however, does not see infinite resistance for the capacitor. If we choose the value correctly for the capacitor, we can make the capacitor a much lower-resistance path to ground, thus shorting out the AC signal to ground.

So let's go over a practical example of how we would select the bypass capacitor value.

Let's say we want to bypass the lowest possible frequency of 50Hz, because the frequency of AC voltages worldwide are 50-60Hz. Therefore, this frequency can be a very problematic because often there is AC ripple at this frequency.

Remember, when we said we bias the value of the bypass capacitor based on the lowest frequency that we want to bypass. So by selecting the frequency of 50Hz, this blocks frequencies from 50Hz and higher; so it covers 60Hz. As frequency of an AC signal increases, the resistance of the capacitor decreases and decreases with each increase. Therefore, all the frequencies above the frequency value that we choose get bypassed easier and easier. We'll demonstrate this all mathematically.

So we decided we want to bypass AC signals 50Hz or higher to ground.

The typical value of an emitter resistor is 400-500‎Ω. The resistance is kept low so that gain on the transistor isn't lowered too much.

So let's say we choose an emitter resistor of 470‎Ω.

This means that we want the reactance of the capacitor to be one-tenth of 470‎Ω or less, which is 47‎Ω or lower. So this is our target.

The formula for the reactance of a capacitor is, XC= 1/2πfc= 1/2(3.14)(50Hz)(C)=47Ω. Solving for the capacitance, C, we get the value of approximately 67μF. So we need a capacitor of at least 67μF to get a resistance of one-tenth the value of 470Ω resistor.

Since a 67μF capacitor isn't readily available, we can round up to 100μF, which is readily available and easy to obtain. This is even better, because with a larger capacitance, the capacitor offers even less resistance to the AC signal. If we plug a 100μF capacitor into the same capacitor reactance formula, we get XC= 1/2πfc= 1/2(3.14)(50Hz)(100μF)=31.8Ω. This is much lower than 1/10 of the 470Ω resistor that we have in parallel. So it will act effectively to short all AC signals 50Hz or higher to ground to clean up the DC signal.

Even if you wanted, you could increase the capacitance even more to allow for less AC noise on the signal. But a lot of times, this will not be done for cost and size constraints reasons. The larger the size a capacitor is, the more it costs per unit. Also the larger the size of a capacitor, the larger physically is. Therefore, if a company is designing a product, the size of the capacitor could be a problem if there are size constraint issues. The way things are going in electronics, companies want products to be as small and concise as possible. So due to reasons such as these, larger value capacitors won't always be chosen, but theoretically, they would increase the purity of the DC signal, by allowing more AC to ground.

So again, this is a summary of what a bypass capacitor is and how to select the value of them based on the lowest AC signal desired to be filtered out and the value of the resistance in parallel with the capacitor.

You can check out our bypass capacitor calculator to calculate the value of a bypass capacitor based on the input AC signal frequency and the value of the resistor in parallel.

Related Resources

cnblogs.com

Unix domain socket 又叫 IPC(inter-process communication 进程间通信) socket,用于实现同一主机上的进程间通信。socket 原本是为网络通讯设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。 UNIX domain socket 是全双工的,API 接口语义丰富,相比其它 IPC 机制有明显的优越性,目前已成为使用最广泛的 IPC 机制,比如 X Window 服务器和 GUI 程序之间就是通过 UNIX domain socket 通讯的。 Unix domain socket 是 POSIX 标准中的一个组件,所以不要被名字迷惑,linux 系统也是支持它的。

下面通过一个简单的 demo 来理解相关概念。程序分为服务器端和客户端两部分,它们之间通过 unix domain socket 进行通信。

服务器端程序

下面是一个非常简单的服务器端程序,它从客户端读字符,然后将每个字符转换为大写并回送给客户端:

复制代码
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdlib.h>  
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

#define MAXLINE 80

char *socket_path = "server.socket";

int main(void)
{
struct sockaddr_un serun, cliun;
socklen_t cliun_len;
int listenfd, connfd, size;
char buf[MAXLINE];
int i, n;

if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket error");
exit(1);
}

memset(&serun, 0, sizeof(serun));
serun.sun_family = AF_UNIX;
strcpy(serun.sun_path, socket_path);
size = offsetof(struct sockaddr_un, sun_path) + strlen(serun.sun_path);
unlink(socket_path);
if (bind(listenfd, (struct sockaddr *)&serun, size) < 0) {
perror("bind error");
exit(1);
}
printf("UNIX domain socket bound\n");

if (listen(listenfd, 20) < 0) {
perror("listen error");
exit(1);
}
printf("Accepting connections ...\n");

while(1) {
cliun_len = sizeof(cliun);
if ((connfd = accept(listenfd, (struct sockaddr *)&cliun, &cliun_len)) < 0){
perror("accept error");
continue;
}

while(1) {
n = read(connfd, buf, sizeof(buf));
if (n < 0) {
perror("read error");
break;
} else if(n == 0) {
printf("EOF\n");
break;
}

printf("received: %s", buf);

for(i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(connfd, buf, n);
}
close(connfd);
}
close(listenfd);
return 0;
}
复制代码

简单介绍一下这段代码:

1
int socket(int family, int type, int protocol);

使用 UNIX domain socket 的过程和网络 socket 十分相似,也要先调用 socket() 创建一个 socket 文件描述符. family 指定为 AF_UNIX,使用 AF_UNIX 会在系统上创建一个 socket 文件,不同进程通过读写这个文件来实现通信。 type 可以选择 SOCK_DGRAM 或 SOCK_STREAM。SOCK_STREAM 意味着会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM 意味着会提供定长的、不可靠、无连接的通信。 protocol 参数指定为 0 即可。 UNIX domain socket 与网络 socket 编程最明显的不同在于地址格式不同,用结构体 sockaddr_un 表示,网络编程的 socket 地址是 IP 地址加端口号,而 UNIX domain socket 的地址是一个 socket 类型的文件在文件系统中的路径,这个 socket 文件由 bind() 调用创建,如果调用 bind() 时该文件已存在,则 bind() 错误返回。因此,一般在调用 bind() 前会检查 socket 文件是否存在,如果存在就删除掉。 网络 socket 编程类似,在 bind 之后要 listen,表示通过 bind 的地址(也就是 socket 文件)提供服务。 接下来必须用 accept() 函数初始化连接。accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。

客户端程序

下面是客户端程序,它接受用户的输入,并把字符串发送给服务器,然后接收服务器返回的字符串并打印:

复制代码
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdlib.h>  
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#define MAXLINE 80

char *client_path = "client.socket";
char *server_path = "server.socket";

int main() {
struct sockaddr_un cliun, serun;
int len;
char buf[100];
int sockfd, n;

if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
perror("client socket error");
exit(1);
}

// 一般显式调用bind函数,以便服务器区分不同客户端
memset(&cliun, 0, sizeof(cliun));
cliun.sun_family = AF_UNIX;
strcpy(cliun.sun_path, client_path);
len = offsetof(struct sockaddr_un, sun_path) + strlen(cliun.sun_path);
unlink(cliun.sun_path);
if (bind(sockfd, (struct sockaddr *)&cliun, len) < 0) {
perror("bind error");
exit(1);
}

memset(&serun, 0, sizeof(serun));
serun.sun_family = AF_UNIX;
strcpy(serun.sun_path, server_path);
len = offsetof(struct sockaddr_un, sun_path) + strlen(serun.sun_path);
if (connect(sockfd, (struct sockaddr *)&serun, len) < 0){
perror("connect error");
exit(1);
}

while(fgets(buf, MAXLINE, stdin) != NULL) {
write(sockfd, buf, strlen(buf));
n = read(sockfd, buf, MAXLINE);
if ( n < 0 ) {
printf("the other side has been closed.\n");
}else {
write(STDOUT_FILENO, buf, n);
}
}
close(sockfd);
return 0;
}
复制代码

与网络 socket 编程不同的是,UNIX domain socket 客户端一般要显式调用 bind 函数,而不依赖系统自动分配的地址。客户端 bind 一个自己指定的 socket 文件名的好处是,该文件名可以包含客户端的 pid 等信息以便服务器区分不同的客户端。

运行上面的程序

分别把服务器端程序和客户端程序保存为 server.c 和 client.c 文件,并编译:

1
2
$ gcc server.c -o server
$ gcc client.c -o client

先启动服务器端程序,然后启动客户端程序输入字符串并回车:

img

还不错,客户端得到了服务器端返回的大写字符串。接下来看看当前目录下的文件:

img

哈哈,多了两个 socket 文件。

总结

Unix domain socket 主要用于同一主机上的进程间通信。与主机间的进程通信不同,它不是通过 "IP地址 + TCP或UDP端口号" 的方式进程通信,而是使用 socket 类型的文件来完成通信,因此在稳定性、可靠性以及效率方面的表现都很不错。

参考: UNIX Domain Socket IPC [linux] unix domain socket 例子

CDN

内容分发网络(Content Delivery Network,CDN)是建立并覆盖在承载网上,由不同区域的服务器组成的分布式网络。将源站资源缓存到全国各地的边缘服务器,供用户就近获取,降低源站压力。

——阿里云

CDN是构建在现有网络基础之上的智能虚拟网络,依靠 部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。

——百度百科

个人理解,实际部署CDN中最重要的概念即 节点缓存服务器中心平台

  • 节点是指在足够广的范围内识别出合适数量的网络拓扑节点,这些节点往往处在网络所及最远之处的各地,能够有效分摊用户访问对主干网络的压力;
  • 而缓存服务器即“部署在各地的边缘服务器”,是指能够 制定规则 自动识别并缓存数据 以供用户快速访问,分担主干网络压力的实体。
  • 中心平台可以对缓存服务器实现负载均衡、内容分发和调度等功能。

工作原理

假设您的加速域名为www.a.com,接入CDN网络,开始使用加速服务后,当终端用户(北京)发起HTTP请求时,处理流程如下图所示。

  1. 当终端用户(北京)向www.a.com下的指定资源发起请求时,首先向LDNS(本地DNS)发起域名解析请求。
  2. LDNS检查缓存中是否有www.a.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向授权DNS查询。
  3. 当授权DNS解析www.a.com时,返回域名CNAME www.a.tbcdn.com对应IP地址。
  4. 域名解析请求发送至阿里云DNS调度系统,并为请求分配最佳节点IP地址。
  5. LDNS获取DNS返回的解析IP地址。
  6. 用户获取解析IP地址。
  7. 用户向获取的IP地址发起对该资源的访问请求。
    • 如果该IP地址对应的节点已缓存该资源,则会将数据直接返回给用户,例如,图中步骤7和8,请求结束。
    • 如果该IP地址对应的节点未缓存该资源,则节点向源站发起对该资源的请求。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,例如,图中的北京节点,并返回给用户,请求结束。配置缓存策略的操作方法,请参见缓存配置

img

——阿里云

应用场景

场景概述

CDN的业务使用场景分为 静态内容加速动态内容加速安全加速

静态内容(静态资源)

静态内容是指在不同请求中访问到的数据都相同的静态文件。例如:图片、视频、网站中的文件(html、css、js)、软件安装包、apk文件、压缩包文件等。

CDN加速的本质是 缓存加速。将您服务器上存储的静态内容缓存在阿里云CDN节点上,当您访问这些静态内容时,无需访问服务器源站,就近访问阿里云CDN节点即可获取相同内容。从而达到加速的效果,同时减轻服务器源站的压力。

动态内容(动态资源)

动态内容是指在不同请求中访问到的数据不相同的动态内容。例如:网站中的文件(asp、jsp、php、perl、cgi)、API接口、数据库交互请求等。

当您访问这些动态内容时,每次都需要访问您的服务器,由服务器动态生成实时的数据并返回给您。因此CDN的缓存加速不适用于加速动态内容,CDN无法缓存实时变化的动态内容。对于动态内容请求,CDN节点只能转发回您的服务器源站,没有加速效果。

如果您的网站或App应用有较多动态内容,例如需要对各种API接口进行加速,则需要使用 阿里云全站加速 产品。全站加速能同时加速动态和静态内容,加速方式如下:

  • 静态内容使用CDN加速。
  • 动态内容通过阿里云的 路由优化传输优化 等动态加速技术以最快的速度访问您的服务器源站获取数据。从而达到全站加速的效果。

——阿里云

全站加速

全站加速(Dynamic Route for Content Delivery Network)是阿里云自主研发的融合了动态加速和静态加速技术的CDN产品。该产品一站式解决了页面动静态资源混杂、跨运营商、网络不稳定、单线源站、突发流量、网络拥塞等诸多因素导致的响应慢、丢包、服务不稳定的问题,提升全站性能和用户体验。

工作原理

阿里云全站加速通过智能路由区分客户请求内容,实现动静态加速。通过架构图,您可以了解全站加速的工作原理。

  • 智能区分动静态内容:域名接入阿里云全站加速后,通过域名访问的动静态内容将被智能识别并区分。
  • 动静态内容同时加速:静态内容使用阿里云CDN加速,缓存在CDN节点上,供您就近访问。动态内容通过 智能路由优化协议优化 等动态加速技术快速回源获取。

全站加速与CDN对比

image-20210414100053078

SSH

SSH is a program for logging into a remote machine and for excuting commands on a remote machine.

It's intended to provide secure encrypted communications between two untrusted hosts over and insecure network.

X11 connections, arbitrary TCP ports and UNIX-domain sockets can also be forwarded over the secure channel.

Authentication

The OpenSSH SSH client supports SSH protocol 2.

Five available authentication methods: GSSAPI-based / host-based / public key / challenge-response and password.

Host-based

If the machine the user logs in from is listed in /etc/hosts.equiv or /etc/ssh/shosts.equiv on the remote machine, the user is non-root and the user names are the same on both sides, or if the files ~/.rhosts or ~/.shosts exist in the user's home directory on the remote machine and contain a line containing the name of the client machine and the name of the user on that machine, the user is considered for login. Additionally, the server must be able to verify the client's host key (see the description of /etc/ssh/ssh_known_hosts and ~/.ssh/known_hosts, below) for login to be permitted. This authentication method closes security holes due to IP spoofing, DNS spoofing, and routing spoofing.

[Note to the administrator: /etc/hosts.equiv, ~/.rhosts, and the rlogin/rsh protocol in general, are inherently insecure and should be disabled if security is desired.]

Public key

The scheme is based on public-key cryptography, using cryptosystems where encryption and decryption are done using separate keys, and it is unfeasible to derive the decryption key from the encryption key. The idea is that each user creates a public/private key pair for authentication purposes.

The server knows the public key, and only the user knows the private key. ssh implements public key authentication protocol automatically, using one of the DSA, ECDSA, Ed25519 or RSA algorithms. The HISTORY section of ssl(8) [i] on non-OpenBSD systems contains a brief discussion of the DSA and RSA algorithms.

The file ~/.ssh/authorized_keys lists the public keys that are permitted for logging in. When the user logs in, the ssh program tells the server which key pair it would like to use for authentication. The client proves that it has access to the private key and the server checks that the corresponding public key is authorized to accept the account.

The user should then copy the public key to ~/.ssh/authorized_keys in his/her home directory on the remote machine. The authorized_keys file corresponds to the conventional ~/.rhosts file, and has one key per line, though the lines can be very long. After this, the user can log in without giving the password.

ssh-keygen

To generate a public key for ssh, we need to use:

1
ssh-kegen -t rsa

The terminal will ask if /root/.ssh/id_rsa the file you save the key:

1
2
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):

The terminal will require you to enter the passphrase twice.

1
2
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

Identification is the private key used by the server(the remote machine). And the public key is generated for the client to authenticate while logging in the server.

1
2
3
4
5
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256: *******
The key's randomart imge is:

The user creates his/her key pair by running ssh-keygen(1).

This stores the private key in ~/.ssh/id_dsa (DSA), ~/.ssh/id_ecdsa (ECDSA), ~/.ssh/id_ecdsa_sk (authenticator-hosted ECDSA), ~/.ssh/id_ed25519 (Ed25519), ~/.ssh/id_ed25519_sk (authenticator-hosted Ed25519), or ~/.ssh/id_rsa (RSA) .

The the public key stores in ~/.ssh/id_dsa.pub (DSA), ~/.ssh/id_ecdsa.pub (ECDSA), ~/.ssh/id_ecdsa_sk.pub (authenticator-hosted ECDSA), ~/.ssh/id_ed25519.pub (Ed25519), ~/.ssh/id_ed25519_sk.pub (authenticator-hosted Ed25519), or ~/.ssh/id_rsa.pub (RSA) in the user's home directory.

Check and copy your .pub file to the local computer.

1
2
cd ~/.ssh/
vim id_rsa.pub
Permissions 0644

image-20210423135807684

1
chmod 0600 ~/.ssh/id_rsa

Login Format

user@hostname : root@qq.com

ssh://root@hostname:port : ssh://root@qq.com:666

SSL

基于 SSL 证书,可将站点由 HTTP(Hypertext Transfer Protocol)切换到 HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer),即基于安全套接字层(SSL)进行安全数据传输的加密版 HTTP 协议。

SSL优势

  • 防流量劫持:全站 HTTPS 是根治运营商、中间人流量劫持的解决方案,不仅可以杜绝网页中显示的小广告,更可以保护用户隐私安全。

  • 提升搜索排名:采用 HTTPS 可以帮忙搜索排名的提升,提高站点的可信度和品牌形象。

  • 杜绝钓鱼网站:HTTPS 地址栏绿色图标可以帮助用户识别出钓鱼网站,保障用户和企业的利益不受损害,增强用户信任。

SSL证书签发

域名型证书由以下品牌提供自动审核认证,快速签发。

  • SecureSite:全球最大的信息安全厂商和服务商,最权威的数字证书颁发机构,为企业、个人用户和服务供应商提供广泛的内容和网络安全解决方案,全球500强中有93%选择了 VeriSign SSL 数字证书,目前均由 SecureSite 提供服务。
  • TrustAsia®(亚洲诚信):亚数信息科技(上海)有限公司应用于信息安全领域的品牌,是 SecureSite 的白金合作伙伴,专业为企业提供包含数字证书在内的所有网络安全服务。
  • GeoTrust:GeoTrust 是全球第二大数字证书颁发机构(CA),也是身份认证和信任认证领域的领导者,该公司各种先进的技术使得任何大小的机构和公司都能安全地低成本地部署 SSL 数字证书和实现各种身份认证。从2001年成立到2006年占领全球市场25%的市场份额,VeriSign 于2006年5月 - 2006年9月以1.25亿美元收购 GeoTrust,目前也同为 SecureSite 旗下 SSL 证书的性价比高的品牌。
  • GlobalSign: GlobalSign 成立于1996年,是一家声誉卓著,备受信赖的 CA 中s心和 SSL 数字证书提供商,在全球总计颁发超过2000万张数字证书。GlobalSign 的专业实力获得中国网络市场众多服务器、域名注册商、系统服务供应商的青睐,成为其数字证书服务的合作伙伴。
  • WoTrus(沃通):沃通电子认证服务有限公司(WoTrus CA Limited)是同时获得国内电子认证服务许可证(由工信部颁发)和通过国际认证的证书颁发机构(CA)。专业为企业提供权威第三方数字身份认证服务,颁发全球信任的各种数字证书产品。
  • DNSPod 品牌国密标准(SM2)证书:DNSPod 为腾讯云自有品牌,采用国密标准,并且是纯国产数字证书,由国内知名 CA 机构提供基础设置支撑,敏捷高效,同时满足国家监管需求。

SSL证书品牌差异

不同品牌的证书在浏览器地址栏、加密强度、赔付保障上均存在差异,最重要的差异点在于根证书。

例如,GeoTrust 通配符是 GeoTrust 根证书签发的,而 SecureSite 通配符是 SecureSite 根证书签发的。Digicert 根证书可以兼容市面上所有的浏览器,对移动端的支持也是最好的,而 Trustasia 通配符也是 Digicert 根证书签发的,GlobalSign 通配符是 GlobalSign 的根证书签发的,DNSPod 是由 Wotrus 的根证书签发的,Wotrus 通配符是 Sectigo 的根证书签发的。

通配符:

根证书:

单纯从技术角度,SecureSite(原 Verisign)和 GeoTrust 的区别如下:

  • 算法支持上 SecureSite(支持 RSA、DSA、ECC 三种算法)优于 GeoTrust(支持 RSA、DSA 两种算法)。
  • 兼容性 SecureSite 优于 GeoTrust,SecureSite 可兼容市面上所有的浏览器,对移动端的支持也是极好的。
  • OCSP 响应速度上 SecureSite 优于 GeoTrust。
  • CA 安全性方面 SecureSite 优于 GeoTrust,SecureSite 是国际知名安全厂商,CA 的安全级别也是国际第一的安全系数。
  • SecureSite 证书除实现加密传输以外,还另外有恶意软件扫描和漏洞评估的附加功能。
  • SecureSite 对证书有商业保险赔付保障,金额最高为175万美金,GeoTrust 最高为150万美金。

SSL证书格式

SSL证书分为 pemkey 这两种格式,分别存储的是 证书base64加密私钥base64加密 还有 格式分割符,也就是说pem存的是证书,key 存的是私钥。

如pem中的内容:

1
2
3
-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----

-----BEGIN CERTIFICATE----------END CERTIFICATE----- 为分割分,表示在这两个中间存的是证书的base64编码

备注:

如key中的内容:

1
2
3
-----BEGIN RSA PRIVATE KEY-----

-----END RSA PRIVATE KEY-----

-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY----- 为分割符号,表示在这两个中间存的是私钥的base64编码

备注:CERTIFICATE 单词代表证书的意思;PRIVATE KEY 单词代表 私钥、秘钥的意思。

SSL证书配置

以下以“阿里云OSS"及“腾讯云COS”的对象存储(bucket)与域名(name)绑定过程中,对SSL证书的配置为例,进行解释和说明,不对详细步骤进行描述,仅解释原理。

域名及Bucket绑定

阿里云及腾讯云都可以申请对象存储和域名,所申请的域名按正规流程均需要经过挂载至某个服务器进行备案(国内公安要求)后才可以与对象存储绑定,进而颁发证书给挂载至对象存储的域名。

SSL证书申请及配置

若域名互相绑定后未配置SSL则会导致https访问失败,配置SSL需要给自己的域名申请免费SSL证书,阿里云及腾讯云都可以免费申请为期一年的证书,按流程填写信息并等待审核下发即可;获得证书后需要选择“其他”类型的证书进行下载。证书中需要的 公钥.pem 格式文件,而 私钥.key 格式文件,分别填写到Bucket的域名管理页面内下的 证书上传 中。

流程大致如下:

graph LR
    开始 --> Bucket申请--可选CDN加速开启--> 获得Bucket域名或CDN加速域名 --CNAME解析-->域名绑定-->结束
    开始 --> 域名申请 --> 域名备案 --CNAME解析--> 域名绑定
    域名申请 --> SSL证书申请 --> 下载证书 --复制粘贴至Bucket的域名管理页面--> 证书上传--> 结束

DDos

常见流量攻击

SYN Flood

Flood即洪水之意,SYN是指TCP通信中用于建立连接时标志位之一。

此时需要用到 TCP Three-way Handshake的知识点,当需要建立连接时:

  • Client向Server 发去SYN标志
  • Server收到后向Client 回复SYN和ACK标志
  • Client收到由Server发过来的SYN和ACK后 回复一个ACK标志

以上即为三次握手协议的主要流程,其实只有Server收到该ACK时,才标志着双方建立连接。以下介绍几种”意外“情况。

情况一:当Client第一次收到SYN和ACK时,Client就单方面“认为”自己已经和Server建立连接了。但网络通信时并不能百分百保证可靠,假如Client发了ACK,但Server并未收到,则Server就并不”认为“自己与Client建立了连接。Server和Client都有自己的定时器,在发送数据之后就开始计时,不管如何,此时Client都已经发了ACK了,“觉得”自己已经建立了连接,而Server不然,Server就会再次发送SYN和ACK以请求Client的确认。

情况二:多个Client向Server同时发送SYN请求,Server收到信息后立即回复SYN和ACK,并且需要为每一个Client设立一个计时器以等待他们的ACK信息。假如此刻Client的数量非常多且每一个都发送了一次SYN请求,则Server的计时器则会立刻被用光,导致任何正常请求都无法连接。(针对此种攻击方式,提出了二次SYN当成一次SYN的防攻击手段,即Client需要连续发两次SYN在标明自己是真的“有意”连接,不是恶意发送攻击的,但此类二次验证方式比较简单,仍然容易被模仿)

ICMP Flood

ICMP也算是网络层协议的一员,封装在IP协议中,是IP协议的附属协议,可以直接被用户进程直接使用。

此处的ICMP Flood指的是利用集群设备在同一时间使用 ping 功能对目标主机发起请求,强制主机进行回复,致使目标主机瘫痪。

以下为ping的英文解释

PING - Send ICMP ECHO_REQUEST to network hosts

Ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to elicit an ICMP ECHO_RESPONSE from a host or gateway.

—— from 'manual of ping' in linux

选择数据库服务器的五个原则

  1)高性能原则

保证所选购的服务器,不仅能够满足运营系统的运行和业务处理的需要,而且能够满足一定时期业务量的增长。一般可以根据经验公式计算出所需的服务器TpmC值(Tpmc是衡量计算机系统的事务处理能力的程序),然后比较各服务器厂商和TPC组织公布的TpmC值,选择相应的机型。同时,用服务器的市场价/报价除去计算出来的TpmC值得出单位TpmC值的价格,进而选择高性能价格比的服务器。

结论:服务器处理器性能很关键,CPU的主频要高,要有较大的缓存

  2)可靠性原则

可靠性原则是所有选择设备和系统中首要考虑的,尤其是在大型的、有大量处理要求的、需要长期运行的系统上。考虑服务器系统的可靠性,不仅要考虑服务器单个节点的可靠性或稳定性,而且要考虑服务器与相关辅助系统之间连接的整体可靠性,如:网络系统、安全系统、远程打印系统等。在必要时,还应考虑对关键服务器采用集群技术,如:双机热备份或集群并行访问技术,甚至采用可能的完全容错机。

结论:服务器要具备冗余技术,同时像硬盘、网卡、内存、电源此类设备要以稳定耐用为主,性能其次。

  3)可扩展性原则

保证所选购的服务器具有优秀的可扩展性原则。因为服务器是所有系统处理的核心,要求具有大数据吞吐速率,包括:I/O速率和网络通讯速率,而且服务器需要能够处理一定时期的业务发展所带来的数据量,需要服务器能够在相应时间对其自身根据业务发展的需要进行相应的升级,如:CPU型号升级、内存扩大、硬盘扩大、更换网卡、增加终端数目、挂接磁盘阵列或与其他服务器组成对集中数据的并发访问的集群系统等。这都需要所选购的服务器在整体上具有一个良好的可扩充余地。一般数据库和计费应用服务器在大型计费系统的设计中就会采用集群方式来增加可靠性,其中挂接的磁盘存储系统,根据数据量和投资考虑,可以采用DAS、NAS或SAN等实现技术。

结论:服务器的IO要高,否则在CPU和内存都是高性能的情况下,会出现瓶颈。除此之外,服务器的扩展性要好,为的是满足企业在日后发展的需要。

  4)安全性原则

服务器处理的大都是相关系统的核心数据,其上存放和运行着关键的交易和重要的数据。这些交易和数据对于拥有者来说是一笔重要的资产,他们的安全性就非常敏感。服务器的安全性与系统的整体安全性密不可分,如:网络系统的安全、数据加密、密码体制等。服务器需要在其自身,包括软硬件,都应该从安全的角度上设计考虑,在借助于外界的安全设施保障下,更要保证本身的高安全性。

结论:首先从服务器的材料上来说要具备高硬度高防护性等条件,其次服务器的冷却系统和对环境的适应能力要强,这样才能够在硬件上满足服务器安全的要求。

  5)可管理性原则

服务器既是核心又是系统整体中的一个节点部分,就像网络系统需要进行管理维护一样,也需要对服务器进行有效的管理。这需要服务器的软硬件对标准的管理系统支持,尤其是其上的操作系统,也包括一些重要的系统部件。

结论:尽量选择支持系统多的服务器,因为服务器兼容的系统越多,你就可以拥有更大选择空间。

总结:首先数据库服务器的性能要求很高,所以在CPU,内存,以及硬盘等方面都有很高的要求,其次是存储,存储要具备良好的稳定性,来满足长期运作的服务器随时读取写入等操作不会出现错误。最后希望通过总结的以上五点,帮助你挑选你所需要的数据库服务器。

服务器产品结构

U是厚度要求,是一种表示服务器外部尺寸的单位,是unit的缩略语,详细的尺寸由作为业界团体的美国电子工业协会(EIA)所决定。 1U=4.445cm , 1U至7U的产品结构是指外形满足EIA规格、厚度为4.445cm-31.115cm的服务器。

多少U是指服务器的尺寸大小。在专业机房,托管的服务器一般是放在机柜里面的,机柜从上到下有很多单位格,我们叫一格为1U(U是unit的意思)。如果一个机柜是42U高,如果每2个服务器中间留1U空间,那么可以放21个1U的主机。同样情况放2U主机就只能放14台了。

很多机房托管费用会按所占U的多少来收费的。

NAT

NAT(Network Address Translation,网络地址转换)是1994年提出的。

当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。 这种方法需要在专用网(私网IP)连接到因特网(公网IP)的路由器上 安装NAT软件 。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址(公网IP地址)。

这样,所有使用本地地址(私网IP地址)的主机在和外界通信时,都要在 NAT路由器 上将其本地地址转换成全球IP地址,才能和因特网连接。 另外,这种通过使用少量的全球IP地址(公网IP地址)代表较多的私有IP地址的方式,将有助于减缓可用的IP地址空间的枯竭。在RFC 2663中有对NAT的说明。

举例

在内网下 192.168.0.100 这台电脑上开了一个http网站服务,那么端口默认是 80,在内网下你直接通过浏览器输入 http://192.168.0.100 直接打开网站,这个内网链接地址在外是打不开的。 通过内网穿透后平台 分配 一个公网地址(给内网的设备),比如 http://test123k.nat.nsloop.com 用户在外时就可以通过这个公网地址打开网站。

要使用内网穿透服务,需要先确定好 内网要映射的IP和端口 ,穿透成功后内网的IP+端口,映射成为公网的域名+端口(如需要IP,可以在CMD命令下PING 服务器的IP地址) 穿透前: 访问IP地址 192.168.0.100 端口 3389 穿透后:访问地址 s0.nsloop.com 端口 12843

内网穿透是通过服务器中继转发数据来实现的将内网端口映射到公网,速度上没有P2P直连的快。

异地组网

内网穿透

内网穿透(Intranet penetration),也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包 不被 NAT 设备屏蔽而正确路由到内网主机

UDP 内网穿透的实质是利用路由器上的NAT 系统。NAT 是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。NAT可以完成重用地址,并且对于内部的网络结构可以实现对外隐蔽

audiouniversityonline.com

In this post, you’ll learn the difference between microphone level and line level, as well as other levels commonly used in professional audio such as instrument level and speaker level.

What is the difference between microphone level and line level?

Microphones and instruments output very low signal voltages, while +4dBu is the line level is the standard voltage level for professional audio equipment.

In pro audio, you’ll generally be dealing with four types of audio signals: Mic Level, Instrument Level, Line Level, and Speaker Level

Microphone Level

A microphone captures sound by converting pressure changes in the air into electrical currents in a wire. The electrical currents created by these pressure changes are very subtle. That’s why we use a microphone preamp – to amplify the signal to a more usable level.

A microphone preamp takes in a mic level signal, amplifies it, and outputs a line level signal. This is controlled by the gain knob on your mixing console, audio interface, or outboard mic pre.

img

Instrument Level

The pickups of an electric guitar convert the vibrations of the strings into electrical currents. Similar to those from a microphone, the electrical currents from a guitar pickup are very weak.

A preamp can also be used to boost instrument level signals to line level.

img

Once an input signal is brought up to line level, it is optimized for use with professional audio equipment, such as mixing consoles, outboard effects, and amplifiers.

img

Professional vs Consumer Line Level

There are two standards for line level: +4 dBu (professional) and -10 dBV (consumer).

Watch this video to learn the difference between professional and consumer line level. I also wrote a post on professional vs consumer audio levels that will help you understand the difference.

Speaker Level

Line level is adequate for sending signals between devices, but not strong enough to power a speaker.In order to power a speaker, the line level signal needs to be amplified again.

This can be done with a power amplifier. A power amp takes in a line level signal, amplifies it, and outputs a speaker level signal that is strong enough to power a speaker.

img

A Complete Audio System

In a complete system, you might run a microphone through a preamp and an electric guitar through another preamp.

Once those signals are at line level, you can send them through outboard effects and eventually to an amplifier, which will add enough gain to the signal to power a speaker.

img

面向对象编程

对象的抽象

抽象 是指一种归纳或总结,对象 是现实世界物体特征的实体。万事万物不论大小皆可看做对象, 则是对各种不同对象的 归纳总结,类是对象的 抽象表示形式

例如,男演员Jack可以看做是一个对象,女演员Rose也可以看过是一个对象,而两者会被统一归纳为Person或者Human的类(或抽象)。每一个对象都有其独特的属性(Property)或功能(Function),而归纳总结出来的类则具有不同对象的相同属性或功能。

基于类的面向对象语言是面向对象世界里的主流。虽然大多数面向对象开发语言都使用类来完成面向对象编程,但类不是面向对象编程的实质内涵。面向对象的实质内涵是将所有业务逻辑单元都视为一个对象(即,对象是目的或结果),且类不是唯一用来完成面向对象编程的方法。

面向对象不能被当做面向类,否则会进入误区。对象和类的关系相当于一般程序设计语言中的 变量变量类型 的关系。所以,有时类也被称为是一种数据类型,可以看做抽象数据类型的具体实现。此时的数据类型则是 数据 操作 的集合。

面向对象与面向过程的区别

面向过程程序设计,即结构化程序设计,诸如Pascal、C。

面向对象程序设计解决了结构化程序设计代码复用的难题,如C++、C#、Java、JavaScript、Python等都是。

image-20210413144641711

面向过程也是把程序定义为“数据+作用于数据的操作算法”,但最重要的区别是:面向过程编程 使用过程操作数据结构, 而面向对象编程将过程和数据结构捆绑,使对象 操作自己的数据结构

JavaScript的类

类的创建

JavaScript可以用关键字 class 进行赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}

// 方法一:参数赋值法,间接调用构造器赋值
var oRectangle = new Rectangle(10, 20);

// 方法二:生成对象后单独调用赋值
// var oRectangle = new Rectangle();
// oRectangle.height = 10;
// oRectangle.width = 30;

console.log(oRectangle.height, oRectangle.width)

在MDN Web Docs中,对JavaScript类的定义如下:

实际上,类是“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式 类声明

1
2
3
4
5
6
7
8
function HelloWrold(){    //To use keyword `function` instead of `class` to define a class
this.printInfo = function(){
return "Hi! JavaScript!\n";
}
}
var oHelloWorld = new HelloWorld(); // Make a new object of the class named `HelloWorld`
var result = oHelloWorld.printInfo(); //Call the method of the object to assign
document.write(result);

构造器

构造器(Constructor),或称 构造方法,一种用于创建和初始化class创建的对象的特殊 方法(Methods)constructor([arguments]) { ... }, 其中 arguments 看情况而定,可以省略。

注意

  1. 在一个类中只能有一个名为 constructor 的特殊方法。 一个类中出现多次构造函数 (constructor)方法将会抛出一个 SyntaxError 错误。
  2. 在一个构造方法中可以使用 super 关键字来 调用一个父类的构造方法
  3. 如果没有显式指定构造方法,则会添加默认的 constructor 方法。
1
2
3
4
5
6
7
8
9
10
11
class Polygon {
constructor(arg0, arg1) {
this.name = 'Polygon';
this.body = arg0 + arg1;
}
}

const poly1 = new Polygon(10,20);

console.log(poly1.name, poly1.body);
// expected output: "Polygon" 30

多核开发涉及多核通信及任务分配管理的问题,任何多核通信都需要针对具体芯片的型号及系统特性去设计。多核通信包括资源共享、竞争、同步、异步等问题;多核任务分配则关系着各核心任务均衡和RTOS系统能否及时响应的问题。

1. 基础知识

并发、并行、异步、同步、共享、互斥、进程、线程

Concurrency 并发

仅表示计算机可以同时执行多项任务,以至于如何实现“同时”执行,则有许多不同形式。

如,单核处理器可以通过分配时间片,轮询任务来达到多任务并发。系统让一个任务运行一段时间,在切换到另一个任务运行,如此循环往复,此过程也被称为 线程的上下文切换(Context Switching)

Parallelism 并行

多个任务于同一时刻在不同的和核心上进行处理,称为并行

Synchronization 同步

指程序任务间的先后关系,后面一个程序必须等前一个任务执行完毕方可启动。因此,在同步中,并无并发或并行概念

Asynchronization 异步

指不同的任务之间不会相互等待

对于I/O资源访问频繁的系统,宜使用异步编程,

Mutual Exclusion 互斥

程序内存开销 及 线程切换开销

堆、栈

题目所指的 heap 和 stack 在 C++ 标准中相对的术语分别是自由存储(free store,即用new创建对象时所分配的空间)和自动变量(automatic variable,或称为局部变量,不要与 C++11 的auto混淆)。

编程角度,要分开两者,是因为两者的生命周期不一样。

如果只需要在作用域内维持变量的生命周期,最好就用自动变量,这样是最简单方便高效的。其他情况可考虑用自由存储、静态局部/全局变量,或类的(静态)成员变量。它们各有不同特点,不在此答案详述。另外,由于 C++ 不支持可变长数组(VLA),不可以定义动态长度的自动变量(成员变量也不行),这个情况下也需要用 new[] 来创建动态长度的数组。

自动变量会在作用域(如函数作用域、块作用域等)结束后析构、释放内存。因为分配和释放的次序是刚好完全相反的,所以可用到堆栈先进后出(first-in-last-out, FILO)的特性,而 C++ 语言的实现一般也会使用到调用堆栈(call stack)来分配自动变量(但非标准的要求)。 自由存储可以在函数结束后继续生存,所以也需要配合 delete 来手动析构、释放内存(也可使用智能指针避免手动 delete)。由于分配和释放次序没有限制,不能使用堆栈这种数据结构做分配,实现上可能采用自由链表(free list)或其他动态内存分配机制。

,英文是 heap,在内存管理的语境下,指的是动态分配内存的区域。这个堆跟数据结构里的堆不是一回事。这里的内存,被分配之后需要手工释放,否则,就会造成内存泄漏。

C++ 标准里一个相关概念是自由存储区(free store),特指使用 newdelete 来分配和释放内存的区域。一般而言,free store是堆(heap)的一个子集,原因如下:

  • newdelete 操作的区域是 free store;mallocfree 操作的区域是 heap
  • newdelete 通常底层使用 mallocfree 来实现

,英文是 stack,在内存管理的语境下,指的是函数调用过程中产生的本地变量和调用数据的区域。这个栈和数据结构里的栈高度相似,都满足“后进先出”(last-in-first-out 或 LIFO)。

RAII,完整的英文是 Resource Acquisition Is Initialization,是 C++ 所特有的资源管理方式。有少量其他语言,如 D、Ada 和 Rust 也采纳了 RAII,但主流的编程语言中, C++ 是唯一一个依赖 RAII 来做资源管理的。

RAII 依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理。对 RAII 的使用,使得 C++ 不需要类似于 Java 那样的垃圾收集方法,也能有效地对内存进行管理。RAII 的存在,也是垃圾收集虽然理论上可以在 C++ 使用,但从来没有真正流行过的主要原因。

嵌入式的设备如DSP上的栈空间是Kb级别,在函数内定义数组或申请空间都不能像linux下那样直接定义和申请,要么定义成全局的,要么指向一块划分好的空间,否则就会造成覆盖代码段等的问题。

DSP的所有变量,函数,以及程序员定义的地址都保存在这三片空间上,程序员在定义变量时,若没有特殊规定,则编译器自动把变量分配到可读写空间上的任意位置,所以当程序员使用 int *p = 0x00810000 ;这种语法的时候,很有可能会覆盖掉程序保存变量和函数的空间,导致程序运行异常,因此需要一个 .cmd 文件来约束,哪些地方用来给程序员自己定义变量地址用,哪些地方用来给程序为变量和函数申请内存来用。

2. 多核通信模块

IPC Modules can be used in a variety of combinations.

以上即是说,各类IPC模块可以根据需要进行组合混用。

IPC以独立插件的形式进行安装与使用,使用时可能需要手动挂载至项目属性中。

Here are some introductions about heap in the <SPRUEX3K.pdf> as follows:

SYS/BIOS provides the following Heap implementations: - HeapMem. Allocate variable-size blocks. Section 6.8.1 - HeapBuf. Allocate fixed-size blocks. Section 6.8.2 - HeapMultiBuf. Specify variable-size allocation, but internally allocate from a variety of fixed-size blocks. Section 6.8.3

Module Module Path
GateMP(门) GateMP Manages gates for mutual exclusion of shared resources by multiple processors and threads.
See Section 2.6.
HeapBufMP(堆缓存) ti.sdo.ipc.heaps.HeapBufMP Fixed-sized shared memory Heaps. Similar to SYS/BIOS’s ti.sysbios.heaps.HeapBuf module, but with some configuration differences.
See Section 2.5.
HeapMemMP(堆储存) ti.sdo.ipc.heaps.HeapMemMP Variable-sized shared memory Heaps.
See Section 2.5.
HeapMultiBufMP(堆混合缓存) ti.sdo.ipc.heaps.HeapMultiBufMP Multiple fixed-sized shared memory Heaps.
See Section 2.5.
Ipc(核间通信) ti.sdo.ipc.Ipc Provides Ipc_start() function and allows startup sequence configuration.
See Section 2.2.
ListMP(列表) ti.sdo.ipc.ListMP Doubly-linked list for shared-memory, multi-processor applications. Very similar to the ti.sdo.utils.List module.
See Section 2.4.
MessageQ (Q报文) ti.sdo.ipc.MessageQ Variable size messaging module. 可拥有不同大小的信息模块。
See Section 2.3.
TransportShm(运输表) ti.sdo.ipc.transports.TransportShm Transport used by MessageQ for remote communication with other processors via shared memory.
See Section 2.3.11.
Notify (通知) ti.sdo.ipc.Notify Low-level interrupt mux/demuxer module.
See Section 2.7.
NotifyDriverShm(通知驱动表) ti.sdo.ipc.notifyDrivers.NotifyDriverShm Shared memory notification driver used by the Notify module to communicate between a pair of processors.
See Section 2.7.
SharedRegion (共享区域) ti.sdo.ipc.SharedRegion Maintains shared memory for multiple shared regions.
See Section 2.8.

Header Files included

除了<ipc_install_dir>/packages/ti/ipc/ 路径下可以找到IPC必须的头文件外, <ipc_install_dir>/packages/ti/sdo/ipc/ 路径下同样有IPC的头文件,但是请勿直接引用至 .c文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <xdc/std.h>
#include <string.h>
/* ---- XDC.RUNTIME module Headers */
#include <xdc/runtime/Memory.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/IHeap.h>
#include <xdc/runtime/Timestamp.h> //not officially included
/* ----- IPC module Headers */
#include <ti/ipc/GateMP.h>
#include <ti/ipc/Ipc.h>
#include <ti/ipc/MessageQ.h>
#include <ti/ipc/HeapBufMP.h>
#include <ti/ipc/MultiProc.h>
/* ---- BIOS6 module Headers */
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
/* ---- Get globals from .cfg Header */
#include <xdc/cfg/global.h>

Standard IPC Function Call Sequence

Standard IPC Function Call Sequence,即标准IPC函数的调用顺序。

MODULE 在本处指任一类型的IPC调用(如,GateMP、IPC、MessageQ等),如 MODULE_Open() 即可替换成 MessageQ_Open() ,具体实参见对应的头文件。

An application that uses IPC APIs—such as MessageQ, GateMP, and ListMPmust include the Ipc module header file and call Ipc_start() in the main() function. Ipc_start() does the following:

  • 初始化:Initializes a number of objects and modules used by IPC.
  • 同步:Synchronizes multiple processors so they can boot in any order.

NOTES: If the main() function calls any IPC APIs, the call to Ipc_start() must be placed before any calls to IPC modules.

调用顺序

  • Firstly, initialize a MODULE_Params structure to its default values via a MODULE_Params_init() function. The creator thread can then set individual parameter fields in this structure as needed.
  • Secondly, calls the MODULE_create() function to creates the instance and initializes any shared memory used by the instance. If the instance is to be opened remotely, a unique name must be supplied in the parameters.
  • Other threads can access this instance via the MODULE_open() function, which returns a handle with access to the instance. The name that was used for instance creation must be used in the MODULE_open() function.
  • Finally, the thread that called MODULE_create() can call MODULE_delete() to free the memory used by the instance.
  • 首先,使用 MODULE_Params_init() 来初始化 MODULE_Params 结构。(创建它的线程)可以根据需要单独调整结构体内的个别参数。
  • 然后,调用 MODULE_create() 函数来创建对象实例,并初始化其内存。如果该对象在别处被打开,需要给被调用的参数取好名字防止重复。
  • 接着, 其他线程可以通过 MODULE_open() 函数接入该对象,并返回一个对应的句柄。该对象的创建名称必须与打开名称保持一致。
  • 最后,调用 MODULE_create() 来创建对象实例的线程就可以调用 MODULE_delete() 来释放被对象占用的内存。

注意:

All threads that opened an instance must close that instance before the thread that created it can delete it. Also, a thread that calls MODULE_create() cannot call MODULE_close(). Likewise, a thread that calls MODULE_open() cannot call MODULE_delete().

在由创建者删除(delete)某IPC对象时,由谁使用(open)就由谁关闭(close)。且决不能由创建者来调用关闭函数,否则创建者无法删除该对象。(顺序如下图所示)

stateDiagram-v2
    [*] --> Core0
    [*] --> Core1
    Core0 --> IPC_Start()
    IPC_Start() --> Module_Create()
    IPC_Start() --> Core1 : Wait for Sychronization
    Core1 --> UsersProgram()
    UsersProgram() --> Module_Open()
    Module_Open() --> Module_Close()
    Module_Create() --> Module_Delete()
    Module_Close() --> Module_Delete() : Closed by who opens

代码示例

Ipc_Start() 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <ti/ipc/Ipc.h>
...
Int main(Int argc, Char* argv[])
{
Int status;
/* Call Ipc_start() */
status = Ipc_start();
if (status < 0) {
System_abort("Ipc_start failed\n");
}
BIOS_start();
return (0);
}

MessageQ_Create() 使用示例

1
2
3
4
messageQ = MessageQ_create(DSP_MESSAGEQNAME, NULL);
if (messageQ == NULL) { //an error occurred when creating the object
System_abort("MessageQ_create failed\n");
}

Error Handling in IPC

Success codes always have values greater or equal to zero. The Failure codes are always negative.

1
2
3
4
5
6
7
8
MessageQ_Msg msg; 
MessageQ_Handle messageQ;
Int status;
...
status = MessageQ_get(messageQ, &msg, MessageQ_FOREVER);
if (status < 0) {
System_abort("Should not happen\n");
}

IPC Module Configuration

Configure how the IPC module synchronizes processors by configuring the Ipc.procSync property. For example:

1
2
3
/* CONFIGURATION ABOUT INTER-PROCESS COMMUNICATION */
var Ipc = xdc.useModule('ti.sdo.ipc.Ipc');
Ipc.procSync = Ipc.ProcSync_ALL;

Here are three options: Ipc.ProcSync_ALL | Ipc.ProcSync_PAIR | Ipc.ProcSync_NONE

Options Conditions Specialties
Ipc.ProcSync_ALL - IPC processors on a device start up at the same time
- Connections should be established between every possible pair of processors
- Ipc_start() API automatically attaches to and synchronizes all remote processors.
- Application should never call Ipc_attach().
Ipc.ProcSync_PAIR (Default Mode) One of the following is true:
- You need to control when synchronization with each remote processor occurs.
- Useful work can be done while trying to synchronize with a remote processor by yielding a thread after each attempt to Ipc_attach() to the processor.
- Connections to some remote processors are unnecessary and should be made selectively to save memory.
- Must explicitly call Ipc_attach() to attach to a specific remote processor.
- Ipc_start() performs system-wide IPC initialization, but does not make connections to remote processors.
Ipc.ProcSync_NONE Use this option with caution.
It is intended for use in cases where the application performs its own synchronization and you want to avoid a potential deadlock situation with the IPC synchronization.
Ipc_start() doesn’t synchronize any processors before setting up the objects needed by other modules.

Attach and Detach (依附与分离)

In addition to the default actions performed when attaching to or detaching from a remote processor, You can configure a function to perform custom actions.

Attach and Detach are provided for the processor synchronization:

  • Ipc_attach() Creates a connection to the specified remote processor.

  • Ipc_detach() Deletes the connection to the specified remote processor.

.cfg 文件中以下为两个互相依赖和两个互相分离的函数配置,每一组函数都会传递一个不同的参数:

1
2
3
4
5
6
7
8
9
10
var Ipc = xdc.useModule('ti.sdo.ipc.Ipc');

var fxn = new Ipc.UserFxn;
fxn.attach = '&userAttachFxn1';
fxn.detach = '&userDetachFxn1';
Ipc.addUserFxn(fxn, 0x1);

fxn.attach = '&userAttachFxn2';
fxn.detach = '&userDetachFxn2';
Ipc.addUserFxn(fxn, 0x2);

These functions run near the end of Ipc_attach() and near the beginning of Ipc_detach() , respectively.

Such functions must be non-blocking and must run to completion. 这些被定义的函数必须为非阻塞且(一旦开始就)运行到底。

注意: Call Ipc_attach() to the processor that owns shared memory region 0 (usually the processor with id = 0) before making a connection to any other remote processor. For example, if there are three processors configured with MultiProc, #1 should attach to #0 before it can attach to #2.

3. 多核任务分配

如果多个核共享一个工程及相同的bios 配置文件,是不能指定某一个任务到特定的core上,如果某个任务只有某个core才会运行,可以在任务中区分core运行。如果多个core分别有不同的工程,则没有“多核任务分配”的问题,每个工程可以根据各自core的应用创建各自的任务,不需要软件区分core。

单核可以是一个out生成最终的bin文件烧写在flash,多核如果存在多个out文件,可以把out文件合并成一个bin烧写,或者多个bin分开烧写均可。bin文件中都有程序的地址及长度信息,在多个bin时,也是一样可以由core0负责对flash的程序文件解析,将程序搬移到相应的地址上,最后core0再向其他core magic address写入入口地址,并发送ipc触发即可。

这个并非由SYS/BIOS分配,需要开发者指定。

可以根据core number来做判断,然后确认该任务是否运行在该核上。

4. Chip Support Library

CSL,即芯片支持库( Chip Support Library)。在程序设计过程中利用CSL库函数可以方便地访问 DSP的寄存器和硬件资源,提高DSP软件的开发效率和速度。

CSL库包含了对INTC、Boot Configuration、BWMNGMT、CACHE、CHIP、CPINTC、EDMA3、EMAC、EMIF4F、GPIO、IDMA、IPC、MDIO、MEMPROT、MPU、MSMC、PLLC、PSC、Semaphore、SGMII、SRIO、TIMER、TSC、VCP2、XMC、CGEM、CPPI、QMSS、CPPI_LLD_SYMBOL 及 CPPI_LLD_ENUM共30种模块的支持。

Cache Module

位于 ti\pdk_C6657_1_1_2_6\packages\ti\csl\ 下的头文件 <csl_cacheAux.h> 提供了缓存(Cache)配置相关的CSL功能层API。

This is the CACHE Auxilary Header File which exposes the various CSL Functional Layer API's to configure the CACHE Module.

Cache API包含了对L1D(L1 Data Cache)、L1P 及 L2的各种操作。关于L1D、L1P及L2 的详细文件参考《SPRS814D》第193页。

该头文件中对三种缓存的可操作方式如下示:

OPERATION L1D L1P L2
setSize
getSize
freeze
unfreeze
getPrevMode
-- get the previous operating state
invAllWait
-- wait for the cache global invalidate operation to complete
invAll
-- globally invalidate cache
wbAllWait
-- wait for the cache writeback operation to complete
wbAll
-- writeback the dirty lines of the cache
wbInvAllWait
-- wait for the cache writeback invalidate operation to complete
wbInvAll
-- invalidate and writeback the dirty lines of the cache
invWait
-- wait for the cache invalidate block operation to complete
inv
-- to invalidate a block in cache
wbWait
-- wait for the cache writeback block operation to complete
wb
-- writeback the dirty lines of the block address
wbInvWait
-- wait for the cache invalidate/writeback block operation to complete
wbInv
-- invalidate and writeback the dirty lines of the block address

IPC Module

IPC,即进程间通信(Inter-process communication)。

位于 ti\pdk_C6657_1_1_2_6\packages\ti\csl\ 下的头文件 <csl_ipcAux.h> 提供了查询定义控制函数。IPC API包含了对 NMI、GEM、Host的各种操作。其中 GEM 对 IPCGRx 及 IPCARx 进行操作;而 Host 对 IPCGRH 及 IPCARH 进行操作。

注意: 下文中, indexsrcId 均是函数形参,如在CSL_IPC_isGEMInterruptAckSet(uint32 index, uint32 srcId)中:index 指需要检查IPCARx寄存器的GEM编号。srcId 指示在指定的索引对应的IPCARx寄存器中需要读取0-27 SRCCx位中的哪一个。

NMI,即 不可屏蔽中断(Non Maskable Interrupt)。NMIG,即 不可屏蔽中断产生寄存器(NMI Generation Register (NMIGRx))。NMIGRx registers are used for generating NMI events to the corresponding CorePac. The C6657 has two NMIGRx registers (NMIGR0 and NMIGR1). The NMIGR0 register generates an NMI event to CorePac0, and the NMIGR1 register generates an NMI event to CorePac1.Writing 1 to the NMIG field generates an NMI pulse. Writing 0 has no effect and reads return 0 and have no other effect.

IPCGRx,即 进程间通信产生寄存器(IPC interrupt generation register)。IPCGRx are to facilitate inter CorePac interrupts. The C6657 has two IPCGRx registers (IPCGR0 and IPCGR1). These registers can be used by external hosts or CorePacs to generate interrupts to other CorePacs. A write of 1to the IPCG field of the IPCGRx register will generate an interrupt pulse to CorePacx (0 <= x <= 1).

IPCARx,即 IPC中断确认寄存器(IPC interrupt-acknowledgement registers)。IPCARx are to facilitate inter-CorePac core interrupts. The C6657 has two IPCARx registers (IPCAR0 and IPCAR1). These registers also provide a Source ID facility by which up to 28 different sources of interrupts can be identified. Allocation of source bits to source processor and meaning is entirely based on software convention. The register field descriptions are shown in the following tables. Virtually anything can be a source for these registers as this is completely controlled by software. Any master that has access to BOOTCFG module space can write to these registers.

IPCGRH,即 主机IPC产生寄存器(IPC Generation Host)。The IPCGRH register facilitates interrupts to external hosts. Operation and use of the IPCGRH register is the same as for other IPCGR registers. The interrupt output pulse created by the IPCGRH register appears on device pin HOUT. The host interrupt output pulse should be stretched. It should be asserted for 4 bootcfg clock cycles (CPU/6) followed by a deassertion of 4 bootcfg clock cycles. Generating the pulse will result in 8 CPU/6 cycle pulse blocking window. Write to IPCGRH with IPCG bit (bit 0) set will only generate a pulse if they are beyond 8 CPU/6 cycle period.

IPCARH,即 主机IPC确认寄存器(Host IPC Acknowledgment Register)。

该头文件中的可操作方式如下示:

NMI GEM Host
genEvent
genInterrupt
-- generate an interrupt pulse
isInterruptSourceSet
-- checks if the SRCSx bit of the IPCGRx register is set

-- checks if the SRCSx bit of the IPCGRH register is set
isInterruptAckSet
-- checks if the SRCCx bit of the IPCARx register is set.

-- checks if the SRCCx bit of the IPCARH register is set.
clearInterruptSource

isGEMInterruptSourceSet() returns 1 if the SRCCx bit corresponding to the srcId is set in the IPCARx register corresponding to the index specified. 如果与srcId对应的SRCCx位在与指定索引对应的IPCARx寄存器中被设置,则返回1。

CSL_IPC_clearGEMInterruptSource() clears the interrupt source IDs by setting the SRCCx bit of IPCARx and SRCSx bit of IPCGRx corresponding to the GEM index and Source ID specified.

CSL_IPC_clearGEMInterruptSource() 通过设置 GEM 索引指定源 ID 对应的 IPCARx寄存器上的SRCCx位IPCGRx寄存器上的SRCSx位 来清除 中断源ID 。

CSL_IPC_clearHostInterruptSource() function clears the interrupt source IDs by setting the SRCCx bit of IPCARH and SRCSx bit of IPCGRH corresponding to the Source ID specified.

CSL_IPC_clearHostInterruptSource() 通过设置 指定源ID 对应的 IPCARH上的SRCCx位IPCGRH上的SRCSx位 来清楚中断源ID。

CACHE_wbInvL1d()

This function is used to invalidate and writeback the dirty lines of the block address.

Although the block size can be specified in the number of bytes, the cache controller operates on whole cache lines.

To prevent unintended behavior "blockPtr" should be aligned on the cache line size and "byteCnt" should be a multiple of the cache line size.

CACHE_invL1d ()

This function is used to invalidate a block in the L1D Cache.

Although the block size can be specified in the number of bytes, the cache controller operates on whole cache lines.

To prevent unintended behavior "blockPtr" should be aligned on the cache line size and "byteCnt" should be a multiple of the cache line size.

Chip Module

#include <csl_chipAux.h>

本头文件是以C( extern "C" )的方式来书写的,包含芯片读取与写入相关操作的API,名称以 CSL_chipReadCSL_chipWrite 开头,所有函数均以静态内联(static inline,重定义成 CSL_IDEF_INLINE )32位非负整数(Uint32)的方式定义,如 CSL_IDEF_INLINE Uint32 CSL_chipRead***();

<csl_chipAux.h> 提供了26个读取寄存器相关的函数,函数定义均是对寄存器的读取并返回数值,某些读取函数需要注意 前置条件(Pre-condition)。

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
CSL_chipReadAMR();    //Addressing Mode control register 寻址模式控制寄存器
CSL_chipReadCSR(); //Control Status register 控制状态寄存器
CSL_chipReadIFR(); //Interrupt Flag register 中断标志寄存器
CSL_chipReadIER(); //Interrupt Enable register 中断使能寄存器
CSL_chipReadISTP(); //Interrupt Service Table Pointer register 中断服务表指针寄存器
CSL_chipReadIRP(); //Interrupt Return Pointer register 中断返回指针寄存器
CSL_chipReadNRP(); //Nonmaskable Interrupt Return Pointer register
CSL_chipReadERP(); //Exception Return Pointer register 异常返回指针寄存器
CSL_chipReadTSCL(); //Time Stamp Counter Lower Order 32-bits register 时间戳计数器低32位
CSL_chipReadTSCH(); //Time Stamp Counter Higer Order 32-bits register 时间戳计数器高32位
CSL_chipReadARP(); //Analysis Return Pointer register
CSL_chipReadILC(); //Inner Loop SPL buffer Counter(ILC) register
CSL_chipReadRILC(); //Reload Inner Loop SPL buffer Counter(RILC) register
CSL_chipReadREP(); //Restricted Entry Point Address register
CSL_chipReadPCE1(); //Program Counter, E1 Phase register E1字段程序计数器
CSL_chipReadDNUM(); //DSP Core Number register 核心数寄存器
CSL_chipReadSSR(); //Saturation Status Register 饱和状态寄存器
CSL_chipReadGPLYA(); //GMPY A-side polynomial register
CSL_chipReadGPLYB(); //GMPY B-side polynomial register
CSL_chipReadGFPGFR(); //Golios Field Multiply Control Register
CSL_chipReadDIER(); //Debug Interrupt Enable Register 调试中断使能寄存器
CSL_chipReadTSR(); //Task State Register 任务状态寄存器
CSL_chipReadITSR(); //Interrupt Task State Register 中断任务状态寄存器
CSL_chipReadNTSR(); //NMI/Exception Task State Register 异常任务状态寄存器
CSL_chipReadEFR(); //Exception Flag Register 异常标志寄存器
CSL_chipReadIERR(); //Internal Exception Report Register 内部异常报告寄存器

<csl_chipAux.h> 提供了24个写入寄存器相关的函数,且与读取寄存器相关函数并不呈现一一对应关系。函数定义均是将一个Uint32类型(重定义为 CSL_Reg32 )的新值赋值给寄存器,并将旧值返回,写入寄存器均不需要注意 前置条件,部分需要注意后置条件(Post-condition)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CSL_chipWriteAMR(CSL_Reg32  val);
CSL_chipWriteCSR(CSL_Reg32 val);
CSL_chipWriteISR(CSL_Reg32 val); //Interrupt Set Register 中断设置寄存器
CSL_chipWriteICR(CSL_Reg32 val); //Interrupt Clear Register 中断清除寄存器
CSL_chipWriteIER(CSL_Reg32 val);
CSL_chipWriteISTP(CSL_Reg32 val);
CSL_chipWriteIRP(CSL_Reg32 val);
CSL_chipWriteNRP(CSL_Reg32 val);
CSL_chipWriteERP(CSL_Reg32 val);
CSL_chipWriteTSCL(CSL_Reg32 val);
CSL_chipWriteARP(CSL_Reg32 val);
CSL_chipWriteILC(CSL_Reg32 val);
CSL_chipWriteRILC(CSL_Reg32 val);
CSL_chipWriteREP(CSL_Reg32 val);
CSL_chipWriteSSR(CSL_Reg32 val);
CSL_chipWriteGPLYA(CSL_Reg32 val);
CSL_chipWriteGPLYB(CSL_Reg32 val);
CSL_chipWriteGFPGFR(CSL_Reg32 val);
CSL_chipWriteDIER(CSL_Reg32 val);
CSL_chipWriteTSR(CSL_Reg32 val);
CSL_chipWriteITSR(CSL_Reg32 val);
CSL_chipWriteNTSR(CSL_Reg32 val);
CSL_chipWriteECR(CSL_Reg32 val); //Exception Clear Register 异常清除寄存器
CSL_chipWriteIERR(CSL_Reg32 val);

共30个寄存器涉及是否可读写:

Register Read Write
AMR
CSR
IFR ×
ISR ×
ICR ×
IER
ISTP
IRP
NRP
ERP
TSCL
TSCH ×
ARP
ILC
RILC
REP
PCE1 ×
DNUM ×
SSR
GPLYA
GPLYB
GFPGFR
DIER
TSR
ITSR
NTSR
ECR ×
EFR ×
IERR

IPC_HW Example in ANC

硬件(中断)层核间通信(不需要修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Ipc_Init(void)
{
Hwi_Params params; //创建HWI对象
Error_Block eb; //创建错误块处理对象

Error_init(&eb); //初始化错误块
Hwi_Params_init(&params); //初始化HWI对象
params.eventId = 90; //IPC INT /* Set the event ID of the associated host interrupt */
params.enableInt = TRUE; //打开中断

Hwi_create(5, &IpcIsr, &params, &eb); //INT5 /* Create Hwi thread Hwi function is CpIntc_dispatch */
Hwi_enable(); //打开中断

if(CSL_chipReadDNUM() == 0){ //core0 //检测运行前触发的ipc,因为运行前写入的ipc无法触发中断
if(CSL_IPC_isGEMInterruptAckSet(0,2)){ //判断索引为0对应的IPCARx中的第2位是否为中断确认位
CSL_IPC_clearGEMInterruptSource(0,2); //是则清除
}
}else{ //core1
if(CSL_IPC_isGEMInterruptAckSet(1,2)){ //判断索引为1对应的IPCARx中的第2位是否为中断确认位
CSL_IPC_clearGEMInterruptSource(1,2); //是则清除
}
}
}

核心同步(不需要修改)

1
2
3
4
5
6
7
8
9
10
11
12
void Ipc_CoreSync(void){
if(CSL_chipReadDNUM() == 0){ //core0
CSL_IPC_genGEMInterrupt(1,1); //IPC_IPCGR_SRCS1 = 1;
while(!CSL_IPC_isGEMInterruptAckSet(0,1)); //等待core1启动
CSL_IPC_clearGEMInterruptSource(0,1); //core1启动成功,清除标志
}
else { //core1
CSL_IPC_genGEMInterrupt(0,1); //IPC_IPCGR_SRCS1 = 1;
while(!CSL_IPC_isGEMInterruptAckSet(1,1)); //等待core0启动
CSL_IPC_clearGEMInterruptSource(1,1); //core0启动成功,清除标志
}
}

核间IPC信号发送(可根据需要进行修改)

可定义多个IPC核间通信函数,但是要区分得清各函数在何时何处被调用到,否则将会引起混乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Ipc_Core0ToCore1(void){
/* 可自定义区域 */
for(i=0;i<16;i++){
ptr0[i] = adData[i];
}
/* 可自定义区域 */
CACHE_wbInvL1d(ptr0,64,CACHE_WAIT);//L1D line size 64bytes
CSL_IPC_genGEMInterrupt(1,2);////ipcgr1
}

void Ipc_Core1ToCore0(void){
/* 可自定义区域 */
for(i=0;i<4;i++){
ptr1[i] = daData[i];
}
/* 可自定义区域 */
CACHE_wbInvL1d(ptr1,64,CACHE_WAIT); //L1D line size 64bytes
CSL_IPC_genGEMInterrupt(0,2); //ipcgr0
}

核间通信中断函数(可根据需要进行修改)

注意:实际使用时,并不需要在某处调用 IpcIsr() 函数,即实际上是被硬件中断进行控制的,在 HWI_Create() 函数中被使用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void IpcIsr(UArg arg){
if(CSL_chipReadDNUM() == 0) { // core0
if(CSL_IPC_isGEMInterruptAckSet(0,2)){ //ipcgr0
CSL_IPC_clearGEMInterruptSource(0,2);
CACHE_invL1d(ptr1,64,CACHE_WAIT);
for(i=0;i<4;i++){
daData[i] = ptr1[i];
}
Semaphore_post(sem0_da); //执行发送程序,将数据通过SPI发送到DA:通过semaphore切换task
}
}
else{ // core1
if(CSL_IPC_isGEMInterruptAckSet(1,2)){ //ipcgr1
CSL_IPC_clearGEMInterruptSource(1,2);
CACHE_invL1d(ptr0,64,CACHE_WAIT);
for(i=0;i<16;i++){
adData[i] = ptr0[i];
}
Semaphore_post(sem1_anc); //执行core1的计算步骤,计算完后应该通知core0的ipc启动结果发送程序:通过semaphore切换task
}
}
}

5. C6657特性

L2缓存

Debug模式下,程序文件都写在L2缓存中。而C6657总共有 2048KB 大小的L2 缓存,其中每个核心分配到 1024KB ,缓存起始地址为 0x00800000

在仿真器中分配内存大小一致,如下:

1
2
3
4
5
6
MEMORY
{
L2SRAM (RWX) : org = 0x800000, len = 0x100000
MSMCSRAM (RWX) : org = 0xc000000, len = 0x100000
DDR3 (RWX) : org = 0x80000000, len = 0x20000000
}

Written in <TMS320C6655/57 DataManual>:

CSS基础

书写方式: CSS属性: 属性值;

1. CSS 类命名规则

公共命名规则

CSS样式命名 说明
wrapper 页面外围控制整体布局宽度
container或content 容器,用于最外层
layout 布局
head, header 页头部分
foot, footer 页脚部分
nav 主导航
subnav 二级导航
menu 菜单
submenu 子菜单
sideBar 侧栏
sidebar_a, sidebar_b 左边栏或右边栏
main 页面主体
tag 标签
msg message 提示信息
tips 小技巧
vote 投票
friendlink 友情连接
title 标题
summary 摘要
loginbar 登录条
searchInput 搜索输入框
hot 热门热点
search 搜索
search_output 搜索输出和搜索结果相似
searchBar 搜索条
search_results 搜索结果
copyright 版权信息
branding 商标
logo 网站LOGO标志
siteinfo 网站信息
siteinfoLegal 法律声明
siteinfoCredits 信誉
joinus 加入我们
partner 合作伙伴
service 服务
regsiter 注册
arr/arrow 箭头
guild 指南
sitemap 网站地图
list 列表
homepage 首页
subpage 二级页面子页面
tool, toolbar 工具条
drop 下拉
dorpmenu 下拉菜单
status 状态
scroll 滚动
.tab 标签页
.left .right .center 居左、中、右
.news 新闻
.download 下载
.banner 广告条(顶部广告条)

其他DIV命名规则

  登录条:loginBar

  标志:logo

  侧栏:sideBar

  广告:banner

  导航:nav

  子导航:subNav

  菜单:menu

  子菜单:subMenu

  搜索:search

  滚动:scroll

  页面主体:main

  内容:content

  标签页:tab

  文章列表:list

  提示信息:msg

  小技巧:tips

  栏目标题:title

  友情链接:friendLink

  页脚:footer

  加入:joinus

  指南:guild

  服务:service

  热点:hot

  新闻:news

  下载:download

  注册:regsiter

  状态:status

  按钮:btn

  投票:vote

  合作伙伴:partner

  版权:copyRight

2. 选择器

基础选择器

选择器按作用大小(从小到大)分为 ID选择器(使用井字符做特殊标注:#)类选择器(使用英文点号做特殊标注:.)标签选择器通配符选择器(使用英文星号做特殊标注:*)

选择器类型 选择器特性 style书写方式 style应用方式
ID选择器 1. 与其他选择器类型一致,特殊之处是大部分情况作JavaScript调用
2. 需要编写唯一id不可重复使用
#DivStyle { color: red; } id="DivStyle"
类选择器 1. 作用于使用class属性的一类标签,同面向对象的其他语言特性一致
2. 单个标签可以使用多个类,中间以空格隔开
.ClassStyle { color: black;} Class="ClassStyle ....."
标签选择器 作用于所有使用本标签的标签 div { color: blue; } /
通配符选择器 作用于所有标签 * { clolor: pink; } /

复合选择器

复合选择器类型 选择器特性 style书写方式
后代选择器 1. 可以选择某个父标签下的某个全部子标签,不管其是否在同一个层级
2. 当标签发生嵌套时,内层标签就称为外层标签的后代
3. 浏览器按照标签层级的关系进行查找,并对最内层标签进行修改,因此可以定义多重标签选择,如可以使用基础选择器(类选择器及ID选择器)进行组合使用
4. 内外层选择器之间 以空格隔开
外层标签 内层标签 {属性}
ol li { color: #000; }
ol li a { color: #777; }
.nav li a { color: pruple;}
子代选择器 1. 即与后代选择器不同,仅对父级标签下的某一类选择器进行操作,不会作用于其他子代的同类标签
2. 使用符号 > 进行操作
.nav>a {color: pink;}
集选择器(相邻选择器) 1. 在两个没有从属关系(子代或后代)和相同特点(如同类标签)的同级不同类标签中,定义一个相同的样式
2. 多个标签进行选择时使用英文逗号 , 进行并列选择
3. 并集选择器内可使用 简单选择器复合选择器(如 后代选择器 和 子代选择器),竖向书写选择器
div, p {color: pink; }
伪类选择器 1. 用于给某些选择器添加特殊效果(如给链接添加特殊效果),或者选择某个元素
2. 结构伪类、链接伪类、表单伪类
3. 伪类选择器其实更像是一个行为选择器(或状态选择器),它定义了一个/类标签在不同状态下的样式,书写形式如:标签: 状态/行为 {属性}
.OnlineTitle:link {
color: #222222;
text-decoration: none;
}

并集选择器

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
<!DOCTYPE HTML>
<!--
如要求将下方熊大、熊二及佩奇一家都改为粉色,则按照<style>中的方式进行书写
-->
<html>
<head>
<style>
//在两个没有从属关系(子代或后代)和相同特点(如同类标签)的同级不同类标签中,定义一个相同的样式
div,
p,
.pig li { //类选择器
color: pink;
}
</style>
</head>
<body>
<div>熊大</div>
<p>熊二</p>
<span>关头强</span>
<ul class="pig">
<li>PIG 1</li>
<li>PIG 2</li>
<li>PIG 3</li>
</ul>
</body>
</html>

链接伪类选择器

四个使用方式: link / visited / hover / active

  1. 为了确保生效,应按照LVHA的顺序进行声明。
  2. <a> 链接在浏览器中具有默认样式,不会因为在<body>中而随之改变,实际工作中需要给链接单独指定样式。
  3. 使用时,加上冒号 : 与 四种状态之一 即可

举例:

1
2
3
4
a:hover
{
background-color:yellow;
}

当鼠标经过父级元素时,显示子元素的遮罩层:

1
2
3
.tudou:hover .mask {
display: block; /* 显示遮罩层 */
}

最小盒子:即只能存放文字了,不能再存放其他标签/元素,如 <p><h1>等文字块级标签;

元素显示模式

是指在页面上的元素以 块状模式行内模式行内块模式 进行显示。元素按显示模式可以分为 块状(级)元素行内元素(内联元素)行内块元素 三种类型。

元素类型 典型标签 特点 注意点
块状元素 <h1>~<h6><p><div><ul><ol><li>...... 1. 即使设置标签宽度,也是独占一行
2. 高、宽、外边距及内边距可调节
3. 默认宽度是父级宽度的100%
4. 是一个容器及盒子,可以放任何标签
文字类的标签内不可以存放块元素
行内元素(内联元素) <a><strong><b><em><i> ...... 1. 相邻行内元素在一行上,一行可以显示多个
2. 直接设置宽高无效,可以间接设置
3. 默认宽度就是其内容宽度
4. 行内元素只能容纳文本及其他行内元素
1. 链接中不允许再放其他链接
2. <a>中可以放置块元素,但是转换成块级元素最安全
行内块元素 <input><image><td>...... 1. 同时具有块元素及行内元素的特定
2. 和相邻行内元素(行内块)在一行上,但是有空白缝隙,可以一行显示多个
3. 默认宽度即内容宽度
4. 高度、行高、外边距及内边距可控制

元素显示模式的转换

即一个模式的元素需要另一个模式的特性,可以通过在CSS的标签样式属性中添加display语句进行转换。

转换为块级元素: display: block;

转换为行内元素: display: inline;

转换为行内块元素: display: inline-block;

权重

选择器类型 权重
继承
通配符选择器、子代选择器、相邻选择器(并集选择器) 0,0,0,0
元素选择器、伪元素选择器 0,0,0,1
类选择器、伪类选择器、属性选择器 0,0,1,0
ID选择器 0,1,0,0
内联选择器 1,0,0,0
!IMPORTANT 无限

选择器的加权结果并非二进制,而是在各个数位上进行单独相加,例如元素选择器与类选择器的加权结果为 0,0,1,1,左侧数位的数值越大,权重越高。

3. List

list-style CSS 属性是一个简写对属性集合,包括list-style-type, list-style-image,list-style-position

list-style: none | circle | square inside ;表示<ul><li> 前的小点样式,如“无、圆圈、实心方形”。

例如:

1
2
3
4
5
6
7
8
9
10
11
<ul class="one">
<li>List Item1</li>
<li>List Item2</li>
<li>List Item3</li>
</ul>
List 2
<ul class="two">
<li>List Item A</li>
<li>List Item B</li>
<li>List Item C</li>
</ul>

1
2
3
4
5
6
7
.one {
list-style: circle;
}

.two {
list-style: square inside;
}

4. Background

background-color 可以设置为 transparent,即透明的,不可视的。

background-image 可以设置为 none,也可以跟上一个 url() 链接。如:background-image: url(http://xxxxxx.com/123.jpg)

background-repeat: repeat | no-repea | repeat-x | repeat-y ,分别是指 重复(平铺)、不平铺、在x轴上平铺 及 在y轴上平铺 。

background-position: top | bottom | left | right | center; 除了可以使用类似于左侧的方位词定位,还可以使用坐标轴数值定位。

5. Font

FONT-FAMILY 字体族

通用属性之一,用于设置网页显示字体,font-family的使用方式为 font-family=" " ,双引号内可以书写多个字体,引擎会按顺序搜索本地字体并使用,如果都没有则使用本地字体。

注意:建议使用字体的全英文进行书写,如 微软雅黑 全英文为 Microsoft YaHei,英文书写时有空格的应用单引号‘’ 进行包裹。

FONT-SIZE 字体大小

通常用于对body标签内所有正文字体大小的设置,对标题(如h3)大小的设置仍需另起样式。

复合写法

font: font-style font-weight font-size/line-height font-family;

font-sizefont-family 不可缺省

6. Text

文本属性

属性 书写方式 注意
color
颜色
1. 常见英文表示法,直接书写该颜色英文即可,如 pink  red  blue  purple
2. 16进制表示法,用#号进行书写,如#FF00FF
3. RGB表示法,如rgb(0,0,255)
16进制简写为#fff
实际开发中用16进制
text-align
文本对齐
仅有左、中、右三种表示方式,用英文 left  center  right 进行书写
text-indent
文本缩进
缩进可以用2种方式表示:
1. 精准缩进:即直接使用 px 单位进行书写,表示缩进多少像素点,缺点是容易出现问题
2. 字符单位缩进:即按照字符大小,自适应缩进字符距离,单位是em,如 2em,可以缩进2个字符
text-decoration
文本修饰
常见的修饰类型有四种:无(none)、上划线(overline)、下划线(underline)、删除线(line-through)
line-height 行高=上行距+下行距+字高,且上行距=下行距

7. Box Module

盒子模型主要分三部分:border(边框)、padding(内边距)及 margin(外边距)。

Border

边框主要有一下三种参数:粗细、颜色及样式

  • 粗细(border-width: 5px;)
  • 颜色(boder-color: #555) (注意:如果这个值没有设置,它的默认值是元素的 color属性值(是文字颜色而非背景色)),可以定义为transparent(透明的)
  • 样式(boder-style: none | hidden | dotted | dashed | solid | double(双层线) | groove(内雕刻) | ridge(外浮雕) | inset(内凹陷) | outset(外凹陷) )

border属性之可以在中间添加上、下、左、右四个方位词和三参数之一,如 border-top-color | border-bottom-style ....

边框使用时会影响盒子大小,需要注意调整。

复合写法

border: [border-width ||border-style ||border-color |inherit] ;

Padding

内边距涉及到的参数包括 上下左右四边的边距。

在未设置内盒子的width时,设置padding对内盒子实际显示大小不起影响,一旦设置width,则会让盒子的width在显示时超过设置参数的大小。

Margin

外边距同内边距。

在标准流下,设置块级盒子width参数后,设置margin左右为auto,可以让盒子实现居中效果。(以下三种写法,推荐第一个,第一个参数表示上下,第二个参数表示左右)

margin: 0 auto;

margin: auto;

margin-left: auto; margin-right: auto;

Q/A

盒子塌陷的解决方案

以下为两个盒子嵌套,同时设置上边距导致塌陷问题的解决方案:

  • 可以为父元素定义上边框/上内边距
  • 可以为父元素添加 overflow: hidden;
  • 利用浮动、固定、绝对定位解决

8. Float

浮动最初的开发目的是让图片和文字产生环绕效果的,浮动可以让多个块级元素在一行内显示。

float: none | left | right;

注意:两个行内块元素中间会有空白间隙存在,如果父级元素设定了宽度,会让元素存放不下。需要给此类行内块元素设定浮动

浮动特性:

  • 脱标

  • 顶端对齐

  • 具有行内块元素特性

外边距合并

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。

注释:只有普通文档流中块框的垂直外边距才会发生外边距合并。行内框、浮动框或绝对定位之间的外边距不会合并。

在标准流中嵌套一个带有外边距的浮动框,不会让他们产生外边距合并。

9. Clean Float

清除浮动也叫闭合浮动,是指在未指定父级元素高度而子级元素为浮动时,会对父级元素同级的标准流造成影响,因而需要清除该类浮动造成的弊端的情况。

清除浮动的四种方式:

方法 说明 写法 备注
额外标签 [w3c推荐]也称隔墙法,指在浮动的块(行内块)级元素后面新增一个带有clear: both;属性的额外标签(要求是块级元素)来屏蔽浮动的影响。 clear: both; 弊端是增加了额外标签,对H5结构造成负担;
要求新增的标签不能是行内元素
父级添加overflow 仅给父级元素添加overflow,添加hidden、auto、scroll皆可。 overflow: hidden | auto | scroll 优点是代码简洁,缺点是无法显示溢出的部分
:after伪元素 额外标签法的升级版,利用CSS样式添加额外标签
仅给父级元素添加
见下方 没有增加额外标签,样式写法较为复杂;
双伪元素 在子级前后各添加一个伪元素 见下方 代码简洁,照顾低版本。

:after伪元素代码示例:

1
2
3
4
5
6
7
8
9
10
11
.clearfix:after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
}

.clearfix { /* IE6、7专有 */
*zoom: 1;
}

双伪元素代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
.clearfix:before, .clearfix:after{
content: "";
display: table;
}

.clearfix:after{
clear: both;
}

.clearfix{
*zoom: 1;
}

10. Position

定位的意义

让盒子自由地在某个盒子中移动或固定在屏幕中的某个位置,并且可以压住其他盒子。

注意:对于标准流和浮动,不可以使用定位。

定位模式

position: static | relative | absolute | fixed ;

定位分类 脱标 使用情况 参考系 其他特点
静态定位 static 否,不能使用边偏移 很少 无,不设置边偏移 按照标准流特性摆放位置
相对定位 relative 否,占用标准流的位置 常用 自身原来的位置 相对于自身原来的位置来移动
设置相对定位后,后面的标准流不会脱标,不会让身后的标准流往前挤或往后挤;
通常是给绝对定位当父级元素的属性
绝对定位 absolute 是,不占用标准流的位置 常用 最近一级父级以上带有定位的盒子(子绝父相) 如果没有已定位的父级元素,则以浏览器进行绝对定位
固定定位 fixed 是,不占用标准流的位置 常用 浏览器的可视窗口 与父级元素无关,不随窗口滚动条滚动
粘性定位 sticky 否,占用标准流的位置 很少 浏览器的可视窗口 相对定位和固定定位的混合体,具有两者的某些特点
必须添加至少一个边偏移参数
需与滚动搭配使用,对IE兼容性差,类似粘性定位的做法通常用JavaScript来做

边偏移

相对于父级盒子(或者有定位的祖辈盒子)的边线偏移量(偏移量可以为负值)。

需要在设置好定位模式后边偏移才能生效。

四种偏移:top、bottom、right、left;

例:top: 10px; right: 20px;

如何将内容固定到版心右侧?

使用固定定位至左侧的50%,在调整margin-left值至版心宽度的一般(直接书写数值px)。

如何让内容固定到屏幕正中心?

使用固定定位至左侧和顶部的50%,再使用margin-leftmargin-right的负值来抵消自身宽高的一半。

定位显示优先级

定位显示优先级,即定位叠放顺序,是指 具有定位属性的盒子(类似于PS图层)在Z轴上的显示优先级,数值越大则优先级越高。

如果设置为auto,则按照H5结构书写顺序以“后来居上”的原则显示。

如果设置为数值,则比auto优先级更高,但是不能写单位。

z-index: (number) | auto;

定位的特殊性质

  • 给行内元素添加绝对或者固定定位后,可以设置宽度和高度(不等同于与块级元素互换特性?)
  • 给块级元素添加绝对或者固定宽度后,不设置宽高也可以则应用内容的大小

11. 显示与隐藏

显示与隐藏的三种方式:display(显示隐藏)、visibility(显示隐藏)、overflow (溢出显示隐藏)

方式 用法 特性
display(显示隐藏) 该属性本来是设置元素如何显示的;
display: none; 用于隐藏对象;
display: block; 除了用于转换成块级元素,还可以用于显示元素;
隐藏元素后,不再占有其原位置;
应用广泛,搭配js做网页特效用;
visibility(显示隐藏) 用于设置元素的可见性;
visibility: visible | hidden | inherit | collapse ;
隐藏元素后,仍占有其原位置;
overflow (溢出显示隐藏) 仅针对溢出元素框的部分进行显示或隐藏;
overflow: visible; 设置时clip属性设置将失效;
overflow: hidden; 不显示超过对象尺寸的内容;
overflow: scroll; 以滚动条形式显示,不管是否内容是否溢出;
overflow: auto; 自动判断内容长度,按需添加滚动条;
如果带有定位属性,应慎用 overflow: hidden; ,会隐藏多余的部分

12. 布局

常见的布局形式

传统布局方式:普通流(文档流、标准流)、浮动、定位。

通栏:是指和浏览器一样宽。

布局设计准则:

  • 纵向块级元素用标准流,横向块级元素用浮动。

  • 先用标准流的父元素排列上下位置,之后内部子元素采取浮动排列左右位置,复合网页布局第一准则。

  • 浮动的盒子只会影响浮动盒子后方的标准流,不影响前方的标准流。

  • 应首先采用“一浮全浮”设计原则。

  • 在产品列表数量众多、文字数量众多等不能明确盒子高度的情况下不应指定父级盒子的高度, 否则会出现父级盒子因高度限制而出现子元素无法显示的情况,应让子元素撑开父元素。

  • 当给父元素设置标准流却未设置高度,而子元素为浮动时,父元素会高度塌陷。因浮动的子元素不占有高度,因此子元素浮动会会导致无高度的标准流父级元素高度塌陷,影响后续布局。

CSS属性书写建议

  1. 布局定位属性:display / position / float / clear / visibility / overflow (建议display第一个写)
  2. 自身属性:width / height / margin / padding / border / background
  3. 文本属性:color / font / text-decoration / text-align / vertical-align / white-space / break-word
  4. 其他属性:content / cursor / border-radius / box-shadow / text-shadow / background:liner-gradient ....

页面布局的整体思路

  1. 确定版心,即可视区
  2. 分析行、块 以及 块中的列模块
  3. 列模块经常浮动布局,确定列大小后确定列的位置
  4. 先结构,后样式

关于导航栏

实际开发中,不会直接只用<a>来制作导航栏,而是用<li>包含链接的<li>+<a>来制作。

<li>+<a>语义更加清晰,更为有条理的列表型内容。如果直接用<a>,搜索引擎容易辨别为有堆砌关键字嫌疑(故意堆砌关键字存在被搜索引擎降权的风险),从而影响网站排名。

13. 特殊符号

小于号 < &lt;

大于号 > &gt;

高级技巧

目标:

  • 能够使用精灵图

  • 能够使用字体图标

  • 能够写出CSS三角

  • 能够写出常见的CSS用户界面样式

  • 能够说出常见的布局技巧

CSS Sprites

CSS精灵技术的目的:减少客户端向服务器的请求次数,减小服务器的压力,提高网页的加载速度。

TCP

在TCP/IP协议族中,网络层IP提供的是一种不可靠的服务。也就是说,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。而另一方面,TCP在不可靠的IP层上提供了一个可靠的运输层。为了提供这种可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。由此可见,运输层和网络层分别负责不同的功能。

在四层网络结构,由下之上分别是:数据链路层网络层传输层应用层

数据链路层 协议典型如:Ethernet、ARP、ICMP

网络层 协议典型如:IP

传输层 协议典型如:TCP、UDP

应用层 协议典型如:HTTP、FTP等

TCPIP-01

图1-4 TCP/IP协议族中不同层次的协议

为协议ICMP和IGMP定位一直是一件很棘手的事情。在图1-4中,把它们与IP放在同一层上,那是因为事实上它们是IP的附属协议。但是在这里,又把它们放在IP层的上面,这是因为ICMP和IGMP报文都被封装在IP数据报中。

使用TCP/IP协议的应用程序通常采用两种应用编程接口(API):socket和TLI(运输层接口:Transport Layer Interface)。前者有时称作“Berkeley Socket”,表明它是从 伯克利 版发展而来的。后者起初是由AT&T开发的,有时称作XTI(X/Open运输层接口),以承认X/Open这个自己定义标准的国际计算机生产商所做的工作。XTI实际上是TLI的一个超集。

TCP Introduce

TCP(Transmission Control Protocol)传输控制协议,是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接。

TCP Structure20210218-03

20210218-02
Definitions Length Explain
源端口 Source Port 16bits(2bytes)
目的端口 Destination Port 16bits(2bytes)
序号 Sequence Number 32bits(4bytes) 指定了当前数据分片中分配给 第一字节数据 的序列号。在TCP传输流中每一个字节为一个序号。如果TCP报文中flags标志位为SYN,该序列号表示 初始化序列号(ISN),此时第一个数据应该是从序列号ISN+1开始。
确认号 Acknowledgement Number 32bits(4bytes) 表示TCP发送者期望接受下一个数据分片的序列号。该序号在TCP分片中Flags标志位为ACK时生效。序列号分片的方向和流的方向同方向,而确认序列号分片方向和流方向反方向。
偏移量 Data-Offset 4bits 数据偏移也叫首部长度。
因为首部长度实际也说明了数据区在分片中的起始偏移值。它表示TCP头包含了多少个32-bit的words。因为4bits在十进制中能表示的最大值为15,32bits表示4个字节,那么Data Offset的最大可表示15*4=60个字节(bytes)。
所以TCP报头长度最大为60字节。如果options field为0的话,报文头长度为20个字节。
保留域 Reserved field 3bits(或6bits) 值全为零
标志位 Flags 9bits(或6bits) 表示TCP包特定的连接状态,一个标签位占一个bit。
窗口 Window 16bits(2bytes) 表示滑动窗口的大小,用来告诉发送端接收端的buffer space的大小。接收端buffer大小用来控制发送端的发送数据数率,从而达到流量控制。最大值为65535.
检验和 Checksum 16bits(2bytes) 用来检查TCP头在传输中是否被修改。
紧急指针 Urgent Pointer 16bits(2bytes) 表示TCP片中第一个紧急数据字节的指针。只有当URG标志置1时紧急指针才有效。
可变部分 Options Field填充部分 Padding Field 可变长度。 表示TCP可选选项以及填充位。当选项不足32bits时,填充字段加入额外的0填充。

TCP Flags

对于旧版本的TCP头定义,Flags有6bits,新版TCP头对flags扩展了3bits。每个TCP flag对应于1bit 。所以旧版TCP头flags值有6个,新版扩展了3个值。

从低位到高位分别是:

Definition Declaration
FIN
(finished 结束)
表示发送者以及发送完数据。通常用在发送者发送完数据的最后一个包中。
SYN
(synchronous 建立联机)
RST
(reset 重置)
重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。
或者发送包发送到一个不是期望的目的主机时,接收端发送reset 重置连接标志的包。
PSH
(push 传送)
通知接收端处理接收的报文,而不是将报文缓存到buffer中。
ACK
(acknowledgement 确认)
URG
(urgent 紧急)
通知接收端处理在处理其他包前优先处理接收到的紧急报文。
ECE
(Explicit Congestion Notification Echo) 【新】
表示TCP peer有ECN能力。
CWR
(Congestion Window Reduced)【新】
发送者在接收到一个带有ECE flag包时,将会使用CWR flag。
NS
(Nonce Sum)【新】
该标签用来保护不受发送者发送的突发的恶意隐藏报文的侵害。

TCP 连接的建立都是采用客户-服务器(Client-Server)方式:

  • 主动发起连接建立的应用进程叫做客户(client)。
  • 被等待连接建立的应用进程叫做服务器(server)。

传输连接就有三个阶段,即:连接建立数据传输连接释放

TCP 采用全双工模式,在连接建立后和连接释放前进行数据传输。数据传输是单向的,从发送端传输给接受端。TCP通过序列号能够保证数据被接受端接受。TCP建立连接是通过三次握手的方式来建立连接的。

TCP Three-Way Handshake

注意: 不管是大小写,ack ( 或 Ack )和 ACK 都是 确认 的意思, 不同之处在于:

  • 在TCP首部中,ACK为确认标志位 ——占 1 字节,只有当 ACK = 1 时确认号字段才有效。当 ACK = 0 时,确认号无效。;
  • Ack为确认号字段(Ack Number)——占 4 字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。

The Steps of Three-Way Handshake

第一次握手:Client发送位码为SYN=1 ,随机产生 seq number=1234567 的数据包到服务器,Server收到 SYN=1,知道Client要求建立联机;

第二次握手:Server收到请求后要确认联机信息,向Client发送ack number=(Client的seq+1)SYN=1ACK=1 ,随机产生 seq number=7654321 的包;

第三次握手:Client 收到后检查 ack number 是否正确,即第一次发送的seq number+1,以及位码 ACK是否为 1,若正确,Client 会再发送 ack number=(主机B的seq+1)ACK=1,Server 收到后确认seq number 值与ACK=1则连接建立成功。

完成三次握手,主机A与主机B开始传送数据。

sequenceDiagram
Client ->> Server: SYN=1  [seq number=1234567]
Server -->> Client: SYN = 1, ACK = 1  [ack number = 1234568,seq number = 7654321]
Client ->> Server: ACK = 1 [ack number = 7654322]

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据.

The Meaning of Three-way handshake

向客户端确认这个请求,这两个数据包(前两次握手)足以证明客户端与服务器之间的网络是畅通的,并且协商数据通信所需要的参数。比如协商接收窗口大小,所支持的数据包最大字节数等。

如果没有最后一个数据包确认(第三次握手),A先发出一个建立连接的请求数据包,由于网络原因绕远路了。A经过设定的超时时间后还未收到B的确认数据包,于是发出第二个建立连接的请求数据包,这次网路通畅,数据包很快到达B,B的确认数据包也很快就到达A。于是A与B开始传输数据,过了一会A第一次发出的建立连接的请求数据包到达了B,B以为是再次建立连接,所以又发出一个确认数据包。由于A已经收到了一个确认数据包,所以会忽略B发来的第二个确认数据包,但是B发出确认数据包之后就要一直等待A的回复,而A永远也不会回复。由此造成服务器资源浪费,这种情况多了B计算机可能就停止响应了。

第三次握手(第三个数据包)作用在于,告诉B计算机,B第二次握手发给A的确认数据包A收到了,是有效的。避免B计算机等待造成资源浪费。随后A与B可进行下一步的通信。

连接建立 过程中要解决以下三个问题:

  • 要使每一方能够确知对方的存在。
  • 要允许双方协商一些参数(如 最大报文段长度最大窗口大小服务质量 等)。
  • 能够对运输实体资源(如 缓存大小连接表中的项目 等)进行分配。

连接建立过程中,客户端存在以下状态:

SYN-SENT:在未与目标服务器建立连接之前始终处于此状态,并将不断向目标服务器发送请求,直到 收到回复 来自服务器的信息后,方进入下一个状态。 ESTABLISHED:稳定连接状态。

连接建立过程中,服务器存在以下状态:

LISTEN: 在未与客户端建立连接之前,始终处于此状态,在收到客户端的连接请求后答复其请求,并进入下一个状态。 SYN-RCVD: 等待SYN信息到达后进入下一个状态。 ESTABLISHED:稳定连接状态。

20210218-01

连接建立文字图解:

Client 发出 同步数据包(请求建立连接的数据包) 并进入SYN-SENT状态。

  • SYN = 1, 表示该为一个连接建立请求数据包;
  • ACK = 0,说明数据包确认号无效,省略;
  • Seq=x,x为所传送数据的第一个字节的序号。

Server 收到Client发出的 同步数据包 后结束LISTEN状态,进入SYN-RCVD状态并向A发出 确认同步数据包

  • SYN=1;
  • ACK=1;
  • seq=y,y的值由B指定表示B发送数据时的 第一个数据字节的序号
  • ack=x+1,表示已经收到A发送的x个字节数据,并告诉A下次应从数据的第x+1个字节开始发送。

Client 收到确认同步数据包之后,向B答复 确认数据包 ,结束 SYN-SENT 状态,进入 ESTABLISHED 状态。

  • SYN=0,表示双方已同意建立连接;
  • ACK=1,表示收到B的确认数据包;
  • seq=x+1,表示发出的数据包就是数据的第x+1个字节;
  • ack=y+1,表示收到了B发送y字节数据,并告诉B下次应从数据的第y+1个字节开始发送。

Server 收到 Client 的 确认数据包 之后,结束SYN-RCVD状态,进入ESTABLISHED状态。

TCP Four-Way-Wavehand

结束连接时,不管是Client或Server均可以主动发起结束信标FIN或RST。此处称主动发起结束信标的一方为主动方,另一方为被动方。

The Meaning of Four-Way-Wavehand

当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。

但未必被动方所有的数据都完整的发送给了主动方,所以 被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。

The Steps of Four-Way-Wavehand

  1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1 状态。

  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入`CLOSE_WAIT 状态。

  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入 LAST_ACK 状态。

  4. 第四次挥手:Client收到FIN后,Client进入`TIME_WAIT 状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入 CLOSED 状态,完成四次挥手

RST

Differences between FIN and RST

关键字: 主动释放、有序释放、终止释放

释放 即发送方和接收方终止连接,解除连接状态。

发送方和接收方均可主动释放。最常见的是,主动释放端会发送FIN包,并且因为TCP是双工的, 仅关闭一个方向上的数据流,从而TCP连接处于半关闭状态,继续完成四次挥手完成连接释放。

上述挥手是一种 有序释放 , 即,标志位为FIN的TCP报文会在之前所有排队的数据发送完之后,才会发送,在socket缓冲区和窗口中的数据也能保证发送成功。

通常我们调用 shutdown() , close() 函数后, TCP会发送FIN报文。

shutdown 和 close 的区别:

  • shutdown是关闭一个socket, 可以关闭读、写、读写;
  • close是关闭一个linux系统的文件描述符fd。

除了“有序释放”, 还有一种 终止释放 ,比如进程异常退出,用来关闭异常连接使用,是通过 RST 标志位实现的。 标志位为 RST 的TCP报文,会立即发送,而之前所有在缓存区排队的数据都将被RST发送方丢弃。

FIN报文需要应答 ACK , RST报文不需要应答 ACK

半开连接:如果发生断点,或网络条件很差,其中一端发送 RST 后会立马关闭连接;而另一端可能感知不到,仍然认为连接正常,造成一种半开连接的状态。

Why do we send RST?

RST标志位被发送,通常是因为一下几种原因:

  1. A向B发起连接,但B之上并没有应用监听相应的端口,这时B操作系统上的TCP处理程序会发RST包。
  2. 请求超时,即由于主动连接端连接请求超时,主动发起RST关闭连接。
  3. 在一个已关闭的socket上收到数据
  4. 字节流接收不完全

情况二: 有89、27两台主机。主机89向主机27发送了一个SYN,表示希望连接8888端口,主机27回应了主机89一个SYN表示可以连接。但是主机89却很不友好,莫名其妙的发送了一个RST表示我不想连接你了。 后来经过排查发现,在主机89上的程序在建立了socket之后,用setsockopt的SO_RCVTIMEO选项设置了recv的超时时间为100ms。而我们看上面的抓包结果表示,从主机89发出SYN到接收SYN的时间多达110ms。(从15:01:27.799961到15:01:27.961886, 小数点之后的单位是微秒)。因此主机89上的程序认为接收超时,所以发送了RST拒绝进一步发送数据。

情况三: 比如,AB正常建立连接了,正在通讯时,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因排查后放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现connect reset by peer错误。

Wireshark Tutorial

Wireshark是一款可以监听和捕捉网络通信数据的软件,对捕获的数据自下而上进行解码和分析至已知协议。如(Ethernet II -- IP -- TCP 或 Ethernet II -- IP -- TCP -- HTTP)。

Wireshark主要通过 捕获过滤显示过滤 对所有数据进行 捕获时过滤捕获后显示过滤

Capturing Filters 捕获过滤

Filtering packets while capturing 捕获时可用的过滤组件

Capture Filters are used to filter out uninteresting packets already at capture time. This is done to reduce the size of the resulting capture (file) and is especially useful on high traffic networks or for long term capturing.

Wireshark uses the pcap (libpcap/WinPcap) filter language for capture filters. This language is explained in the tcpdump man page under "expression" (http://www.tcpdump.org and search for "selects which").

Note: This capture filter language is different from the one used for the Wireshark display filters!

捕获过滤器的使用方式为:菜单栏上的 捕获 --> 选项 --> 所选择接口的捕获过滤器 中输入对应的过滤条件。


Some common examples 一些通用示例

Example Ethernet: capture all traffic to and from the Ethernet address 08:00:08:15:ca:fe

ether host 08:00:08:15:ca:fe

Example IP: capture all traffic to and from the IP address 192.168.0.10

host 192.168.0.10

Example TCP: capture all traffic to and from the TCP port 80 (http) of all machines

tcp port 80

Examples combined: capture all traffic to and from 192.168.0.10 except http

host 192.168.0.10 and not tcp port 80

Beware: if you capture TCP/IP traffic with the primitives "host" or "port", you will not see the ARP traffic belonging to it!


Capture Filter Syntax 捕获过滤器标志

The following is a short description of the capture filter language syntax. For a further reference, have a look at: http://www.tcpdump.org/tcpdump_man.html

A capture filter takes the form of a series of primitive expressions, connected by conjunctions (and/or) and optionally preceded by not:

[not] primitive [and|or [not] primitive ...]

A primitive is simply one of the following:

[src|dst] host

This primitive allows you to filter on a host IP address or name. You can optionally precede the primitive with the keyword src|dst to specify that you are only interested in source or destination addresses. If these are not present, packets where the specified address appears as either the source or the destination address will be selected.

ether [src|dst] host

This primitive allows you to filter on Ethernet host addresses. You can optionally include the keyword src|dst between the keywords ether and host to specify that you are only interested in source or destination addresses. If these are not present, packets where the specified address appears in either the source or destination address will be selected.

gateway host

This primitive allows you to filter on packets that used host as a gateway. That is, where the Ethernet source or destination was host but neither the source nor destination IP address was host.

[src|dst] net [{mask }|{len }]

This primitive allows you to filter on network numbers. You can optionally precede this primitive with the keyword src|dst to specify that you are only interested in a source or destination network. If neither of these are present, packets will be selected that have the specified network in either the source or destination address. In addition, you can specify either the netmask or the CIDR (Classless Inter-Domain Routing) prefix for the network if they are different from your own.

[tcp|udp] [src|dst] port

This primitive allows you to filter on TCP and UDP port numbers. You can optionally precede this primitive with the keywords src|dst and tcp|udp which allow you to specify that you are only interested in source or destination ports and TCP or UDP packets respectively. The keywords tcp|udp must appear before src|dst. If these are not specified, packets will be selected for both the TCP and UDP protocols and when the specified address appears in either the source or destination port field.

less|greater

This primitive allows you to filter on packets whose length was less than or equal to the specified length, or greater than or equal to the specified length, respectively.

ip|ether proto

This primitive allows you to filter on the specified protocol at either the Ethernet layer or the IP layer.

ether|ip broadcast|multicast

This primitive allows you to filter on either Ethernet or IP broadcasts or multicasts.

relop

This primitive allows you to create complex filter expressions that select bytes or ranges of bytes in packets. Please see the tcpdump man pages for more details.

Capturing 捕获

This section will explain the capturing options and give hints on what to do in some special cases.

Capture options 捕获设置

The capture options can be logically divided into the following categories:

-input -filtering -stop conditions -storing -display while capturing

Input options 输入设置

-Interface: You have to choose which interface (network card) will be used to capture packets from. Be sure to select the correct one, as it's a common mistake to select the wrong interface.

-Link-layer header type: unless you are in the rare case that you will need this, just keep the default.

Filtering options 过滤设置

-Capture packets in promiscuous mode: Usually a network card will only capture the traffic to its own network address. If you want to capture all traffic that the network card can "see", mark this option. See the FAQ for some more details of capturing packets from a switched network.

-Limit each packet to xy bytes: Will limit the maximum size to be captured of each packet, this includes the link-layer header and all subsequent headers. This can be useful when an error is known to be in the first 20 bytes of a packet, for example, as the size of the resulting capture file will be reduced.

-Capture Filter: Use a capture filter to reduce the amount of packets to be captured. See "Capture Filters" in this help for further information how to use it.

Storing options 存储设置

-File: You can choose the file to which captured data will be written. If you don't enter something here a temporary file will be used.

-Use multiple files: Instead of using a single capture file, multiple files will be created. The generated filenames will contain an incrementing number and the start time of the capture. For example, if you choose "/foo.cap" in the "File" field, files like "/foo_00001_20040205110102.cap", "/foo_00002_20040205110102.cap", ... will be created. This feature can be useful if you do long term capturing, as working with a single capture file of several GB usually isn't very fast.

Stop condition options 终止设置

These three fields should be obvious; the capture process will be automatically stopped if one of the selected conditions is exceeded.

Display while capturing options

-Update list of packets in real time: Using this will show the captured packets immediately on the main screen. Please note: this will slow down capturing, so increased packet drops might appear.

-Automatic scrolling in live capture: This will scroll the "Packet List" automatically to the latest captured packet, when the "Update List of packets in real time" option is used.

-Name resolution: perform the corresponding name resolution while capturing.

High performance capturing 高性能模式捕获

When your network traffic is high, you might need to take some steps to ensure Wireshark doesn't get behind on its capture, particularly if you're running it on a slow computer.

When Wireshark cannot keep up, packets are dropped. To help avoid this as much as possible:

  1. Don't use the "Update list of packets in real time" option (see above). This has a significant performance penalty.

  2. Close other programs that might slow down your system, such as virus scanner software, server processes, etc.

  3. It might be a good idea not to use a capture filter. This will depend on the task you have to do. As a rule of thumb: if you want to see most of the packets and only filter a small number out, don't use a capture filter (you can use a display filter later). If you only want to capture a small proportion of the packets, it might be better to set a capture filter, as this will reduce the number of packets that have to be saved.

  4. If you still get packet drops, it might be an idea to use a tool dedicated to packet capturing and only use Wireshark for displaying and analyzing the packets.

Have a look at tshark, the command line variant of wireshark, which is included in this package. XXX: add a list of possibly useful standalone capture programs.

Long term capturing 长期捕获

By "Long term capturing", it's meant to capture data from a network for several hours or even days. Long term capturing will usually result in huge capture files, being hundreds of MB's or even several GB's in size!

Before doing a long term capture, get familiar with the options to use for it, as you might not get what you desire. Doing a long term capture not getting the results needed, is usually wasting a lot of time. ;-)

Rules of thumb for this task: -Use the ring buffer feature when you expect very large capture files. -Don't use the "Update list of packets in real time" option. -Set an appropriate capture filter, when you are only interested in some special packets from the net.

Display Filter 显示过滤器

Filtering packets while viewing 观察时的过滤器组件

After capturing packets or loading some network traffic from a file, Wireshark will display the packet data immediately on the screen.

Using display filters, you can choose which packets should (not) be shown on the screen. This is useful to reduce the "noise" usually on the network, showing only the packets you want to. So you can concentrate on the things you are really interested in.

The display filter will not affect the data captured, it will only select which packets of the captured data are displayed on the screen.

Every time you change the filter string, all packets will be reread from the capture file (or from memory), and processed by the display filter "machine". Packet by packet, this "machine" is asked, if this particular packet should be shown or not.

Wireshark offers a very powerful display filter language for this. It can be used for a wide range of purposes, from simply: "show only packets from a specific IP address", or on the other hand, to very complex filters like: "find all packets where a special application specific flag is set".

Note: This display filter language is different from the one used for the Wireshark capture filters!

特殊字符

constains 和 matches

contains 用来判断是否包含一个值,matches 用来判断是否匹配一个表达式


Some common examples 一些通用示例

Example Ethernet: display all traffic to and from the Ethernet address 08.00.08.15.ca.fe

eth.addr==08.00.08.15.ca.fe

Example IP: display all traffic to and from the IP address 192.168.0.10

ip.addr==192.168.0.10

Example TCP: display all traffic to and from the TCP port 80 (http) of all machines

tcp.port==80

Examples combined: display all traffic to and from 192.168.0.10 except http

ip.addr==192.168.0.10 && tcp.port!=80

Beware: The filter string builds a logical expression, which must be true to show the packet. The && is a "logical and", "A && B" means: A must be true AND B must be true to show the packet (it doesn't mean: A will be shown AND B will be shown).


Hint

Filtering can lead to side effects, which are sometimes not obvious at first sight. Example: If you capture TCP/IP traffic with the primitive "ip", you will not see the ARP traffic belonging to it, as this is a lower protocol layer than IP!

WIRESHARK中的各种标志(TCP)

OUT OF ORDER

TCP Out-of-Order

正常情况:在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的 Seq 号等于前一 个包的 Seq + Len (三次握手和四次挥手是例外)。

异常情况:当Wireshark发现后一个包的 Seq小于 前一个包的 Seq + Len 时,就会认为是乱序了,因此提示 TCP Out-of-Order

Previous segment not captured

Previous segment not capturedd ,即报文缺失,指存在未抓取的数据包

正常情况:在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的 Seq 号等于前一个包的 Seq + Len (三次握手和四次挥手是例外)。

异常情况:如果Wireshark发现后一个包的 Seq 号大于前一个包的 Seq + Len ,就知道中间缺失了一段数据。假如缺失的那段数据在整个网络包中都找不到(即排除了乱序),就会提示TCP Previous segment not captured

TCP Previous segment lost - Occurs when a packet arrives with a sequence number greater than the "next expected sequence number" on that connection, indicating that one or more packets prior to the flagged packet did not arrive. This event is a good indicator of packet loss and will likely be accompanied by "TCP Retransmission" events.

-- Wireshark

TCP DUP ACK

Tcp Dup Ack xxx#y ,即重复确认。

当乱序或者丢包发生时,接收方会收到一些Seq号比期望值大的包。

接收方每收到一个这种包就会进行答复,Ack 一次期望的 Seq 值,以此方式来提醒发送方,于是就产生了一些重复的 Ack

Wireshark会在这种重复的Ack上标记 TCP Dup ACK ,代表了数据段丢失 TCP 状态,xxx 代表数据丢失的位置, y 后代表第几次丢失报文。

TCP Fast Retransmission

快速重传,当发送方收到来自接收方的3个或以上TCP Dup ACK ,就意识到之前发的包可能丢了,于是发送方快速重传该数据(这是RFC的规定)。

TCP Spurious Retransmission

TCP Retransmission

超时重传。

如果一个包真的丢了,且无后续包,则可以在接收方触发 Dup Ack ,就不会快速重传。

这种情况下发送方只好等到超时了再重传,此类重传包就会被Wireshark标上 TCP Retransmission

TCP ACKed unseen segment

抓取遗漏。

当Wireshark发现被Ack的那个包没被抓到,就会提示 TCP ACKed unseen segment, 即此为由于抓包不到造成的报错。

这可能是最常见的Wireshark提示了,幸好它几乎是永远可以忽略的。

以图3为例,32号包的Seq=6889 Len=1448 ,相加得 8337,说明服务器发出的下一个包应该是 Seq=8337。而我们看到的却是35号包的Seq=11233,这意味着 8337~11232 这段数据没有被抓到。这段数据本应该出现在34号之前,所以Wireshark提示了TCP ACKed unseen segment

TCP Zerowindow

窗口清零。

TCP包中的 win= 代表接收窗口的大小,即表示这个包的发送方当前还有多少缓存区可以接收数据。

当Wireshark在一个包中发现 win=0 时,就会给它打上 TCP zerowindow 的标志,表示缓存区已满,不能再接受数据了。下图就是服务器的缓存区已满,所以通知客户端不要再发数据了。我们甚至可以在3258~3263这几个包中看出它的窗口逐渐减少的过程,即从 win=15872 减小到 win=1472

TCP Window Update

其他

Sockets API和Internet在许多 竞争性协议族(包括 IPXAppleTalkDECNetOSISNATCP/IP)的世界中逐渐成长起来,并且 Sockets被设计成支持所有这些协议

IPv4映射

IPv4映射的地址是通过在IPv4地址前添加4个字节的前缀 ::fff: 而构成。

如,132.3.23.7 的IPv4地址映射至IPv6即 ::ffff:132.3.32.7

协议互操作性

回送地址

IPv4的回送地址是127.0.0.1

IPv6的回送地址是 0:0:0:0:0:0:0:1

专用网络地址

10192.168172.16~172.31开头的地址最初被指定在不属于全球Internet的专用网络中使用。

Name

Hostname是指 计算机名称 ,Domain Name是指 域名

通常情况下,一个name都是指Hostname。

Socket in Posix

Headerfiles

在Posix标准中,以下文件被用于在Unix/Linux/Mac中进行网络编程。

1
2
3
4
5
6
7
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

各文件包含的内容如下:

  • <sys/types.h> 定义了在socket网络编程中用到的数据类型;

  • <sys/socket.h> 定义了接口类型(socket types)、接口标志(socket flags)、额外设置(addtional options)、地址簇(address family)和地址存储结构体(address storage structure)特定机器的一些设定。

  • <netinet/in.h> 定义了RFC协议(protocol),和IP网络层相关的宏定义。其中 in 是 internet 的缩写。

  • <arpa/inet.h> 则定义了对网络层操作的函数,例如,“将host地址与net地址互相转换” 的 htonl()htons()ntohl()ntohs() 都定义在里面,还有 inet_addr()*inet_ntoa()*inet_ntop()inet_pton() 等操作

  • <netdb.h> 定义了网络数据库(network database)的操作, 相关手册可以查看 Michael 的 在线手册

  • <unistd.h> 定义了系统线程、文件读取等的操作和宏定义。

  • <errno.h> 定义了 普通文件操作和网路文件操作的错误码(error codes)。

Socket in Texas Instrument’s SysBIOS

NETCTRL.H 及 SOCKET.H

以下两句话是TI官方对<netctrl.h> 的描述,即简易控制网络开断的包装函数,以此类方法实现接口的目的是隐藏可以被调用的HAL/STACK功能

  • Shell functions for simplified net startup and shutdown
  • The idea behind this API is to hide the user callable HAL/STACK functions

<netctrl.h> 是用于初始化和维护服务的。为了完成此功能,其调用了NETTOOLS库提供的配置管理器。要注意的是,此处的配置定义和结构声明是对针对NETCTRL 的,而不是针对 CONFIG。

NETCTRL is used to initialize the stack and maintain services. To accomplish this, it makes use of the configuration manager provided in the NETTOOLS library. Note that the configuration definitions and structures defined here are specific to NETCTRL, not CONFIG.

NETCTRL API

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
// 初始化运行环境
_extern int NC_SystemOpen( int Priority, int OpMode );

//第一个参数可选择任务等级高或任务等级低
#define NC_PRIORITY_LOW OS_SCHEDULER_LOWPRI
#define NC_PRIORITY_HIGH OS_SCHEDULER_HIGHPRI

//定义打开模式为POLLING或中断
#define NC_OPMODE_POLLING 1
#define NC_OPMODE_INTERRUPT 2

// NC_SystemOpen()的返回结果
#define NC_OPEN_SUCCESS 0
#define NC_OPEN_ILLEGAL_PRIORITY -1
#define NC_OPEN_ILLEGAL_OPMODE -2
#define NC_OPEN_MEMINIT_FAILED -3
#define NC_OPEN_EVENTINIT_FAILED -4

// 关闭运行环境
_extern void NC_SystemClose();

// 使用提供的配置信息开启网络
_extern int NC_NetStart( HANDLE hCfg, void (*NetStart)(),
void (*NetStop)(), void (*NetIP)(IPN,uint,uint) );

// 断开网络
_extern void NC_NetStop( int rc );

// 当Boot线程完成时被调用
_extern void NC_BootComplete();

// 当IP地址被添加或移除时被调用
_extern void NC_IPUpdate( IPN IPAddr, uint IfIdx, uint fAdd );

SOCKET API

UNIVERSAL_TCP

defined in <ws2def.h>

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct addrinfo
{
int ai_flags; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST
int ai_family; // PF_xxx
int ai_socktype; // SOCK_xxx
int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6
size_t ai_addrlen; // Length of ai_addr
char * ai_canonname; // Canonical name for nodename
_Field_size_bytes_(ai_addrlen) struct sockaddr * ai_addr; // Binary address
struct addrinfo * ai_next; // Next structure in linked list
}
ADDRINFOA, *PADDRINFOA;

defined in TI <socket.h>

IPv4套接字地址数据结构
1
2
3
4
5
6
7
8
// AF_INET family (IPv4) Socket address data structure.
struct sockaddr_in {
UINT8 sin_len; // total length
UINT8 sin_family; // address family
UINT16 sin_port; // port
struct in_addr sin_addr;
INT8 sin_zero[8]; // fixed length address value
};
内核用地址存储数据结构

这个套娃里还有个套娃 in_addr ,这个结构体供内核调用,以储存更多地址数据。

1
2
3
4
// Structure used by kernel to store most addresses.
struct in_addr {
UINT32 s_addr; // 32 bit long IP address, net order
};
通用套接字地址储存数据结构
1
2
3
4
5
6
7
8
9
// Generic Socket address storage data structure.
struct sockaddr {
UINT8 sa_len; // Length
UINT8 sa_family; // address family
char sa_data[14]; // socket data
};

typedef struct sockaddr SA;
typedef struct sockaddr *PSA;

以下为Socket 接口协议簇接口类型接口协议

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
//
// Socket address families
//
#define AF_TASK 1 // Intertask Communication
#define AF_INET 2 // Internet: UDP, TCP, etc.
#define AF_INET6 10 // IPV6
#ifdef _INCLUDE_NIMU_CODE
#define AF_RAWETH 12 // Raw Ethernet Protocol
#endif

//
// Socket Types
//
#define SOCK_STREAM 1 // stream socket
#define SOCK_DGRAM 2 // datagram socket
#define SOCK_RAW 3 // raw-protocol interface
#define SOCK_STREAMNC 4 // non-copy stream socket
#ifdef _INCLUDE_NIMU_CODE
#define SOCK_RAWETH 5 // non-copy raw eth socket
#endif

//
// Protocols
//
#define IPPROTO_IP 0 // IP Placeholder
#define IPPROTO_ICMP 1 // ICMP
#define IPPROTO_IGMP 2 // IGMP
#define IPPROTO_TCP 6 // TCP
#define IPPROTO_UDP 17 // UDP
#define IPPROTO_IPV6 41 // IPV6
#define IPPROTO_ICMPV6 58 // ICMPV6 Header.

以下Socket接口均以Ti NDK为载体,以C为实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Socket Oriented Functions
SOCKET accept( SOCKET s, PSA pName, int *plen ); //接受一个套接字的连接
int bind( SOCKET s, PSA pName, int len ); //给套接字绑定一个名字
int connect( SOCKET s, PSA pName, int len ); //在一个套接字上初始化连接
int getpeername( SOCKET s, PSA pName, int *plen ); //在已连接的peer上返回名称地址
int getsockname( SOCKET s, PSA pName, int *plen ); //返回套接字的本地名称地址
int getsockopt( SOCKET s, int level, int op, void *pbuf, int *pbufsize ); //获取套接字设置信息
int listen( SOCKET s, int maxcon ); //监听数据
int recv( SOCKET s, void *pbuf, int size, int flags ); //接收数据
int recvfrom( SOCKET s, void *pbuf, int size, int flags, PSA pName, int *plen ); //从指定对象处接收信息

int recvnc( SOCKET s, void **ppbuf, int flags, HANDLE *pHandle ); //
int recvncfrom( SOCKET s, void **ppbuf, int flags,
PSA pName, int *plen, HANDLE *pHandle );
void recvncfree( SOCKET Handle );

int send( SOCKET s, void *pbuf, int size, int flags ); //发送信息
int sendto( SOCKET s, void *pbuf, int size, int flags, PSA pName, int len ); //在未连接的套接字上往指定目的地发送数据
int setsockopt( SOCKET s, int level, int op, void *pbuf, int bufsize ); //设置套接字设置
int shutdown( SOCKET s, int how ); //关闭一半的套接字连接
SOCKET socket( int domain, int type, int protocol ); //创建套接字

SOCKET API IN CLIENT


第一步 创建套接字

SOCKET socket( int domain, int type, int protocol );

如果创建成功,则返回一个代表套接字的文件描述符。否则就返回一个 INVALID_SOCKET 值,并且可以调用 fdError() 来诊断错误原因。

domain是指链路层类型IPv4还是IPv6,分别书写为 AF_INET | AF_INET6

type是指传输层套接字类型,共有 报文数据流式数据原始数据 三种可选,分别是 SOCK_DGRAM | SOCK_STREAM | SOCK_RAW

protocol是指网络层协议类型,IPPROTO_TCP | IPPROTO_UDP,在套接字类型是原始数据时可以任意指定,如果是套接字类型是 SOCK_STREAM,则协议需要指定为 IPPROTO_TCP

第二步 设定套接字参数

通常在套接字创建之后,使用Pv4套接字地址数据结构 sockaddr_in设定参数,以下实例是下位机做客户端时的代码。

1
2
3
4
5
bzero(&sin1, sizeof(struct sockaddr_in));            /* Set Port, IP address = IPAddrSend */
sin1.sin_family = AF_INET;
sin1.sin_len = sizeof(sin1);
sin1.sin_addr.s_addr = inet_addr(REMOTE_IPADDR_STRING); //连接服务器的地址
sin1.sin_port = htons(TCP_CLIENT_PORT);
第三步 连接

int connect( SOCKET s, PSA pName, int len );

PSAsockaddr 结构体的 指针 对象类型,定义为 typedef struct sockaddr *PSA;

另,SAsockaddr 结构体的对象类型。

sockaddr 的定义如下:

1
2
3
4
5
6
// 通用套接字地址存储数据结构
struct sockaddr {
UINT8 sa_len; //套接字长度
UINT8 sa_family; //套接字类型,AF_INET
char sa_data[14]; //套接字数据
};

实际连接时,通常设置一定时间的任务休眠以等待网络稳定后再行连接,且连接次数自定。

1
2
3
4
5
6
7
8
9
10
11
for(count = 0; count < 30; count ++){
res = connect(stcp, (PSA) &sin1, sizeof(sin1));
if(res < 0){
ConsoleWarning("网络连接失败!\n");
TaskSleep(SLEEPTIME);
}
else{
ConsoleWarning("网络连接成功!\n");
break;
}
}

通过特定配置设置,打开网络。

1
_extern int NC_NetStart( HANDLE hCfg,   void (*NetStart)(),   void (*NetStop)(),    void (*NetIP)(IPN,uint,uint) );

套接字

HANDLE is a void pointer* defined in the <usertype.h>.

<socket.h>中,SOCKET类型其实是个HANDLE,而HANDLE其实是void*数据。

typedef HANDLE SOCKET; // OS Socket Type

typedef void * HANDLE;

文件描述符

文件描述符,即 File Descriptor,其实一个是 void*类型

FD集
1
2
3
4
5
6
7
8
9
10
11
//
// Select uses bit masks of file descriptors. These macros
// manipulate handle lists. FD_SETSIZE can be modified as
// needed.
//
#define FD_SETSIZE 16

typedef struct _fd_set {
uint count;
HANDLE fd[FD_SETSIZE];
} fd_set;

字节顺序转换函数

在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到htons() , ntohl() , ntohs()htons()这4个函数。

网络字节顺序与本地字节顺序之间的转换函数:

htonl()--"Host to Network Long" ntohl()--"Network to Host Long" htons()--"Host to Network Short" ntohs()--"Network to Host Short"

网络字节顺序(NBO, Network Byte Order): 按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

主机字节顺序(HBO, Host Byte Order): 不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。

如 Intel x86结构下, short型数 0x1234 表示为34 12, int型数 0x12345678 表示为78 56 34 12

如 IBM power PC结构下, short型数0x1234 表示为 12 34 , int型数 0x12345678 表示为 12 34 56 78

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power pc那样的顺序. 在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换.

timeval结构体

Ti SysBios中,timeval 是在 <SOCKET.H> 中被定义,被 fdSelect() 使用的;

结构体定义与Linux C中的一致

1
2
3
4
struct timeval {
INT32 tv_sec; //Second Level
INT32 tv_usec; //Microsecond Level
};

Linux C 中,timevaltimezone 结构体都隶属于 sys/time.h 头文件

1
2
3
4
struct timezone{
int tz_minuteswest;
int tz_dsttime;
}

Linux C 中对 TIMEVAL 使用的补充资料:

1
int gettimeofday(struct timeval*tv, struct timezone *tz);

其参数tv是保存获取时间结果的结构体,参数tz用于保存时区结果,tz 参数若不使用则传入NULL即可。

gettimeofday() 使用举例-1:

1
2
3
4
struct timeval tv_begin, tv_end ;
gettimeofday(&tv_begin, NULL);
foo();
gettimeofday(&tv_end, NULL);

gettimeofday() 使用举例-2:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc,char * argv[])
{
struct timeval tv;
while(1)
{
gettimeofday(&tv,NULL);
printf("time %u:%u\n",tv.tv_sec,tv.tv_usec);
sleep(2);
}
return 0;
}

源自:http://www.cnblogs.com/Neddy/archive/2012/01/31/2332957.html

SOCKET API IN SERVER

第一步 创建套接字

同client,略

第二步 监听套接字

int listen( SOCKET s, int maxcon );

maxcon 参数用于定义最大的阻塞数,如果阻塞值最高,则会发送一个 ECONNREFUSED 错误给客户端

  • listen() 监听套接字上的连接请求。为了连接请求,需要先由socket() 函数创建套接字。

  • listen() 函数用于等待设备接入并声明有限接入数的队列。

  • 新连接接入时需要调用 accept() 函数。

第三步 从套接字接收数据

int recv( SOCKET s, void *pbuf, int size, int flags );

pbuf 参数用于储存数据

size 为欲接收数据的大小

flags 为接收不到数据时的行为定义

FLAGS CONDITIONS
MSG_DONTWAIT Requests that the operation not block when no data is available
MSG_OOB Requests receipt of out-of-band data that would not be received in the normal data stream. Some protocols place expedited data at the head of the normal data queue, and thus, this flag cannot be used with such protocols.
MSG_PEEK Causes the receive operation to return data from the beginning of the receive queue without removing that data from the queue. Thus, a subsequent receive call will return the same data.
MSG_WAITALL Requests that the operation block until the full request is satisfied. However, the call may still return less data than requested if an error or disconnect occurs, or the next data to be received is of a different type than that returned.

SOCKET API IN SERVER (LINUX C)

第一步 创建套接字
1
2
int servSock;  //Socket descriptor for server
if( ( servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) < 0 ) printf("socket() failed.\n");
第二步 套接字地址初始化
1
2
3
4
5
struct sockaddr_in servAddr;
memset(&servAddr, 0, sizeof( servAddr ) );
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl( INADDR_ANY );
servAddr.sin_port = htons( servPort );
第三步 绑定套接字

注意: 客户端把服务器的地址提供给 connect() 以供连接至服务器,而 服务器 必须将自己的地址指定给 bind() 进行绑定。

1
if( ( bind( servSock, (struct sockaddr*) &servAddr, sizeof( servAddr ) ) ) < 0) printf("bind() failed.\n");
第四步 监听套接字
1
2
short MAXPENDING = 5;
if( ( listen( servSock, MAXPENDING ) ) < 0 ) printf("listen() failed.\n");
第四步 处理程序
1
2
3
4
5
6
7
8
9
10
11
12
    for(;;){
struct sockaaddr_in cintAddr;
socklen_t clntAddrLen = sizeof( clntAddr );
int clntSock = accept( servSock, (struct sockaddr*)&clntAddr, &clntAddrLen );
if( clntSock < 0 ) printf("accept() failed.\n");

char clntName[ INET_ADDRSTRLEN ];
if( inet_ntop( AF_INET, &clntAddr.sin_addr.s_addr, clntName, sizeof( clntName ) ) != NULL ) printf("Handling client %s/%d.\n", clntName, ntohs( clntAddr.sin_port ));
else printf("Unable to get client address.\n");

HandleTCPClient(clntSock);
}

Daemon API

DAEMON,即TCP/UDP Server Daemon Support。

A server daemon is a single network task that monitors the socket status of multiple network servers. When activity is detected, the daemon creates a task thread specifically to handle the new activity. This is more efficient than having multiple servers, each with their own listening thread.

要使用服务器守护,首先要创建入口(entry),创建成功时会返回一个句柄,失败则返回NULL。

1
2
void *DaemonNew(uint32_t Type, uint32_t LocalAddress, uint32_t LocalPort, int(*pCb)(SOCKET,uint32_t), 
uint32_t Priority, uint32_t StackSize, uint32_t Argument, uint32_t MaxSpawn);

Type Socket type (SOCK_STREAM, SOCK_STREAMNC, or SOCK_DGRAM) LocalAddress Local IP address (set to NULL for wildcard) LocalPort Local Port to serve (cannot be NULL) pCb Pointer to callback to handle server event (connection or activity) Priority Priority of new task to create for callback function StackSize Stack size of new task to create for callback function Argument Argument (besides socket) to pass to callback function MaxSpawn Maximum number of callback function instances (must be 1 for UDP)

在TCP环境中,当新连接稳定时,新任务线程会被创建,套接字Session会被打开。在新的任务线程上,用户的回调函数会被调用以供新连接上的套接字和调用的指定参数使用。回调函数可以一直维护套接字和任务线程。一旦完成连接它将从回调任务中返回。该任务能够判断是否关闭套接字(或翻译成:该任务能够在需要时关闭套接字)。

In the case of TCP, when a new connection is established, a new task thread is created, and a socket session is opened. Then the user's callback function is called on the new task thread, being supplied with both the socket to the new connection and the caller specified argument (as supplied to DaemonNew()). The callback function can keep the socket and task thread for as long as necessary. It returns from the callback once it is done with the connection. The function can choose to close the socket if desired. The return code informs the daemon whether the socket has been closed (0) or is still open (1).

NETCFG.H

CI means Configuration Item , and CFG means Configuration .

Data-type UINT32 and IPN are both defined in the header file <usertype.h> .

1
2
typedef unsigned int   UINT32;
typedef UINT32 IPN; // IP Address in NETWORK format

Structure CI_IPNET and structure CI_ROUTE are different from each other.

Structure CI_IPNET is as follow:

1
2
3
4
5
6
7
8
// IPNet Instance
typedef struct _ci_ipnet {
uint NetType; // 网络地址类型标志
IPN IPAddr; // 32bits地址((2^8)*4)
IPN IPMask; // 子网掩码
HANDLE hBind; // 绑定句柄
char Domain[CFG_DOMAIN_MAX]; // 域名
} CI_IPNET;

Structure CI_ROUTE is as follow:

1
2
3
4
5
6
7
// Route Instance
typedef struct _ci_route {
IPN IPDestAddr; // 目的地址
IPN IPDestMask; // 目的地址掩码
IPN IPGateAddr; // 默认网关地址
HANDLE hRoute; // Route handle (resets to NULL)
} CI_ROUTE;

CONFIGIF.H

请先阅读 <spru524k.pdf>

Configuration 特性

  • 任何对(已激活)配置的作用都将立即生效。

The configuration is based on an active database. That is, any change to the database can cause an immediate reaction in the system. For example, if a route is added to the configuration, it is added to the system route table. If the route is then removed from the configuration, it is removed from the system route table.

  • 配置存在激活与失效两种状态。

Configurations can be set active or inactive. When a configuration is active, any change to the configuration results in a change in the system. When a configuration is inactive, it behaves like a standard database. Part of the main initialization sequence is to make the system configuration active, and then inactive when shutting down.

  • 配置(Configurations)和配置入口(Configuration Entries)都使用句柄(handle)来映射,但不同的是,配置使用CfgHandle,而配置入口使用CfgEntryHandle,所以不能混淆。

Both the configurations and configuration entries are referenced by a generic handle. Configuration functions (named as CfgXxx()) take a configuration handle parameter, while configuration entry functions (name as CfgEntryXxx()) take a configuration entry handle parameter. These handles are not interchangeable.

  • 配置条目(Entry)包含着(contains) 一个内部引用计数(Internal Reference Count),即如果有任务想使用它,它就不能被其他任务销毁。配置条目被引用一次,引用计数就会加一。

Configuration entry handles are referenced. This means that each handle contains an internal reference count so that the handle is not destroyed by one task while another task expects it to stay valid. Functions that return a configuration entry handle supply a referenced handle in that its reference count has already been incremented for the caller.

  • 理论上句柄能够被无限持有,一旦释放则将被dereference。

The caller can hold this handle indefinitely, but should dereference it when it is through.

IF 是指 Interface。ifconfig 是unix系统上的ip接口查看语句。而 CONFIGIF 是配置管理接口的意思。

DeRef ,即 Dereference 之意。

1
_extern int  CfgAddEntry( HANDLE hCfg, uint Tag, uint Item, uint Mode, int Size, UINT8 *pData, HANDLE *phCfgEntry );

HANDLE hCfg

uint Tag

1
2
3
4
5
6
7
8
9
// Defined Configuration Tags
#define CFGTAG_OS 0x0001 // OS Configuration
#define CFGTAG_IP 0x0002 // IP Stack Configuration
#define CFGTAG_SERVICE 0x0003 // Service
#define CFGTAG_IPNET 0x0004 // IP Network
#define CFGTAG_ROUTE 0x0005 // Gateway Route
#define CFGTAG_CLIENT 0x0006 // DHCPS Client
#define CFGTAG_SYSINFO 0x0007 // System Information
#define CFGTAG_ACCT 0x0008 // User Account

uint Item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Currently Used DHCP Compatible Items
// Multiple instances are always to be stored as multiple config entries, not a concatenated byte string in a single config entry.
//
#define CFGITEM_DHCP_DOMAINNAMESERVER 6 // Stack's DNS servers
#define CFGITEM_DHCP_HOSTNAME 12 // Stack's host name
#define CFGITEM_DHCP_NBNS 44 // Stack's NBNS servers
#define CFGITEM_DHCP_CLIENT_OPTION 61 // Stack DHCP Client Identifier

#define CFGITEM_SYSINFO_REALM1 256 // Realm Name 1 (max 31 chars)
#define CFGITEM_SYSINFO_REALM2 257 // Realm Name 2 (max 31 chars)
#define CFGITEM_SYSINFO_REALM3 258 // Realm Name 3 (max 31 chars)
#define CFGITEM_SYSINFO_REALM4 259 // Realm Name 4 (max 31 chars)
#define CFGITEM_SYSINFO_REALMPPP 260 // Realm Name PPP (max 31 chars)
#define CFGITEM_SYSINFO_EVALCALLBACK 261 // Callback function to notify
// application 5 min before
// end of stack evaluation period

uint Mode

1
2
3
4
// Add Entry Flags
#define CFG_ADDMODE_UNIQUE 0x0001 // Replace all previous instances
#define CFG_ADDMODE_DUPLICATE 0x0002 // Allow duplicate data entry
#define CFG_ADDMODE_NOSAVE 0x0004 // Don't include this entry in CfgSave

Socket in Windows

Windows Sockets可以保证应用程序在任何支持Windows Sockets API 的网络内正常通信。

流式Socket:基于TCP,数据无差错且无重复发送;

数据报Socket:基于UDP,不能保证数据按发送顺序接收,可能丢失或重复。

真正与客户端Socket对象通信都不是服务器 Socket对象,而是新创建的“临时”Socket对象

构造函数:CAsyncSocket();

Create();【成功返回非0,失败返回0】 //SOCK_DGRAM 数据报

GetSockName(); //用于获取Socket对象的本地名称(自己的信息),ip地址及端口号,或

GetPeerName(); //用于获取连接的Socket对象名称(对方的信息)

Listen(); //面向流式使用,参数范围1~5,表示等待连接队列的最大长度

Accept(); //用以接收等待队列中存在的连接请求

Connect(); //建立连接请求函数

Send(); //

除字符型与布尔型外,其余整型用于表示(可能)不同尺寸的整数。

C++规定一个int(最小16bit)至少和一个short(16bit)一样大,一个long(最小32bit)至少和一个int一样大,一个long long(64bit)至少和一个long一样大。

其中,long long 是在C++ 11中新定义的。

尽管字符型char有三种(char、signed char、unsigned char),但是字符的表现形式只有2种,signed或unsigned。类型char实际上会表现为其中一种,具体由编译器决定。

字面值常量

20 //十进制

020 //0开头的为8进制

0x20 //0x开头的为16进制

SEND() BLOCKING

In some cases where send() would block, it instead returns without copying all of the data as requested.

In this case, the return value of send() indicates how many bytes were actually copied. One example of this is if your program is blocking on send() and then receives a signal from the operating system.

In these cases, it is up to caller to try again with any remaining data.

套接字及流输入输出接口(IO)

TOPIC

  • 文件描述符环境
  • 文件描述符编程接口
  • 套接字编程接口
  • 元以太网套接字编程接口
  • 全双工管道编程接口
  • 因特网群组管理协议(IGMP)

在各嵌入式系统中,对文件描述符的支持都大相径庭。大部分情况,都只支持基本功能(bare minimum functionality), 通常都以通用名称命名和提供被修剪过的函数(trimmed down support functions)。

TI NDK支持标准套接字接口函数,这些函数也要求文件描述符的支持,堆栈提供一个小型文件系统。

在堆栈代码内部的基本构建块是一个对象句柄。在其内部,套接字和管道都通过对象句柄寻址。然而,在应用层,套接字和管道都被当做文件描述符看待。文件描述符内涵附加状态信息(additional state information),允许根据套接字活动阻塞和解除阻塞任务。

注意:尽管文件描述符能够在传统函数中使用,如select(), 但在这种实现方式中,他们仍然是句柄,而不是整型。

出于兼容性考虑,网络程序必须使用NDK头文件,然后使用INVALID_SOCKET作为错误情况判定,并且在检查SOCKET有效性时,不要直接与(<0)比较。

使用文件描述符前,需要一个任务首先创建一个文件描述符表格(FD table / session)。只需要在应用层调用文件描述符函数 fdOpenSession() 来完成。

当任务结束使用FD接口,或被关闭时,调用 fdCloseSession()

为确保堆栈操作正确,每个任务在使用FD相关的函数前,都应创建FD session,在使用完毕后关闭它。

Open FD Session

  1. 最简单的方式就是用 TaskCreate() ,他可以(自动)在内部打开和关闭FD session。

  2. 另一种方式就是在函数调用的最开头进行session创建,在函数结束的末尾进行session关闭。如下:

1
2
3
4
5
6
7
8
9
void socket_task(int IPAddr, int TcpPort)
{
SOCKET s;
// Open the file session
fdOpenSession(TaskSelf());
// < socket application code >
// Close the file session
fdCloseSession(TaskSelf());
}
  1. 另一种方式就是创建子线程。注意的是,父线程需要保证子线程的任务被执行前打开子线程的session。可通过任务优先级或信标来完成,但会增加任务创建复杂度,并不是理想方案。
  2. 也可以通过让子任务调用session创建函数,并且由父线程来监控和关闭子线程门。

栈库支持一些通常被认为是文件函数的功能,因此套接字应用程序可以在更传统的意义上编程。

The stack library supports a handful of what are normally considered file functions, so that sockets applications can be programmed in a more traditional sense.

fdPoll() 远比 fdSelect() 来得更有效率。它轮询提供的套接字列表,并指定以毫秒为单位的超时(或使用POLLINFTIM 来设置无限超时)。拥有 fdSelect() 的优点,即对原始文件描述符列表(或者套接字)的检验不会被结果所改写,因此可以不用重建便多次使用。

1
2
3
4
5
typedef struct _fdpollitem {
void *fd; //the fd or socket to check
uint16_t eventsRequested; //a set of flags for requested events
uint16_t eventsDetected; //a set of resulting flags for a detected event
} FDPOLLITEM;

参考

  1. Michael Kerrisk

C++语法及基础

运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符,并提供了以下类型的运算符:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 杂项运算符

位运算符

位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A = 1100 0011

下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:

运算符 描述 实例
& 按位与操作,按二进制位进行"与"运算。运算规则: 0&0=0; 0&1=0; 1&0=0; 1&1=1; (A & B) 将得到 12,即为 0000 1100
| 按位或运算符,按二进制位进行"或"运算。运算规则: 0|0=0; 0|1=1; 1|0=1; 1|1=1; (A | B) 将得到 61,即为 0011 1101
^ 异或运算符,按二进制位进行"异或"运算。运算规则: 0^0=0; 0^1=1; 1^0=1; 1^1=0; (A ^ B) 将得到 49,即为 0011 0001
~ 取反运算符,按二进制位进行"取反"运算。运算规则: ~1=-2; ~0=1; (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
<< 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 A << 2 将得到 240,即为 1111 0000
>> 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 A >> 2 将得到 15,即为 0000 1111

Mangling

重载,包括 函数重载操作符重载

函数重载

函数重载 也叫 方法重载。是编译器通过把原方法名称与其参数相结合产生一个独特的内部名字来取代原方法名称的技术。

基本上,支持函数重载的语言都需要进行Name Mangling。Mangling的目的就是为了给重载的函数不同的签名,以避免调用时的二义性调用。

Name Mangling 不是一个非常新的技术,在C语言中也有,***在汇编C语言时经常看到的以 下划线“_”开头的函数名,其实就是C编译器将函数名进行了 Name Mangling*** 。

但是在C++中Name-mangling要复杂的多。 因为C++中支持 overloadoverride ,这就导致了C++编译器必须要有完成的Name-mangling把函数名或者变量名进行调整。

在面向对象编程语言出现之前,如果你想要打印不同类型的数据,需要写多个方法 ,象是 PrintInteger(int i)PrintString(string s)PrintFloat(float f) 。也就是说必须通过命名来区别行为和数据类型,因为 OOP语言出现前,任一语言都(像是C)不允许使用相同的名字命名函数, 即使参数类型不同。

但在C++中,像是 Print(int i)、Print(string s) 和 Print(float f),编译器自会准确调用特定的Print方法。当调用 Print(1) 的时候, 编译器可能在内部用源于参数类型的前缀重命名Print方法,这样一来 Print(1) 可能就变成 i_Print (1)

下面是更详细的例子:

C++编译器实际上将下面这些重载函数:

1
2
3
4
void print(int i);
void print(char c);
void print(float f);
void print(char* s);

编译为:

1
2
3
4
_print_int
_print_char
_print_float
_pirnt_string

这样的函数名,来唯一标识每个函数。

注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用 print(3) 时,它会去查找 _print_int(3) 这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。

C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。

方法重载 仅是多态性的一种情形。

名称重整 是一种支持方法重载的机制。更普遍的情况下,多态性是与继承相联系。

Inherit

继承就是一个新类 (称为子类) 从被继承类(称为父类或超类)取得自身的部分定义同时增加一些自己的新的信息。

如果你在相同的类中重载方法, 数据类型必须是不同的。如果你在继承关系下重载方法, 子类与父类的方法可能完全相同,而且名称重整器生成同样的重整名称。

举例来说,假设一个超类定义一个 Print(int i) 方法而一个从它继承的子类也定义了一个 Print(int i) 方法。当你有一个子类的实例时,运用多态性调用 Child.Print(int) ;而当你产生一个父类的实例时运用多态性调用 Parent.Print(int) 。这就是继承多态性:相同的名字和签字但是类却不同。

继承多态性 是通过使用一种与名称重整相关的另外一种机制实现的。编译器把方法放置在一个被称为虚拟方法表(其实是一个方法数组)的地方。每一个方法在VMT中都有一个索引, 如此当 Print(int) 被调用的时候, 编译器将被路由到VMT处找寻Print方法和类的内在索引。这样一来,编译器就可以调用正确的方法实现。由编译器负责管理所有的VMT索引和类偏移量。

简言之,多态性使你能够用非常相似的名字定义许多方法,这里的名字往往都是直观易记的。 OOP编译器自会根据调用者类理解到底该调用哪个方法。

Only one version of an overloaded function can appear within the extern C block. The code in the following example would result in an error.

While you can use name overloading in your SYS/BIOS C++ applications, only one version of the overloaded function can be called from the configuration.

1
2
3
4
extern “C” { // Example causes ERROR
Int addNums(Int x, Int y);
Int addNums(Int x, Int y, Int z); // error, only one version of addNums is allowed
}

Inline Function

内联方法 即内联函数,成员函数,inline functions,是指定义在类体内的函数。

该函数可以在类体内被声明和定义,也可以在类体内声明同时在体外使用 inline 关键字进行定义,如:

1
2
3
4
5
6
7
8
9
10
class angle{
private:
double value;
public:
void SetValue(double);
}

inline void angle::SetValue(double x){ //定义内联函数
value = x;
}

注意:内联函数无法递归。

Constructor and Destructor

构造函数(Constructor Function) 在调用时会为对象开辟储存空间、作初始化 及 其他管理操作。

  • 如果为编写,则系统默认生成
  • 可以接受参数不能有返回值
  • 可以有多个构造函数,因此可以接受名称重载(Name Mangling)。

析构函数(Destructor Function) 仅在释放对象的内存空间时使用,如 程序超出类对象的作用域类指针运行delete运算符 时。

Friend Function

友元函数 是指 在类内部声明,可以 自由访问该类的私有部分 并且 不属于类成员函数

在友元函数声明时定义一个该类的对象,可以通过引用该对象作为参数进行对类的访问。

为了确保数据完整性并遵循数据封装和隐藏的原则,因尽量少用或不用友元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student;
class Teacher{
public:
//....
}
protected:
int NoOfStudent;
Student * pList[100];
};

class Student{
public:
friend class Teacher; //友元类声明
//....
}

Preprocessing

定义

预处理是指将源文件的文本作为翻译的第一阶段操作的文本处理步骤。 预处理不会分析源文本,但会为了查找宏调用而将源文本细分为标记。 主要包括了下面三个方面:

  • 预处理指令
  • 预处理运算符
  • 预定义宏,这个有很多了,比如__FILE__、__LINE__和__DATA__等。

常识 - 预处理并不是编译,也不是“预编译” - 预处理并不是每个语言都有 - C/C++预处理仅仅是把源程序划分和整理成一个个的段(phase),并不进行编译。 - 预处理器在UNIX传统中通常缩写为PP,在自动构建脚本中C预处理器被缩写为CPP的宏指代。为了不造成歧义,C++(c-plus-plus) 经常并不是缩写为CPP,而改成CXX

Preprocessing Directives 预处理指令

#include #import #using #progma
#if #ifdef #ifndef #elif
#lese #endif #define #undef
#error #line

预处理运算符号

运算符 操作
字符串化运算符(#) 导致对应的实参括在双引号内
Charizing运算符(#@)
标记粘贴运算符(##)
定义的运算符

# and

字符串化运算符

# 除了是 预处理符号,也是一种 运算符 ,即 字符串化运算符,只能出现在带参的宏的替换文本中,将跟在后面的参数转换成一个字符串常量

1
2
3
4
5
#define PF_INT(i) printf(#i"=%d\n",i)
void main(){
int x=100;
PF_INT(x);
}

预处理后:

1
printf("x""=%d\n",x);

C语言常将相邻的字符串合并处理:

1
printf("x=%d\n",x);
标记粘贴运算符

## 是一种 运算符 ,即 标记粘贴运算符,是将两个 运算对象 连接(拼接)在一起,只能出现在带参宏定义的替换文本中。如:

1
#define NUM(h,t,u)  h##t##u

假设u代表个位,t代表十位,h代表百位,则x=NUM(1,2,3)后,x=123。

注:##也可以用于拼接一些开头一样,尾巴不一样的宏,这样的宏一般用于描述代表特定意义的对象的不同状态等。宏开头固定,根据不同条件则选择拼接不同尾巴,最后拼接的字符串代表一个具体的状态等。

Differences between C & C++

  • 从语法要求来说,C++的语法要求更为严格,编译器对参数变量的检查要求更高,更容易报错;很多在C中可以被顺利编译的语句,在C++中会被严厉拒绝,例如,在C++中,当形参为unsigned char,而实参为const char时会报错。
  • C是面向过程语言,代码复用复杂。C++是面向对象语言。

C++11

定义与声明

变量 只可以被定义(definition)一次,但是可以被多次声明(declaration)。

extern 是跨文件编译(分离式编译,seperate compilation)的 变量声明符,只要没有显式(explicitly)地初始化变量,如extern int num = 100 就可以,任何包含了显式初始化的声明,即成为了定义

关键字

关于用户自定义的标识符,需要注意的是:

  • 不能连续出现两个下划线。
  • 不能以下划线紧连大写字母开头。
  • 定义在函数体外的标识符不能以下划线开头。
image-20210801144743194

顶层const

指针本身是一个对象,指针本身可以指向另一个对象。

“指针本身是不是常量” 和 “指针所指的是不是一个常量” 是两个互相独立的问题。

顶层const(top-level const)表示 指针本身是个常量

底层const(low-level const)表示 指针所指的对象是一个常量

初始化

C++语言定义了初始化的好几种不同 形式,如下:

1
2
3
4
int sold = 0;
int sold = {0};
int sold{0};
int sold(0); //亲测可用

注意初始化并不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义把对象的当前值擦除,并用一个新的值来替代

拷贝初始化(Copy Initialization):使用等号(=)初始化一个变量,编译器把等号右侧的初始值拷贝到新创建的对象上去。

直接初始化(Direct Initialization):不使用等号进行初始化。

列表初始化(List Initialization):无论是为对象初始化或赋新值,都可以使用一组由花括号括起来的初始值。

命名空间

image-20211009225305135

这里说的头文件 不应包含,是只说不建议包含,

常见问题及错误

C++常见的内存错误及解决方法

(1)内存分配未成功,却使用了它。

在使用内存之前先检查指针是否是NULL。如果是用malloc来申请内存,应该用if(p == NULL)或if(p != NULL)进行防错处理。如果是new来申请内存,申请失败会抛出异常,所以应该捕捉异常来进行防错处理。

(2)内存虽然分配成功,但尚未初始化就引用它。

尽管有时候缺省时会自动初始化,但无论什么时候创建对象均要对其进行初始化,即使是赋0值也是不可忽略的。

(3)内存分配成功,但访问越界

对数组for循环时要把握越界,否则可能会导致数组越界。

(4)忘记释放内存,导致内存泄漏

动态内存的申请和释放必须配对,new-delete和malloc-free其使用次数必须相等。

(5)已经释放内存还在使用它

free或delete后 ,没有将指针设为NULL,产生“野指针”。

C++中struct与class的区别是什么?

如果没有多态和虚拟继承,在C++中,struct和class的存取效率完全相同,存取class的数据成员与非虚函数效率和struct完全相同,不管该数据成员是定义在基类还是派生类。

class的数据成员在内存中的布局不一定是数据成员的声明顺序,C++只保证处于同一个access section的数据成员按照声明顺序排列。

C++中,class和struct做类型定义是只有两点区别:

  • 默认继承权限不同,class继承默认是private继承,而struct默认是public继承
  • class还可用于定义模板参数,像typename,但是关键字struct不能同于定义模板参数。

C++保留struct关键字,原因:

  • 保证与C语言的向下兼容性,C++必须提供一个struct。
  • C++中的struct定义必须百分百地保证与C语言中的struct的向下兼容性,把C++中的最基本的对象单元规定为class而不是struct,就是为了避免各种兼容性要求的限制。
  • 对struct定义的扩展使C语言的代码能够更容易的被移植到C++中。

如何将结构体传递给函数?

与类对象一样,结构体变量也可以通过值、引用和常量引用传递给函数。

默认情况下,它们通过值传递,这意味着需要生成整个原始结构的副本并传递给函数。

因为不希望浪费时间来复制整个结构体,所以,除非结构很小,否则一般会通过 引用 将结构体传递给函数。但是,这样意味着函数可以访问原始结构的成员变量,从而可能更改它们。

如果不想让函数更改任何成员变量值,那么可以考虑将结构体变量作为一个 常量引用 传递给函数。

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
39
40
41
42
43
44
45
46
//程序1
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

struct Invltem // Holds data for an inventory item
{
int partNum; // Part number
string description; // Item description
int onHand; // Units on hand
double price; // Unit price
};
// Function prototypes
void getltemData(InvItem &) ; //普通引用,函数可能会对结构体数据造成影响

void showItem(const InvItem &); //常量引用,不会让函数对结构体造成数据变化

int main()
{
InvItem part; // Define an Invltem structure variable.
getItemData(part);
showItem(part);
return 0;
}

void getItemData(InvItem &item)
{
cout << "Enter the part number: ";
cin >> item.partNum;
cout << "Enter the part description: ";
cin.get();
getline (cin, item.description);
cout << "Enter the quantity on hand: ";
cin >> item.onHand;
cout << "Enter the unit price: ";
cin >> item.price;
}
void showItem(const InvItem &item)
{
cout << fixed << showpoint << setprecision(2) << endl;
cout << "Part Number : " << item.partNum << endl;
cout << "Description : " << item.description << endl;
cout << "Units On Hand : " << item.onHand << endl;
cout << "Price : $" << item.price << endl;
}

程序输出结果:

1
2
3
4
5
6
7
8
9
Enter the part number: 800
Enter the part description: Screwdriver
Enter the quantity on hand: 135
Enter the unit price: 1.25

Part Number : 800
Description : Screwdriver
Units On Hand: 135
Price : $1.25

如何从函数返回一个结构体?

也可以从函数返回结构体变量。在这种情况下,函数的返回类型是结构体的名称。可以改写程序 1 以允许 getItemData 函数创建 Invltem 结构体的局部实例,将数据值放入其成员变量中,然后将其传递回 main,而不是将其作为引用变量从 main 接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InvItem getItemData()
{
InvItem item;
cout << "Enter the part number:";
cin >> item.partNum;
cout << "Enter the part description: ";
cin.get();
getline(cin, item.description);
cout << "Enter the quantity on hand: ";
cin >> item.onHand;
cout << "Enter the unit price: ";
cin >> item.price;
return item;
}

以下是从 main 中调用它的方法:

1
part = getItemData();

注意: C++ 只允许从函数返回单个值。然而,结构体提供了解决这一限制的方法。即使一个结构体可能有几个成员,它在技术上还是一个单一的对象。通过在结构体中打包多个值,可以从函数返回任意数量的值。

. | -> | :: | :符号区分?

  1. A.B则A为对象或者结构体;

  2. A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针;

  3. :: 是作用域运算符,A::B表示作用域A中的名称B,A可以是名字空间、类、结构;

  4. : 一般用来表示继承;

计算机上正在运行的句柄、线程、进程分别是什么意思?

https://www.cnblogs.com/bluestorm/p/5712238.html

所谓 句柄 实际上是一个数据,是一个Long (整长型)的数据。

句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点像C语言中的文件句柄。

从上面的定义中的我们可以看到,句柄是一个 标识符,是拿来标识对象或者项目的,它就像我们的姓名一样,每个人都会有一个,不同的人的姓名不一样,但是,也可能有一个名字和你一样的人。从数据类型上来看它只是一个 16位的无符号整数应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。

句柄是一种 指向指针的指针。所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是驻留在内存的。如果简单地理解,似乎只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是非也,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,该到哪里去找该对象呢?

为了解决这个问题,Windows操作系统为各应用程序腾出一些 内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。

句柄地址(稳定)→记载着对象在内存中的地址→对象在内存中的地址(不稳定)→实际对象

本质:WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。

但是必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的一个座位是一样的道理。

线程 是指程序的一个指令执行序列,WIN32 平台支持多线程程序,允许程序中存在多个线程。 在单 CPU 系统中,系统把 CPU 的时间片按照调度算法分配给各个线程,因此各线程实际上是分时执行的,在多 CPU 的 Windows NT 系统中, 同一个程序的不同线程可以被分配到不同的 CPU 上去执行。由于一个程序的各线程是在相同的地址空间运行的,因此设及到了如何共享内存, 如何通信等问题,这样便需要处理各线程之间的同步问题,这是多线程编程中的一个难点。

线程,也被称为轻量进程(lightweight processes)。计算机科学术语,指运行中的程序的调度单位。

线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。线程可以创建和撤消线程,从而实现程序的并发执行。一般,线程具有 就绪阻塞运行 三种基本状态。

在多中央处理器的系统里,不同线程可以同时在不同的中央处理器上运行,甚至当它们属于同一个进程时也是如此。大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度(affinity)。

进程 是程序在一个数据集合上运行的过程(注:一个程序有可能同时属于多个进程),它是操作系统进行资源分配和调度的一个独立单位,进程可以简单的分为 系统进程 (包括一般Windows程序和服务进程)和 用户进程

预编译

#Error 预编译中断及错误提示

Encountered with text

错误信息会在预编译期遇到错误的时候停止并给出,也就是错误信息的上一条执行代码出现了错误。

而上一条代码是 #if !defined( __BYTE_ADDRESSING__ ) && defined ( __ADSPSHARC__ ) ,需要综合给出的错误提示 “Only Byte addressing mode is supported” 。

image-20210520154525815

Symbol could not be resolved

此类情况都是 “未定义” 或 “定义重复”。按住 左ctrl 并点击该定义可快速查看,是否定义或重复定义。

重复定义

以下错误的出现则是因为出现了两处重复定义:

image-20210520154603077
image-20210521093104044

未定义

NULL通常在 <stddef.h> (标准定义)头文件中给出,通常这些文件也被 <stdlib.h><stdio.h> 头文件引用。

image-20210520154631949

也可以进行自定义:

1
2
3
#ifndef NULL
#define NULL ((void *)0)
#endif

以下代码源自 <stddef.h> :

1
2
3
4
5
6
7
#ifndef NULL
#if defined (__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

参考Eclipse CDT “Symbol NULL could not be resolved”

image-20210520232442191

Error: li1050 - MULTIPLY DEFINED SYMBOL

错误编号

Error li1050

解决方法

将错误提示中所涉及到的变量(Tx_Count)或函数(initPLL_SDRAM()Init_TWI())分离至单独的一个 .c 文件中,在名称对应的头文件中,仅做函数及变量的声明,详细内容不要写在头文件中。

头文件中不能声明全局变量。

image-20210521163422688

实践

将错误提示中的变量或函数逐一移动到新的同名 .c 文件后,错误提示也在变少:

减少一个image-20210524100548198

减少两个image-20210524100816631

问题解决image-20210524101126445

解决方法的灵感来源于EZ论坛上的发言(以下回答其实也没细看):

image-20210524101743988