Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2778211
  • 博文数量: 505
  • 博客积分: 1552
  • 博客等级: 上尉
  • 技术积分: 2514
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-23 18:24
文章分类

全部博文(505)

文章存档

2019年(12)

2018年(15)

2017年(1)

2016年(17)

2015年(14)

2014年(93)

2013年(233)

2012年(108)

2011年(1)

2009年(11)

分类: LINUX

2013-04-15 14:55:57

chapter 7 Socket程式設計

 

7-1 TCP/IP 簡介

如何以Linux C來設計網路程式

TCP/IP是一個協定,是一組支援網路溝通協定的集合。TCP/IP通訊協定的主要工作,是定義網路傳送時的資料單位,並說明一個資料單位應包含哪些資訊,以便讓接收此資料單位的電腦可以正確的解析訊息。

TCP/IP協定系統分成幾個不同的階層式元件,每一層元件負責一項工作,這個模型稱之為TCP/IP模組架構。

 

TCP/IP模組架構

一個TCP/IP模組架構包含4個階層,分為網路存取層、網際網路層、傳輸層和應用層。

階層 說明
網路存取層 提供一個與實體網路之間的界面,可讓主機具有與網路連接並發送IP封包的能力。
網際網路層 提供與硬體無關的邏輯位址,並將資料封包(packet)發送至網路上,並讓它們獨立的到達目的地。在此層中定義[IP協定](internet protocol),此協定為網際網路上的電腦,提供一個共同的定址結構,稱為IP位址。
傳輸層 為網際網路提供流程控制、錯誤檢查、回報服務,是與網路應用程式間的界面。TCP/IP模組定義二組協定,TCP和UDP。TCP是一個連結協定,透過 TCP可確保接收端收到完整、正確的資料。另一方面,UDP則是一個非連結協定,比TCP快,但可靠度差。
應用層 為網路除錯、檔案傳輸、遠端控制和網際網路活動提供應用程式,包含FTP、HTTP、SMTP、TELNET、與DNS等功能。

 

7-2 socket基本概念

Linux作業系統使用socket觀念來設計網路程式,在使用TCP和UDP網路程式之前,必須先了解socket基本觀念。

socket 是一種可做雙向資料傳輸的通道,Linux程序可經由此裝置與本地端或是遠端的程序做溝通。

Linux socket分成unix-domain socket和internet-domain socket二種。unix-domain socket又稱為local socket,主要用來與本地端的程序溝通。internet-domain socket用來與遠地端的程序溝通。

 

7-3 IPv4 socket定址結構

internet domain socket的定址結構可用來儲存IP位址、通訊埠等訊息。

IPv4是目前internet使用的網路定址模式,他的定址結構稱為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 umber,設定sin_port必需使用htons函數作位元排序的動作。
sin_addr 是一個in_addr的結構變數,用來表示ip位址。

 

in_addr結構的定義如下:

struct in_addr {

    unsigned long int s_addr;

};

 

7-4 設定IPv4 socket定址結構

用來存放IP位址的s_addr是一個32位元unsigned integer,但是ip通常會表示為xxx.xxx.xxx.xxx,例如192.168.1.100。可使用inet_addr()位址轉換函數將 192.168.1.100轉換為s_addr所要的32位元unsigned integer。

 

inet_addr()

用來將xxx.xxx.xxx.xxx格式的ip位址轉換成32位元unsigned integer。格式

#include

#include

#include

unsigned long inet_addr(const char *string);

其中string是一個ip字串,格式為xxx.xxx.xxx.xxx。

 

位元排序函數

由於不同的CPU在儲存32位元整數時,會將4個位元組由高到低(例:motorola CPU),或由低到高(例:intel CPU)儲存,造成網路處理上的錯誤。因此在儲存TCP/IP的port number時,可以用位元排序函數來消除此問題。

常用的位元排序函數有二個:

格式

#include

unsigned long htonl(unsigned long hostlong);

unsigned short htons(unsigned short hostshort);

其中的引數說明

