溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

socket網絡編程之TCP、UDP

發布時間:2020-07-15 04:50:04 來源:網絡 閱讀:1990 作者:SherryX 欄目:系統運維

之前說的用于進程間通信的幾種方式:消息signal、管道pipe、消息隊列msg、共享內存shm、信號量sem。都只適用于一臺主機上的進程間通信,那么如何實現兩臺計算機之間的進程通信呢?所以,來了解一下異地進程通信。

1 異地進程通信

  • 協議層為雙方的主機通信進程分配“端口”和緩沖區,以便異地進程間的通信。

    1.1TCP/IP協議

    以下是OSI參考模型與TCP/IP參考模型的對應關系:
    socket網絡編程之TCP、UDP
    socket網絡編程之TCP、UDP

    1.1.1 TCP/IP協議族

    TCP/IP 協議組大體上分為三部分:
    1.Internet 協議(IP)
    2.傳輸控制協議(TCP)和用戶數據報文協議(UDP)
    3.處于TCP 和UDP 之上的一組協議專門開發的應用程序。它們包括:TELNET,文件傳送協議(FTP),域名服務(DNS)和簡單的郵件傳送程序(SMTP)等許多協議。
    應用層協議

  • Telnet
  • 文件傳送協議(FTP和TFTP)
  • 簡單的文件傳送協議(SMTP)
  • 域名服務(DNS)等協議

    2 網絡編程基礎

  • socket標準被擴展成window socket和unix socket
  • linux中的網絡編程通過socket接口實現。
  • Socket既是一種特殊的IO,它也是一種文件描述符。
  • 一個完整的Socket 都有一個相關描述{協議,本地地址,本地端口,遠程地址,遠程端口};每一個Socket 有一個本地的唯一Socket 號,由操作系統分配。

    2.1 SOCKET分類

    流式套接字(SOCK_STREAM)
    流式的套接字可以提供可靠的、面向連接的通訊流。它使用了TCP協議。TCP 保證了數據傳輸的正確性和順序性。
    數據報套接字(SOCK_DGRAM)
    數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠,無差錯。使用數據報協議UDP協議。
    原始套接字。
    原始套接字允許對低層協議如IP或ICMP直接訪問,主要用于新的網絡協議實現的測試等。

    2.2 編程流程

    TCP
    socket網絡編程之TCP、UDP
    UDP
    socket網絡編程之TCP、UDP
    具體函數的用法,就自己man了。

    2.2.1 套接字地址結構

    重點講一下套接字地址結構:

    #include < netinet/in.h>
    struct sockaddr
    {
    unsigned short sa_family; /* address族, AF_xxx */
    char sa_data[14];     /* 14 bytes的協議地址 */
    };
  • sa_family的取值,一般來說,IPV4使用“AF_INET”
  • sa_data包含了一些遠程電腦的地址、端口和套接字的數目,里面的數據是雜溶在一起的。一般我們不用這個結構體,因為我們一般使用的地址都是IP+端口號。比如:IP192.168.159.2 port3306 。這樣來記錄地址。所以一般使用下面這個地址結構,而知數據類型是等效的,可以互相轉換。
    #include < netinet/in.h>
    struct sockaddr_in {
    short int sin_family; /* Internet地址族 */
    unsigned short int sin_port; /* 端口號 */
    struct in_addr sin_addr; /* Internet地址 */
    unsigned char sin_zero[8]; /* 添0(和struct sockaddr一樣大?。?/
    };

    2.2.2 字節序列轉換

  • 因為每一個機器內部對變量的字節存儲順序不同(有的系統是高位在前,底位在后,而有的系統是底位在前,高位在后 ),而網絡傳輸的數據大家是一定要統一順序的。所以對與內部字節表示順序和網絡字節順序不同的機器,就一定要對數據進行轉換。
  • htons()——“Host to Network Short”
    主機字節順序轉換為網絡字節順序(對無符號短型進行操作2bytes)
  • htonl()——“Host to Network Long” 
    主機字節順序轉換為網絡字節順序(對無符號長型進行操作4bytes)
  • ntohs()——“Network to Host Short”
    網絡字節順序轉換為主機字節順序(對無符號短型進行操作2bytes)
  • ntohl()——“Network to Host Long ”
    網絡字節順序轉換為主機字節順序(對無符號長型進行操作4bytes)

    2.2.3地址格式轉換

    -linux提供將點分格式的地址轉于長整型數之間的轉換函數。

  • inet_addr()能夠把一個用數字和點表示IP 地址的字符串轉換成一個無符號長整型。
  • inet_ntoa()能夠把網絡字節順序轉換為地址結構的數據。

    2.2.4基本套接字調用

    socket() bind() connect()
    listen() accept() send()
    recv() sendto() shutdown()
    recvfrom() close() getsockopt()
    setsockopt() getpeername()
    getsockname() gethostbyname()
    gethostbyaddr() getprotobyname()
    fcntl()

    練習1-TCP

    TCP連接,等待客戶端輸入,將內容發送給服務器,并獲取客戶端地址。
    這里,getsocketname()表示獲得本地(自己)的地址;
    getpeername()表示獲得連接上的客戶端的地址(源IP地址)。
    <br>
    server.c

    #include < sys/types.h>     
    #include < sys/socket.h>
    #include < netinet/in.h>    //sockaddr_in
    #include < stdio.h>
    #include < string.h>
    
    int main()
    {
        int fd;
        int clientfd;
        int ret;
        pid_t pid;
        int addrLen = 0;
        char acbuf[20] = "";
        struct sockaddr_in addr = {0};  //自己的地址
        struct sockaddr_in clientAddr = {0};    //連上的客戶端的地址
    
        //1.socket()
        fd = socket(PF_INET,SOCK_STREAM,0);
        if(fd == -1)
        {
            perror("socket");
            return -1;
        }
    
        //2.bind()
        addr.sin_family = AF_INET;
        addr.sin_port = htons(1234);
        addr.sin_addr.s_addr = inet_addr("192.168.159.6");
        ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
        if(ret == -1)
        {
            perror("bind");
            return -1;
        }
    
        //3.listen()
        ret = listen(fd,10);
        if(ret == -1)
        {
            perror("listen");
            return -1;
        }
    
        //4.阻塞 等待 accept()
        clientfd = accept(fd,NULL,NULL);
        if(clientfd == -1)
        {
            perror("accept");
            return -1;
        }
    
    //獲取客戶端地址
    addrLen = sizeof(struct sockaddr_in);
    ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);
    if(ret == -1)
    {
        perror("getpeername");
        return -1;
    }
    printf("client login.\nip: %s , port: %d\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
    
    //5.通信
    while(1)
    {
        memset(acbuf,0,20);
        if (read(clientfd,acbuf,20) > 0)
        {
            printf("receive: %s\n",acbuf);
        }
    
    }
    
    //6.close()
    close(fd);
    return 0;
    }

client.c

#include <sys/types.h>     
#include <sys/socket.h>
#include <netinet/in.h> //sockaddr_in
#include <stdio.h>
#include <string.h>

int main()
{
    int fd;
    int ret;
    char acbuf[20] = "";
    struct sockaddr_in serAddr = {0};

    //1.socket();
    fd = socket(PF_INET,SOCK_STREAM,0);
    if(fd == -1)
    {
        perror("socket");
        return -1;
    }

    //2.連接connect() 服務器的地址
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(1234);
    serAddr.sin_addr.s_addr = inet_addr("192.168.159.6");
    ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
    if(ret == -1)
    {
        perror("connect");
        return -1;
    }

    //3.通信
    while(1)
    {
        printf("send: ");
        fflush(stdout);
        scanf("%s",acbuf);
        if(strcmp(acbuf,"exit") == 0)
        {
            break;
        }
        write(fd,acbuf,strlen(acbuf));
    }

    //4.close()
    close(fd);
    return 0;
}

運行結果:
socket網絡編程之TCP、UDP
做個改進,以上代碼,只能一個客戶端連接上。因為TCP是基于點對點的,一個accept()對應一個connnect()。要想連接多個客戶端,就得使用fork(),一個進程用來專門阻塞等待客戶端的連接,一個用來處理與已連接上客戶端的通信。
代碼如下:

server.c

int main()
    {
        int fd;
        int clientfd;
        int ret;
        pid_t pid;
        int addrLen = 0;
        char acbuf[20] = "";
        char client_addr[100] = "";
        struct sockaddr_in addr = {0};  //自己的地址
        struct sockaddr_in clientAddr = {0};    //連上的客戶端的地址

        signal(SIGCHILD,SIG_IGN);

        //1.socket()
        fd = socket(PF_INET,SOCK_STREAM,0);
        if(fd == -1)
        {
            perror("socket");
            return -1;
        }

        //會出現沒有活動的套接字仍然存在,會禁止綁定端口,出現錯誤:address already in use .
        //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態會保留2-4分鐘
        //if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
            //{
        //      perror("setsockopet error\n");
        //      return -1;
            //}

        //2.bind()
        addr.sin_family = AF_INET;
        addr.sin_port = htons(1234);
        addr.sin_addr.s_addr = inet_addr("192.168.159.6");
        ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
        if(ret == -1)
        {
            perror("bind");
            return -1;
        }

        //3.listen()
        ret = listen(fd,10);
        if(ret == -1)
        {
            perror("listen");
            return -1;
        }

        while(1)        
        {
            //4.阻塞等待 accept()
            clientfd = accept(fd,NULL,NULL);
            if(clientfd == -1)
            {
                perror("accept");
                return -1;
            }

            pid = fork();   //父進程負責繼續監聽等待,子進程父子與已連接客戶端通信

            if(pid == -1)
            {
                perror("fork");
                return -1;
            }
            if(pid == 0)    //子進程
            {
                //獲取客戶端地址
                addrLen = sizeof(struct sockaddr_in);
                ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);
                if(ret == -1)
                {
                    perror("getpeername");
                    return -1;
                }
                sprintf(client_addr,"ip: %s , port: %d\n",\
                    inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
                printf("client longin.\n%s\n",client_addr);

                //5.通信
                while(1)
                {
                    memset(acbuf,0,20);
                    if (read(clientfd,acbuf,20) == 0)   //客戶端退出
                    {
                        //結束相應的server進程
                        close(clientfd);
                        exit(0);    //僵尸進程
                    }
                    printf("from %sreceive : %s\n\n",client_addr,acbuf);
                }
            }
            else    //父進程
            {
                //返回while,繼續等待
            }

        }

        //6.close()
        close(fd);
        return 0;
    }

這里一定要注意,每結束一個客戶端,一定要關掉相應的文件描述符,并且結束掉子進程(僵尸進程),不然,隨著客戶端的增加,進程數會越來越大
client.c

int main()
{
    int fd;
    int ret;
    int addrLen;
    char acbuf[20] = "";
    struct sockaddr_in serAddr = {0};
    struct sockaddr_in myAddr = {0};

    //1.socket();
    fd = socket(PF_INET,SOCK_STREAM,0);
    if(fd == -1)
    {
        perror("socket");
        return -1;
    }

    //2.連接connect() 服務器的地址
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(1234);
    serAddr.sin_addr.s_addr = inet_addr("192.168.159.6");
    ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
    if(ret == -1)
    {
        perror("connect");
        return -1;
    }

    //獲取自己的地址
    addrLen = sizeof(struct sockaddr_in);
    ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);
    if(ret == -1)
    {
        perror("getsockname");
        return -1;
    }
    printf("client---ip: %s , port: %d\n",\
                inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
    //3.通信
    while(1)
    {
        printf("send: ");
        fflush(stdout);
        scanf("%s",acbuf);
        if(strcmp(acbuf,"exit") == 0)
        {
            break;
        }
        write(fd,acbuf,strlen(acbuf));
    }

    //4.close()
    close(fd);
    return 0;
}

運行結果:
socket網絡編程之TCP、UDP

練習2-UDP

使用UDP連接,完成上述內容。但是發現,使用UDP,因為是面向無連接的,所以在沒有收到或者發送包之前,是無法得知源IP地址的。
那UDP如何知道客戶端的IP地址和端口呢?
1、由客戶端顯示地高速服務器IP地址和端口,發消息。
2、隱式的。服務器從收到的包頭中得到源IP和端口號。
server.c

int main()
{
    int sockfd;
    int ret;
    char acbuf[20] = "";
    char client_addr[100] = "";
    struct sockaddr_in addr = {0};
    struct sockaddr_in clientAddr = {0};
    int addrLen = sizeof(struct sockaddr_in);
    int reuse = 0;

    //1.socket()
    sockfd = socket(PF_INET,SOCK_DGRAM,0);
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }

    //2.bind()
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1235);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ret = bind(sockfd,(struct sockaddr *)&addr,addrLen);
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }

    //3.通信
    while(1)
    {
        memset(acbuf,0,20);
        if(recvfrom(sockfd, acbuf, 100,0,(struct sockaddr *)&clientAddr,&addrLen) == -1)
        { 
            perror("recvfrom"); 
            return -1;
         } 
         //收到客戶端的數據包之后,就可以知道客戶端地址
        sprintf(client_addr," ip: %s , port: %d\n",\
                inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
         printf("receive from %s: %s\n",client_addr,acbuf);

    }

    //4.close
    close(sockfd);

    return 0;
}

client.c

int main()
{
    int sockfd;
    int ret;
    int addrLen = sizeof(struct sockaddr_in);
    char acbuf[20] = "";
    struct sockaddr_in serAddr = {0};
    struct sockaddr_in myAddr = {0};

    //1.socket()
    sockfd = socket(PF_INET,SOCK_DGRAM,0);
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(1235);
    serAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //2.通信
    while(1)
    {
        printf("send: ");
        fflush(stdout);
        scanf("%s",acbuf);
        if(strcmp(acbuf,"exit") == 0)
        {
            break;
        }
        sendto(sockfd, acbuf, 20,0,(struct sockaddr *)&serAddr,addrLen);

        //獲取自己的地址
        ret = getsockname(sockfd,(struct sockaddr *)&myAddr,&addrLen);
        if(ret == -1)
        {
            perror("getsockname");
            return -1;
        }
        printf("client---ip: %s , port: %d\n\n",\
                    inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));

    }

    //3.close
    close(sockfd);

    return 0;
}

運行結果:
socket網絡編程之TCP、UDP
會發現,此時是可以直接運行多個客戶端的,因為,UDP是面向無連接的,可以是一對多,多對一,多對多的,只要客戶端知道服務器地址,就可以連上。
<br>
Ps:本人理解有限,還未學習完,有錯請指出。

向AI問一下細節
推薦閱讀:
  1. TCP與UDP協議
  2. UDP-TCP

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女