Linux——网络编程——UDP

网络编程之 UDP  用户数据报

1、特性: 无链接  不可靠  大数据   

2、框架: C/S模式 

   server:socket() ===>bind()===>recvfrom()===>close()
   client:socket() ===>bind()===>sendto() ===>close()


注意:socket()的参数需要调整。

      socket(PF_INET,SOCK_DGRAM,0);

      bind() 客户端是可选的,服务器端是比选的。

1、数据有边界,

2、发送数据和接受数据的次数使一致的,

同时长度要匹配,不然会产生数据丢弃的情况。

3、recvfrom会阻塞

4、  没有recvfrim 不会导致sendto阻塞 

一、发送接收函数:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
             const struct sockaddr *dest_addr, socklen_t addrlen);

功能:用于UDP协议中向对方发送数据。
参数:sockfd  本地的套接字id
      buff    本地的数据存储,一般是要发送的数据。
      len     要发送的数据长度
      flags   要发送数据方式,0 表示阻塞发送。

      dest_addr: 必选,表示要发送到的目标主机信息结构体。
      addrlen :目标地址长度。

返回值:成功  发送的数据长度
        失败   -1;


二ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                  struct sockaddr *src_addr, socklen_t *addrlen);

功能:用于UDP协议中获取对方发送的数据。
参数:sockfd 本地的套接字id
      buff   要存储数据的内存区,一般是数组或者动态内存。
      len    要获取的数据长度,一般是buff的大小。
      flags  获取方式,0 阻塞

      src_addr 可选,表示对方的地址信息结构体,
                  如果为NULL,表示不关心对方地址。
      addrlen  对方地址信息结构体大小。
                  如果对方地址是NULL,则该值也为NULL。
返回值:成功 接收到的数据长度
        失败  -1;

sever.c

#include <stdio.h>     // 标准输入输出库  
#include <stdlib.h>    // 标准库,包含exit等函数  
#include <unistd.h>    // UNIX标准函数定义,如sleep(但这里未使用)  
#include <string.h>    // 字符串操作函数,如bzero(注意:bzero已被弃用,建议使用memset)  
#include <sys/types.h> // 基本数据类型定义  
#include <sys/socket.h> // 套接字编程接口  
#include <netinet/in.h> // 定义IPv4地址  
#include <netinet/ip.h> // 虽然包含了,但在本程序中未直接使用到IP协议头  
#include <arpa/inet.h>  // 定义IP地址转换函数  
#include <time.h>       // 包含时间处理函数  

typedef struct sockaddr * (SA);  
  
int main(int argc, char *argv[])  
{  
    // 创建一个UDP套接字  
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if (-1 == sockfd) {  
        // 如果创建套接字失败,则打印错误信息并退出  
        perror("socket");  
        exit(1);  
    }  
  
    // 定义服务器和客户端地址结构体变量,并清零  
    struct sockaddr_in ser, cli;  
    bzero(&ser, sizeof(ser)); // 使用bzero函数清零结构体,注意bzero函数已被废弃,建议使用memset  
    bzero(&cli, sizeof(cli)); // 同样,建议使用memset  
  
    // 设置服务器地址信息  
    ser.sin_family = AF_INET;  
    ser.sin_port = htons(50000); // 设置服务器端口号,进行网络字节序转换  
    ser.sin_addr.s_addr = inet_addr("192.168.203.128"); // 设置服务器IP地址  
  
    // 将套接字绑定到服务器地址和端口上  
    // 注意:这通常是在服务器程序中完成的,而不是客户端  
    int ret = bind(sockfd, (SA)&ser, sizeof(ser));  
    if (-1 == ret) {  
        perror("bind");  
        exit(1);  
    }  
  
    // 初始化客户端地址长度变量  
    socklen_t len = sizeof(cli);  
  
    // 进入无限循环,接收数据并发送响应  
    while (1) {  
        char buf[512] = {0}; // 定义一个接收缓冲区,并初始化为空  
        // 接收数据,注意这里也更新了cli的地址和长度信息  
        recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);  
  
        // 获取当前时间  
        time_t tm;  
        time(&tm);  
  
        // 将接收到的数据和当前时间追加到buf中(注意:这可能会导致缓冲区溢出)  
        // 更安全的做法是先检查buf中剩余的空间  
        sprintf(buf, "%s %s", buf, ctime(&tm));  
  
        // 发送响应给客户端  
        // 注意:这里使用了之前从recvfrom获取的cli地址和长度信息  
        sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len);  
        // 但注意:strlen(buf)可能不包含ctime返回的字符串的结束符'\n'  
        // 更安全的做法是使用snprintf来确保不会超出buf的大小  
  
        // 这里没有添加延时,程序会尽可能快地处理每一个接收到的数据包  
    }  
  
    // 注意:由于存在无限循环,这里的close和return语句实际上不会被执行  
    // 但为了代码的完整性,我还是保留了它们  
    // 如果需要退出程序,应该在循环内部添加适当的退出条件  
  
    // 关闭套接字(这行代码实际上不会被执行)  
    close(sockfd);  
    return 0; // 程序正常结束(这行代码实际上也不会被执行)  
}  

 client.c

