2015年10月5日 星期一

Socket網路程式設計(1):

    Linux OS使用Socket的觀念來設計網路程式,所以在介紹UDP,TCP網路程式設計前,先了解一下Socket的基本觀念。

    "Socket"有時又稱為[接口],是一種可做雙向資料傳輸的通道,可以將Socket想像成一種裝置,Linux程序可經由此裝置與本地端或是遠端的程序溝通。

    本次要介紹的UDP及TCP的網路程式主要是用到Internet-domain socket,如果我們能了解Internet-domain socket結構的定義相關函數的意義,就能寫出UDP及TCP的網路程式。

結構與相關函數:
        
        在設計網路程式之前,先說明Internet-domain socket的定址結構,此結構可以用來儲存IP位址、通訊口等訊息。
     

[sockaddr_in]定址結構:

    struct sockaddr_in{
             sa_family_t  sin_family;  /*AF_INET*/
             unsigned short int  sin_port;  /*port number*/
             struct in_addr  sin_addr;  /*Internet address*/
};

  sin_family:是用來說明socket所使用的定址模式,在此必須設為[AF_INET],表示是Internet-                          domain socket。
  sin_port:用來表示TCP/IP的port number,設定sin_port必須使用htons函數做位元排序的動作,                 會在之後說明。
  sin_addr:用來表示IP位址,結構如下。
                  struct in_addr{
                            unsigned long int s_addr;/*儲存IP位址*/
                   };

[inet_addr()函數]:

  inet_addr()函數可用來將[xxx.xxx.xxx.xxx]格式的IP位址轉換成ˇ32-bit unsigned integer。
  [格式]
             #include <sys/socket.h>
             #include <netinet/in.h>
             #include <arpa/inet.h> 
             unsigned long inet_addr(const char *string);
其中[string]為一個IP字串,他的格式為[XXX.XXX.XXX.XXX] 


位元排序函數:   

 在設定IPv4 Socket定址結構時,還有一點需要注意的是port number 的設置。
 假如我們要設定IP位址[192.168.1.10],port number為[8000],我們會做如下設定
  
  struct sockaddr_in adr_srvr;
  adr_srvr.sin_addr.s_addr = inet_addr("192.168.10");
  adr_srvr.sin_port = htons(8000);


  Socket():

  要完成TCP及UDP網路程式設計,需要呼叫許多的SOCKET函數,來幫助我們撰寫網路程式,不論是使用TCP及UDP作為傳輸協定,要透過SOCKET做資料傳輸,首要的工作就是建立SOCKET,要建立SOCKET就可使用socket()函數。

[Socket格式]

                       #include <sys/types.h>
                       #include <sys/socket.h>
                       int socket(int domain, int type, int protocol);

domain:網域,只要設定為[AF_INET]即可,表示使用Internet協定。

type:連結的型態,若設定值為[SOCK_STREAM]表示為TCP傳輸層協定,若設定值為                         [SOCK_DGRAM]表示為UDP傳輸層協定。

protocol:通訊協定,一般設為[0],表示自動選擇。


  Bind():

在通訊協定中我們可以使用Bind()函數將IPv4 socket定址結構,連結到我們所建立的socket,如此一來每當有封包到達網路介面時,Linux核心就會將這個封包導向所連結的socket。


[Bind格式]

                   #include<sys/socket.h>
                   int bind (int sockfd, const struct sockaddr *my_addr, size_t adr_len);

sockfd: socket函數執行後所傳回的socket描述子

my_addr:指向struct sockaddr_in結構的指標,用來存放欲連結的IPv4定址結構

adr_len: struct sockaddr_in結構的長度,可將其指定為sizeof(struct sockaddr_in)

Listen():

建立好socket並做好bind後,在server端,我們可以使用listen()函數來通知Linux核心,將socket設為[listening socket],等待client端的連線要求。

[Listen格式]

                    #include <sys/socket.h>
                    int listen(int sockfd, int backlog);

sockfd: socket函數執行後所傳回的socket描述子

backlog:指定最大連線數量,通常設為5

Accept():

當server端建立好listening socket並接收到client端的連線請求時,就會將連線請求放入連線佇列中,接著server端必須呼叫accept()函數,來處理並接受佇列中的連線請求。

[Accept格式]

                    #include <sys/socket.h>
                    int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen);

sockfd: socket函數執行後所傳回的socket描述子

addr:指向struct sockaddr_in結構的指標,用來存放client端的IP address

addrlen:存放client IP address變數的長度,初值應為sizeof(struct sockaddr_in),accept()執行成                 功後會存回實際的client IP address 長度


Connect():

在TCP中,當client端建立好了socket後可以呼叫connect()函數向server端要求建立連線,建立完畢後才能互傳資料。

[Connect格式]

                    #include <sys/socket.h>
                    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd: socket函數執行後所傳回的socket描述子

serv_addr:指向struct sockaddr_in結構的指標,用來存放server端的IP address

addrlen: struct sockaddr_in結構的長度,可指定為sizeof(struct sockaddr_in)

Close():

我們可以呼叫close()函數來中止連線。

[Close格式]

                    #include <sys/socket.h>
                    int close(int sockfd);

sockfd: socket函數執行後所傳回的socket描述子

TCP程式設計流程:

在設計網路程式我們會分為客戶端與伺服端,綜合以上的說明,我們可以將Linux TCP程式設計流程,以下圖表示。





TCP輸出入函數:

當我們在server端建立了socket做好bind,產生listening socket,接受client端連線要求後,server端便可以與client端彼此傳遞資料,要傳遞資料,需要使用輸出入函數,TCP socket的輸出入函數有許多種,最基礎的便是read()、write()函數。

Read():

read()函數會從已開啟的socket讀取資料。

