1.Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别
(1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载
(2) Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;
(3) Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。
(4) Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。
(5) 关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。
2.分配给标准输入标准输出及标准错误输出的文件描述符
文件描述符       对象
 0                  标准输出
 1                  标准输出
 2                  标准错误
 
3.WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本: 
较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;
最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。
使用#pragma命令,在编译时加载:
 #pragma comment (lib, "ws2_32.lib")
WinSock 编程的第一步就是加载 ws2_32.dll,然后调用 WSAStartup() 函数进行初始化,并指明要使用的版本号
WSADATA wsaData;
WSAStartup( MAKEWORD(, ), &wsaData);

4. socket编程

(1) 使用socket()函数创建套接字
(2) 使用bind()函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理
(3) 使用connect()函数来建立连接
(4) sockaddr结构体
可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体
另外还有 sockaddr_in6,用来保存 IPv6 地址
(5) 使用listen()函数让套接字进入被动监听状态
(6) 使用accept()函数可以随时响应客户端的请求
(7) accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号
(8) listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到accept()
(9) accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
(10) TCP服务器端函数调用顺序:socket()->bind()->listen()->accept()->read()/write()->close()
(11) TCP客户端函数调用顺序:socket()->connect()->read()/write()->close()
 
 
例子:
(1)基于linux环境的tcp回声服务器的实现
 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 1024
void error_handling(char *message); int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
char message[BUF_SIZE];
int str_len, i; struct sockaddr_in serv_adr;
struct sockaddr_in clnt_adr;
socklen_t clnt_adr_sz; if(argc!=) {
printf("Usage : %s <port>\n", argv[]);
exit();
} serv_sock=socket(PF_INET, SOCK_STREAM, );
if(serv_sock==-)
error_handling("socket() error"); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[])); if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-)
error_handling("bind() error"); if(listen(serv_sock, )==-)
error_handling("listen() error"); clnt_adr_sz=sizeof(clnt_adr); for(i=; i<; i++)
{
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
if(clnt_sock==-)
error_handling("accept() error");
else
printf("Connected client %d \n", i+); while((str_len=read(clnt_sock, message, BUF_SIZE))!=)
write(clnt_sock, message, str_len); close(clnt_sock);
} close(serv_sock);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}

(2)基于linux环境的tcp回声客户端的实现

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 1024
void error_handling(char *message); int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr; if(argc!=) {
printf("Usage : %s <IP> <port>\n", argv[]);
exit();
} sock=socket(PF_INET, SOCK_STREAM, );
if(sock==-)
error_handling("socket() error"); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[]);
serv_adr.sin_port=htons(atoi(argv[])); if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-)
error_handling("connect() error!");
else
puts("Connected..........."); while()
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin); if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break; write(sock, message, strlen(message));
str_len=read(sock, message, BUF_SIZE-);
message[str_len]=;
printf("Message from server: %s", message);
} close(sock);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
5.Ack号 = Seq号 + 传递的字节数 + 1
 
6.
 (1) 调用 close()/closesocket() 函数意味着完全断开连接,即不能发送数据也不能接收数据
 (2) 使用 shutdown() 函数可以只断开一条数据传输通道,而保留另一条

int shutdown(int sock, int howto);  //Linux
int shutdown(SOCKET s, int howto); //Windows

sock 为需要断开的套接字,howto 为断开方式

howto 在 Linux 下有以下取值:

  • SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
  • SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
  • SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。

howto 在 Windows 下有以下取值:

  • SD_RECEIVE:关闭接收操作,也就是断开输入流。
  • SD_SEND:关闭发送操作,也就是断开输出流。
  • SD_BOTH:同时关闭接收和发送操作。

shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将调用 close()/closesocket() 关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了
 (3) 默认情况下,close()/closesocket() 会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包, 也就意味着,调用 close()/closesocket() 将丢失输出缓冲区中的数据,而调用 shutdown() 不会。

例子:
(1)file_server.c
 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 30
void error_handling(char *message); int main(int argc, char *argv[])
{
int serv_sd, clnt_sd;
FILE * fp;
char buf[BUF_SIZE];
int read_cnt; struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz; if(argc!=) {
printf("Usage: %s <port>\n", argv[]);
exit();
} fp=fopen("file_server.c", "rb");
serv_sd=socket(PF_INET, SOCK_STREAM, ); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[])); bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
listen(serv_sd, ); clnt_adr_sz=sizeof(clnt_adr);
clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); while()
{
read_cnt=fread((void*)buf, , BUF_SIZE, fp);
if(read_cnt<BUF_SIZE)
{
write(clnt_sd, buf, read_cnt);
break;
}
write(clnt_sd, buf, BUF_SIZE);
} shutdown(clnt_sd, SHUT_WR);
read(clnt_sd, buf, BUF_SIZE);
printf("Message from client: %s \n", buf); fclose(fp);
close(clnt_sd); close(serv_sd);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}

(2)file_client.c

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h> #define BUF_SIZE 30
void error_handling(char *message); int main(int argc, char *argv[])
{
int sd;
FILE *fp; char buf[BUF_SIZE];
int read_cnt;
struct sockaddr_in serv_adr;
if(argc!=) {
printf("Usage: %s <IP> <port>\n", argv[]);
exit();
} fp=fopen("receive.dat", "wb");
sd=socket(PF_INET, SOCK_STREAM, ); memset(&serv_adr, , sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[]);
serv_adr.sin_port=htons(atoi(argv[])); connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr)); while((read_cnt=read(sd, buf, BUF_SIZE ))!=)
fwrite((void*)buf, , read_cnt, fp); puts("Received file data");
write(sd, "Thank you", );
fclose(fp);
close(sd);
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
 
7. recv() 返回 0 的唯一时机就是收到FIN包时
 
8. 网络字节序
不同 CPU 保存和解析数据的方式不同(主流的 Intel 系列 CPU 为小端序),小端序系统和大端序系统通信时会发生数据解析错误。因此在发送数据前,要将数据转换为统一的格式——网络字节序(Network Byte Order)。网络字节序统一为大端序。
 
9.
ping 域名可以查看域名对应的IP地址
nslookup命令可以查看计算机中注册的默认DNS服务器地址
 
10.
 (1) 下列函数可以通过传递字符串格式的域名获取IP地址

#include <netdb.h>
struct hostent *gethostbyname(const char *hostname);

成功时返回hostnet结构体指针,失败时返回NULL指针

例子:gethostbyname.c

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message); int main(int argc, char *argv[])
{
int i;
struct hostent *host;
if(argc!=) {
printf("Usage : %s <addr>\n", argv[]);
exit();
} host=gethostbyname(argv[]);
if(!host)
error_handling("gethost... error"); printf("Official name: %s \n", host->h_name); for(i=; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+, host->h_aliases[i]); printf("Address type: %s \n",
(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6"); for(i=; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
struct hostnet
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_lenght;
char **h_addr_list;
}

(2) gethostbyaddr()函数利用IP地址获取域相关信息

#include <netdb.h>
struct hostnet *gethostbyaddr(const char *addr, socklen_t len, int fanily);

成功时返回hostnet结构体变量地址值,失败时返回NULL指针

addr:含有IP地址信息的in_addr结构体指针
 len:  向第一个参数传递的地址信息的字节数,IPv4时为4,IPv6时为6
 family: 传递地址族信息,IPv4时为AF_INET, IPv6时为AF_INET6

例子:gethostbyaddr.c

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
void error_handling(char *message); int main(int argc, char *argv[])
{
int i;
struct hostent *host;
struct sockaddr_in addr;
if(argc!=) {
printf("Usage : %s <IP>\n", argv[]);
exit();
} memset(&addr, , sizeof(addr));
addr.sin_addr.s_addr=inet_addr(argv[]);
host=gethostbyaddr((char*)&addr.sin_addr, , AF_INET);
if(!host)
error_handling("gethost... error"); printf("Official name: %s \n", host->h_name); for(i=; host->h_aliases[i]; i++)
printf("Aliases %d: %s \n", i+, host->h_aliases[i]); printf("Address type: %s \n",
(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6"); for(i=; host->h_addr_list[i]; i++)
printf("IP addr %d: %s \n", i+,
inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}
 
 (3)字节序转换函数

 unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

(4) 将字符串信息转换为网络字节序的整数型

 #include <arpa/inet.h>
in_addr_t inet_addr(const char *string);

成功时返回32位大端序整数型值,失败时返回INADDR_NONE

 (5) inet_aton()也将字符串形式IP地址转换位32位网络字节序整数并返回,只不过该函数利用in_addr结构体,且其使用频率跟高
 

#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);

成功时返回1(true),失败时返回0(false)

 (6) inet_ntoa()函数可以把网络字节序整数型IP地址转换成字符串形式

 #include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);

成功时返回转换的字符串地址值,失败时返回-1;

        调用完该函数后应立即将字符串信息复制到其他内存空间,因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息
 

 (7) 网络地址信息初始化方法:

struct sockaddr_in addr;
char *serv_ip = "211.217.168.13";
char *serv_port "";
memset(&addr, , sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip);
addr.sin_port = htons(atoi(serv_port));

利用常数INADDR_ANY分配服务器的IP地址,可以自动获取运行服务器端的计算机IP地址

addr.sin_addr.s_addr = htonl(INADDR_ANY);

11.套接字的可选项
getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen)
  • SO_SNDBUF:输入缓冲大小相关可选项
  • SO_RCVBUF:输出缓冲大小相关可选项
  • SO_REUSEADDR:该可选项设置为TRUE可将Time_wait状态下的套接字端口号重新分配给新的套接字

TCP_NODELAY设置为1可禁用Nagle算法
 

12.多进程
 (1) 通过调用fork函数创建进程

 #include <unistd.h>
pid_t fork(void);

 父进程:fork函数返回子进程的ID

子进程:fork函数返回0

(2) 应当向创建子进程的父进程传递子进程的exit参数值或return语句的返回值,防止僵尸进程的产生
 销毁僵尸进程1:利用wait函数

#include <unistd.h>
pid_t wait(int *statloc);

->成功时返回终止的子进程ID,失败时返回-1

子进程终止时传递的返回值将保存到statloc所指内存空间,需要用下列宏进行分离

  • WIFEXITED 子进程正常终止时返回“真”true
  • WEXITSTATUS 返回子进程的返回值

销毁僵尸进程2:利用waitpid函数

 #include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);

->成功时返回终止的子进程ID,失败时返回-1

pid 等待终止的目标子进程ID,若传递-1,则可以等待任意子进程终止

 (3) statloc与wait函数的statloc参数具有相同含义
 (4) options 传递sys/wait.h中声明的常量WNOHANG,即使没有终止的子进程也不会进入组赛状态,而是返回0并退出函数
 (5)信号与signal函数

 #include <signal.h>
void (*signal(int signal, void (*func)(void)))(int);

->为了在产生信号时调用,返回之前注册的函数指针

(6) 利用sigaction函数进行信号处理

 #include <signal.h>
int sugacyion(int signo, const struct sigaction *act, struct sigaction *oldact);

->成功时返回0,失败时返回-1

通过fork函数复制套接字文件描述符后,同一端口将对应多个套接字,只有这些套接字描述符都终止,才能销毁套接字
 
13.进程间通信
 创建管道的函数:

 #include <unistd.h>
int pipe(int filedes[]);

->成功时返回0,失败时返回-1

filedes[0]:通过管道接收数据时使用的文件描述符,即管道出口
 filedse[1]:通过管道传输数据时使用的文件描述符,即管道入口
 
14.I/O复用
 (1) 针对fd_set变量的操着的宏:

 FD_ZERO(fd_set *fdset)
FD_SET(int fd, fd_set *fdset)
FD_CLR(int fd, fd_set *fdset)
FD_ISSET(fint fd, d_set *fdset)

(2) select函数:

 #include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

15.多种I/O函数

(1) 收到MSG_OOB紧急消息时,操着系统将产生SIGURG消息,并调用注册的信号处理函数
 (2) 处理SIGURG信号时必须指定处理信号的进程,而geipid函数返回调用此函数的进程ID

 (3) fcntl函数用于控制文件描述符

fcntl(recv_sock, F_SETOWN, getpid());

上述调用的含义是“将文件描述符recv_sock指向的套接字拥有者(F_SETOWN)改为把getpid函数返回值用作ID的进程

(4) 紧急指针指向紧急消息的下一个位置(偏移量+1),紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息
 (5) 调用recv函数的同时传递MSG_PEEK可选项,是为了保证即使不存在待读取的数据也不会进入阻塞状态,设置MSG_PEEK选项并调用recv函数时,即使读取了输入缓冲的数据也不会删除,该选项通常与MSG_DONTWAIT合作,用于调用以非阻塞方式验证待读取数据存在与否的函数

《TCP/IP网络编程》读书笔记的更多相关文章

  1. csapp读书笔记-并发编程

    这是基础,理解不能有偏差 如果线程/进程的逻辑控制流在时间上重叠,那么就是并发的.我们可以将并发看成是一种os内核用来运行多个应用程序的实例,但是并发不仅在内核,在应用程序中的角色也很重要. 在应用级 ...

  2. CSAPP 读书笔记 - 2.31练习题

    根据等式(2-14) 假如w = 4 数值范围在-8 ~ 7之间 2^w = 16 x = 5, y = 4的情况下面 x + y = 9 >=2 ^(w-1)  属于第一种情况 sum = x ...

  3. CSAPP读书笔记--第八章 异常控制流

    第八章 异常控制流 2017-11-14 概述 控制转移序列叫做控制流.目前为止,我们学过两种改变控制流的方式: 1)跳转和分支: 2)调用和返回. 但是上面的方法只能控制程序本身,发生以下系统状态的 ...

  4. CSAPP 并发编程读书笔记

    CSAPP 并发编程笔记 并发和并行 并发:Concurrency,只要时间上重叠就算并发,可以是单处理器交替处理 并行:Parallel,属于并发的一种特殊情况(真子集),多核/多 CPU 同时处理 ...

  5. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  6. 读书笔记--SQL必知必会18--视图

    读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...

  7. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  8. C#温故知新:《C#图解教程》读书笔记系列

    一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...

  9. C#刨根究底:《你必须知道的.NET》读书笔记系列

    一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...

  10. Web高级征程:《大型网站技术架构》读书笔记系列

    一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...

随机推荐

  1. 大二组队(NABCD)

    (Need)需求 很多时候,外人都不了解我们的校园.为了宣传铁大.让更多人了解校园.走进铁大. (Approach)做法 我们有最基本的展示.对校园风貌有基本的讲解.有论坛供应大家讨论. (Benef ...

  2. SpringMVC 配置.html拦截时,返回JSON数据时出现406错误解决方案

    [说明]在SpringMVC框架的使用中常常会使用@ResponseBody注解,修饰"处理器"(Controller的方法),这样在处理器在返回完毕后,就不走逻辑视图,而是将返回 ...

  3. C#中字节数组byte[]和字符串string类型的相互转换

    C#中字节数组byte[]和字符串string类型的相互转换: string转byte[]: byte[] byteArray = System.Text.Encoding.Default.GetBy ...

  4. java 发送简单邮件(不带附件)

    引入依赖 邮件实体类 可用邮件服务器地址(网易为例) 邮件工具类 import com.me.beans.Mail; import lombok.extern.slf4j.Slf4j; import ...

  5. codeforces 1284C. New Year and Permutation(组合数学)

    链接:https://codeforces.com/problemset/problem/1284/C 题意:定义一个framed segment,在区间[l,r]中,max值-min值 = r - ...

  6. MySql -- default 默认约束

    常用数据库约束: 一.default 默认约束: 二.not null:非空约束,指定某列不为NULL: 三.unique:唯一约束,指定某列和几列组合的数据不能重复: 四.primary key:主 ...

  7. MySql5.6表操作

    MySql5.6表操作 数据类型 整型 浮点型 字符类型 日期类型 枚举类型与集合类型 约束条件 Primary key Unique key Not null Foreign key 创建表的完整语 ...

  8. sql sever登录问题

    重启电脑后会发现连不上数据库了 按下win+r:输入cmd.连接你的ip,(telnet 127.0.0.1 xxxx)发现连接不上 正在连接127.0.0.1..无法打开到主机的连接. 在端口 14 ...

  9. 515,前端性能优化--减少http请求(待补充)

    对于影响页面呈选的因素有三个地方:服务器连接数据库并计算返回数据,http请求以及数据(文件)经过网络传输,文件在浏览器中计算渲染呈选:其中大约80%的时间都消耗在了http的请求上,所以要想大幅度的 ...

  10. HTML5学习(4)文本元素

    使用VSCode编辑器,内置emmet插件. ctrl+/ 注释/取消注释 ctrl+enter 新起一行 ctrl+shift+enter 往上新起一行 h$*4>lorem10 <h1 ...