#include <stdio.h>     // 标准输入输出库  
#include <stdlib.h>    // 标准库,包含exit等函数  
#include <unistd.h>    // UNIX标准函数定义,如sleep  
#include <string.h>    // 字符串操作函数,如strlen, bzero  
#include <sys/types.h> // 基本数据类型定义  
#include <sys/socket.h> // 套接字编程接口  
#include <netinet/in.h> // 定义IPv4地址  
#include <netinet/ip.h> // IP协议头  
#include <arpa/inet.h>  // 定义IP地址转换函数  
#include <time.h>       // 包含时间处理函数,但本程序中未使用  
  
// 注意:这里的typedef使用方式不正确,它应该是一个类型别名,而不是一个函数指针  
// 正确的写法应该是:typedef struct sockaddr *SA; 去掉了括号和多余的空格  
typedef struct sockaddr *SA;  
  
int main(int argc, char *argv[])  
{  
    // 创建一个UDP套接字  
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if (-1 == sockfd) {  
        // 如果创建套接字失败,则打印错误信息并退出  
        perror("socket");  
        exit(1);  
    }  
  
    // 定义服务器地址结构体变量,并清零  
    struct sockaddr_in ser;  
    bzero(&ser, sizeof(ser)); // 使用bzero函数清零结构体
  
    // 设置服务器地址族为IPv4  
    ser.sin_family = AF_INET;  
    // 设置服务器端口号,使用htons函数进行网络字节序转换  
    ser.sin_port = htons(50000);  
    // 设置服务器IP地址,通过inet_addr函数将点分十进制字符串转换为网络字节序  
    ser.sin_addr.s_addr = inet_addr("192.168.203.128");  
  
    // 进入无限循环,持续发送数据并接收响应  
    while (1) {  
        // 定义一个发送缓冲区,并初始化为测试消息  
        char buf[512] = "hello,this is udp test";  
        // 向服务器发送数据  
        sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));  
  
        // 清零接收缓冲区,准备接收数据  
        bzero(buf, sizeof(buf));  
        // 接收服务器响应的数据,但注意这里传递了NULL作为地址和长度的参数,这是错误的  
        // 正确的做法应该是提供一个有效的sockaddr_in结构体变量和socklen_t类型的变量来接收客户端地址和长度  
        // 这里我们假设服务器和客户端在同一台机器上,且端口固定,因此可以省略这部分信息  
        // 但为了代码的健壮性和可移植性,建议总是检查来源地址  
        recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); // 这里需要修正  
  
        // 打印接收到的数据  
        printf("buf is %s\n", buf);  
  
        // 暂停一秒后再次发送  
        sleep(1);  
    }  
  
    // 注意:由于存在无限循环,这里的close和return语句实际上不会被执行  
    // 但为了代码的完整性,我还是保留了它们  
    // 如果需要退出程序,应该在循环内部添加适当的退出条件  
    close(sockfd); // 关闭套接字  
    return 0;      // 程序正常结束  
}  
  

练习:
1、根据以上知识点编写UDP测试程序,验证UDP协议的无链接性质。

1、将照片传输到客户端:

注意要将ip地址改成想要发送的ip地址

     ip: ifconfig

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        exit(1);
    }

    // man 7 ip 
    struct sockaddr_in ser,cli;
    bzero(&ser,sizeof(ser));
    bzero(&cli,sizeof(cli));
    ser.sin_family = AF_INET;
    // 大小端转化 host to net short 
    ser.sin_port = htons(50000);
    //ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    ser.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(sockfd,(SA)&ser,sizeof(ser));
    if(-1 == ret)
    {
        perror("bind");
        exit(1);
    }
    socklen_t len = sizeof(cli);
    int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(-1 == fd)
    {
        perror("open");
        exit(1);
    }
    while(1)
    {
        char buf[512]={0};
        int rd_ret = recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);
        if(0 == strcmp(buf,"^_^"))
        {
            break;
        }
        write(fd,buf,rd_ret);
        bzero(buf,sizeof(buf));
        strcpy(buf,"go on");
        sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);
    }
    close(sockfd);
    close(fd);
    return 0;
}

clink.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);


int main(int argc, char *argv[])
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in ser;
    bzero(&ser,sizeof(ser));
    ser.sin_family = AF_INET;
    // 大小端转化 host to net short 
    ser.sin_port = htons(50000);
    ser.sin_addr.s_addr = inet_addr("192.168.203.128");
    int fd = open("/home/linux/1.png",O_RDONLY);
    if(-1 == fd)
    {
        perror("open");
        exit(1);
    }
    char buf[512]={0};
    while(1)
    {
        bzero(buf,sizeof(buf));
        int rd_ret = read(fd,buf,sizeof(buf));
        if(0==rd_ret)
        {
            break;
        }
        sendto(sockfd,buf,rd_ret,0,(SA)&ser,sizeof(ser));
        bzero(buf,sizeof(buf));
        recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
    }
    
    bzero(buf,sizeof(buf));
    strcpy(buf,"^_^");
    sendto(sockfd,buf,3,0,(SA)&ser,sizeof(ser));
    close(sockfd);
    close(fd);
    return 0;
}