[Read格式]

                   #include <sys/socket.h>
                   int read(int sockfd, char *buf, int len);


sockfd: socket函數執行後所傳回的socket描述子

buf:指向字元暫存器的指標,用來儲存讀取到的資料

len:欲讀取的字元長度

Write():

write()函數可以將資料寫入已開啟的socket。

[Read格式]

                   #include <sys/socket.h>
                   int write(int sockfd, char *buf, int len);

sockfd: socket函數執行後所傳回的socket描述子

buf:指向字元暫存器的指標,用來儲存欲寫入的資料

len:欲寫入的字元長度

Recv():

recv()函數可以經由socket來接收資料。

[Recv格式]

                   #include <sys/types.h>
                   #include <sys/socket.h>
                   int recv(int s, void *buf, int len, unsigned int flags);
[note]:recv()用來接收遠端socket傳來的資料,並把資料儲存在buf指向的記憶體中,len為可接              收的最大長度,flags一般設為0

Send():

send()函數可以經由socket來傳送資料。

[Send格式]

                   #include <sys/types.h>
                   #include <sys/socket.h>
                   int send(int s, const void *msg, int len, unsigned int flags);
[note]:send()函數可以將msg指向的資料,經由socket傳送給遠端的主機,len則為傳送資料最大            長度,flags一般設為0。

--------------------------------------------------------------------------------------------------------------------------
由上述介紹後,我們來實作TCP通訊協定的server程式與client程式

動作要求:

1.server端會建立socket,連結socket,等待client端連線
2.client端會建立socket,與server連線,若成功,將str字串傳給server,str字串內容為[品號 品      名 售價]
3.server端若接受client端的連線,會讀取字串並解析內容,算出售價的含稅價,再將處理後的    字串回傳給client
4.client端收到server端的訊息,會將訊息顯示在螢幕上

程式:server端

/* server.c */

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>

int port = 8000;

int main(void)
{
  struct sockaddr_in sin;
  struct sockaddr_in pin;
  int mysock;
  int tempsock;
  int addrsize;
  char str[100],str1[20],str2[20],str3[20];
  char buf[100];
  int i, len1, len2;
  float c;

  mysock = socket(AF_INET, SOCK_STREAM, 0);
  if (mysock == -1) {
  perror("call to socket");
  exit(1);
  }

  bzero(&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  sin.sin_port = htons(port);

  if (bind(mysock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
  perror("call to bind");
  exit(1);
  }

  if (listen(mysock, 20) == -1) {
  perror("call to listen");
  exit(1);
  }

  printf("Accepting connections ...\n");

  while(1) {
  tempsock = accept(mysock, (struct sockaddr *)&pin, &addrsize);
  if (tempsock == -1){
    perror("call to accept");
    exit(1);
  }

  len1=recv(tempsock, str, 100, 0);
  printf("\n收到字元數: %d\n",len1);
str[len1]=0;
printf("received from client: %s\n", str);

if (len1 > 0) {
strcpy(str1,strtok(str," "));
printf("第 1 個字串為: %s\n",str1);
strcpy(str2,strtok(NULL," "));
printf("第 2 個字串為: %s\n",str2);
strcpy(str3,strtok(NULL," "));
printf("第 3 個字串為: %s\n",str3);
c=atof(str3)*1.05;
sprintf(buf,"品號為 %s\n品名為 %s\n含稅價為: %.2f\n",str1, str2, c);
}
  len2 = strlen(buf);

  if (send(tempsock, buf, len2, 0) == -1) {
    perror("call to send");
    exit(1);
  }
  close(tempsock);
  }
  return 0;
}

程式:client端

/* client.c */

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int port = 8000;

int main(int argc, char *argv[])
{
  struct sockaddr_in pin;
  int mysock;
  char buf[8192];
  char *str="A001 電視機 20000.00 ";

  if (argc < 2) {
printf("使用方法: client 字串\n");
printf("使用預設字串\n");
  } else {
str=argv[1];
  }
  
  bzero(&pin, sizeof(pin));
  pin.sin_family = AF_INET;
  pin.sin_addr.s_addr = inet_addr("192.168.1.20");
  pin.sin_port = htons(port);

  mysock = socket(AF_INET, SOCK_STREAM, 0);
  if (mysock == -1) {
  perror("call to socket");
  exit(1);
  }

  if (connect(mysock, (void *)&pin, sizeof(pin)) == -1) {
  perror("call to connect");
  exit(1);
  }

  printf("Sending message %s to server ...\n", str);

  if (send(mysock, str, strlen(str), 0) == -1) {
  perror("Error in send\n");
  exit(1);
  }


  if (recv(mysock, buf, 8192, 0) == -1) {
  perror("Error in receiving\n");
  exit(1);
  }

  printf("\nResponse from server: \n\n%s\n", buf);

  close(mysock);
  return 0;
}
--------------------------------------------------------------------------------------------------------------------------

 sin.sin_addr.s_addr = htonl(INADDR_ANY):

       讓Server端的socket可以接收同一個網域內所有的IP位址,INADDR_ANY可視為[萬用位            址],同樣地,若要讓socket接收同區網的所有port number,指令如下:
        sin.sin_port = htons(0);

指令列引數:

int main(int argc, char *argv[]){....}
此為C語言的一項功能,此功能可以讓程式在執行時,接受由使用者輸入一些字串,作為主程式的參數,其中第一個引數[argc]會儲存使用者輸入的字串個數,而第二個引數[argvp[]]為字串指標陣列,指向使用者所輸入的字串內容。
例如,當我們執行client程式可以輸入以下字串:
./client "A002 音響 15000.00"
此時,argc的值為2,argv[0]的內容為client,argv[1]的內容為"A002 音響 15000.00"。



沒有留言:

張貼留言