引數 說明
hostlong 欲轉換的整數為長整數
hotshort 欲轉換的整數為短整數

範例

struct sockaddr_in adr_srvr;

adr_srvr.sin_addr.s_addr=inet_addr("192.168.1.100");

adr_srvr.sin_port=htons(8000);

 

7-5 socket相關函數

socket()

不論是TCP或UDP作為傳輸協定,都要透過socket作資料傳輸,首要工作是先建立socket。要建立socket可用socket()函數。

格式:

include

include

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

其中的引數說明:

引數 說明
domain 設定為AF_INET表示internet協定
type 連結的型態。
設定為SOCK_STRREAM表示TCP傳輸層協定。
設定為SOCK_DGRAM表示UCP傳輸層協定。
protocol 通訊協定,一般為0,表示自動選擇。

傳回值:

成功:傳回socket ID。

失敗:傳回-1。

 

bind()

將IPv4 socket定址結構連結到所建立的socket,以後當有封包抵達往路介面時,Linux核心便會將這個封包導向到其連結的socket。

格式

#include

int bind(int sockfd, const struct sockaddr *my_addr, size_t adr_len);

其中的引數說明:

引數 說明
sockfd socket函數執行後傳回的socket ID
my_addr 指向socket sockaddr_in結構的指標,用來存放連結的IPv4定址結構
adr_len struct sockaddr_in結構的長度,可將其指定為sizeof(struct sockaddr_in)

傳回值

成功:傳回0

失敗:傳回-1

 

listen()

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

格式:

#include

int listen(int sockfd, int backlog);

其中的引數說明:

引數 說明
sockfd socket函數執行後傳回的socket ID
backlog 指定最大連線的數量,通常為5

傳回值

成功:傳回0

失敗:傳回-1

 

accept()

當server端接收到client端的連線請求時,就會把連線請求放在連線佇列中,接著server端必須呼叫accept函數來處理並接受佇列中的連線請求。

格式:

#include

int accept(int socketfd, struct sockaddr *addr, socklen_t addrlen);

其中的引數說明:

引數 說明
sockfd socket函數執行後傳回的socket ID
addr 指向struct sockaddr_in結構的指標,用來存放client端的IP address
addrlen 存放client IP address變數的長度,初值為sizeof(struct sockaddr_in),accept()執行成功後會回存實際的client ip address長度。

傳回值

成功:傳回client的socket ID,稱為connect socket。

失敗:傳回-1

 

connect()

client端建立socket後,可用connect()函數向server端要求建立連線,在確定連線後,client端和sever端就能開始互相傳送資料。

格式:

#include

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

其中的引數說明:

引數 說明
sockfd socket函數執行後傳回的socket ID
serv_addr 指向struct sockaddr_in結構的指標,用來存放server的address1
addrlen struct sockaddr_in結構的長度,可指定為sizeof(struct sockaddr_in)

傳回值

成功:傳回0

失敗:傳回-1

 

close()

呼叫close()終止client端和server端的連線。

格式

#include

int clode(int sockfd);

傳回值

成功:傳回0

失敗:傳回-1

 

 

7-6 設計tcp網路程式

網路程式會分成client端和server端。

設計TCP client端

1.建立socket(使用socket()函數)

2.向server要求連線(使用connect()函數)

3.若連線成功,使用輸出入函數和server端互傳訊息

4.關閉socket,結束連線(使用close()函數)

 

設計TCP server端

1.建立socket(使用socket()函數)

2.連結socket(使用bind()函數)

3.開啟listening socket(使用listen()函數)

4.等待client連線要求(使用accept()函數)

5.若連線成功,使用輸出入函數和client端互傳訊息

6.關閉socket,結束連線(使用close()函數)

 

TCP程式設計流程

特殊的TCP port number

