Linux下的UDP通信程序设计

简介: 任务 设计一个基于UDP的文件下载工具,client可从server处下载其路径下的文件 编码前的前置背景知识 实现两端上传下载的步骤 server端 接收将被下载的文件名称,并将该名称发送给client
本文首发于稀土掘金。该平台的作者 逐光而行 也是本人。

theme: channing-cyan

highlight: ally-light

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天, 点击查看活动详情 这也是第7篇文章。

任务

设计一个基于UDP的文件下载工具,client可从server处下载其路径下的文件

编码前的前置背景知识

实现两端上传下载的步骤

server端

  1. 接收将被下载的文件名称,并将该名称发送给client;
  2. 当收到client的回复后,打开文件并读取内容,将读取的内容发送给client;
  3. 当文件所有内容发送完成后,给client发送一个上传完成的标识,关闭socket并退出。

client端

  1. 向server发送要下载的文件名称;
  2. 在本地创建该文件并确认开始接收文件;

3. 从server中接收文件的内容,并保存在创建好的文件中;
4. 接收完毕,关闭socket并退出。

调用操作系统为程序员提供的基于socket访问TCP/IP协议栈的编程接口

socket函数需要的头文件

#include <sys/types.h>
#include <sys/socket.h>

socket函数原型声明:

int socket(int domain, int type, int protocol);
其中:

domain

image.png

type

image.png

protocol

指定某个协议的特定类型,如果只有一种类型,就只能为0

代码中出现的sockaddr_in结构体

在编写程序时,通常约定:
使用结构体sockaddr_in来设置地址,然后通过强制类型转换成sockaddr类型。

两者各自的组成部分及转换关系如下图所示:

image.png

绑定端口

函数原型

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中:

  • sockfd:已经创建的套接字;
  • addr:是一个指向sockaddr参数的指针,其中包含了IP地址、端口;
  • addrlen:addr结构的长度;
  • 返回值:调用成功,返回值为0,否则返回-1,并设置错误代码errno。

注意:

如果创建套接字时使用的是AF_INET协议族,则addr参数所使用的结构体为struct sockaddr_in指针。

当设置addr参数的sin_addr为INADDR_ANY而不是某个确定的IP地址时,就可以绑定到任何网络接口。对于只有一个IP地址的计算机,INADDR_ANY对应的就是它的IP地址;对于有多个网卡的主机,INADDR_ANY表示本服务器程序将处理来自任何网卡上相应端口的连接请求。

代码及注释

server端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

//定义数据块大小
char data[20000];
//定义端口号
int PORT=1234;

int download()
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        printf("UDP套接字创建失败: %s\n", strerror(errno));
        return  -1;
    }
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));  
        memset(&addr,0,sizeof(addr))
        //以上这两步都是将数组内元素清0,个人感觉可能打个或者号会好些
        
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        printf("绑定端口失败: %s\n", strerror(errno));
        return  -1;
    }
        
    //存放客户端主机信息
    struct sockaddr_in clientAddr;
    int clientAddrLen = sizeof(clientAddr);
    int fd;
    char filePath[100];
    char ack[5];
    
    //int len=sizeof(addr);
    //getsockname(sockfd, (struct sockaddr *)&addr, &len);  
        //上述操作取出套接字,内含本地端通信用的ip和端口,有个问题问socket中有没有这些信息,其实是有的
    //提示当前状态
        //这个目前是静态的显示,有需要的可以稍加修改成对系统特定文件目录的扫描并显示可下载的文件
    printf("本地端可供下载的文件【%s,%d】:1.txt",inet_ntoa(addr.sin_addr),PORT);
        
       
    //接收要下载的文件名称
    recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr *)&clientAddr, &clientAddrLen);
        
    //data为下载文件的名称
    sprintf(filePath, "%s", data);
    printf("客户端【%s,%d】下载文件:%s\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),filePath);
    fd = open(filePath, O_RDONLY);
    
    if(fd==-1)
    {
     data[0] = 'n';
     sendto(sockfd, data, 1, 0, (struct sockaddr *)&clientAddr, clientAddrLen); 
     printf("文件不存在!\n");
     close(fd);
     close(sockfd);  //及时关闭套接字,这一步很重要!
     return -1;    
         }
         
    //设置data类型为文件内容(c-content)
    data[0] = 'c';
    int readSize = 0;
        
    //每次读取2000字节,最后一次不保证
    while((readSize = read(fd, &(data[1]), 2000)) != 0)
    {
        //向客户端发送数据
        sendto(sockfd, data, readSize+1, 0, (struct sockaddr *)&clientAddr, clientAddrLen);
        //等待客户端接收确认
        recvfrom(sockfd, ack, sizeof(ack), 0, (struct sockaddr *)&clientAddr, &clientAddrLen);
        //清空缓存,等待下一波2000字节
        memset(&(data[1]), 0, sizeof(data)-1);
    }

    //表示已经读到文件尾(e-end)
    data[0] = 'e';
    sendto(sockfd, data, 1, 0, (struct sockaddr *)&clientAddr, clientAddrLen); 
    close(fd);
    close(sockfd);   //关闭socket
    return 0;
}

