前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >网络编程(二).UDP

网络编程(二).UDP

作者头像
franket
发布2021-09-15 19:59:47
6230
发布2021-09-15 19:59:47
举报
文章被收录于专栏:技术杂记技术杂记

udpclient.c

代码语言:javascript
复制
#include <stdio.h> //printf,sprintf,perror 相关函数在此声明
#include <string.h> //memset 相关函数在此声明
#include <unistd.h> //read,close 相关函数在此声明
#include <arpa/inet.h> //sockaddr_in,socket,AF_INET,SOCK_DGRAM,htons,inet_addr,sendto,recvfrom  相关函数和宏在此声明和定义
#include <fcntl.h> //open,O_RDONLY 相关函数和宏在此声明和定义

#define BUF_SIZE 1024
#define PORT 9000 

int main(int argc,char *argv[])
{
  struct sockaddr_in server_sai;
  int sfd=0,res=-1,recvbytes=0,sendbytes=0,readbytes=0,fa=0;
  int addrlen=sizeof(struct sockaddr);
  char buf[BUF_SIZE],buf2[5]={0};
  char *filename=argv[2]; //进行变量的定义和初始化
  
  if(argc != 3)
  {
    printf("error number of argc:%d\n",argc);
    return res;
  }

  if (-1==(fa=open(argv[2],O_RDONLY,0644))) //将最后一个参数作为文件名,打开文件
  {
    printf("cannot open file:%s\n",filename);
    return res;
  }

  if(-1 == (sfd=socket(AF_INET,SOCK_DGRAM,0))) //创建一个IPV4的UDP socket
  {
    perror("socket");
    return res;
  }

  server_sai.sin_family=AF_INET; //IPV4 协议族
  server_sai.sin_port=htons(PORT); //9000端口
  server_sai.sin_addr.s_addr=inet_addr(argv[1]); //使用第一个参数作为IP地址
  
  memset(&(server_sai.sin_zero),0,sizeof(server_sai.sin_zero)); //将结构体剩余部分填零
  
  memset(buf,0,sizeof(buf)); //将buf清零

  do
  {
    if(-1 == (readbytes=read(fa,buf,sizeof(buf)))) //从指定文件中读取数据写到buf中
    {
      printf("read error on:%s\n",filename);
      return res;
    }
    if (-1 == (sendbytes=sendto(sfd,buf,readbytes,0,(struct sockaddr *)&server_sai,sizeof(struct sockaddr)))) //将buf中的数据写到远端
    {
      perror("sendto");
      return res;
    }
    if (-1 == (recvbytes=recvfrom(sfd,buf2,5,0,(struct sockaddr *)&server_sai,(socklen_t *)&addrlen))) //从远端获取信息,用于同步节奏
    {
      perror("recvfrom");
      return res;
    }
  }while(readbytes == sizeof(buf)); //如果读取的数据不再是一整块,就意味着已经读完,随即跳出循环

  
  if (-1 == (recvbytes=recvfrom(sfd,buf2,5,0,(struct sockaddr *)&server_sai,(socklen_t *)&addrlen))) //从远端读取数据到buf2中
  {
    perror("recvfrom");
    return res;
  }

  printf("%d --> %s\n",recvbytes,buf2);   //将接收到的字节数和数据内容打印出来
  
  close(sfd);
  close(fa); //进行清理工作,关闭描述符
  
  res=0;
  return res;
}

编译执行

代码语言:javascript
复制
emacs@ubuntu:~/c$ alias gtc
alias gtc='gcc -Wall -g -o'
emacs@ubuntu:~/c$ gtc udpserver.x udpserver.c; gtc udpclient.x udpclient.c
emacs@ubuntu:~/c$ 

此时系统中并没有开放9000端口

代码语言:javascript
复制
emacs@ubuntu:~/c$ netstat -anu | grep 9000
emacs@ubuntu:~/c$ 

运行服务端

代码语言:javascript
复制
emacs@ubuntu:~/c$ ./udpserver.x 

此时系统中多了一个9000端口

代码语言:javascript
复制
emacs@ubuntu:~/c$ netstat -anu | grep 9000
udp        0      0 0.0.0.0:9000            0.0.0.0:*                          
emacs@ubuntu:~/c$ 

服务端也并没有 /tmp/x.download 这个文件

代码语言:javascript
复制
emacs@ubuntu:~/c$ ll /tmp/x.download 
ls: 无法访问/tmp/x.download: 没有那个文件或目录
emacs@ubuntu:~/c$  

运行客户端,会立刻返回

代码语言:javascript
复制
emacs@ubuntu:~/c$ du -sh 4.png 
8.6M	4.png
emacs@ubuntu:~/c$ ./udpclient.x 127.0.0.1 4.png 
4 --> DONE
emacs@ubuntu:~/c$  

服务端会打印信息并且返回,对比两个文件也没有差异

代码语言:javascript
复制
emacs@ubuntu:~/c$ ./udpserver.x 
i:8786
recvbytes:860
emacs@ubuntu:~/c$ diff /tmp/x.download 4.png 
emacs@ubuntu:~/c$

编译执行过程中没有报错,从结果来看,符合预期


recvfrom

sys/socket.h 中有关于 recvfrom 的声明