20 TCP FTP - data port Official
21 TCP FTP - control (command) port Official
22 TCP SSH (Secure Shell) - used for secure logins, file transfers (scp, sftp) and port forwarding Official
23 TCP Telnet protocol - unencrypted text communications Official
25 TCP SMTP - used for e-mail routing between mailservers E-mails Official
53 TCP DNS (Domain Name System) Official
80 TCP HTTP (HyperText Transfer Protocol) - used for transferring web pages Official
80 TCP HTTP - HTTP listening port Official
110 TCP POP3 (Post Office Protocol version 3) - used for sending/retrieving E-mails
137 TCP NetBIOS NetBIOS Name Service Official
138 TCP NetBIOS NetBIOS Datagram Service Official
139 TCP NetBIOS NetBIOS Session Service Official

 

7-7 TCP 輸出入函數

read()

從已經開啟的socket讀取資料

格式

#include

int read(int sockfd, char *buf, int len);

其中的引數說明:

引數 說明
sockfd socket函數執行後傳回的socket ID
buf 指向字元暫存器的指標,用來存放讀取到的資料
len 欲讀取的字元長度。

傳回值

成功:傳回接收的字元數

失敗:傳回-1

 

write()

將資料寫入已經開啟的socket

格式

#include

int write(int sockfd, char *buf, int len);

其中的引數說明:

引數 說明
sockfd socket函數執行後傳回的socket ID
buf 指向字元暫存器的指標,用來存放讀取到的資料
len 欲讀取的字元長度。

傳回值

成功:傳回寫入的字元數

失敗:傳回-1

 

recv()

從已經開啟的socket接收資料

格式

#include

#include

int recv(int s, void *buf, int len, unsigned int flags);

其中的引數說明:

ru; y xul4

將資料存到buf指向的記憶體,len為可接收的最大長度,flags一般設為0

引數 說明
MSG_OOB 接收以out-of-band送來的資料
MSG_PEEK 遠端socket傳來的資料,不會在接收受刪除
MSG_WAITALL 固定接收len引數指定長度的資料,除非有錯誤或訊號發生
MSG_NOSIGNAL 接收動作不會因SIGPIPE訊號中斷

傳回值

成功:傳回接收的字元數

失敗:傳回-1

 

send()

從已經開啟的socket傳送資料

格式

#include

#include

int send(int s, const void *msg, int len, unsigned int flags);

其中的引數說明:

ru; y xul4

buf指向的資料經由socket傳送到遠端,len為可傳送的最大長度,flags一般設為0

引數 說明
MSG_OOB 接收的資料以out-of-band送出
MSG_DONTROUTE 取消route的查詢
MSG_DONTWAIT 傳送過程不可以被阻斷
MSG_NOSIGNAL 傳送動作不會因SIGPIPE訊號中斷

傳回值

成功:傳回傳送的字元數

失敗:傳回-1

 

7-8 TCP程式設計

目的:

撰寫TCP通訊協定的server端和client端程式。

 

要求:

1.server端建立socket,連結socket,等待client端連線。

 2. client端建立socket,與server連線,連線成功後,將字串str傳給server。

3.server端若接受client端連線請求,則讀取client端傳過來的字串。處理後,將字串結果傳回client端。

4.client端收到結果後,將結果顯示在螢幕。

 

server端

 
/* server.c */

#include 
#include 
#include 
#include 
#include 
#include 
#include 

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");  //用來將上一個函數發生錯誤的原因輸出到標準錯誤(stderr) exit(1);
    }

    bzero(&sin, sizeof(sin));  //將一段內存內容全清為零 sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);  //將32位主機字符順序轉換成網絡字符順序 sin.sin_port = htons(port);  //用來將參數指定的16位元hostshort轉換成網絡字符順序 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," ")); //將參數src字符串拷貝至參數dest所指的地址 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) { //經socket傳送數據 perror("call to send");
       exit(1);
     }
     close(tempsock);
    }
    return 0; } 

 

client端

 
/* client.c */

#include 
#include 
#include 
#include 
#include 

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; } 

 

程式設計 TCP

server端
#include
#include
#include
#include
#include
#define PORT 1234
#define MAXSOCKFD 10

