Linux(程序设计):42---UDP网络编程(单播)_数据Linux(程序设计):42---UDP网络编程(单播)_#include_02



服务端通信步骤如下:


  • socket​();         //建立一个socket
  • bind​();            //将这个socket绑定在某个端口上
  • sendto​();       //向客户端的某个端口发起请求
  • recvfrom​();    //如果没有客户端发起请求,则会阻塞在这个函数里
  • close​();          //通信完成后关闭socket

客户端通信步骤如下:


  • socket​();        //建立一个socket
  • sendto​();        //向服务器的某个端口发起请求
  • recvfrom​();    //如果没有服务端发起请求,则会阻塞在这个函数里
  • close​();         //通信完成后关闭socket


 一、socket()函数


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

int socket(int protofamily, int type, int protocol);


  •  参数1:即协议域,又称为协议族(family)

域参数指定通信域;这将选择用于通信的协议系列。这些族在<sys/socket.h>中定义

AF_INET用ipv4地址(32位的)与端口号(16位的)的组合AF_INET6IPV6的

AF_LOCAL/或者AF_UNIX(Unix域socket)

用一个绝对路径名作为地址。本地通信AF_ROUTE​ ​AF_IPXIPX-Novell协议AF_PACKET低层包接口包AF_NETLINK内核用户界面设备


 参数2:指定socket类型,与第三个参数有关

套接字具有指定的类型,该类型指定通信语义

并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。

SOCK_STREAM字节流套接字。​提供序列化的、可靠的、双向连接的字节流。支持带外数据传输(TCP使用)​SOCK_DGRAM数据报套接字。(UDP使用)支持数据报(固定最大长度的无连接、不可靠的消息)SOCK_RAW原始套接字。​RAW类型,提供原始网络协议访问SOCK_RDM​提供了一个不保证排序的可靠数据报层。不过可能数据会有乱序​SOCK_PACKET专用类型。​不能在通用程序中使用,它直接从设备驱动接受数据​SOCK_SEQPACKET有序分组套接字。​序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出


  •  参数3:指定协议类型

参数2的socket类型都有一个默认的协议,如果想使用默认的协议,参数3填0即可

IPPROTO_TCP​TCP传输协议(SOCK_STRAM默认使用)​IPPTOTO_UDP​UDP传输协议(SOCK_DGRAM默认使用)​IPPROTO_SCTP​STCP传输协议​IPPROTO_TIPC​TIPC传输协议


返回值:


  • socket创建成功:​返回一个socket描述符(sockfd),这个描述字不是固定的,为int类型
  • socket创建失败:​返回-1,并设置errno变量的值



errno变量

  • 若socket创建失败,就会设置errno为相应的值,用户可以检测errno判断出现什么错误

EACCES​没有权限建立制定的protofamily的type的socket​EAFNOSUPPORT​不支持所给的地址类型​EINVAL​不支持此协议或者协议不可用​EMFILE​进程文件表溢出​ENFILE​已经达到系统允许打开的文件数量,打开文件过多​ENOBUFS/ENOMEM​内存不足。socket只有到资源足够或者有进程释放内存​EPROTONOSUPPORT​制定的协议type在domain中不存在


什么是socket描述符???


  • 我们在使用C语言用fopen()会返回一个文件指针,这个文件指针就代表这个这个文件
  • 在Linux中,我们使用sokcet()函数打开一个socket文件,会返回一个socket描述符,这个描述符就代表着这个socket。描述符也称描述字


 二、bind()函数


  • 功能:​bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket
  • #include<sys/types.h>
  • #include<sys/socket.h>

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



  • 参数1: 服务端的socket描述符
  • 参数2:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址(IP、端口号)
  • 参数3:对应的地址的长度。服务器在启动的时候都会绑定一个地址(如ip地址+端口号)



返回值(int类型)


  • 建立成功:成功返回0
  • 建立失败:返回-1,并设置errno变量的值


