Linux TCP Socket程序分析

/************************

c語言編寫的tcp socket通信的server端。

可以持續監聽myprot指定的端口

打印端口接收到的字符流

頭文件因爲尖括號被轉義,所以用了引號

************************/

#include "stdio.h"

#include "stdlib.h"
#include "errno.h"
#include "string.h"

#include "sys/types.h"

#include "netinet/in.h"
#include "sys/socket.h"
#include "sys/wait.h"

int main(int argc,char **argv)
{
    int sockfd, new_fd;
    struct sockaddr_in my_addr;
    struct sockaddr_in their_addr;
    unsigned int sin_size,myport,listnum;
    myport = 9785;  //綁定的端口號
    listnum = 10;
    /*************************************************
     Socket接口:是TCP/IP網絡的API,Socket接口定義了許多的函數,可以
                 在此基礎上開發Internet上的TCP/IP網絡編程
    
     Create Socket: int socket(int domain, int type, int protoco);
    
     Argument Description:domain 指明所有協議族,通常是PF_INET(TCP/IPV4)
                          當然他也可以支持IPV6,和更多的網絡協議,根據
                          具體的應用來選擇
                          type 分SOCK_STREAM(TCP),SOCK_DGRAM(UDP),SOCK_RAW
                          (允許程序使用底層協議)
                          protolol 通常賦值“0”
   
    Return Value: Socket描述符是一個指向內部數據結構的指針,它指向描述符表
                    入口。調用Socket函數時,socket執行體將建立一個Socket,
                    實際上"建立一個Socket"意味着爲一個Socket數據結構分配存
                    儲空間。Socket執行體爲你管理描述符表。
    **************************************************/
    if((sockfd = socket(PF_INET,SOCK_STREAM,0)) == -1 )
    {
        perror("socket is error/n;");
        exit(1);
    }
    my_addr.sin_family = PF_INET;           //指定協議族


    /***************************************************
      計算機數據存儲有兩種字節優先順序:高位字節優先和低
      位字節優先。Internet上數據以高位字節優先順序在網絡
      上傳輸,所以對於在內部是以低位字節優先方式存儲數據
      的機器,在Internet上傳輸數據時就需要進行轉換,否則
      就會出現數據不一致。
      htonl():把32位值從主機字節序轉換成網絡字節序
      htons():把16位值從主機字節序轉換成網絡字節序
      ntohl():把32位值從網絡字節序轉換成主機字節序
      ntohs():把16位值從網絡字節序轉換成主機字節序
    ****************************************************/
    my_addr.sin_port = htons(myport);      //如果填入0,系統將隨機選擇一個端口
   
    my_addr.sin_addr.s_addr = INADDR_ANY;  //填入本機IP地址
    bzero(&(my_addr.sin_zero),0);

    /**************************************************
      int   bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
      Description:創建socket,並綁定該端口,申明已經使用,別人不會再佔用該端口

      Argument Description: sockfd:是Socket系統調用返回的socket 描述符
                            my_addr:需要綁定在套接字上的地址,
                                     是類似於以下結構體的變量
                            struct sockaddr {
                                                sa_family_t sa_family;
                                                char        sa_data[14];
                                            }
     Return Value: 成功執行時,返回0。失敗返回-1,errno被設置出錯信息
    ***************************************************/
     if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind is error/n");
        exit(1);
    }

    /**************************************************
     int listen(int sockfd, int ListenSum)
     Description: Listen()函數使socket處於被動的監聽模式,
                  併爲該socket建立一個輸入數據隊列,將
                  到達的服務請求保存在此隊列中,直到程
                  序處理它們。
     Argument Description:sockfd: 是Socket系統調用返回的socket 描述符
                          ListenSum: 指定在請求隊列中允許的最大請求數
     Return Value: 成功執行時,返回0。失敗返回-1,errno被設置出錯信息
    ***************************************************/
    if(listen(sockfd,listnum) == -1)
    {
        perror("listen is error/n");
        exit(1);
    }
        printf("start to listen/n");
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);

        /***********************************************************
         int accept(int sockfd, void *addr, int *addrlen)
         Description:accept()函數讓服務器接收客戶的連接請求。
                     在建立好輸入隊列後,服務器就調用accept
                     函數,然後睡眠並等待客戶的連接請求。
       Argument Description:sockfd: 是Socket系統調用返回的socket 描述符
                            addr  : 通常是一個指向sockaddr_in變量的指針,
                                    該變量用來存放提出連接請求服務的主
                                    機的信息(某臺主機從某個端口發出該請求)
                            addrten: 通常爲一個指向值爲
                                     sizeof(struct sockaddr_in)的整型指針變量
        Return Value: 失敗返回-1,errno被設置出錯信息
                      成功返回  當accept函數監視的socket收到連接請求時,
                                 socket執行體將建立一個新的 socket,執
                                 行體將這個新socket和請求連接進程的地址
                                 聯繫起來,收到服務請求的初始socket仍可
                                 以繼續在以前的 socket上監聽,同時可以在
                                 新的socket描述符上進行數據傳輸操作
        ***************************************************************/
        if((new_fd = accept(sockfd,(struct sockaddr *)&their_addr,&sin_size)) == -1)
        {
            perror("accept is error/n");
            continue;
       
        }
        printf("server:got connection from %s/n",inet_ntoa(their_addr.sin_addr));


                char *p;
                char sock_buf[1024];
                bzero(sock_buf, 1024);
                p = sock_buf;
                int rval=0;

    /**************************************************************************
int recv(SOCKET s, char FAR *buf, int len, int flags );
不論是客戶還是服務器應用程序都用recv函數從TCP連接的另一端接收數據。

該函數的第一個參數指定接收端套接字描述符;

第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據;

第三個參數指明buf的長度;

第四個參數一般置0。

這裏只描述同步Socket的recv函數的執行流程。當應用程序調用recv函數時,recv先等待s的發送緩衝中的數據被協議傳送完畢,如果協 議在傳送s的發送緩衝中的數據時出現網絡錯誤,那麼recv函數返回SOCKET_ERROR,如果s的發送緩衝中沒有數據或者數據被協議成功發送完畢 後,recv先檢查套接字s的接收緩衝區,如果s接收緩衝區中沒有數據或者協議正在接收數據,那麼recv就一直等待,只到協議把數據接收完畢。當協議把 數據接收完畢,recv函數就把s的接收緩衝中的數據copy到buf中(注意協議接收到的數據可能大於buf的長度,所以在這種情況下要調用幾次 recv函數才能把s的接收緩衝中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的),recv函數返回其實際copy 的字節數。如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回0。
    ***************************************************************************/

                if ((rval = recv(new_fd, p, 1024, 0)) < 0)
                {
                        printf("recv errror/n");
                }
                else
                {
                        printf("recv %s/n",p);
                }
    /**************************************************************************
      fork一個子進程對此次連接同父進程並行運行    ***************************************************************************/
    if(!fork())
    {
       
        /***********************************************************************
         int send(int sockfd, const void *msg, int len, int flags)
         Description:通過子進程發送信息給客戶端
         Argument Description: sockfd: 是你想用來傳輸數據的socket描述符
                               msg   : 是一個指向要發送數據的指針
                               Len   : 是以字節爲單位的數據的長度
                               flags : 一般情況下置爲0(這個參數涉及到阻塞和非阻塞問題)
        ************************************************************************/
        if(send(new_fd,"hello,HuHan/n",14,0) == -1)
        {
            perror("send is error/n");
            close(new_fd);
            exit(0);
        }
    }

    /******************************************************************************
     當所有的數據操作結束以後,你可以調用close()函數來釋放該socket,從而停止在
     該socket上的任何數據操作

     如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。

   int shutdown(int sockfd,int how);

  Sockfd是需要關閉的socket的描述符。參數 how允許爲shutdown操作選擇以下幾種方式:
    ·0-------不允許繼續接收數據
    ·1-------不允許繼續發送數據
    ·2-------不允許繼續發送和接收數據
    shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno。
    *******************************************************************************/
    close(new_fd);
   
    /****************************************************************************
     pid_t waitpid(pid_t pid, int *stat_loc, int option)
     pid參數指定需要等待的子進程的PID,如果是-1,waitpid將返回任一子進程的信息,與
     wati()一樣,如果stat_loc不是空指針,waitpid將把狀態信息寫到所指向的位置。option
     參數允許我們改變waitpid的行爲,其中最有用的一個選項是WNOHANG,他的作用是防止
     waitpid()調用將調用者執行掛起,可以用這個選項來查找是否有子進程已經結束,如果沒有
     將繼續執行。
    *****************************************************************************/
    waitpid(-1,NULL,WNOHANG);
    }
}

 

