setsockopt

获取或者设置与某个套接字关联的选 项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,

选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该 将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选

项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议 号TCP。

option

SO_LINGER选项

struct linger
{
    int l_onoff;
    int l_linger;
};
  • l_onoff = 0, 数据保持发送完成后立即返回
  • l_onoff = 1, l_linger = 0, 立即放回 放弃发送, 发送rst 自身立即复位
  • l_onoff = 1, l_linger = 1, 阻塞到超时或数据发送完成, 保持尝试发送,超时后立即结束

SO_REUSEADDR选项

改变了通配绑定时处理源地址冲突的处理方式, 让端口释放后立即就可以被再次使用

  • 允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在
  • 允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可
  • 允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。一般不用于tco服务器
  • 允许完全重复的捆绑

Re:

https://www.cnblogs.com/my_life/articles/5174585.html

https://www.jianshu.com/p/141aa1c41f15

https://blog.csdn.net/u010144805/article/details/78579528


int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);

fcntl函数有5种功能:

  • 1.复制一个现有的描述符(cmd=F_DUPFD).
  • 2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  • 3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  • 4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  • 5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

Re: https://www.cnblogs.com/xuyh/p/3273082.html


非阻塞I/O使我们的操作要么成功,要么立即返回错误,不被阻塞。

对于一个给定的描述符两种方法对其指定非阻塞I/O:

  • 1.调用open获得描述符,并指定O_NONBLOCK标志
  • 2.对已经打开的文件描述符,调用fcntl,打开O_NONBLOCK文件状态标志。
flags = fcntl( s, F_GETFL, 0 ) )
fcntl( s, F_SETFL, flags | O_NONBLOCK )

Re: https://blog.csdn.net/zhulinfeiba/article/details/5011573


  • htonl()–“Host to Network Long int” 32Bytes

将主机的无符号长整形数转换成网络字节顺序。//将无符号长整型网络字节序转换为主机字节序

  • ntohl()–“Network to Host Long int” 32Bytes

将一个无符号长整形数从网络字节顺序转换为主机字节顺序。

  • htons()–“Host to Network Short int” 16Bytes

将主机的无符号短整形数转换成网络字节顺序。//将无符号短整型主机字节序转换为网络字节序

  • ntohs()–“Network to Host Short int” 16Bytes

将一个无符号短整形数从网络字节顺序转换为主机字节顺序。

  • inet_addr()

将一个点间隔地址转换成一个in_addr

  • inet_ntoa()

是编程语言,功能是将网络地址转换成“.”点隔的字符串格式。

  • inet_aton()

与inet_ntoa()作用相反。本函数将点分十进制转换为整数

  • atoi()

array to integer将字符串转换为整形数


新型网路地址转化函数inet_pton和inet_ntop 这两个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用,函数中p和n分别代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构的二进制值。

#include <arpe/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);     //将点分十进制的ip地址转化为用于网络传输的数值格式
        返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1

const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);     //将数值格式转化为点分十进制的ip地址格式
        返回值:若成功则为指向结构的指针,若出错则为NULL
  • 1.这两个函数的family参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。如果,以不被支持的地址族作为family参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.

  • 2.第一个函数尝试转换由strptr指针所指向的字符串,并通过addrptr指针存放二进制结果,若成功则返回值为1,否则如果所指定的family而言输入字符串不是有效的表达式格式,那么返回值为0.

  • 3.inet_ntop进行相反的转换,从数值格式(addrptr)转换到表达式(strptr)。inet_ntop函数的strptr参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果len太小,不足以容纳表达式结果,那么返回一个空指针,并置为errno为ENOSPC。

inet_pton(AF_INET, ip, &foo.sin_addr);   //  代替 foo.sin_addr.addr=inet_addr(ip);

char str[INET_ADDRSTRLEN];
char *ptr = inet_ntop(AF_INET,&foo.sin_addr, str, sizeof(str));      // 代替 ptr = inet_ntoa(foo.sin_addr)

示例代码

int main()
{
    char IPdotdec[20]; // 存放点分十进制IP地址
    struct in_addr s;  // IPv4地址结构体
    // 输入IP地址
    printf("Please input IP address: ");
    scanf("%s", &IPdotdec);
    // 转换
    inet_pton(AF_INET, IPdotdec, (void *)&s);
    printf("inet_pton: 0x%x\n", s.s_addr); // 注意得到的字节序
    // 反转换
    inet_ntop(AF_INET, (void *)&s, IPdotdec, 16);
    printf("inet_ntop: %s\n", IPdotdec);
}

Run:
Please input IP address: 127.0.0.1
inet_pton: 0x100007f
inet_ntop: 127.0.0.1

Re: https://blog.csdn.net/zyy617532750/article/details/58595700

https://www.cnblogs.com/wuyuxuan/p/10772779.html


int send( SOCKET s,char *buf,int len,int flags )

功能:不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

  • 参数一:指定发送端套接字描述符;

  • 参数二:存放应用程序要发送数据的缓冲区;

  • 参数三:实际要发送的数据的字节数;

  • 参数四:一般置为0。

int recv( SOCKET s, char *buf, int len, int flags)

功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。

  • 参数一:指定接收端套接字描述符;

  • 参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

  • 参数三:指明buf的长度;

  • 参数四 :一般置为0。

Re: https://blog.csdn.net/lanzhihui_10086/article/details/40681617


read(sock, buf, sizeof(buf) -1)

read从套接字文件中读取数据, fd为要读取的文件的描述符,buf为要接收数据的缓冲区地址,nbytes为要读取的数据的字节数。


    /*
     * 本函数向服务ip发起请求 服务器ip port 保存在sockaddr_in中
     * int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  //Linux
     * int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  //Windows
     * sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
     */
    connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr));

linux mutex block

pthread_mutexattr_t mattr

int pthread_mutexattr_init(pthread_mutexattr_t *mattr)

pthread_mutex_init() 函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。

  • Success: return 0

POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

动态方式是采用pthread_mutex_init()函数来初始化互斥锁

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)

其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性, pthread_mutex_destroy()用于注销一个互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex)

销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

2.属性 3. 锁操作

Re: https://www.cnblogs.com/lidabo/p/4566693.html


互斥锁的类型:有以下几个取值空间:

  • PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

  • PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

  • PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。

  • PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。 *pthread_mutexattr_settype(pthread_mutexattr_t attr , int type) **pthread_mutexattr_gettype(pthread_mutexattr_t attr , int type)

Re: https://blog.csdn.net/happylzs2008/article/details/89067028


销毁互斥锁属性对象

  • pthread_mutexattr_destroy(3C) 可用来取消分配用于维护 pthread_mutexattr_init() 所创建的属性对象的存储空间。

对于互斥锁属性对象,必须首先通过调用pthread_mutexattr_destroy(3C)将其销毁,才能重新初始化该对象。pthread_mutexattr_init() 调用会导致分配类型为 opaque 的对象。如果未销毁该对象,则会导致内存泄漏

Re: https://blog.csdn.net/jasmineal/article/details/8807744


对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个。

  • int pthread_mutex_lock(pthread_mutex_t *mutex)
  • int pthread_mutex_unlock(pthread_mutex_t *mutex)
  • int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待

pthread_mutex_lock 用于解决由于竞争产生的优先级反转问题。没锁更新所有权,锁住

Re: https://blog.csdn.net/jasmineal/article/details/8807744

https://blog.csdn.net/oqqYuJi12345678/article/details/100585669


backtrace
backtrace_symbols
backtrace_symbols_fd

To: https://vcvvvc.github.io/post/backtrace/


pthread_cond_broadcast(&cond1)的作用是唤醒所有正在pthread_cond_wait(&cond1,&mutex1)的线程。

while(lock_status_[lid] == LOCKED) 
{
    pthread_cond_wait(&c_, &m_);
}
    pthread_cond_broadcast(&c_);

一旦某个锁被释放,所有的阻塞线程都会被唤醒,但唯有阻塞在这个锁的线程才能真正被唤醒。

pthread_cond_signal(&cond1)的的作用是唤醒所有正在 pthread_cond_wait(&cond1,&mutex1)的至少一个线程。(虽然我还没碰到过多于一个线程的情况,但是man帮组手册上说的是至少一个)

Re:

https://www.cnblogs.com/XiaoXiaoShuai-/p/11855408.html

https://www.cnblogs.com/zhouzhuo/p/3781511.html


struct hostent{
    char *h_name;  //official name
    char **h_aliases;  //alias list
    int  h_addrtype;  //host address type
    int  h_length;  //address lenght
    char **h_addr_list;  //address list
}
  • h_name:官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册。
  • h_aliases:别名,可以通过多个域名访问同一主机。同一 IP 地址可以绑定多个域名,因此除了当前域名还可以指定其他域名。
  • h_addrtype:gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6。
  • h_length:保存IP地址长度。IPv4 的长度为 4 个字节,IPv6 的长度为 16 个字节。
  • h_addr_list:这是最重要的成员。通过该成员以整数形式保存域名对应的 IP 地址。对于用户较多的服务器,可能会分配多个 IP 地址给同一域名,利用多个服务器进行均衡负载。

