DSP

UDP的编程流程

2019-07-13 16:50发布

UDP编程流程:

服务器端:socket(),  bind(),  recvfrom()/sendto(),  close(); 客户端:socket(),  sendto()/recvfrom(),  close();
以下是各个函数的具体介绍: 首先提一句:linux下一切皆文件,socket也不例外,它是可读、可写、可控制、可关闭的文件描述符。 创建socket #include #include int socket(int domain,int type,int protocol);//创建socket
  • domain  表示底层协议族。其中IPv4用PF_INET(Protocol Family of Internet),IPv6用PF_INET6。
  • type 指定服务类型。服务类型主要有SOCK_STREAM(字节流服务)服务和SOCK_DGRAM服务(数据报服务)。对于TCP/IP协议族来说,SOCK_STREAM表示传输层使用TCP协议,SOCK_DGRAM表示传输层使用UDP协议。
  • protocol 在前两个参数构成的协议集合下,再选择一个具体的协议。一般设置为0,表示使用默认协议。
socket()系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。 命名(绑定)socket #include #include int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addlen);//命名(绑定)socket bind的作用是将未命名的sockfd文件描述符指向my_addr所指的socket地址。其中socket地址长度由参数addlen指出。 bind成功时返回0,失败则返回-1并设置errno。 其中struct sockaddr是通用的socket地址,而TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPv4和IPv6,这里我们只介绍sockaddr_in: struct sockaddr_in { sa_family_t sin_family; //地址族:AF_INET u_int16_t sin_port; //端口号(要用网络字节序) struct in_addr sin_addr; //IPv4地址结构体 }; struct in_addr //IPv4地址结构体 { u_int32_t s_addr; //IPv4地址(要用网络字节序) }; 所有专用socket地址类型的变量在实际使用时都需要转化为通用的socket地址类型sockaddr(强转)。我们看到结构体中的端口号和IP地址都要求用网络字节序。 首先看端口号,两台主机之间要通过TCP/IP协议进行通信的时候需要调用相应的函数进行主机序 和网络序的转换。因为主机字节序一般为小端模式(Little-Endian),而网络字节序为大端模式(Big-Endian),也就是说两者的存储方式不同。所以我们介绍4个函数来完成主机字节序和网络字节序之间的转换: #include unsigned long int htonl(unsigned long int hostlong);//主机字节序转网络字节序(32bit的长整型) unsigned short int htonl(unsigned short int hostshort);//主机字节序转网络字节序(16bit的短整型) unsigned long int ntohl(unsigned long int netlong);//网络字节序转主机字节序(32bit的长整型) unsigned short int ntohs(unsigned short int netshort);//网络字节序转主机字节序(16bit的短整型) 这些函数的含义很明确,第一个函数 htonl 表示“host to network long”,即长整型(32bit)的主机字节序转化为网络字节序。 接下来我们看IP地址,我们习惯用点分十进制这样的可读性好的字符串来表示IPv4地址,这里介绍3个IP地址转换函数: #include in_addr_t inet_addr(const char* strptr); int inet_aton(const char* cp,struct in_addr* inp); char* inet_ntoa(struct in_addr in); inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。 inet_aton函数完成inet_addr函数同样的功能,但是将转化结果存储于参数inp指向的地址结构中。该函数成功时返回1,失败返回-1。 inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。 数据读写 对文件的读写操作read和write同样适用于socket。但是socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制。UDP与TCP不同,UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址。所以UDP的读写函数比TCP的读写函数参数要多。 #include #include int recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen);//读取sockfd上的数据 recvfrom用于读取sockfd上的数据。
  • buf  指定读缓冲区的位置。
  • len 指定读缓冲区的大小。
  • src_addr 发送端的socket地址。 
  • addrlen 发送端的socket地址的长度。
  • flags 通常设置为0(具体含义见下)。
recvfrom 成功时返回实际读取到的数据的长度(recv返回0时表示通信对方已经关闭连接),出错时返回-1并设置errno。 #include #include int sendto(int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen);//往sockfd上写入数据 sendto 是往sockfd上写入数据。
  • buf  指定写缓冲区的位置。
  • len 指定写缓冲区的大小。
  • src_addr 接收端的socket地址。 
  • addrlen 接收端的socket地址的长度。
  • flags 通常设置为0(具体含义见下)。
sendto 成功时返回实际写入的数据的长度(recv返回0时表示通信对方已经关闭连接),出错时返回-1并设置errno。 flags为数据收发提供额外的控制,flag参数的可选值如下表,这些选项的具体使用这里不作详述。 ps:recvfrom/sendto系统调用也可以用于面向连接的socket数据读写,只需要把最后两个参数都设置为NULL以忽略发送端、接收端的socket地址(因为我们已经建立了连接,所以已经知道其socket地址了)。 关闭连接 #include int close(int fd);//关闭连接
  • fd  待关闭的socket。
close系统调用并非总是立即关闭一个连接,而是将fd的引用计数-1;只有当fd的引用计数为0时,c才真正关闭连接。

UDP编程实例

实现客户端输入数据,服务器端打印客户输入的数据,并且每次打印数据后给客户反馈。 服务器端代码: #include #include #include #include #include #include #include #include #include int main() { int sockfd=socket(PF_INET,SOCK_DGRAM,0);//创建socket assert(sockfd!=-1); struct sockaddr_in cli,ser; ser.sin_family=AF_INET; ser.sin_port=htons(6000); ser.sin_addr.s_addr=inet_addr("127.0.0.1"); int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));//绑定socket while(1)//保持服务器常驻 { char buff[128]={0}; int len=sizeof(cli); int n=recvfrom(sockfd,buff,127,0,(struct sockaddr*)&cli,&len);//接收客户端的数据 if(n<=0) { printf("recvfrom error "); continue; } printf("%s ",buff); sendto(sockfd,"OK",2,0,(struct sockaddr*)&cli,len);//给客户端回馈 } close(sockfd);//关闭服务器 } 客户端代码: #include #include #include #include #include #include #include #include #include int main() { int sockfd=socket(PF_INET,SOCK_DGRAM,0);//创建socket assert(sockfd!=-1); struct sockaddr_in ser,cli; ser.sin_family=AF_INET; ser.sin_port=htons(6000); ser.sin_addr.s_addr=inet_addr("127.0.0.1"); while(1)//实现与服务器端多次交互 { printf("please input:"); fflush(stdout); char buff[128]={0}; fgets(buff,127,stdin); buff[strlen(buff)-1]=0; if(strncmp(buff,"end",3)==0)//客户输入end时关闭与服务器的交互 { close(sockfd);//停止与服务器的连接 break; } sendto(sockfd,buff,127,0,(struct sockaddr*)&ser,sizeof(ser));//向服务器发送数据 memset(buff,0,128); recvfrom(sockfd,buff,127,0,NULL,NULL);//相当于已建立连接,可用NULL printf("%s ",buff); } } 运行结果: 客户端: 服务器端: