zoukankan      html  css  js  c++  java
  • 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
    ");
            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
    ",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
    ");
            return -1;
        }
    
        ret = bind(sk, (struct sockaddr*)&src_addr, src_addr_len);
        if(ret < 0)
        {
            printf("socket bind failure
    ");
            return -1;
        }
    
    
        while (1)
        {
            printf("Waiting for data from sender 
    ");
            count = recvfrom(sk, message, MAX_LEN, 0, (struct sockaddr*)&cli_addr, &cli_addr_len);
            if(count==-1)
            {
                printf("receive data failure
    ");
                return -1;
            }
            addr.s_addr = cli_addr.sin_addr.s_addr;
    
            printf("Receive info: %s from %s %d
    ", 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
    ");
            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
    ");
            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
    ");
                return -1;
            }
            addr.s_addr = loc_addr.sin_addr.s_addr;
            printf("Receive info: %s from %s %d
    ", buf,inet_ntoa(addr),loc_addr.sin_port);
        }
    
        printf("communicate end
    ");
        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
    ");
            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
    ");
            return -1;
        }
    
        //将本地配置使用bind绑定
        ret = bind(sk,(struct sockaddr*)&loc_addr,loc_addr_len);
        if(ret < 0)
        {
            printf("socket bind failure
    ");
            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
    ");
                return -1;
            }
            addr.s_addr = loc_addr.sin_addr.s_addr;
            printf("Receive info: %s from %s %d
    ", buf,inet_ntoa(addr),loc_addr.sin_port);
        }
    
        printf("communicate end
    ");
        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。这里将数组全部发送了,没用,还占带宽

    补充:以后可以考虑--网络-一个进程是否能拥有多个端口

  • 相关阅读:
    关于数据库索引,必须掌握的知识点
    Java基础知识面试题(最详细版)
    基于WinForm制作的用户名密码存储器
    DataGridView点击列名自动排序
    WebRequest.Create(url)无效的URI:无效端口指定的URL时
    knockout 数据绑定,同一个页面table位置加载两个不同的表格数据
    pipeline管道初体验
    Socket,长连接,消息推送,消息提醒,未读消息提醒,消息通知,未读消息通知
    搭建SVN服务器
    C#解决jsonp跨域问题jsonp跨域配置
  • 原文地址:https://www.cnblogs.com/ssyfj/p/12107455.html
Copyright © 2011-2022 走看看