Linux shell 脚本:介绍和使用 bash(详细)
Shell:一般我们是用图形界面和命令去控制计算机,真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),由于安全、复杂、繁琐等原因,用户不能直接接触内核,需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核,内核和用户之间就多了一层“中间代理”,Shell 其实就是一种脚本语言,也是一个可以用来连接内核和用户的软件,我们编写完源码后不用编译,直接运行源码即可。
常用的Shell:bash由 GNU 组织开发,sh 是 UNIX 上的标准 shell,是第一个流行的 Shell,bash保持了对 sh shell 的兼容性,是各种 Linux 发行版默认配置的 shell。现在sh 已经基本被 bash 代替,bash是sh的扩展补充,但是也有些是不兼容的,大多数情况下区别不大,特殊场景可以使用 bash 代替 sh。
在ubuntu下,上图是我们打开的终端,这里输入的命令就是我们shell的命令,一般$表示的是普通用户,而#表示的是超级用户(root)
目录
1、第一个脚本程序:
2、shell获取字符串长度:
3、shell变量:
4、引用shell变量:
5、shell变量的赋值、修改、删除:
5、shell特殊变量:
6、shell中字符串的拼接:
7、字符串的截取
8、shell中的数组:
9、shell中条件判断if:
10、shell中的 case语句:
11、shell中的 for循环:
12、shell中的 while循环:
13、shell中的 until循环:
14、shell循环中的break和continue:
15、shell中的函数:
1、第一个脚本程序:
在终端创建一个.sh文件,vi test.sh
第一行,#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,用的是哪种shell,后面的/bin/bash
就是指明了解释器的具体位置。 第二行#是注释行,用来解释说明,当然#!是特殊的,不在此类。 第三行在是终端输出 hello,is me!
执行shell脚本:
./test.sh: ./表示当前目录,执行./test.sh会说明权限不够,不能执行。需要改变文件的权限:chmod 777 test.sh,就能执行
. ./test.sh: 在不想改变权限的时候,测试脚本是不是能够正常使用。. 临时增加权限。
2、shell获取字符串长度:
#!/bin/bash
#获取字符串长度
str="123456"
echo "${#str}"
结果:
3、shell变量:
在bash shell中,每一个变量的值都是字符串, 当然也可以用declare 关键字显式定义变量的类型,在赋值的时候等号两边不能有空格,如:str=1 ,str='1' ,str="1",变量名必须有字母、下划线、数字组成,开头必须字母或者下划线,不能用shell。
- 局部变量:shell也有自定义函数,函数里面的变量为局部变量,但是它也是相当于全局变量,函数中的变量,在函数外调用也是可以的,如果要仅限函数使用,需要在函数变量前加个关键字:local
- 全局变量:每打开一个终端就是一个shell会话,在这个shell会话(终端)定义的变量就是全局变量,它在这个shell会话有效,当你打开另一个终端就是另一个shell会话,这个变量在另一个终端就失效了。
- 环境变量:在全局变量前加 export ,如:export a=1 那么这个变量就是环境变量了。创建这个变量的shell成为父shell,这个shell中,在创建一个shell叫做子shell,环境变量可以由父shell往下一级一级传,而不能逆转往上传递。当shell会话销毁时,这个环境变量也会随之销毁。想要永久保存就得环境变量写到启动文件中去。
4、引用shell变量:
使用shell变量在变量前面加一个$,而标准的是 &{},目的是在一长串字符中可以识别出这个变量,而不会引起误会,如下:
#!/bin/bash
#引用shell变量
str="abc"
echo "$str"
echo "the str vaile is: $str1"
#后面增加一个1,就不能正确的识别变量
echo "the str vaile is: ${str}1"
#所以我们引用变量,最好统一用 ${} 的形式
结果:
5、shell变量的赋值、修改、删除:
a)、shell变量的赋值:
#!/bin/bash
#变量的赋值
n=1
v1=${n}
v2='${n}'
v3="${n}"
echo "${v1}"
echo "${v2}"
echo "${v3}"
可以从结果看出不加引号和加双引号的结果是相同的,而单引号是原样输出变量后面赋值的内容。
b、shell变量的修改、删除:
#!/bin/bash
#变量值的修改
a=1
echo "a: ${a}"
a=2
echo "a: ${a}"
#只读变量是不可以修改的,在变量前加readonly,就是只读变量
c=1
echo "c: ${c}"
readonly c
c=2
echo "c: ${c}"
#只要在变量前面加一个 unset,如: unset a 就可以删除变量。
unset a
echo "a: ${a}"
5、shell特殊变量:
$0 | 当前脚本的文件名或者解释器。 |
$n(n≥1) | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。当被双引号" " 包含时,$@ 与 $* 稍有不同,$*的所有参数是一个数据,而$@一个参数就是一份数据 |
$? | 上个命令的退出状态,或函数的返回值 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
脚本:如果参数个数太多,达到或者超过了 10 个,那么就得用${n}
的形式来接收了,例如 ${10}、${11}
#!/bin/bash
#shell特殊变量
#$n: $0表示本脚本,$1表示输入的第一个参数:11,$2表示输入的第一个参数:22
echo "$ 0: $0"
echo "a: $1"
echo "b: $2"
#$*: 输入的参数 11 22
echo "$ *: $*"
#$@: 输入的参数 11 22
echo "$ @: $@"
#$$: shell进程id
echo "$ $: $$"
#$#: 参数个数 2
echo "$ #: $#"
函数:如果参数个数太多,达到或者超过了 10 个,那么就得用${n}
的形式来接收了,例如 ${10}、${11}
#!/bin/bash
#shell特殊变量
#$n: $0表示本脚本,$1表示输入的第一个参数:11,$2表示输入的第一个参数:22
function temp(){
echo "$ 0: $0"
echo "a: $1"
echo "b: $2"
#$*: 输入的参数 11 22
echo "$ *: $*"
#$@: 输入的参数 11 22
echo "$ @: $@"
#$$: shell进程id
echo "$ $: $$"
#$#: 参数个数 2
echo "$ #: $#"
}
temp 11 12
$?:上一个命令的退出状态,或者返回值:
#!/bin/bash
if [ $1 == 11 ]
then
return 0
else
return 1
fi
$?在shell函数中的应用:
#!/bin/bash
function temp(){
return $1
}
temp 3
#$?: 这里是函数的返回值
echo "$ ?: $?"
6、shell中字符串的拼接:
#!/bin/bash
#shell字符串的拼接
n1=ab
n2=cd
temp1=${n1}${n2} #中间不能有空格
temp2="${n1}${n2}"
temp3="${n1}cdefg"
echo "${temp1}"
echo "${temp2}"
echo "${temp3}"
7、字符串的截取
- ${string: start :length}:字符串从左边start开始的位置截取length个字符,如果没有length,就从左边start开始到结束
- ${string: 0-start :length}:字符串从右边start开始的位置截取length个字符,如果没有length,就从右边数start开始到结束
- ${string#*chars}:字符串从左边开始的第一个chars开始的位置截取到右边结束,chars本身不在截取范围内
- ${string##*chars}:字符串从左边开始的最后一个chars开始的位置截取到右边结束,chars本身不在截取范围内
- ${string%*chars}:字符串从右边边开始的第一个chars开始的位置截取到左边结束,chars本身不在截取范围内
- ${string%%*chars}:字符串从右边开始的最后一个chars开始的位置截取到左边结束,chars本身不在截取范围内
#!/bin/bash
#shell截取字符串
str=http//:baidu.com/.c
echo "length: ${str:7:5}" # baidu
echo "no length: ${str:7}" # baidu.com/.c
echo "length: ${str:0-7:5}" # .com/
echo "no length: ${str:0-7}" # .com/.c
echo "#: ${str#*/}" # /:baidu.com/.c
echo "##: ${str##*/}" # .c
echo "%: ${str%/*}" # http//:baidu.com
echo "%%: ${str%%/*}" # http
8、shell中的数组:
bash shell 中只支持一维,而不支持二维,定义的形式:array=(n1 n2 n3) ,数组名等号两边不能有空格,数组变量的值用空格隔开表示不同的值,一个数组变量的值可以用数字或者字符串不同的形式组成:array=(1 2 ab) 。
Shell 数组的长度不是固定的,定义之后还可以增加元素,如:array[3]=cd ;就在最后增加了一个元素。
也不用逐个赋值;如:array=([2]=ab);但他的长度是1
调用数组:
- ${array[0]}
- a=${array[0]}
- ${array[*]} //表示数组的所有元素
- ${array[@]} //表示数组的所有元素
#!/bin/bash
#shell中的数组
array=( 1 2 ab)
n=${array} #array中的第一个元素赋值给n
echo "array[0]: ${array[0]}"
echo "array[*]: ${array[*]}"
echo "n[*]: ${n[*]}"
array[5]=cd
echo "array[5]: ${array[5]}"
echo "array[@]: ${array[@]}"
获取shell数组的元素个数或者shell数组里字符串的长度:${#array[*]}、${#array[@]}、${#array[4]}
#!/bin/bash
#shell中的数组
array=( 1 2 abc)
echo "#array[*] ${#array[*]}"
array[4]=12345
echo "#array[@] ${#array[*]}" #这边多加了一个元素就是4
echo "#array[4] ${#array[4]}" #这边是计算array[4]中字符串的长度
数组的拼接:利用${array[*]}、${array[@]}:
#!/bin/bash
#shell中的数组的拼接
array1=( 1 2 abc)
array2=(cd ef)
array3=(${array1[@]} ${array2[@]})
echo "array3: ${array3[@]}"
echo "array3 length: ${#array3[@]}"
shell数组元素的删除:unset
#!/bin/bash
#shell中的数组
array1=(1 2 abc)
array2=(1 2 abc)
unset array1[2] #删除array1数组中的一个元素abc
echo "array1[*]: ${array1[*]}"
unset array2 #删除整个数组array2
echo "array2[*]: ${array2[*]}"
9、shell中条件判断if:
a)、if单分支:
-eq 等于,如:if [ "$a" -eq "$b" ]
-ne 不等于,如:if [ "$a" -ne "$b" ]
-gt 大于,如:if [ "$a" -gt "$b" ]
-ge 大于等于,如:if [ "$a" -ge "$b" ]
-lt 小于,如:if [ "$a" -lt "$b" ]
-le 小于等于,如:if [ "$a" -le "$b" ]
#!/bin/bash
#shell中if:
#if单分支第一中形式:if []
if [ 1 -eq 1 ] #[]里面的数据和中括号必须一个空格
then
echo "first if:真"
fi
#if单分支第二中形式:if []; then
if [ 1 -eq 1 ]; then #then和if写在同一行必须要用;
echo "second if:真"
fi
#if单分支第三中形式:if (())
if ((1==1))
then
echo "third if:真"
fi
b、双分支if:
#!/bin/bash
#shell中if:
#if双分支
if ((1==0)) #判断1和o是不是相等
then
echo "我是真的"
else
echo "我是假的"
fi
#................................
if [ 1 -eq 0 ] #判断1和o是不是相等,
then
echo "我是真的"
else
echo "我是假的"
fi
3、iif多分支:
#!/bin/bash
#shell中if:
#if多分支
if ((1==0)) #判断1和o是不是相等
then
echo "1=0;是真的"
elif ((1>0))
then
echo "1>0;是真的"
else
echo "1>0;是假的"
fi
#..............................
if [ 1 -eq 0 ] #判断1和o是不是相等
then
echo "1=0;是真的"
elif [ 1 -gt 0 ]
then
echo "1>0;是真的"
else
echo "1>0;是假的"
fi
10、shell中的 case语句:
#!/bin/bash
#shell中的case
read VAILE #在屏幕中输入一个数,1或者2,如果不是这两个数就代表其他;即 *),
case ${VAILE} in
"1")
echo "我是1"
;;
"2")
echo "我是2"
;;
*)
echo "我不是1,也不是2"
;;
esac
11、shell中的 for循环:
a)、for循环形式1:
#!/bin/bash
#shell中的 for
for n in 1 3 5 #n是我们自定义的变量,in后面三个数就是循环3次,每次的值从第一个数的值开始
do
echo "我是n,我的值是:${n}"
done
b)、for循环形式2:与c语言的形式类似
#!/bin/bash
#shell中的 for
for ((n=0;n<3;n++))
do
echo "我是n,我的值是:${n}"
done
12、shell中的 while循环:
#!/bin/bash
#shell中的 while
n=0
while [ ${n} -lt 2 ] #n<2;就循环,否则退出循环
do
n=$((${n}+1))
echo "我是n,我的值是:${n}"
done
echo "我退出循环了"
13、shell中的 until循环:
#!/bin/bash
#shell中的 until
n=-2
until ((n>1)) #不满足条件就循环,满足条件则退出循环
do
n=$((${n}+1))
echo "我是n,我的值是:${n}"
done
echo "我退出循环了"
14、shell循环中的break和continue:
#!/bin/bash
#shell中的 break/continue
n=0
while ((n<6)) #n<6;就循环,否则退出循环
do
n=$((${n}+1))
if ((n==2))
then
echo "我应该打印2,但我continue了"
continue
fi
if ((n==5))
then
echo "n循环6次,应该等于6,但我break了,我现在的值是:${n}"
break
fi
echo "我是n,我的值是:${n}"
done
echo "我退出循环了"
15、shell中的函数:
function 函数名(){
..........
}
#!/bin/bash
#shell中的函数
function fun(){
if(($1 > 1)); then
echo "条件成立"
fi
if(($2 > 1)); then
echo "条件成立"
else
echo "条件不成立"
fi
}
fun 2 1 #这边传参数给fun()函数
上一篇: 如何利用 "二八法则 "实现人生的飞跃?
下一篇: 使用 FAB 规则快速获得客户
推荐阅读
-
` 自动填充为 `cp test.txt`
- 文件和目录名补全:输入文件名首字母后按 Tab,如 `vi ed
` 显示可用的编辑器列表 - 查看命令帮助: - 使用 `man` 命令配合具体命令名获取详尽帮助,如 `man ls` 或者 `man grep --help`"> 在 Linux 中操作指令指南 - 基本构造与种类 - 指令组成: 1. **主指令 + 选项 + 参数**: 如 `ls -l /home`,`main-action option object` - 内置指令:系统预装的 shell 功能,如 `cd`, `pwd` - 外部指令:独立可执行文件,直接用文件名当作命令,如 `rm`, `mv` - **选项与参数**: - 选项:定制命令行为, `-l` 或 `--long-help` - 短选项:简写形式,例如 `-v` 和 `-V` 可能合并使用 - 长选项:详细描述的选项,如 `--version` 或 `--human-readable` - 参数:命令作用的目标,如 `ls` 对 `/home` 目录的操作 - **指令应用**: - 不同指令需要不同的参数 - 选项可带或不带参数,比如 `grep -i "keyword"` (忽略大小写搜索) - 参数间通常用空格分隔,如 `cp file1 file2 file3` - **中断与完成提示**: - 终止当前指令:按下 Ctrl+C - **自动完成**: - 输入部分命令关键词后,按 Tab 键补全命令,如 `cp ta
` 自动填充为 `cp test.txt` - 文件和目录名补全:输入文件名首字母后按 Tab,如 `vi ed ` 显示可用的编辑器列表 - 查看命令帮助: - 使用 `man` 命令配合具体命令名获取详尽帮助,如 `man ls` 或者 `man grep --help` -
Linux shell 脚本:介绍和使用 bash(详细)
-
Linux 教程 - 在 Shell 脚本中声明和使用布尔变量示例
-
epoll简介及触发模式(accept、read、send)-epoll的简单介绍 epoll在LT和ET模式下的读写方式 一、epoll的接口非常简单,一共就三个函数:1. int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close关闭,否则可能导致fd被耗尽。2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epoll的事件注册函数,它不同与select是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create的返回值,第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */};events可以是以下几个宏的集合:EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLIN事件:EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。现在明白为什么说epoll必须要求异步socket了吧?如果同步socket,而且要求读完所有数据,那么最终就会在堵死在阻塞里。 EPOLLOUT:表示对应的文件描述符可以写; EPOLLOUT事件:EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那要先准备好下面条件:1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!其实,如果真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。1. 缓冲区由满变空.2.同时注册EPOLLIN | EPOLLOUT事件,也会触发一次EPOLLOUT事件这个两个也会触发EPOLLOUT事件 EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);EPOLLERR:表示对应的文件描述符发生错误;EPOLLHUP:表示对应的文件描述符被挂断;EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的产生,类似于select调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。-------------------------------------------------------------------------------------------- 从man手册中,得到ET和LT的具体描述如下EPOLL事件有两种模型:Edge Triggered (ET)Level Triggered (LT)假如有这样一个例子:1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符2. 这个时候从管道的另一端被写入了2KB的数据3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作4. 然后我们读取了1KB的数据5. 调用epoll_wait(2)......Edge Triggered 工作模式:如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。 i 基于非阻塞文件句柄 ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered 工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。然后详细解释ET, LT:LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。(未测试)另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取: 这里只是说明思路(参考《UNIX网络编程》) while(rs) {buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);if(buflen < 0){// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读// 在这里就当作是该次事件已处理处.if(errno == EAGAIN)break; else return; }else if(buflen == 0) { // 这里表示对端的socket已正常关闭. } if(buflen == sizeof(buf) rs = 1; // 需要再次读取 else rs = 0; } 还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send内部,当写缓冲已满(send返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send内部,但暂没有更好的办法. ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) { ssize_t tmp; size_t total = buflen; const char *p = buffer; while(1) { tmp = send(sockfd, p, total, 0); if(tmp < 0) { // 当send收到信号时,可以继续写,但这里返回-1. if(errno == EINTR) return -1; // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, // 在这里做延时后再重试. if(errno == EAGAIN) { usleep(1000); continue; } return -1; } if((size_t)tmp == total) return buflen; total -= tmp; p += tmp; } return tmp; } 二、epoll在LT和ET模式下的读写方式 在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK) 从字面上看, 意思是: * EAGAIN: 再试一次 * EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block * perror输出: Resource temporarily unavailable 总结: 这个错误表示资源暂时不够, 可能read时, 读缓冲区没有数据, 或者, write时,写缓冲区满了 。 遇到这种情况, 如果是阻塞socket, read/write就要阻塞掉。 而如果是非阻塞socket, read/write立即返回-1, 同 时errno设置为EAGAIN. 所以, 对于阻塞socket, read/write返回-1代表网络出错了. 但对于非阻塞socket, read/write返回-1不一定网络真的出错了. 可能是Resource temporarily unavailable. 这时你应该再试, 直到Resource available. 综上, 对于non-blocking的socket, 正确的读写操作为: 读: 忽略掉errno = EAGAIN的错误, 下次继续读 写: 忽略掉errno = EAGAIN的错误, 下次继续写 对于select和epoll的LT模式, 这种读写方式是没有问题的. 但对于epoll的ET模式, 这种方式还有漏洞. epoll的两种模式 LT 和 ET