网络编程(二).UDP

2021-09-15 19:59:47 浏览数 (1)

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:%dn",argc);
    return res;
  }

  if (-1==(fa=open(argv[2],O_RDONLY,0644))) //将最后一个参数作为文件名,打开文件
  {
    printf("cannot open file:%sn",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:%sn",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 --> %sn",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

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

原文地址

0 人点赞