linux下實現UDP通信


一:實驗簡介

(一)功能實現

除了實現簡單UDP通信外,還實現了:UDP客戶端使用指定端口與服務器通信

(二)知識回顧

一個IP+端口可以唯一確定主機的一個socket對象,通過該socket實例,我們可以進行數據發送和接收

(三)實驗對比(普通網絡通信)---這里用TCP(主要是了解bind函數)

面向連接的網絡應用程序分為客戶端和服務器端。服務器端的執行流程一般為4步,客戶端程序相對簡單,一般需要兩個步驟。

服務器端執行流程4步如下:

1)調用socket函數,建立一個套接字,該套接字用於接下來的網絡通信。

(2)調用bind函數,將該套接字綁定到一個地址,並制定一個端口號,

(3)調用listen函數,使用該套接字監聽連接請求

(4)當請求來到時,調用accept函數復制該套接字處理請求

客戶端執行流程2步如下:

1)調用socket函數,創建一個套接字

(2)調用connect函數使用該套接字與服務器進行連接

比較:

服務器端和客戶端程序的顯著區別在於客戶端程序不需要調用bind函數,bind函數的作用是將套接字綁定一個IP地址和端口號,因為這兩個元素可以在網絡環境中唯一地址表示一個進程。
如果套接字沒有使用bind函數綁定地址和端口,那么調用listen函數和connect函數的時候內核會自動為套接字綁定。
由此可知,如果沒有使用bind函數,調用listen函數和connect函數的時候內核會自動為套接字綁定。
看起來好像bind函數是多余的,但事實並不是這樣。
我們先來看看listen函數和connect是怎么綁定套接字的,connect函數綁定套接字的時候使用的是一個設置好的地址結構(sockaddr_in)作為參數,結構中指定了服務器的地址和需要通信的端口號。
但是listen函數沒有這個參數,多以listen函數不能夠使用設置好的地址結構,只能由系統設置IP地址和端口號。也就是說在服務器端,如果不使用bind函數的話,創建套接字時使用的是當前系統中空閑端口的套接字。
這樣的話,服務器端的程序不關心客戶端的IP地址,也就說是對應的端口號是內核臨時指派的一個端口,是隨機的,每次執行服務器程序的時候,使用的都是不同的端口。
但是在客戶端是需要指定通信的服務器的端口的,如果不使用bind函數,每次的端口是隨機的話,那么每次重啟服務程序之后都要對客戶端的程序進行調整,這樣做不僅不合理,而且工作量很大,因此在服務器端bind函數作用非常重要。

(四)實驗思路

在客戶端,使用bind函數,為客戶端socket綁定一個固定端口即可

二:實驗開始

(一)普通UDP實現

服務器端:

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

#define MAX_LEN 1000

int str_to_number(const char* str);

int main(int argc, char** argv)
{
    char message[MAX_LEN];
    int sk;
    struct sockaddr_in src_addr;    //用於指定本地監聽信息
    struct sockaddr_in cli_addr;    //獲取客戶端地址信息
    int src_addr_len,cli_addr_len;
    int count,ret;
    struct in_addr addr;

    if (argc != 2)    //獲取監聽端口
    {
        printf("Error: you must enter port to monite\n");
        exit(0);
    }

    bzero(&src_addr, sizeof(src_addr));
    src_addr.sin_family = AF_INET;
    src_addr.sin_addr.s_addr = htonl(INADDR_ANY);    //作為服務器,可能有多塊網卡,設置INADDR_ANY,表示綁定一個默認網卡進行監聽
    src_addr.sin_port = htons(str_to_number(argv[1]));

    printf("port:%d\n",str_to_number(argv[1]));

    src_addr_len = sizeof(src_addr);
    cli_addr_len = sizeof(cli_addr);

    sk = socket(AF_INET, SOCK_DGRAM, 0);
    if(sk<0)
    {
        printf("socket create failure\n");
        return -1;
    }

    ret = bind(sk, (struct sockaddr*)&src_addr, src_addr_len);
    if(ret < 0)
    {
        printf("socket bind failure\n");
        return -1;
    }


    while (1)
    {
        printf("Waiting for data from sender \n");
        count = recvfrom(sk, message, MAX_LEN, 0, (struct sockaddr*)&cli_addr, &cli_addr_len);
        if(count==-1)
        {
            printf("receive data failure\n");
            return -1;
        }
        addr.s_addr = cli_addr.sin_addr.s_addr;

        printf("Receive info: %s from %s %d\n", message,inet_ntoa(addr),cli_addr.sin_port);

        sendto(sk, message, sizeof(message), 0, (struct sockaddr*)&cli_addr, cli_addr_len);
    }

    close(sk);

    return 0;
}

int str_to_number(const char* str)
{
    int i,len, num = 0;
    len= strlen(str);

    for (i = 0; i < len;i++)
        num = num * 10 + str[i] - '0';

    return num;
}

客戶端:

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

#define MAX_LEN 1000

int str_to_number(const char* str);

