基于数据报(UDP)编程的接口总结

Udp编程接口

socket():创建套接字
  • 1.使用socket()函数创建一个文件描述符
int socket(AF_INET,SOCK_DGRAM,0);
//参数一:表示改文件描述符使用的是IPV4
//参数二:表示该文件描述符使用的是UDP协议
//参数三:一般不使用
  • 参数domain 用来说明网路程序所在的主机采用的是那种通信协议簇,这些协议族再头文件<sys/socket.h>中定义

  • AF_INET //表示IPv4网络协议

  • AF_INET6 //IPv6网络协议

  • 参数type 用来指明创建的套接字类型,

  • SOCK_STREAM:流式套接字,面向连接可靠的通信类型

  • SOCK_DGRAM:数据报套接字,非面向连接和不可靠的通信类型

  • SOCK_RAW:原始套接字,用来直接访问IP协议

  • 参数protocol 指定套接字使用的协议,一般采用默认值0,表示让系统根据地址格式和套接字类型,自动选择一个合适的协议

  • 返回值:

  • 调用成功就创建了一个新的套接字并返回它的描述符,在之后对该套接字的操作中都要借助这个文件描述符,

  • 否则返回-1(<0) 表示套接字出错.应用程序可调用WSAGetLastError() 获取相应的错误代码

bind ():将套接字绑定到指定的网络地址

使用这个函数前需要将服务器的ip和端口号赋值到结构体sockaddr_in中

使用bind()函数前的准备工作

  • sockaddr 结构:针对各种通信域的套接字,存储它们的地址信息
struct sockaddr{
    u_short sa_family;   /*16位协议家族*/
    char sa_data[14]    /*14字节协议地址*/
};
  • sockaddr_in结构:专门针对Internet通信域,存储套接字相关的网络地址信息(IP地址、传输层端口号等信息)
struct sockadd_in{
  short int sin_family;   //地址家族
  unsigned short int sin_port; //端口号
  struct in_addr sin_addr;  //IP地址
  unsigned char sin_zero[8];  //全为0    
};
  • in_addr 结构:专门用来存储IP地址