客戶端程序:

#i nclude <stdlib.h>

#i nclude <stdio.h>

#i nclude <errno.h>

#i nclude <string.h>

#i nclude <netdb.h>

#i nclude <sys/types.h>

#i nclude <netinet/in.h>

#i nclude <sys/socket.h>

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

{

int sockfd;

char buffer[1024];

struct sockaddr_in server_addr;

struct hostent *host;

int portnumber,nbytes;

if(argc!=3)

{

printf("Usage:%s hostname portnumber/a/n",argv[0]);

exit(1);

}

/*gethostbyname 可以通過主機名稱得到主機的 IP 地址 */

if((host=gethostbyname(argv[1]))==NULL)

{

printf("Gethostname error/n");

exit(1);

}

/*portnumber 爲端口號 */

if((portnumber=atoi(argv[2]))<0)

{

printf("Usage:%s hostname portnumber/a/n",argv[0]);

exit(1);

}

/* 客戶程序開始建立 sockfd 描述符 */

if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)

{

printf("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)

{

printf("Connect Error:%s/a/n",strerror(errno));

exit(1);

}

/* 連接成功了 */

if((nbytes=read(sockfd,buffer,1024))==-1)

{

printf("Read Error:%s/n",strerror(errno));

exit(1);

}

buffer[nbytes]='/0';

printf("I have received:%s/n",buffer);

/* 結束通訊 */

close(sockfd);

exit(0);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章