int main(int argc, char** argv)
{
    int sk;
    char buf[MAX_LEN];
    struct sockaddr_in ser_addr;                                //是用於指定對方(目的主機)信息
    struct sockaddr_in loc_addr;                                //可以用來指定一些本地的信息,比如指定端口進行通信,而不是讓系統隨機分配
    int ser_addr_len,loc_addr_len;
    int ret,count;
    struct in_addr addr;

    if (argc != 3)
    {
        printf("Error: the number of args must be 3\n");
        exit(0);
    }

    //配置服務器信息
    bzero(&ser_addr, sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;                                //設置為IPV4通信
    ser_addr.sin_addr.s_addr = inet_addr(argv[1]);                //設置目的ip
    ser_addr.sin_port = htons(str_to_number(argv[2]));            //設置目的端口去鏈接服務器
    ser_addr_len = sizeof(ser_addr);

    sk = socket(AF_INET, SOCK_DGRAM, 0);                        //設置UDP報文傳輸    0表示默認    SOCK_DGRAM 默認使用UDP
    //其中第三位 0 是調用方式標志位,設置socket通方式,比如非阻塞
    if(sk<0)
    {
        printf("socket create failure\n");
        return -1;
    }


    for (;;)
    {
        printf("Input info:>>>");
        scanf("%s", buf);
        if (!strcmp(buf, "quit"))
            break;
        sendto(sk, buf, sizeof(buf), 0, (struct sockaddr*)&ser_addr, ser_addr_len);

        count = recvfrom(sk,buf,sizeof(buf),0,(struct sockaddr*)&loc_addr,&loc_addr_len);
        if (count==-1)
        {
            printf("receive data failure\n");
            return -1;
        }
        addr.s_addr = loc_addr.sin_addr.s_addr;
        printf("Receive info: %s from %s %d\n", buf,inet_ntoa(addr),loc_addr.sin_port);
    }

    printf("communicate end\n");
    close(sk);
    return 0;
}

int str_to_number(const char* str)
{
    int i,len, num = 0;
    len= strlen(str);

    for (i = 0; i < len;i++)
        num = num * 10 + str[i] - '0';

    return num;
}

實驗通信:

可以看到:
由於服務器使用bind綁定一個固定端口8080,所以通信的端口始終是8080(發送和接收都是8080)
而客戶端由於並沒有使用bind函數,所以如一中所說每次的端口是隨機的,故這里是使用了一個隨機端口34071端口進行通訊

(二)修改客戶端,實現指定功能

服務器端同上

客戶端如下:

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

#define MAX_LEN 1000
#define LOC_PORT 9999

int str_to_number(const char* str);

int main(int argc, char** argv)
{
    int sk;
    char buf[MAX_LEN];
    struct sockaddr_in ser_addr;                                //是用於指定對方(目的主機)信息
    struct sockaddr_in loc_addr;                                //可以用來指定一些本地的信息,比如指定端口進行通信,而不是讓系統隨機分配
    int ser_addr_len,loc_addr_len;
    int ret,count;
    struct in_addr addr;

    if (argc != 3)
    {
        printf("Error: the number of args must be 3\n");
        exit(0);
    }

    //配置服務器信息
    bzero(&ser_addr, sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;                                //設置為IPV4通信
    //ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    ser_addr.sin_addr.s_addr = inet_addr(argv[1]);                //設置目的ip
    ser_addr.sin_port = htons(str_to_number(argv[2]));            //設置目的端口去鏈接服務器
    ser_addr_len = sizeof(ser_addr);

    //配置本地信息 bzero(&loc_addr, sizeof(loc_addr)); loc_addr.sin_family = AF_INET; //設置為IPV4通信 //loc_addr.sin_addr.s_addr = htonl(INADDR_ANY); loc_addr.sin_addr.s_addr = htonl(INADDR_ANY); //設置目的ip loc_addr.sin_port = htons(LOC_PORT); //設置本地端口去鏈接服務器 loc_addr_len = sizeof(loc_addr);

    sk = socket(AF_INET, SOCK_DGRAM, 0);                        //設置UDP報文傳輸    0表示默認    SOCK_DGRAM 默認使用UDP
    //其中第三位 0 是調用方式標志位,設置socket通方式,比如非阻塞
    if(sk<0)
    {
        printf("socket create failure\n");
        return -1;
    }

    //將本地配置使用bind綁定 ret = bind(sk,(struct sockaddr*)&loc_addr,loc_addr_len); if(ret < 0)
    {
        printf("socket bind failure\n");
        return -1;
    }

    for (;;)
    {
        printf("Input info:>>>");
        scanf("%s", buf);
        if (!strcmp(buf, "quit"))
            break;
        sendto(sk, buf, sizeof(buf), 0, (struct sockaddr*)&ser_addr, ser_addr_len);

        count = recvfrom(sk,buf,sizeof(buf),0,(struct sockaddr*)&loc_addr,&loc_addr_len);
        if (count==-1)
        {
            printf("receive data failure\n");
            return -1;
        }
        addr.s_addr = loc_addr.sin_addr.s_addr;
        printf("Receive info: %s from %s %d\n", buf,inet_ntoa(addr),loc_addr.sin_port);
    }

    printf("communicate end\n");
    close(sk);
    return 0;
}

int str_to_number(const char* str)
{
    int i,len, num = 0;
    len= strlen(str);

    for (i = 0; i < len;i++)
        num = num * 10 + str[i] - '0';

    return num;
}

可以看到:客戶端端口是我們所指定的9999

(三)實驗需改進

每次發送數據大小應該用strlen進行計算,而不是sizeof。這里將數組全部發送了,沒用,還占帶寬

補充:以后可以考慮--網絡-一個進程是否能擁有多個端口


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM