2010年3月30日
#
Unix下我们可用的五种IO模型是:
- 阻塞I/O
- 非阻塞I/O
- I/O复用
- 信号驱动I/O
- 异步I/O(POSIX.1的aio_序列函数
- 阻塞I/O
是最流行的I/O模型,以recvfrom为例,此系统调用直到数据到达且拷贝到应用缓冲区或出错才返回.进程阻塞于该系统调用,然后1).等待数据到达内核缓冲区,2)将数据从内核缓冲区中拷贝到用户空间中应用程序缓冲区. 拷贝完成后,返回成功指示.
- 非阻塞I/O
进程不阻塞于I/O函数(recvfrom),不会等待数据, 如果数据没有到达,就立即从recvfrom返回到应用进程,然后等待一段时间,再次调用recvfrom,这个过程叫轮询(进程对一个非阻塞描述字循环调用I/O函数). 通过轮询不断查询内核,看看操作是否准备好,这对CPU时间是极大浪费.
- I/O复用
通过select或poll调用,在这两个调用中的某一个阻塞,而不是阻塞于真正的系统调用. 这样的好处在于select可这等待多个描述字,阻塞IO模型在真正的系统调用recvfrom上阻塞,而recvfrom一次只能读一个描述字,对它的阻塞使得进程只能为这个描述字傻傻等待. 一般应用系统中都会有多个描述字.使用select可这很方便地对所有提供的描述字进行等待.
- 信号驱动I/O
在描述字准备好后,让内核给应用进程发一个信号SIGIO,应用进程捕捉到该信号后再对描述字调用recvfrom. 首先允许套接口进行信号驱动,并通过系统调用sigaction安装一个信号处理程序,此系统调用立即返回,进程继续工作.
- 异步I/O(POSIX.1的aio_序列函数
异步I/O是指Posix.1的1993版本中的新内容。异步的意思是,让内核启动操作,并在整个操作完成后(包括将数据从内核拷贝到应用进程的缓冲区中)通知我们。这种模型与其他四种模型的主要区别在于,信号驱动IO是由内核通知我们什么时候可以启动一个io操作, 而异步io模型是由内核通知我们io操作何时完成。
各种模型比较:
1.前四种模型在第一阶段(等待数据阶段)的行为不同, 在第二阶段基本相同:在将数据从内核拷贝到调用者的缓冲区时,进程都阻塞于recvfrom调用。
2.异步io模型处理的两个阶段都不同于前四种。
同步I/O 与异步I/O
同步I/O操作引起请求进程阻塞,直到IO操作完成
异步I/O操作不引起请求进程阻塞
上述前四种IO模型都属于同步IO, 因为真正的IO操作(recvfrom)阻塞进程。
2010年3月25日
#
编写unix网络通信程序时,经常会遗留一些状态为CLOSE_WAIT的进程,使用netstat 命令查看,结果中没有进程相关的信息:
netstat -a|grep 9877
tcp 1 0 ylei-laptop.local:53773 ylei-linux.local:9877 CLOSE_WAIT
tcp 1 0 ylei-laptop.local:54080 ylei-laptop.local:9877 CLOSE_WAIT
这时可这使用lsof工具,它可这显示出状态为CLOSE_WAIT的进程的程序名(command),进程id(pid), 等等。
lsof -i@ylei-laptop.local
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
gvfsd-smb 2217 ylei 18u IPv4 41440 0t0 TCP ylei-laptop.local:60953->ylei-linux.local:netbios-ssn (ESTABLISHED)
tcpcli 8055 ylei 3u IPv4 312580 0t0 TCP ylei-laptop.local:54080->ylei-laptop.local:9877 (CLOSE_WAIT)
tcpcli 8057 ylei 3u IPv4 312872 0t0 TCP ylei-laptop.local:53773->ylei-linux.local:9877 (CLOSE_WAIT)
这时就可这用KILL命令来杀死这些进程。
这种CLOSE_WAIT
进程遗留多了会对系统有一定的影响,
如何避免出现CLOSE_WAIT状态的通信进程?
http://blog.chinaunix.net/u/19782/showart_218982.html
这是一篇lsof使用的文章,可作参考。
在我的64位Ubuntu9.10上,有的pdf文档中文无法显示,是空白。解决方案:
1. sudo apt-get install xpdf-chinese-simplified
2. sudo apt-get install xpdf-chinese-traditional
3. sudo apt-get install poppler-data
重新打开pdf文档,中文正常显示
今天在QT中编译纯linux程序时出现一个编译错误:
multiple definition of main
搜不半天,也没有合适的办法。
问题的出现原因在于项目的创建过程:
使用QT创建了一个含有main.cpp的项目,然后添加了一些*.c文件,因为同一个项目中不想同时包含两种不同类型的源文件,所以就把main.cpp改成了main.c. 但是,QT的项目文件(tcpserver.pro)中的内容多出了一个main.c来,这样,编译时,qmake会根据这个.pro文件生成Makefile, 这个Makefile中就会包含两个相同的目标main.o,连接的时候就会出现上面的错误。
QT -= gui
TARGET = tcpserver
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.c \
../../../lib/wrapsock.c \
main.c \
../../../lib/str_echo.c \
../../../lib/error.c \
../../../lib/wrapunix.c \
../../../lib/writen.c \
../../../lib/readline.c
HEADERS += ../../../lib/unp.h
生成的Makefile如下:
####### Compile
main.o: main.c ../../../lib/unp.h \
../config.h \
../../../lib/addrinfo.h
$(CC) -c $(CFLAGS) $(INCPATH) -o main.o main.c
wrapsock.o: ../../../lib/wrapsock.c ../../../lib/unp.h \
../config.h \
../../../lib/addrinfo.h
$(CC) -c $(CFLAGS) $(INCPATH) -o wrapsock.o ../../../lib/wrapsock.c
main.o: main.c ../../../lib/unp.h \
../config.h \
../../../lib/addrinfo.h
$(CC) -c $(CFLAGS) $(INCPATH) -o main.o
str_echo.o: ../../../lib/str_echo.c ../../../lib/unp.h \
../config.h \
../../../lib/addrinfo.h
$(CC) -c $(CFLAGS) $(INCPATH) -o str_echo.o ../../../lib/str_echo.c
error.o: ../../../lib/error.c ../../../lib/unp.h \
../config.h \
../../../lib/addrinfo.h
$(CC) -c $(CFLAGS) $(INCPATH) -o error.o ../../../lib/error.c
解决办法是:
修改项目文件(tcpserver.pro)
,去掉重复的main.c.然后依次调用
clean all
qmake
make
2010年3月23日
#
TCP状态转换看起来很复杂,实质很只有两个主要状态,
1. 从CLOSED 到 ESTABLISHED
2. 从ESTABLISHED到CLOSED
TCP应用一般涉及客户和服务器应用,根据客户和服务器端的不同,对TCP的状态进行细分:
对照Unix 网络编程卷1 第33页的图,下面分别是客户端和服务器端的TCP状态转换过程。
客户端
客户端的TCP状态转换
- 客户端调用connect(),TCP发送SYN给服务器,执行主动打开,状态从CLOSED到SYN_SENT
- 客户处于SYN_SENT状态时,可能出现三种情况:
- 如果发送SYN后没有收到服务器响应,出现超时,状态回到CLOSED
- 如果接收到服务器的ACK和SYN,客户就会发送ACK作为对服务器的SYN确认,这时状态转换到ESTABLISHED,表示连接已建立
- 如果客户发送SYN的同时接收到服务器发来的SYN,状态会变为SYN_RCVD(同时打开),这属于不正常的客户端TCP状态,在unp第33面的TCP状态转换图中,使用细实线来连接这种状态转换,表示不正常的状态转换。在图例中说明,正常的客户端TCP状态转换使用粗实线来连接,正常的服务器TCP状态转换使用粗虚线连接。
- 客户端经过上述第二步进入ESTABLISHED后,它发送完数据,然后客户端应用程序调用close(),它的下一个状态是关闭,即CLOSED,但是,有几种情况可以进入CLOSED状态
- 客户端应用程序调用close后,TCP发送FIN给服务器,TCP处于FIN_WAIT_1状态,这个状态wait_1表示等待服务器对FIN的确认ACK,如果接收到服务器对FIN的确认,客户端TCP状态转换为FIN_WAIT_2,wait_2表示客户还在等待服务器的FIN.如果接收到服务器的FIN,客户就发送ACK对服务器的FIN进行确认,然后状态进入TIME_WAIT
- 同时接收到FIN和ACK,然后发送ACK给服务器,进入TIME_WAIT状态
- 客户端应用程序调用close后,TCP发送FIN给服务器,如果发送的同时接收到服务器的FIN,状态转换为CLOSING,表示同时关闭
服务器
服务器应用程序启动后,一直等待客户端连接, 当等到以后, 执行被动打开。
- 服务器程序启动后,如果程序编写正确,自动从初始状态CLOSED转换为LISTEN
- 服务器收到客户端的SYN, 然后发送SYN和ACK给客户端,这时服务器处于SYN_RCVD
- 服务器收到客户端的ACK, 状态变为ESTABLISHED
- 服务器收到客户端的FIN, 然后发送ACK给客户端,状态变为CLOSE_WAIT
- 服务器应用程序调用close时,TCP发送FIN给客户,状态为LAST_ACK,表示等待最后一个ACK.
- 服务器收到客户的ACK, 状态变为初始状态 CLOSED.
2010年3月19日
#
By default installation, Ubuntu does not include the manual pages for developer, use the following command to install them manually:
1. apt-cache search manpages-dev
This command search the man pages and output the following:
manpages-dev - Manual pages about using GNU/Linux for developmentmanpages-de-dev - German development manpagesmanpages-fr-extra - French version of the manual pages2. sudo apt-get install manpages-dev
this command install the man pages in the computer.
2010年2月4日
#
- 函数指针的定义:
void (*funcPtr)();
这个表达式定义一个指向没有参数,没有返回值的函数。函数指针变量名是funcPtr. 分析一个较复杂的函数指针定义表达式时,可按下列步骤进行:
- 先找到变量名
- 找变量名右边的项,然后找左边的项,然后右边,...这种右-左-右的方法适用于大多数的表达式。
- void (*funcPtr)();的分析
- 变量名是funcPtr,
- 找右边,右边没有项了,只是一个右括号:")"
- 找左边,变量左边是*表示funcPtr是一个指针
- 找右边, 是(),表示一个空参数列表
- 找左边,*的左边是void, 表示函数的返回类型。
- 结果:
funcPtr是一个指向函数的指针,该函数无参数,返回类型是void.
- void * (*(*fp1)(int))[10];的分析:
- fp1
- 右: )
- 左: *, fp1是一个指针
- 右:(int), fp1指向的函数的参数是int
- 左:*,fp1指向的函数的返回值是一个指针
- 右[10],fp1指向的函数的返回值是一个指针数组
- 左void *,指针数组指向的是void类型。
- float (*(*fp2)(int,int,float))(int);
- fp2指向一个带有三个参数的函数,这个函数f返回一个指针,该指针又指向一个函数,这个函数有一个int参数,返回类型是float.
- 使用函数指针
- 定义函数指针
- 定义函数
- 将函数地址赋给函数指针
- 通过函数指针调用函数
#include <iostream>
using namespace std;
void func(){
cout<<"func() called" << endl;
}
int main(){
void (*fp)(); // define a function pointer
fp = func; // Initialize it
(*fp)(); // Dereferencing calls the function
void (*fp2)() = func; // define and initialize
(*fp2)();
}
通过#define来使用#来输出变量的名字
1 #define P(A) cout << #A << ": " << (A) << endl;
2
3 int _tmain(int argc, _TCHAR* argv[])
4 {
5 int a = 1, b = 2, c = 3;
6 P(a);
7 P(b);
8 P(c);
9 P(a + b);
10 return 0;
11 }
输出:
a: 1
b: 2
c: 3
a + b: 3
在Think In C++中,有这样一段描述:
When you put a # before an argument in a preprocessor macro, the preprocessor turns that argument into a character array.
在定义宏的时候,1. 在变量名前加#, 2. 输出变量值的时候使用()括号括起来。