3、将以上知识点融合,考虑如何实现一个基于UDP的聊天室程序。
    要求如下:
    1、要有注册过程,每个客户端必须在服务器端有注册信息。
    2、任意客户端发送的消息必须由服务器转发给所有在线客户端。
    3、任意客户端下线必须通知其他在线用户主机。
    
    服务端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
typedef enum {CMD_LOGIN,CMD_CHAT,CMD_LOGOUT}TYPE;
typedef struct 
{
    TYPE type;
    char name[50];
    char context[128];

}MSG;
typedef struct 
{
    struct sockaddr_in cli;
    int flag; // 0  free 1 occu
}LIST;
#define MAX 10
LIST list[MAX]={0};
int do_login(int sockfd,MSG* msg,struct sockaddr_in* cli)
{
    int i = 0 ;
    for(i=0;i<MAX;i++)
    {
        if(1 == list[i].flag )
        {
            sendto(sockfd,msg,sizeof(MSG),0,(SA)&list[i].cli,sizeof(list[i].cli));
        }
    }
    for(i=0;i<MAX;i++)
    {
        if(0 == list[i].flag )
        {
            list[i].flag =1;
            //list[i].cli = *cli;
            memcpy(&list[i].cli,cli,sizeof(*cli));
            break;
        }
    }
    return 0;
}

int do_chat(int sockfd, MSG* msg,struct sockaddr_in*cli)
{
     int i = 0 ;
    for(i=0;i<MAX;i++)
    {
        if(1 == list[i].flag && 0!=memcmp(&list[i].cli,cli,sizeof(*cli)) )
        {
            sendto(sockfd,msg,sizeof(MSG),0,(SA)&list[i].cli,sizeof(list[i].cli));
        }
    }
}
int do_logout(int sockfd, MSG* msg,struct sockaddr_in*cli)
{
    return 0;
}
int main(int argc, char *argv[])
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        exit(1);
    }

    // man 7 ip 
    struct sockaddr_in ser,cli;
    bzero(&ser,sizeof(ser));
    bzero(&cli,sizeof(cli));
    ser.sin_family = AF_INET;
    // 大小端转化 host to net short 
    ser.sin_port = htons(50000);
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    int ret = bind(sockfd,(SA)&ser,sizeof(ser));
    if(-1 == ret)
    {
        perror("bind");
        exit(1);
    }
    socklen_t len = sizeof(cli);
    MSG msg;
    while(1)
    {
        bzero(&msg,sizeof(msg));
        recvfrom(sockfd,&msg,sizeof(msg),0,(SA)&cli,&len);
        switch(msg.type)
        {
            case CMD_LOGIN:
                do_login(sockfd,&msg,&cli);
                break;
            case CMD_LOGOUT:
                do_logout(sockfd,&msg,&cli);
                break;
            case CMD_CHAT:
                do_chat(sockfd,&msg,&cli);
                break;
        
        }
    }
    close(sockfd);
    return 0;
}


    客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
typedef enum {CMD_LOGIN,CMD_CHAT,CMD_LOGOUT}TYPE;
typedef struct 
{
    TYPE type;
    char name[50];
    char context[128];

}MSG;
int main(int argc, char *argv[])
{
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        exit(1);
    }

    // man 7 ip 
    struct sockaddr_in ser,cli;
    bzero(&ser,sizeof(ser));
    ser.sin_family = AF_INET;
    // 大小端转化 host to net short 
    ser.sin_port = htons(50000);
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    socklen_t len = sizeof(cli);
    MSG msg;
    char name[50]={0};
    printf("input name:");
    fgets(name,sizeof(name),stdin);
    name[strlen(name)-1]='\0';

    msg.type = CMD_LOGIN;
    strcpy(msg.name ,name);
    strcpy(msg.context,"login");

    sendto(sockfd,&msg,sizeof(msg),0,(SA)&ser,sizeof(ser));

    pid_t pid = fork();
    if(pid>0)
    {
        while(1)
        {
            bzero(&msg,sizeof(msg));
            recvfrom(sockfd,&msg,sizeof(msg),0,NULL,NULL);    
            printf("%s:%s\n",msg.name,msg.context);
        }
    }
    else if(0==pid)
    {
        while(1)
        {
            printf("to all");    
            char buf[128]={0};
            strcpy(msg.name,name);
            msg.type = CMD_CHAT;
            fgets(msg.context,sizeof(msg.context),stdin);//#quit
            msg.context[strlen(msg.context)-1]='\0';
            if(0==strcmp(msg.context,"#quit"))
            {
                msg.type = CMD_LOGOUT;
                strcpy(msg.context,"CMD_LOGOUT");
            }
            sendto(sockfd,&msg,sizeof(msg),0,(SA)&ser,sizeof(ser));
        }
    }
    else 
    {
        perror("fork");
        exit(1);
    }
    close(sockfd);
    return 0;
}


    
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值