gethostbyname

struct hostent *gethostbyname(const char *hostname);

gethostbyaddr 根据ip地址获取主机的完整信息

struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

Re:

http://c.biancheng.net/view/2357.html


getservbyname 根据名称获取某个服务的完整信息

struct servent *getservbyname(const char *name, const char *proto)

getservbyport 根据端口号获取某个服务的完整信息

struct servent *getservbyport(int port, const char *proto)

getaddrinfo 通过主机名获得IP地址也能通过服务名获得端口号

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);

之后用freeaddrinfo 释放getaddrinfo所分配的内存

void freeaddrinfo(struct addrinfo *res);

getnameinfo 通过socket地址同时获得字符串表示的主机名和服务名

int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags);


readv 将数据从文件描述符读到分散的内存块中

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

writev 将多块分散的数据一并写入文件描述符中

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

sendfile 在两个文件描述符之间传递数据 避免内核缓冲区和用户之间的数据拷贝-零拷贝

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);


mmap 申请一段内存空间 将这段内存作为进程间通信的共享内存,可以将文件直接映射到其中

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); mmap_port mmap_flags

munmap 释放mmap创建的内存空间

int munmap(void *addr, size_t length);

splice 用于在两个文件描述符之间移动数据-零拷贝

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

splice_flags splice_errno


syslog和rsyslogd 守护进程通信

void syslog(int priority, const char *format, ...); syslog

openlog改变syslog默认输出方式 进一步结构化日志内容

void openlog(const char *ident, int option, int facility); openlog

setlogmask 设置日志掩码

int setlogmask(int mask);

closelog关闭日志功能

void closelog();


get_uid获取和设置当前进程真实用户

uid_t getuid(void); //真实用户ID
uid_t geteuid(void); //有效用户ID
gid_t getgid(void); //真实组ID
gid_t getegid(void); //有效组ID
int setuid(uid_t uid); //设置真实用户ID
int seteuid(uid_t euid); //设置有效用户ID
int setgid(gid_t gid); //设置真实组ID
int setegid(gid_t egid); //设置有效组ID

getpgid 获取进程组ID

pid_t getpgid(pid_t pid);

setpgid 设置用户组ID int setpgid(pid_t pid, pid_t pgid);

setsid 创建一个会话

pid_t setsid(void);

getsid 读取sid

pid_t getsid(pid_t pid);

rlimit 系统资源读取\设置

int getrlimit(int resource, struct rlimit *rlim);

int setrlimit(int resource, const struct rlimit *rlim);

struct rlimit
{
    rlim_t rlim_cur;
    rlim_t rlim_max;
}

rlimit

cwd 获取进程当前工作目录和改变进程工作目录

char *getcwd(char *buf, size_t size);

int chdir(const char *path);

chroot 改变进程根目录

int chroot(const char * path);

daemon 守护进程

int daemon(int nochdir, int noclose);


基本框架

server_framework

reactor 要求主线程只负责监听文件描述上是否有事情发生 有的话立即将该事件通知工作线程

reactor

proactor 将所有I/0操作都交给主线程和内核来处理 工作线程仅仅负责业务逻辑

proactor

同步模拟的Procator

同步io

并发模式

同步异步分析

半同步/半异步模式

半同步半异步 反应堆

存在的缺点

缺点

高效模式

高效模式


领导者/追随者模式 是多个工作线程轮流获得事件源集合, 轮流监听、分发并处理事件的一种模式

leader

1. 句柄集 句柄表示I/O资源 在linux下通常是一个文件描述符 句柄集管理众多句柄

2. 线程集 负责各线程之间的同步 以及新领导者线程的推选. 线程集里的线程在任何时间都必须处于一下三种状态之一

thread1 thread2 转换关系

领导者/追随者 工作流程图

领导追随者模式


select 在一段指定时间内 监听用户感兴趣的文件描述符上的可读、可写、异常等事件, readfds\writefds\exceptfds分别指向可读、可写、异常

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

poll 指定时间内轮询一定数量的文件描述符 测试其中是否有就绪者

int poll(struct pollfd *fds, nfds_t nfds, int timeout); poll1 poll2

epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中, 从而无需每次调用都要重复传入文件描述符集或事件集, 但epoll需要额外的文件描述符, 来唯一标识内核中的事件表

epoll_create 创建文件描述符

int epoll_create(int size);

操作epoll的内核事件表

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

op

epoll_wait 在一段超时时间内等待一组文件描述符上的事件

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

LT 水平触发是默认工作模式、ET边缘触发

select\poll\epoll 区别比较

poll_compare


kill 一个进程给其他进程发送信号

int kill(pid_t pid, int sig);

kill

kill出错的情况

kill_error

信号处理函数SIG_DFL使用信号的默认处理方式、SIG_IGN表示忽略目标信号

//原型: typedef void (* __sighandler_t) (int);
#define SIG_DFL((__sighandler_t) 0)
#define SIG_IGN((__sighandler_t) 1) 

signal信号设置处理函数

sighandler_t signal(int signum, sighandler_t handler);

signal更健壮的接口

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

sa_flags

信号集

int sigemptyset(sigset_t * _set) //清空信号集
int sigfillset(sigset_t * _set) //在信号集中设置所有信号
int sigaddset(sigset_t * _set, int _signo) //将信号 _signo添加至信号集中
int sigdelset(sigset_t * _set, int _signo) //将信号 _signo从信号集中删除
int sigismember(const sigset_t * _set, int _signo) //测试 _signo是否在信号集中

设置或查看进程信号掩码

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

sigprocmask

sigpending 获得进程当前被挂起的信号集 成功返回0 失败-1

int sigpending(sigset_t *set);


SO_RCVTIMEO接收数据超时时间 SO_SNDTIMEO发送数据超时时间

timeo

高性能定时器 时间轮

时间轮


fork 复制当前进程, 在内核进程表中创建一个新的进程表项

pid_t fork(void);

exec 在子进程中执行其他程序, 即替换当前进程映像

int execl(const char* path, const char * arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

wait 在父进程中调用, 等待子进程的结束, 并获取子进程的返回信息,避免了僵尸进程的产生或使子进程的僵尸态立即结束, wait函数将阻塞进程,直到进程的某个子进程结束运行为止

pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

子进程状态信息

semget 系统调用创建一个新的信号量集, 或者获取一个已经存在的信号量集

int semget(key_t key, int nsems, int semflg);

semop 对信号量的操作是对这些内核变量的操作

int semop(int semid, struct sembuf *sops, size_t nsops);

semctl 系统调用允许调用者对信号量进行直接控制

int semctl(int semid, int semnum, int cmd, ...);

shmget 系统调用创建一段新的共享内存,或者获取一段已存在的共享内存

int shmget(key_t key, size_t size, int shmflg);

shmat shmdt 共享内存被创建\获取后,不能立即访问,而是需要先将它关联到进程的地址空间中,使用完共享内存之后,也需要将它从进程地址空间中分离

void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);

shmctl系统调用控制内存的某些属性

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmctl

shm_open 创建或打开POSIX共享内存对象. Linux提供了一种利用mmap在无关进程之间共享内存的方式,这种方式无须任何文件支持,但它需要先使用shm_open 创建\打开一个POSIX共享内存对象

//编译时需要指定链接: -lrt
int shm_open(const char *name, int oflag, mode_t mode);

shm_open_oflag

shm_unlink 删除创建的共享内存对象

int shm_unlink(const char *name);


socketpair()函数用于创建一对无名的、相互连接的套接子。

https://blog.csdn.net/weixin_40039738/article/details/81095013

int socketpair(int d, int type, int protocol, int sv[2]);

如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。

基本用法:

    1. 这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读; 
    1. 如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功; 
    1. 读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。 

sendmsg、recvmsg、send函数的使用**

#sendmsg()用来将数据由指定的socket传给对方主机. 失败返回-1
int sendmsg(int s, const strcut msghdr *msg, unsigned int flags);

#函数说明:recvmsg()用来接收远程主机经指定的socket 传来的数据.  失败返回-1
int recvmsg(int s, struct msghdr *msg, unsigned int flags);

#send()用来将数据由指定的socket 传给对方主机.  失败返回-1
int send(int s, const void * msg, int len, unsigned int falgs);

函数send参数flags 一般设0, 其他数值定义如下:

  • MSG_OOB 传送的数据以out-of-band 送出.
  • MSG_DONTROUTE 取消路由表查询
  • MSG_DONTWAIT 设置为不可阻断运作
  • MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断.