int main(int argc, char* argv[])
{
    if(argc>2) {printf("用法:uploadFileServer  [端口]\n"); return -1;}
    if(argc==2)  PORT = atoi(argv[1]);
    download();
    return 0;
}

client端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


char data[20000];
int PORT=1234;
char* SERVER_IP="127.0.0.1";

int main(int argc, char *argv[])
{
    int sockfd;
        
    //client端用户指定的下载文件的名称
    char *downLoadFileName;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd == -1)
    {
        return -1;
    }
    if(argc!=2 && argc!=4) {printf("用法:uploadFileClient [IP 端口] >文件名\n"); return -1;}
    if(argc==4) {
        SERVER_IP = argv[1];
        PORT = atoi(argv[2]);
        downLoadFileName=argv[3];
        }
    else {downLoadFileName = argv[1];};
    struct sockaddr_in servAddr;
    int servAddrLen = sizeof(servAddr);
    bzero(&servAddr, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(PORT);
    servAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    
    //在本地创建要下载的文件
    int fd  = open(downLoadFileName, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

//向server端发送要下载的文件名称
sendto(sockfd, downLoadFileName, strlen(downLoadFileName)+1, 0, (struct sockaddr*)&servAddr, servAddrLen);
    int recvLen;
    //接收服务器发来的数据
    while((recvLen = recvfrom(sockfd, data, sizeof(data), 0, (struct sockaddr *)&servAddr, &servAddrLen)) > 0)
    {
      if(data[0] == 'c')  
          {
           write(fd, &(data[1]), recvLen - 1);
            //确认接收
           sendto(sockfd, "OK", strlen("OK"), 0, (struct sockaddr *)&servAddr, servAddrLen);
           memset(data, 0, sizeof(data));
          }
          else
          {   
          if(data[0]=='n') printf("文件不存在\n");//(n-none)
             if(data[0]=='e') printf("文件下载完毕\n");
             close(fd);  //关闭套接字
             break;
           }
    }    
    close(sockfd);    
    return 0;
}

注意事项

在C/S模型中,必须先启动server,再启动client。

参考资料

计算机网络课程实验:linux下的UDP通信程序设计

相关文章
|
1月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
37 0
Linux C/C++之TCP / UDP通信
|
1月前
|
网络协议 Linux
linux学习之套接字通信
Linux中的套接字通信是网络编程的核心,允许多个进程通过网络交换数据。套接字提供跨网络通信能力,涵盖本地进程间通信及远程通信。主要基于TCP和UDP两种模型:TCP面向连接且可靠,适用于文件传输等高可靠性需求;UDP无连接且速度快,适合实时音视频通信等低延迟场景。通过创建、绑定、监听及读写操作,可以在Linux环境下轻松实现这两种通信模型。
37 1
|
1月前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
115 1
|
2月前
|
C语言
C语言 网络编程(七)UDP通信创建流程
本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。
|
3月前
|
Linux
Linux源码阅读笔记13-进程通信组件中
Linux源码阅读笔记13-进程通信组件中
|
3月前
|
消息中间件 安全 Java
Linux源码阅读笔记13-进程通信组件上
Linux源码阅读笔记13-进程通信组件上
|
3月前
|
网络协议 数据处理 C语言
网络编程进阶:UDP通信
网络编程进阶:UDP通信
190 0
|
3月前
|
域名解析 网络协议 Linux
在Linux中,我们都知道,dns采用了tcp协议,又采用了udp协议,什么时候采用tcp协议?什么 时候采用udp协议?为什么要这么设计?
在Linux中,我们都知道,dns采用了tcp协议,又采用了udp协议,什么时候采用tcp协议?什么 时候采用udp协议?为什么要这么设计?
|
3月前
|
C语言
【C语言】UDP通信
【C语言】UDP通信
43 0
|
4月前
|
网络协议 Java 数据处理
(一)Java网络编程之计网基础、TCP-IP协议簇、TCP、UDP协议及腾讯QQ通信原理综述
就目前而言,多数网络编程的系列的文章都在围绕着计算机网络体系进行阐述,但其中太多理论概念,对于大部分开发者而言,用途甚微。因此,在本系列中则会以实际开发者的工作为核心,从Java程序员的角度出发,详细解读Java的网络编程核心内容。
下一篇
无影云桌面