三、sendto()函数


  • #include <sys/types.h>
  • #include <sys/socket.h>
  • 发送数据

int sendto(int sockfd, const void * buf, size_t len, int flags, const struct sockaddr * dest_addr, socklen_t addrlen);



  • 参数1:​自己的socket描述符
  • 参数2:​要发送的数据的起始地址
  • 参数3:​发送的数据大小
  • 参数4:​一般情况下为0
  • 参数5:​发送到的目的地址
  • 参数6:​地址长度



返回值(ssize_t类型)


  • 传递成功:返回发送的数据的字节大小
  • 传递失败:返回-1,并设置errno变量的值


四、recvfrom()函数


  • #include<unistd.h>
  • 接收数据

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



  • 参数1:​自己的socket描述符
  • 参数2:​接收到的数据的存放位置
  • 参数3:​接受数据的大小
  • 参数4:​一般情况下为0
  • 参数5:​数据来自于哪个地址
  • 参数6:​参数5地址的长度



返回值(ssize_t类型)


  • 成功:返回接收的数据的字节大小
  • 失败:返回-1,并设置errno变量的值
  • 当对等机执行有序关闭时,返回值将为0


五、close()函数


  • 关闭相应的socket描述符
  • #include <unistd.h>

int close(int fd);


  •  参数:对应的socket描述符


六、sendto和recvfrom最后一个参数的注意事项



  • sendto最后一个参数的类型是socklen_t类型
  • recvfrom最后一个参数的类型是socklen_t*类

因此这两个函数的最后一个参数在使用时一定要注意!!!!



案例


  • struct sockaddr_in local;
  • int len=sizeof(local);
  • socklen_t  addr_len=sizeof(local);

sendto可以使用


  • sendto(...,..,..,len);
  • sendto(...,..,..,addr_len);

recvfrom可以使用


  • sendto(...,..,..,&len);
  • sendto(...,..,..,&addr_len);


七、代码演示

服务端 

#include <stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>

int main( int argc,char *argv[] )
{
if(argc!=3){
printf("Please Enter %s [IP] [PORT]\n",argv[0]);
return 1;
}

//Create Socket
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
printf("Create Socket error\n");
return 2;
}

struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(atoi(argv[2]));
local.sin_addr.s_addr=inet_addr(argv[1]);

//bind
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
printf("Bind Error\n");
close(sock);
return 3;
}

//recvfrom and sendto
int done=0;
struct sockaddr_in peer;
int len=sizeof(peer);

char buf[1024];
while(!done){
memset(buf,'\0',sizeof(buf));
recvfrom(sock,buf,sizeof(buf),0,(struct sockaddr*)&peer,&len);
printf("get a clien ,socket:%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
printf("client say:%s\n",buf);

sendto(sock,buf,sizeof(buf),0,(struct sockaddr*)&peer,len);
}
close(sock);
return 0;
}

客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main( int argc,char *argv[] )
{
if(argc!=3){
printf("Please Enter %s [IP] [PORT]\n",argv[0]);
return 1;
}

int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
perror("Create Socket Error:");
return 2;
}

struct sockaddr_in remote;
remote.sin_family=AF_INET;
remote.sin_port=htons(atoi(argv[2]));
remote.sin_addr.s_addr=inet_addr(argv[1]);

int done=0;
struct sockaddr_in peer;
int len=sizeof(peer);

char buf[1024];
while(!done){
memset(buf,'\0',sizeof(buf));
printf("Please enter:");
fflush(stdout);
ssize_t _s=read(0,buf,sizeof(buf)-1);
if(_s>0){
buf[_s-1]='\0';
sendto(sock,buf,sizeof(buf),0,(struct sockaddr*)&remote,sizeof(remote));

memset(buf,'\0',sizeof(buf));
recvfrom(sock,buf,sizeof(buf),0,(struct sockaddr*)&peer,&len);
printf("server echo:%s,socket :%s:%d\n",buf,inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
}
}
close(sock);
return 0;
}