struct in_addr{
    unsigned long s_addr;
};
#include<netinet/in.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
  • 参数sockfd:是未经绑定的套接字文件描述符,是由socket()函数返回的,要将它绑定到指定的网络地址上

  • 参数addr : 是一个指向sockaddr结构变量的指针,所指结构体中保存着特定的网络地址,就是要把套接字sockfd绑定到这个地址上.

  • 参数 addrlen :是结构sockaddr结构的长度,等于sizeof(struct sockadd

  • 返回值

  • 返回0:表示已经正确的实现了绑定

  • 如果返回SOCKET_ERROR表示有错。应用程序可调WSAGetLastError()获取相应的错误代码

  • 最后在函数调用的时候,将这个结构强制转换成sockaddr类型
本机字节序和网络字节序
  • 本机字节序在不同的计算机中,存放多字节值的顺序是不一样的,有的是从低到高,有的是从高到低.计算机中的多字节数据的存储顺序称为本机字节顺序.

  • 网络字节序:在网络协议中,对多字节数据的存储,有它自己的规定。多字节数据在网络协议报头中的存储顺序,称为网络字节序.在套接字中必须使用网络字节序

在这里插入图片描述

所以把IP地址和端口号装入套接字时,我们需要将本机字节序抓换成网络字节序,在本机输出时,我们需要将它们从网络字节序转换成本机字节序

  • 套接字编程接口专门为解决这个问题设置了4个函数

  • 1.htons():短整数本机顺序转换为网络顺序,用于端口号

  • 2.htonl():长整数本机顺序转换成网络顺序,用于IP地址

  • 3.ntohs():短整数网络顺序转换为本机顺序,用于端口号

  • 4.ntohl():长整数网络顺序转换为本机顺序,用于IP地址

这4个函数将被转换的数值作为函数的参数,函数返回值作为转换后的结果

  • 点分十进制的IP地址的转换

在Internet中,IP地址常常是用点分十进制的表示方法,但在套接字中,IP地址是无符号的长整型数,套接字编程接口设置了两个函数,专门用来两种形式IP地址的转换

  • 1.inet_addr函数
unsigned long inet_addr(const char* cp);

/*
入口参cp:点分十进制形式的IP地址
返回值:网络字节顺序的IP地址,是无符号的长整数
*/
  • 2.inet_ntoa函数
char* inet_ntoa(struct in_addr in)

/*
入口参数in: 包含长整数IP地址的in_addr结构变量
返回值:指向点分十进制IP地址的字符串的指针
*/
recvfrom()接收一个数据报并保存源地址,从数据报套接字接收数据
调用格式
int recvfrom(SOCKET s,char *buf,int len,int falgs,
struct sockaddr* from, int* fromlen);
  • 参数s:接收端的数据报套接字描述符,包含接收方的网络地址,从这个套接字接收数据报

  • 参数buf:字符串指针,指向用户进程的接收缓冲区,用来接收从套接字收到的数据报

  • 参数len:用户接收缓冲区的长度,制定了所能接收的最大字节数

  • 参数flags:接收的方式,一般置为0.

  • 参数from: 指向sockaddr 结构的指针,实际上是一个出口参数,当本次调用成功执行后,在这个结构中返回了发送方的网络地址,包括对方的IP地址和端口号

  • 参数fromlen:整数型指针,也是一个出口型参数,本调用结束时,返回存在from中的网络地址长度

  • 返回值: 如果正确接收,则返回实际收到的字节数,如果出错,返回SOCKET_ERROR,应用程序可通过WSAGetLastError()获取相应的错误代码

函数功能
  • 本函数从s套接口 的接收缓冲区队列中,取出第一个数据报,把它放到用户进程 的缓冲区buf中,但最多不超过用户缓冲区的大小。如果数据报大于用户缓冲区长度,那么用户缓冲区中只有数据报的前面部分,后面的数据都会丢失,并且recvfrom()函数WSAEMSGSIZE错误
sendto():按照指定目的地向数据报套接字发送数据
调用格式
int sendto(SOCKET s,char* buf,int len,int flags,struct sockaddr* to,
int tolen)
  • 参数s:发送方的数据报套接字 描述符,包含发送方的网络地址,数据报通过这个套接字向对方发送

  • 参数buf:指向用户进程发送缓冲区的字符串指针,该缓冲区包含将要发送的数据,

  • 参数len:用户发送缓冲区中要发送的数据的长度,是可以发送的最大字节数

  • 参数flags: 指定函数的执行方式,一般置为0.

  • 参数to:指向sockaddr 结构的指针,指定接收数据报的目的套接字的完整的网络地址

  • 参数tolen: to的地址长度,等于sizeof(struct sockaddr)

  • 返回值:如果发送成功,则返回实际发送的字节数,注意这个数字可能小于len中所规定的长度,如果出错返回错误码

函数功能

本函数专用于数据报套接字,用来向发送端的本地套接字发送一个数据报.套接字会将数据下交给传输层的UDP,由它向对方发送.

  • 容易看出,这个调用需要决定通信的两个端点,需要一个全相关的五元组信息,既协议(UDP)、源IP地址、源IP地址、源端口号、目的IP地址和目的端口.通信一端由发送方套接字s指定,通信的另一端由to结构决定
实例代码
udp_server.cc
//先实现UDP版本的服务器

#include<stdio.h>
#include<sys/socket.h>  //socket所用的头文件
#include<netinet/in.h>  //socketaddr_in 结构体的头文件
#include<arpa/inet.h>   //inet.addr的头文件
#include<cstring>
#include<unistd.h>

int main(){
  //1.先创建一个socket
  // AF_INET是一个宏 ,表示使用 ipv4 协议
  //SOCK_DGRAM   表示使用 UDP 协议
  int sock=socket(AF_INET,SOCK_DGRAM,0);
  if(sock<0){
    perror("socket");
    return 1;
  }
  //2.把当前的socket  绑定上一个ip + 端口号
  sockaddr_in addr;   //是一个结构体
  addr.sin_family=AF_INET;
  addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //0.0.0.0将本地所有的ip都包含进来 
  //把点分十进制转换成32位整数
  //设置ip地址
  //ip 地址也是一个整数,也需要转成网络字节序,只不过
  //int_addr 函数自动帮我们转了
  addr.sin_port=htons(8080);
  //端口号必须得先转成网络字节序
  //9090 TCP端口,这个端口不是系统默认的端口,
  //属于自定义的端口
  int ret=bind(sock,(sockaddr*)&addr,sizeof(addr));
  if(ret<0){
    perror("bind");
    return 1;
  }

  printf("server start ok!\n");
  //3.处理服务器收到的请求
  while(true){
    //1.读取客户端的请求
    //面向数据报的函数
    sockaddr_in peer;//获取客户端的ip
    socklen_t len=sizeof(peer);//表示一个整数
    char buf[1024]={0};
    ssize_t n= recvfrom(sock,buf,sizeof(buf)-1,0,(sockaddr*)&peer,&len);
  
    printf("客户端发来消息:%s\n",buf);
    printf("请输入回复内容:");
   if(n<0){
     perror("recvfrom");
     continue;//不要因为一次请求的失利就结束整个程序
   }
   buf[n]='\0';
   //double check
    //2.根据请求计算响应时间
    //[略]此处写的是一个回显服务器(echo server)
    //3.把响应写回客户端
    char buf2[1024]="0";
    scanf("%s",buf2);
    n=sendto(sock,buf2,strlen(buf2),0,(sockaddr*)&peer,len);
    if(n<0){
      perror("sendto");
      continue;
    }
    printf("[%s:%d] buf: %s\n",inet_ntoa(peer.sin_addr),
      ntohs(peer.sin_port), buf2);
  }
  close(sock);
  return 0;
}
ucp_client.cc
//实现UDP版本的客户端

#include<stdio.h>
#include<sys/socket.h>  //socket所用的头文件
#include<netinet/in.h>  //socketaddr_in 结构体的头文件
#include<arpa/inet.h>   //inet.addr的头文件
#include<cstring>
#include<unistd.h>


//./client [ip]
int main(int argc,char *argv[]){

  //1.先创建一个socket
  int sock=socket(AF_INET,SOCK_DGRAM,0);
  if(sock<0){
    perror("socket");
    return 1;
  }
  //2.客户端一般不需要bind
  //bind  意味着和某个具体的端口号关联在一起
  //如果没有bind,操作系统会随机分配一个端口号
  //如果是服务器程序不去bind ,就会导致每次启动服务器的端口发生变化
  //客户端就没法连接了
  //如果客户端bind的话,可能会出现问题
  //通常情况下,一个端口号只能被一个进程bind
  //如果客户端bind一个端口号其他客户端也可能bind同一个端口号
  //就会出错
  //客户端最好还是让操作系统随机分配更科学
  
  //2.准备好服务器的sockaddr_in 结构
  
  sockaddr_in  server_addr;
  server_addr.sin_family=AF_INET;
  server_addr.sin_addr.s_addr=inet_addr(argv[1]);
  server_addr.sin_port=htons(8080);
   
  //3.客户端直接发送数据即可
  while(1){
    char buf[1024]={0};
    printf("请输入一段内容:");
    fflush(stdout);
    scanf("%s",buf);
    sendto(sock,buf,strlen(buf),0,
        (sockaddr*)&server_addr,sizeof(server_addr));
    //从服务器接收一下返回结果
    char buf_output[1024]={0};
    //后两个参数填NULL 表示不关注对端的地址
    recvfrom(sock,buf_output,sizeof(buf_output)-1,0,
          NULL,NULL);
    printf("客户端回应:%s\n",buf_output);
  }
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章