main()
{
    int sockfd,newsockfd,is_connected[MAXSOCKFD],fd;
    struct sockaddr_in addr;
    int addr_len = sizeof(struct sockaddr_in);
    fd_set readfds;
    char buffer[256];
    char msg[ ] =”Welcome to server!”;

    /* 建立socket */
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))<0){
      perror(“socket”);
      exit(1);
    }

    /* 填寫server端的sockaddr_in結構 */
    bzero(&addr,sizeof(addr));
    addr.sin_family =AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    /* 連結socket */
    if(bind(sockfd,&addr,sizeof(addr))<0){
      perror(“connect”);
      exit(1);
    }

    /* 等待連線 */
    if(listen(sockfd,3)<0){
      perror(“listen”);
    exit(1);
    }

    /*  */
    for(fd=0;fd}


client端
/* 利用socket的TCP client
此程序會連線TCP server,並將鍵盤輸入的字符串傳送給server。
*/

#include
#include
#include
#include
#include
#include
#include
#define PORT 1234
#define SERVER_IP “127.0.0.1”
main()
{
    int s;
    struct sockaddr_in addr;
    char buffer[256]; /* 建立socket */ if((s = socket(AF_INET,SOCK_STREAM,0))<0){ 
      perror(“socket”);
      exit(1);
    }

    /* 填寫server端的sockaddr_in結構 */
    bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port=htons(PORT);
    addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    
    /* 建立連線*/
    if(connect(s,&addr,sizeof(addr))<0){
      perror(“connect”);
      exit(1);
    }

    /* 接收由server端傳來的信息*/
    recv(s,buffer,sizeof(buffer),0);
    printf(“%s\n”,buffer);
    
    
    /* 將標準輸入的資料傳到server端 */
    while(1){
      bzero(buffer,sizeof(buffer));

      /* 從標準輸入設備取得字符串*/
      read(STDIN_FILENO,buffer,sizeof(buffer));

      /* 將字符串傳給server端*/
      if(send(s,buffer,sizeof(buffer),0)<0){
        perror(“send”);
        exit(1);
      }
    } }

 

7-9 指令引數列

指令列引數的使用方法,此功能可讓程式在執行時,接受使用者輸入一些字串,作為主程式的參數。

int main(int argc, char *argv[])

{

    ...
}

其中第一個引數argc會儲存使用者輸入的字串個數,第二個引數argv[]為字串指標陣列,指向使用者輸入字串的內容。例如,在執行client端時輸入下列字串:

./client "A002 音響 15000.00"

引數argc的值為2,引數argv[0]的值為client,引數argv[1]的值為A002 音響 15000.00。

 

7-10 設計UDP網路程式

UDP網路程式一樣分成client端程式設計和server端程式設計。

設計UDP client端

使用UDP通訊協定設計client端程式,步驟如下:

1. 建立socket(使用socket()函數)

2.以sendto()函數傳送資料給server端。

3.以recvfrom()函數接收server端傳來的資料。

4.關閉socket(使用close()函數)

 

設計UDP client端

1. 建立socket(使用socket()函數)

2.連結socket(使用bind()函數)

3.以recvfrom()函數接收client端傳來的資料。

4.以sendto()函數傳送資料給client端。

4.關閉socket(使用close()函數)

 

UDP函數設計流程


 

UDP與TCP網路程式設計的比較

由於UDP為非連結,不可信賴的協定,在傳送端和接收端不須建立連線。當UDP server建立socket,連結socket後,client與server便可以透過sendto()函數與recvfrom()函數進行通訊。

 

特殊的UDP port number

20 UDP FTP - data port Official
21 UDP FTP - control (command) port Official
22 UDP SSH (Secure Shell) - used for secure logins, file transfers (scp, sftp) and port forwarding Official
23 UDP Telnet protocol - unencrypted text communications Official
25 UDP SMTP - used for e-mail routing between mailservers E-mails Official
53 UDP DNS (Domain Name System) Official
67 UDP BOOTP (BootStrap Protocol) server; also used by DHCP (Dynamic Host Configuration Protocol) Official
137 UDP NetBIOS NetBIOS Name Service Official
138 UDP NetBIOS NetBIOS Datagram Service Official
139 UDP NetBIOS NetBIOS Session Service Official

 

7-11 UDP輸出入函數

sendto()

用來將資料從指定的socket傳給遠端主機,並且指定接收端網路位址。

格式:

#include

#include

int sendto (int s, const void *buf, int len, unsigned flags, const struct sockaddr *to, int tolen);

引數說明如下:

引數 說明
s 傳送資料的socket ID
buf 暫存器指標,用來存放欲傳送的訊息
len sizeof(buf)
flags 一般設為0
to 接收端網路位址
tolen sizeof(to)

傳回值

成功:傳回傳送的字元數

失敗:傳回-1

 

recvfrom()

接收遠端主機從socket傳來的資料,並且同時接收傳送端的網路位址

格式:

#include

#include

int recvfrom (int s, void *buf, int len, unsigned flags, struct sockaddr *from, int fromlen);

引數說明如下:

引數 說明
s 傳送資料的socket ID
buf 暫存器指標,用來存放欲傳送的訊息
len sizeof(buf)
flags 一般設為0
from 接收端網路位址
fromlen sizeof(from)

傳回值

成功:傳回接收的字元數

失敗:傳回-1

 

7-12 UDP程式設計

server端

 
/* udp_server.c */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int port = 8000;

int main()
{
    int sockfd;
    int len;
    int z;
    char buf[256];
    struct sockaddr_in adr_inet;
    struct sockaddr_in adr_clnt;

    printf("等待 Client 端傳送資料...\n");

    bzero(&adr_inet, sizeof(adr_inet));
    adr_inet.sin_family = AF_INET;
    adr_inet.sin_addr.s_addr = inet_addr("192.168.1.20");
    adr_inet.sin_port = htons(port);

    len = sizeof(adr_clnt);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd == -1) {
      perror("socket error");
      exit(1);
    }

    z = bind(sockfd,(struct sockaddr *)&adr_inet, sizeof(adr_inet));

    if (z == -1) { 
       perror("bind error");
      exit(1);
    }

    while(1) {
      z = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&adr_clnt, &len);
      if (z < 0) {
        perror("recvfrom error");
        exit(1);
      }
      buf[z] = 0;
      printf("接收資料: %s", buf);
      if (strncmp(buf, "stop", 4) == 0) {
        printf("結束連結...\n");
        break;
      }
    }
    close(sockfd);
    exit(0); } 

 

client端

 
/* udp_client.c */

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int port = 8000;

int main()
{
    int sockfd;
    int i=0;
    int z;
    char buf[80],str1[80];
    struct sockaddr_in adr_srvr;
    FILE *fp;

    printf("開啟檔案...\n");

    fp=fopen("product.txt","r");
    if (fp == NULL) {
      perror("open file");
      exit(1);
    }

    printf("連結 server...\n");
    bzero(&adr_srvr, sizeof(adr_srvr));
    adr_srvr.sin_family = AF_INET;
    adr_srvr.sin_addr.s_addr = inet_addr("192.168.1.20");
    adr_srvr.sin_port = htons(port);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd == -1) {
      perror("socket error");
      exit(1);
    }

    printf("傳送檔案內容...\n");

    for(i=0; i<3; i++) {
      fgets(str1,80,fp);
      printf("第 %d 筆: %s\n",i,str1);
      sprintf(buf, "第 %d 筆: %s\n", i,str1);
      z = sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&adr_srvr, sizeof(adr_srvr));
      if (z < 0) {
        perror("sendto error");
        exit(1);
      }
    }

    printf("傳送結束字串...\n");

    sprintf(buf, "stop\n");
    z = sendto(sockfd,buf, sizeof(buf), 0, (struct sockaddr *)&adr_srvr, sizeof(adr_srvr));
    if (z < 0) {
      perror("sendto error");
      exit(1);
    }

    fclose(fp);
    close(sockfd);
    printf("訊息傳送結束.\n");
    exit(0);
}

 

 

阅读(1523) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~