Linux下网络编程-UDP协议探测在线好友

本文正在参与 “网络协议必知必会”征文活动

1. UDP协议介绍

UDP协议 相对TCP协议来讲属于不可靠协议,UDP协议是广播方式发送数据,没有服务器和客户端的概念。

在Linux下使用socket创建UDP的套接字时,属性要选择数据报类型SOCK_DGRAM

 sockfd=socket(AF_INET,SOCK_DGRAM,0);
复制代码

2. UDP协议发送和接收数据的函数

2.1 recvfrom函数

UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明数据的目的地址。

#include <sys/types.h>  
#include <sys/socket.h>  
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * from, size_t *addrlen);
复制代码

返回值

成功返回接收到数据的长度,负数失败 

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。最后两个参数类似于accept的最后两个参数(接收客户端的IP地址)。

2.2 sendto函数

UDP使用sendto()函数发送数据,他类似于标准的write(),但是在sendto()函数中要指明目的地址。

#include <sys/types.h>  
#include <sys/socket.h>  
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr * to, int addrlen);
复制代码

返回值

成功返回发送数据的长度,失败返回-1

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。

扫描二维码关注公众号,回复: 13471359 查看本文章

参数to指明数据将发往的协议地址,他的大小由addrlen参数来指定。

2.3 设置套接字属性

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
复制代码

setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。

参数介绍:

sockfd:标识一个套接口的描述字。
level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
optname:需设置的选项。
optval:指针,指向存放选项值的缓冲区。
optlen:optval缓冲区的长度。
UDP协议发送数据时,设置具有广播特性: 默认情况下socket不支持广播特性
char bBroadcast=1;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(char));
复制代码

3.案例: UDP协议完成数据收发

3.1 接收数据示例

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
/* According to POSIX.1-2001 */
#include <sys/select.h>

int main(int argc,char **argv)
{
    if(argc!=2)
    {
        printf("参数: ./tcp_server <端口号>\n");
        return 0;
    }

     /*1. 创建socket套接字*/
    int sockfd;
    sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        printf("服务器:套接字创建失败.\n");
        return 0;
    }
    /*2. 绑定端口号*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET; //IPV4
    addr.sin_port=htons(atoi(argv[1])); //65535
    //addr.sin_addr.s_addr=inet_addr("192.168.2.16");
    addr.sin_addr.s_addr=INADDR_ANY; //本地所有IP地址 "0.0.0.0"
    if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr)))
    {
        printf("服务器:端口号绑定失败.\n");
        return 0;
    }
    /*3. 接收数据*/
    char buff[100];
    struct sockaddr_in from;
    size_t addrlen=sizeof(struct sockaddr);
    ssize_t len;
    while(1)
    {
        //带阻塞功能,收到数据才会返回
        len=recvfrom(sockfd,buff,sizeof(buff)-1,0,(struct sockaddr*)&from,&addrlen);
        buff[len]='\0';
        printf("接收到数据:%s,长度=%d,数据来自于:%s:%d\n",buff,len,inet_ntoa(from.sin_addr),ntohs(from.sin_port));
    }
}
复制代码

3.2 发送数据示例

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
/* According to POSIX.1-2001 */
#include <sys/select.h>
#include <string.h>

int main(int argc,char **argv)
{
     if(argc!=4)
    {
        printf("参数: ./tcp_client <IP地址> <端口号> <发送的数据>\n");
        return 0;
    }

    /*1. 创建socket套接字*/
    int sockfd;
    sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        printf("服务器:套接字创建失败.\n");
        return 0;
    }

    const int opt = 1;
    //设置该套接字为广播类型,
    int nb = 0;
    nb = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt));
    if(nb == -1)
    {
        printf("设置广播类型错误.\n");
    }

    /*2. 开始发送数据*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET; //IPV4
    addr.sin_port=htons(atoi(argv[2])); //65535  服务器的端口号
    addr.sin_addr.s_addr=inet_addr(argv[1]); //服务器IP地址

    ssize_t len;
    while(1)
    {
        len=sendto(sockfd,argv[3],strlen(argv[3]),0,(const struct sockaddr*)&addr,sizeof(struct sockaddr));
        printf("成功发送:%d\n",len);
        sleep(1);
    }
}
复制代码

4. 案例: 使用UDP协议探测在线好友

前面几篇文章介绍了Linux下TCP协议设计的群聊天室的一个程序,如果想要知道同一个网络下有多少好友在线,就可以使用UDP协议进行广播探测。 大家的端口号是固定的,也就是只要在这个网络范围内,大家都跑这个同一个聊天室程序,就可以互相探测,得到对方IP地址之后,再完成TCP协议建立,完成点对点聊天通信。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/epoll.h>
#include <poll.h>
​
​
#define SEND_MSG "1314520"   //发送的数据包
#define PORT 8888           //固定的端口号int sockfd;
int main(int argc,char **argv)
{   
    if(argc!=2)
    {
        printf("./app <广播地址>  当前程序固定的端口号是8888\n");
        return 0;
    }

    /*1. 创建socket套接字*/
    sockfd=socket(AF_INET,SOCK_DGRAM,0);

    //设置端口号的复用功能
    int on = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    /*2. 绑定端口号与IP地址*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(PORT); // 端口号0~65535
    addr.sin_addr.s_addr=INADDR_ANY;    //inet_addr("0.0.0.0"); //IP地址
    if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr))!=0)
    {
        printf("UDP服务器:端口号绑定失败.\n");
        return 0;
    }
    /*3. 接收数据*/
    unsigned char buff[1024+1];
    int cnt;
    struct sockaddr_in client_addr;
    socklen_t addrlen=sizeof(struct sockaddr_in);

    struct pollfd fds;
    fds.fd=sockfd;
    fds.events=POLLIN;
    while(1)
    {
        cnt=poll(&fds,1,1000);
        if(cnt>0)
        {
            cnt=recvfrom(sockfd,buff,1024,0,(struct sockaddr *)&client_addr,&addrlen);
            buff[cnt]='\0';
            
            //判断是不是探测包数据
            if(strcmp(buff,SEND_MSG)==0)
            {
                printf("在线好友:%s,%d-->%s:%d\n",buff,cnt,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
                cnt=sendto(sockfd,SEND_MSG,strlen(SEND_MSG),0,(const struct sockaddr *)&client_addr,sizeof(struct sockaddr));
                printf("回应探测包:%d字节.\n",cnt);
​
                //这里可以继续写代码,将存在的好友保存在链表,并记录在线好友数量
            }
        }
        else
        {
            ssize_t cnt;
            struct sockaddr_in addr;
            addr.sin_family=AF_INET;
            addr.sin_port=htons(PORT); // 端口号0~65535
            addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址
​
            cnt=sendto(sockfd,SEND_MSG,strlen(SEND_MSG),0,(const struct sockaddr *)&addr,sizeof(struct sockaddr));
            printf("探测包发送:%d字节.\n",cnt);
        }
    }
    return 0;
}
复制代码

猜你喜欢

转载自juejin.im/post/7036990476536250399