代码语言:javascript
复制
/* Read N bytes into BUF through socket FD.
   If ADDR is not NULL, fill in *ADDR_LEN bytes of it with tha address of
   the sender, and store the actual size of the address in *ADDR_LEN.
   Returns the number of bytes read or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern ssize_t recvfrom (int __fd, void *__restrict __buf, size_t __n,
                         int __flags, __SOCKADDR_ARG __addr,
                         socklen_t *__restrict __addr_len);

从套接口上接收数据,并捕获数据发送源的地址

__fd 标识一个已连接套接口的描述字

__buf 接收数据缓冲区

__n 缓冲区长度

__flags 调用操作方式

__addr (可选)指针,指向装有源地址的缓冲区

__addr_len (可选)指针,指向__addr缓冲区长度值

返回值:>0 返回读入的字节数; ==0 连接已中止; <0 返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码

代码语言:javascript
复制
EBADF 参数s非合法的socket处理代码
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数s为一文件描述词,非socket
EINTR 被信号所中断
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确

sendto

sys/socket.h 中有关于 sendto 的声明

代码语言:javascript
复制
/* Send N bytes of BUF on socket FD to peer at address ADDR (which is
   ADDR_LEN bytes long).  Returns the number sent, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern ssize_t sendto (int __fd, __const void *__buf, size_t __n,
                       int __flags, __CONST_SOCKADDR_ARG __addr,
                       socklen_t __addr_len);

适用于发送未建立连接的UDP数据包

__fd 一个标识套接口的描述字

__buf 包含待发送数据的缓冲区

__n buf缓冲区中数据的长度

__flags 调用方式标志位

__addr (可选)指针,指向目的套接口的地址

__addr_len 所指地址的长度

返回值 :>0 返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小);==0 连接已中止 ;<0 返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码

代码语言:javascript
复制
EBADF 参数s非法的socket处理代码
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数 s为一文件描述词,非socket
EINTR 被信号所中断
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断的
ENOBUFS 系统的缓冲内存不足
EINVAL 传给系统调用的参数不正确

SOCK_DGRAM

bits/socket.h 中有关于 SOCK_DGRAM 的定义

代码语言:javascript
复制
/* Types of sockets.  */
enum __socket_type
{
  SOCK_STREAM = 1,              /* Sequenced, reliable, connection-based
                                   byte streams.  */
#define SOCK_STREAM SOCK_STREAM
  SOCK_DGRAM = 2,               /* Connectionless, unreliable datagrams
                                   of fixed maximum length.  */
#define SOCK_DGRAM SOCK_DGRAM
  SOCK_RAW = 3,                 /* Raw protocol interface.  */
#define SOCK_RAW SOCK_RAW
  SOCK_RDM = 4,                 /* Reliably-delivered messages.  */
#define SOCK_RDM SOCK_RDM
  SOCK_SEQPACKET = 5,           /* Sequenced, reliable, connection-based,
                                   datagrams of fixed maximum length.  */
#define SOCK_SEQPACKET SOCK_SEQPACKET
  SOCK_DCCP = 6,                /* Datagram Congestion Control Protocol.  */
#define SOCK_DCCP SOCK_DCCP
  SOCK_PACKET = 10,             /* Linux specific way of getting packets
                                   at the dev level.  For writing rarp and
                                   other similar things on the user level. */
#define SOCK_PACKET SOCK_PACKET

  /* Flags to be ORed into the type parameter of socket and socketpair and
     used for the flags parameter of paccept.  */

  SOCK_CLOEXEC = 02000000,      /* Atomically set close-on-exec flag for the
                                   new descriptor(s).  */
#define SOCK_CLOEXEC SOCK_CLOEXEC
  SOCK_NONBLOCK = 04000         /* Atomically mark descriptor(s) as
                                   non-blocking.  */
#define SOCK_NONBLOCK SOCK_NONBLOCK
};

里面规定 SOCK_DGRAM 为一种不连接,不可靠的传输模式


附:TCP和UDP的区别

Tip: 引自 《TCP和UDP的最完整的区别》

代码语言:javascript
复制
TCP与UDP基本区别
  1.基于连接与无连接
  2.TCP要求系统资源较多,UDP较少; 
  3.UDP程序结构较简单 
  4.流模式(TCP)与数据报模式(UDP); 
  5.TCP保证数据正确性,UDP可能丢包 
  6.TCP保证数据顺序,UDP不保证 
  
UDP应用场景
  1.面向数据报方式
  2.网络数据大多为短消息 
  3.拥有大量Client
  4.对数据安全性无特殊要求
  5.网络负担非常重,但对响应速度要求高
 
具体编程时的区别
  1.socket()的参数不同 
  2.UDP Server不需要调用listen和accept 
  3.UDP收发数据用sendto/recvfrom函数 
  4.TCP:地址信息在connect/accept时确定 
  5.UDP:在sendto/recvfrom函数中每次均 需指定地址信
  6.UDP:shutdown函数无效

TCP与UDP区别总结
  1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
  2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3.TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
  4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5.TCP首部开销20字节;UDP的首部开销小,只有8个字节
  6.TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

总结

以下函数可以进行socket的创建与控制,是UDP网络编程的基础

  • socket
  • setsockopt
  • bind
  • recvfrom
  • sendto

通过各方面资料弄懂其参数的意义和返回值的类型,是熟练掌握的基础

原文地址

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 编译执行
  • recvfrom
  • sendto
  • SOCK_DGRAM
  • 附:TCP和UDP的区别
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档