linux 网络 笔记(听课笔记)由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“linux网络学习笔记”。
一网络预备知识
1.IP 主机的标识,32bit 无符号的二进制,通常用点分十进制表示
3个基本类:
A 类:最高字节高位0 1 + 3 网络号 + 主机号
0.0.0.0191.255.255.255 2^162
192.0.0.0239.255.255.255
用途:常用作组播地址
E 类:最高字节高位1111 0
240.0.0.065535
150000 //系统用的5000124];//填充字段
本地地址结构体 struct sockaddr_un {
sa_family_t sun_family;// 协议族
char sun_path[108];
//108字节协议地址
};// 传参
void * arg;
通用地址结构体:
struct sockaddr {
sa_family_t sa_family;// 协议族
char sa_data[14];//14字节协议地址
};
一创建套接字
Int socket(int domain, int type, int protocol);功能
domain:指明所使用的协议族,通常为PF_INET/AF_INET,表示互联网协议族(TCP/IP协议族);
type:指定socket的类型:SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)protocol:协议的编号通常赋值“0” 返回值
Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
失败返回-1 Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议。绑定端口
Int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);功能:把sockfd 绑定一个具体的端口 sockfd : 描述符
addr : 本机的结构ip的地址不允许绑定非本机IP 如:(192.168.2.10)addrlen: 告知内核ip地址大小, 必须为实际的地址大小网络为16 正确返回0 失败返回-1
udp接收数据端
Ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr,socklen_t *addrlen);)功能接收数据 Sockfd 描述符
Buf 存放接收到的数据
Len 最多可以接收的数据大小
Flags 接收的方式(默认是阻塞,通常是0)Src_addr 获取发送端的ip地址信息
Addrlen 告知内核发送端ip地址大小(结构大小)返回值
成功返回实际接收的数据大小
返回值如果为0
表示对方已关闭 失败返回-1
udp发送数据端
Ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,struct sockaddr *dest_addr,socklen_t addrlen);功能:发送数据 Sockfd : 描述符
Buf: 用户需要发送的数据缓存地址 Len:用户最多发送的数据大小
Flags:发送方式(默认是阻塞,通常设为0)Dest_addr: 当前数据发送的目标主机ip地址值
Addrlen: 告知目标主机ip地址的大小(结构大小)返回值
成功返回实际发送的数据大小 失败返回 – 1
Tcp申请三次握手(客户端)
Int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);实现客户端与服务器的连接 Sockfd: 套接字描述符 Addr:服务器ip结构地址
Addrlen:服务器ip结构地址大小 成功返回 0
失败返回-1 注意:再次申请,它会断开先前建立的连接,重新建立新的连接连接:获知对方存在
(Tcp)监听(服务器端)
Int listen(int sockfd, int backlog);功能:实现对客户端请求的监听(队列机制)Sockfd: 套接字描述符
Backlog:队列的长度,一般设为5 追打可设为8 返回值:
成功返回 0,失败返回-1
(Tcp)回复三次握手(服务器端)功能:回复客户端握手申请,建立连接
Int accept(int sockfd,struct sockaddr *addr, socklen_t *addrlen);Sockfd: 套接字描述符
Addr: 对方(客户端)ip 地址
Addrlen:对方(客户端)ip 地址大小
成功返回非负整数(新的socket描述符)失败返回
Tcp 数据接收
Ssize_t recv(int sockfd,void *buf,size_t len,int flags);功能接收数据
Sockfd:(客户端)是套接字描述符(服务器端)是accept()的返回值 Buf: 存放接收到的数据
Len: 最多可以接收的数据大小
Fags: 发送方式(默认是阻塞,通常设为0)成功返回实际接收的数据大小失败返回-1; 返回值如果为0
表示对方已关闭
Tcp数据发送
Ssize_t send(int sockfd,void *buf,size_t len,int flags);功能:发送数据
Sockfd :(客户端)是套接字描述符(服务器端)是accept()的返回值 Buf: 用户需要发送的数据缓存地址 Len:用户最多发送的数据大小
Flags:发送方式(默认是阻塞,通常设为0)成功返回实际发送的数据大小失败返回-1 关闭套接字 @1
Int Shutdown(int sockfd,int how)功能:指定方式关闭套接字 Socket: 套接字描述符
How: SHUT_RD 或0(关闭读)SHUT_WR或1(关闭写)SHUT_RDWR或2(关闭读写)相当于close()成功返回
0 失败返回-1 @2
Int close(int sockfd)关闭套接字
Socket: 套接字描述符
成功返回
0 失败返回-1
UDP 客户端创建流程 初始化网络地址结构体(服务器端)
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(50001);服务器端口号-------网络字节序
ser_addr.sin_addr.s_addr = inet_addr(“192.168.1.230”);服务器点分制地址-》网络字节序 2 创建数据报套接字
Int sockfd = socket(AF_INET,SOCK_DGRAM,0);(数据报套接字)3 发送数据到服务器端 Int sento_udp = sendto(sockfd,buf,strlen(buf)+1,0,(struct sockaddr *)&ser_addr,sizeof(struct sockaddr_in));
(服务器地址)4 接收服务器端的回复
Int recvfrom_udp = recvfrom(sockfd,buf, sizeof(buf),0,NULL,NULL); 5 关闭套接字
Close(sockfd);
UDP 服务器端创建流程 初始化网络地址结构体(服务器端)
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(50001);服务器端口号-------网络字节序
ser_addr.sin_addr.s_addr = inet_addr(“192.168.1.230”);服务器点分制地址-》网络字节序 2 创建数据报套接字
Int sockfd = socket(AF_INET,SOCK_DGRAM,0);(数据报套接字)3 绑定套接字
Int bind_udp = bind(sockfd,(struct sockaddr *)&ser_addr,sizeof(struct sockaddr_in));
(服务器地址)4 接收客户端请求
struct sockaddr_in client_addr;size = sizeof(struct sockaddr_in)int recvfrom_udp = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&size);(客户端地址)5
回复客户端
Int sendto_udp =
sendto(sockfd,buf,recvfrom_udp,0,(struct sockaddr *)&client_addr, sizeof(struct sockaddr_in))
(客户端地址)6 关闭套接字
Close(sockfd);
TCP 客户端创建流程 初始化网络地址结构体(服务器端)
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(50001);服务器端口号-------网络字节序
ser_addr.sin_addr.s_addr = inet_addr(“192.168.1.230”);服务器点分制地址-》网络字节序 2 创建流式套接字
Int sockfd = socket(AF_INET,SOCK_STREAM,0);(流式套接字)3
申请三次握手
Int connect_tcp = connect(sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr))
(服务器地址)4 发送数据到服务器端
Int send_tcp = send(sockfd,buf,strlen(buf)+ 1,0)5 接收服务器端的回复
Int recv_tcp = recv(sockfd,buf,sizeof(buf),0)6 关闭套接字
Close(sockfd);
TCP 服务器端创建流程 初始化网络地址结构体(服务器端)
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(50001);服务器端口号-------网络字节序
ser_addr.sin_addr.s_addr = inet_addr(“192.168.1.230”);服务器点分制地址-》网络字节序 2 创建数据报套接字
Int sockfd = socket(AF_INET,SOCK_DGRAM,0);(流式套接字)3 绑定套接字
Int bind_tcp = bind(sockfd,(struct sockaddr *)&ser_addr,sizeof(struct sockaddr_in));
(服务器地址)4
监听客户端请求
Int listen_tcp = listen(sockfd,5)
一般为5 最大为8 5 回复客户端的三次握手请求
struct sockaddr_in client_addr;int len = sizeof(struct sockaddr_in);
Int connectfd = accept(sockfd,(struct sockaddr *)&client_addr,&len)
(客户端地址)6 接收客户端请求 7 8 Int recv_tcp = recv(connectfd,buf,sizeof(buf),0);回复客户端
Int send_tcp = send(connectfd,buf,recv_tcp,0);关闭套接字
Close(sockfd);
Close(connectfd);1.TCP CS 模型
client:
socket //创建流式套接字
|
ser_addr:(struct sockaddr_in)//目标地址结构体(服务器)
|
connect()//3次握手
|
sendto/send/write
|
recvfrom/recv/read
|
.......|
close()/shutdown()
server:
(1)循环服务器,可以多个客户端服务,但是不能在同一时刻
ser_addr :(struct sockaddr_in)//本机的地址结构体
|
socket(流式套接字)
|
bind(使套接字具有地址属性)
|
listen(创建监听队列)
| while(1){
accept(握手建立连接,获取对方地址)| while(1){
recvfrom/recv/read //接收对端(客户端)信息
|
sendto/send/write //向对端回射信息
} |
.....} close
(2)并发服务器:可以同一时刻为多个客户端服务
ser_addr :(struct sockaddr_in)//本机的地址结构体
|
socket(流式套接字,sockfd)
|
bind(使套接字具有地址属性)
|
listen(创建监听队列)
| while(1){ connectfd = accept(握手建立连接,获取对方地址)
|
pid = fork()
if(pid == 0)
{
close(sockfd);
while(1)
{
recvfrom/recv/read //接收对端(客户端)信息
|
sendto/send/write //向对端回射信息
}
} close(connectfd);|.....} close(sockfd);
2.分析三次握手
client
server
SYN = 1(请求标志)
seq_no = 0(client)
第一次
------------------------------>
SYN = 1(请求标志)
ACK = 1(应答标志)
seq_no = 0(server), ack_no = 1(==seq_no(client)+ 1)第二次
ACK = 1(应答标志)
seq_no = 1(ack_no(server)),ack_no = 1(== seq_no(server)+ 1)第三次------------------------------>
3.数据包分析
一帧数据(TCP)= mac头 + IP头 + TCP头 + 用户数据
ttl: 数据包每经过一个路由器,如果停留的时间小于1s,ttl 减一,当ttl 小于0时数据丢弃掉
第三天
fcntl
int fcntl(int fd, int cmd,.../* arg */);获取或改变文件描述符的属性,一般我们需要改变文件状态标志位 @1 fd : 文件描述符 @2 cmd : 对文件描述符的操作(一般可以获取或者设置当前 file status flags)(F_GETFD,F_SETFD)@3......: 不定参,取决于cmd @4 成功返回值取决于 cmd
失败返回-1 例如 @1
int flag = fcntl(0,F_GETFL)
查看属性有返回值 @2 fcntl(0,F_SETFL,flag | O_NONBLOCK)
添加属性无返回值
IO的特性与接口没有关系,与描述符属性有关,调用fcntl/ioctl(可以直接把你用户的命令传递到内核,可以实现对底层驱动的控制)
一 IO 模型(4种)
1.阻塞IO
当资源未准备好时,程序睡眠或者等待,不浪费CPU,效率低,实现非常简单,但是它是应用最广泛的IO
read(sockfd,buf,BUFF_SIZE)/recv/recvfrom
2.非阻塞IO
当资源未准备好时,直接返回错误码(errno),不断的轮循,浪费CPU,效率高
3.信号驱动IO(SIGIO)
一种异步的通信机制,底层(内核)向上层(用户层)发信号(SIGIO),当资源可用时,内核向当前进程发送SIGIO信号,用户捕捉(signal)此信号,读取IO资源,如果用户不捕捉,进程会被杀死
signal(SIGIO,hander);//更安全 获取套接字的原有属性
int flag = fcntl(sockfd,F_GETFL);//O_NONBLOCK 添加异步属性,文件描述符可以被多个进程打开,此时内核不知信号发给谁 fcntl(sockfd,F_SETFL,flag | O_ASYNC);获取当前描述符对应的默认进程号(默认为0)pid = fcntl(sockfd,F_GETOWN);改变描述符对应的进程号
fcntl(sockfd,F_SETOWN,getpid());.IO 多路复用
可以同时对多个IO控制,哪个准备好了,执行哪个
IO 多路复用:
1.建立一个统计表:
fd_set readfds;2.添加fd到表中
FD_ZERO(&readfds);//将表清空
FD_SET(fd,&readfds);//将fd 加入 readfds 3.监测readfds 这张表(监测已经加入表的fd),会将没有就绪的fd 清0 n = select(fd+1,&readfds,NULL,NULL,NULL);//n ==>有多少fd 就绪,此时不知道哪个就绪 4.判断哪个fd 就绪,这张表只会保存就绪的fd
FD_ISSET(fd,&readfds)== 1
就绪
FD_ISSET(fd,&readfds)== 0
未就绪
相关函数
void FD_CLR(int fd, fd_set *set);int FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);注意:描述符不受限与套接字,任何描述符都行
nfds:select()函数监视的描述符数的最大值,一般取监视的描述符数的最大值+1,其上限设置在sys/types.h中有定义 #define FD_SETSIZE 256
readfds:select()函数监视的可读描述符集合 wtitefds:select()函数监视的可写描述符集合 errnofds:select()函数监视的异常描述符集合timeout:select()函数监视超时结束时间,取NULL表示永久等待 返回值:返回总的位数这些位对应已准备好的描述符,否则返回-1 相关宏操作:
FD_ZERO(fd_set *fdset):清空fdset与所有描述符的关系 FD_SET(int fd, d_set * fdset):建立描述符fd与fdset得关系 FD_CLR(int fd, d_set * fdset):撤销描述符fd与fdset得关系
FD_ISSET(int fd, d_set * fdset):检查与fdset联系的描述符fd是否可以读写,返回非零表示可以读写
5.select()函数实现IO多路复用的步骤(1)清空描述符集合(2)建立需要监视的描述符与描述符集合的关系(3)调用select函数
(4)检查监视的描述符判断是否已经准备好(5)对已经准备好的描述符进程IO操作
表的存放规则:
fd_set readfds;FD_ZERO(&readfds);//将表清空
FD_SET(0,&readfds);//将0 加入 readfds
FD_SET(3,&readfds);//将3 加入 readfds
FD_SET(4,&readfds);//将4 加入 readsds
表头:
|
| 1 0 0 1 1 0...........………… 0
| | | | |
|
| 0 1 2 3 4 5
1023
n = select(4+1,&readfds,NULL,NULL,NULL);检测就绪,返回就绪个数,未就绪的清零(由于处理器的速度很快,n通常为1)
若此时0就绪:(n = 1)表头:
|
| 1 0 0 0 0 0...........………… 0
| | | | |
|
| 0 1 2 3 4 5
1023
若此时有0 和3同时就绪(n = 2)表头:
|
| 1 0 0 1 0 0...........………… 0
| | | | |
|
| 0 1 2 3 4 5
1023
判断是那个fd就序
If(FD_ISSET(fd,&readfds)== 1)
{
。。。。
}
例:
int sockfd,maxfd,n;int connectfd ,fd;char buf[BUFF_SIZE];
fd_set readfds, tempfds;
maxfd = sockfd;
FD_ZERO(&readfds);
tempfds = readfds;
while(1){ tempfds = readfds;FD_SET(sockfd,&tempfds);//如:有50 client,某一时刻只有sockfd就绪
if(-1 ==(n = select(maxfd + 1,&tempfds,NULL,NULL,NULL)))
exit(-1);
for(fd = 0;fd
{
if(FD_ISSET(fd,&tempfds))//套接字两种都有可能就绪,如果不是sockfd,那么必定是以连接的套接字
{
if(fd == sockfd){ if(-1 ==(connectfd = accept(sockfd,NULL,NULL)))
exit(-1);puts(“hander shake!!n”);
FD_SET(connectfd,&readfds);//将新的客户端添加至只读表
maxfd = maxfd > connectfd ? maxfd : connectfd;//时刻保证maxfd 最大
}else //不能用if(fd == connectfd){ bzero(buf,BUFF_SIZE);
if(-1 ==(n = recv(fd,buf,BUFF_SIZE,0)))
exit(-1);if(n == 0){
FD_CLR(fd,&readfds);//将退出的客户端从只读表清除 close(fd);
if(maxfd == fd){
while(1){
maxfd--;
if(!FD_ISSET(maxfd,&readfds))//maxfd 是最后一个需
要监测的continue;
else {
break;
}
}
}
}
printf(“[%d] client buf:%sn”,n,buf);
}
} } }
获取套接字属性信息
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
sockfd套接字描述符 level选项级别SOL_SOCKET(man 7 solcket)(通用套接字)
IPPROTO_IP
(man 7 ip)得到选项名
IPPROTO_TCP
(man 7 tcp)optname选项名
SO_BROADCAST(广播)……
optval存放获取到的选项值的缓冲区地址&n
int n;optlen存放缓冲区长度的地
&len
int len = sizeof(n)成功返回 0
失败返回-1
第四天
设置套接字属性信息
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
sockfd套接字描述符
evel选项级别SOL_SOCKET
(man 7 solcket)(通用套接字)
IPPROTO_IP
(man 7 ip)得到选项名
IPPROTO_TCP
(man 7 tcp)optname选项名
SO_BROADCAST(广播)IP_ADD_MEMBERSHIP(组播)
组播结构体:struct ip_mreqn { truct in_addr imr_multiaddr;/* 组播ip
struct in_addr imr_addre;/* 服务器ip
int imr_ifindex;
/* interface index */通常为0
};optval存放需要设置的选项值的缓冲区地址&n int n = 1(打开广播), int n = 0(关闭广播)optlen存放缓冲区长度的地址
sizeof(n)成功返回 0
失败返回-1
Int on = 1 setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))使先前进程创建的端口能重新绑定
一单播广播组播
1.单播:接收方为一个,用户发送的包,可以到达指定的主机,一对一的通信,数据包经过路由器或者交换机,不经过复制,需要转发
好处:服务器可以及时的为客户端响应 坏处:如果客户端的个数太大,会造成超载
host1-------route/swith(转发)-----------> host2 2.广播:接收方为局域网内,所有主机, 属于一对所有,数据包经过路由器或者交换机 需要经过复制,转发,不存在CS,存在发送方,和接收方,使用UDP 注意:默认不允许发送 好处:效率高
坏处:如果大量发送会造成广播风暴
广播地址:主机号为全1,如,C类私有网络:192.168.1.255 广播MAC :FF:FF:FF:FF:FF:FF
host1------------route/swith(复制,转发)------------------->host2
|
-------->host3
|
....|
-------->host254
udp广播发送方:默认不允许
1).创建数据报套接字(填充地址结构体(广播IP))
2).设置套接字属性,允许发送广播包(setsockopt)int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(int))(设置属性)
int on;socklen_t len;len = sizeof(on);getsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,&len)
2).发送数据报到广播ip
udp广播接收方:默认允许
1).创建数据报套接字(填充地址结构体(广播ip))
2).绑定广播地址(ip)
3).直接接收对方ip
3.组播
接收方为局域网多个主机,将具有相同需求的主机加入一个组,然后组内任何一个主机的包,组内所有成员都会收到,是广播的优化 优点:有针对性,相对广播可以降低网络带宽 缺点:相对单播,缺少校错机制
组播地址:D类IP 224.0.0.0-239.255.255.255 组播的MAC:01:00:5e:*.*.*(IP地址的低23bit)
(获取属性)host1------route(IGMP 网络组管理协议网络层)/swith(复制,转发)------->host2
|
------------------------->host3
|
....|
-------->host(多个
Udp组播发送方
1).创建数据报套接字(填充地址结构体组播ip)
2).直接发送组播ip
Udp组播接收方
1).创建数据报套接字(填充地址结构体组播IP)
2).绑定组播地址(ip)
3).设置属性,将当前主机ip加入组(IGMP)//需要路由器
struct ip_mreqn mulgroup;bzero(&mulgroup,sizeof(mulgroup));mulgroup.imr_multiaddr.s_addr = inet_addr(组播ip);
mulgroup.imr_addre.s_addr = inet_addr(本机ip);mulgroup.imr_ifindex = 0;
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mulgroup,sizeof(mulgroup))
4).直接接收对方ip
第五天: 说明:
网络中套接字通常是,当没有相应资源时是阻塞的,如果有资源可读,就会直接返回 网络中环境是异常复杂的,这个时候我们对异常处理,需要超时检测
一超时检测
1.设置套接字选项
Struct timeval tv;套接字超时属性(结构体)
tv.tv_sec = 3;秒
tv.tv_usec = 1000;微秒
setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv))connectfd = accept(sockfd,NULL,NULL)connectfd 继承sockfd的属性(这里应用的是超时属性)if(errno == EAGAIN)continue;
2.select
//如果没有任何一个fd就绪则超时,超时返回0,每次超时之后,tv值不会重置,需要用户自己重置
struct timeval tv;tv.tv_sec = 2;tv.tv_usec = 0;n = select(sockfd + 1,&readfds,NULL,NULL,&tv))if(n == 0){
printf(“timeout %d....n”,++count);
continue;}
3.alarm信号 //特性不会阻塞,会更新
/* function: 中断当前进程阻塞的系统调用,在哪里阻塞在哪里中断
signum: 捕捉的信号
act: 设置之后的信号属性
oldact: 获取先前默认的属性
*
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void(*sa_handler)(int);//signal 的信号处理函数
void(*sa_sigaction)(int, siginfo_t *, void *);//sigaction最早的信号处理函数
sigset_t
sa_mask;//信号屏蔽码,可以屏蔽指定信号
int
sa_flags;//信号属性
void(*sa_restorer)(void);//linux 不支持
};
二 UNIX 域套接字编程
1.本地(本机)进程间通信
2.不经过OSI /TCP/IP 体系结构,不存在打包和拆包过程
3.可以完全套用TCP/UDP CS 模型
练习: 实现UNIX本地进程通信
三 tftp 实现
听课笔记教学内容:苏教版小学语文第七册《九色鹿》 教学时间:2005年11月教师:海安县实验小学仲剑锋 教学过程:一、复习巩固1、回忆主要内容2、九色鹿给你留下了什么印象? 师引导:......
第一篇执笔教师: 胡小红 听课时间:9月12日 听课老师: 张美燕听课地点:建华小太阳幼儿园 活动名称:花手套 班级:小班 活动领域:美术活动目标:1、让幼儿尝试滚画2、对色彩感兴......
一、记教学过程教学过程包括:教学环节、教学思路、教学设计等。教师应注重记录教学中的精华、别出心裁的手法、优秀的教学设计及特色;记录教学中的重点、难点;捕捉新颖的情节、......
听课笔记(精选8篇)由网友“h1ub”投稿提供,以下是小编为大家准备的听课笔记,仅供参考,大家一起来看看吧。篇1:听课笔记摘录 听课笔记摘录上午听了一节王老师的数学课,颇不理想。尽......
优秀到卓越执行素养。什么是优秀?聪明、知识层面、学习、什么事卓越?智慧、思想层面、转化、优秀是一件事(结果)。卓越是格局(成果)。优秀是方法、卓越是规律、优秀的人会做事、卓......