• 基本套接字编程(7) -- udp篇


    1. UDP概述

            UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。
    UDP协议全称是用户数据报协议 ,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层--传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
    与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP和TCP都属于传输层协议。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
    在使用TCP编写的应用程序与使用UDP编写的应用程序之间存在一些本质差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议,非常不同于TCP提供的面向连接的可靠字节流协议。然而,相比TCP,有些场合确实更适合使用UDP,典型应用程序又:DNS(域名系统)、NFS(网络文件系统)和SNMP(简单网络管理协议)。

    2. UDP套接字编程

    与面向连接的协议相比,面向无连接协议极为不同。其中一个重要的不同点就是客户端与服务器之间不必建立连接。
    对于UDP套接字编程而言,服务器创建套接字后,调用bind()函数将套接字与准备接收数据的接口绑定在一起。和TCP编程不同的是,应用程序不必调用listen()和accept()函数等待客户端的连接。而只需要等待接收数据了。开发UDP套接字应用程序,有两个重要的函数sendto()和recvfrom()。服务器采用recvfrom()来接收来自客户端的数据报,并获得客户端的端地址,之后向客户端发送数据时,采用sendto()函数。
    下图为UDP套接字编程流程图

    从图示中可以明显的看出UDP套接字网络编程与TCP的区别。

    3. UDP套接字函数

    套接字创建socket()、地址绑定bind()函数与TCP套接字编程相同,具体请参考基本套接字编程(1) -- tcp篇,此处仅介绍消息传输函数sendto()与recvfrom();

    3.1 消息发送函数sendto()

    函数原型:
    #include <sys/socket.h>
    ssize_t sendto(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *to , socklen_t addrlen);
    		<span style="white-space:pre">								</span>返回值:成功返回写的字节数,出错返回-1
    函数说明:
    sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数buf指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。参数addrlen为sockaddr的结构长度。
    参数:
    • 前三个参数sockfd , buff 和 nbytes等同于read 和 write函数的三个参数:描述符、指向写出缓冲区的指针和写字节数;
    • falgs参数一般置0;
    • to参数指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen指定;
    返回值:
    若无错误发生,返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误(-1),应用程序可通过WSAGetLastError()获取相应错误代码。
    • WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup();
    •   WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效;
    •   WSAEACESS:要求地址为广播地址,但相关标志未能正确设置;
    •   WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用;
    •   WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中;
    •   WSAEFAULT:buf或to参数不是用户地址空间的一部分,或to参数太小(小于sockaddr结构大小);
    •   WSAENETRESET:由于WINDOWS套接口实现放弃了连接,故该连接必需被复位;
    •   WSAENOBUFS:WINDOWS套接口实现报告一个缓冲区死锁;
    •   WSAENOTCONN:套接口未被连接;
    •   WSAENOTSOCK:描述字不是一个套接口;
    •   WSAEOPNOTSUPP:已设置了MSG_OOB,但套接口非SOCK_STREAM类型;
    •   WSAESHUTDOWN:套接口已被关闭。一个套接口以1或2的how参数调用shutdown()关闭后,无法再用sned()函数;
    •   WSAEWOULDBLOCK:套接口被标志为非阻塞, 但该调用会产生阻塞;
    •   WSAEMSGSIZE:套接口为SOCK_DGRAM类型,且数据报大于WINDOWS套接口实现所支持的最大值;
    •   WSAECONNABORTED:由于超时或其他原因引起虚电路的中断;
    •   WSAECONNRESET:虚电路被远端复位;
    •   WSAEADDRNOTAVAIL:所指地址无法从本地主机获得;
    •   WSAEAFNOSUPPORT:所指定地址族中地址无法与本套接口一切使用;
    •   WSAEDESADDRREQ:需要目的地址;
    •   WSAENETUNREACH:当前无法从本主机联上网络;

    3.2 消息接收函数recvfrom()

    函数原型:
    #include <sys/socket.h>
    ssize_t recvfrom(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *from, socklen_t addrlen);
    										返回值:成功返回写的字节数,出错返回-1
    函数说明:
    recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数nbytes为可接收数据的最大长度.参数,flags一般设0,其他数值定义参考recv(),参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数,参数fromlen为sockaddr的结构长度。
    参数说明:
    • 前三个参数sockfd , buff 和 nbytes等同于read 和 write函数的三个参数:描述符、指向写出缓冲区的指针和写字节数;
    • falgs参数一般置0;
    • from参数指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen指定;
    返回值:
    同sendto()函数!若无错误发生,返回所接收数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误(-1),应用程序可通过WSAGetLastError()获取相应错误代码。
    • WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup();
    •   WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效;
    •   WSAEACESS:要求地址为广播地址,但相关标志未能正确设置;
    •   WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用;
    •   WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中;
    •   WSAEFAULT:buf或to参数不是用户地址空间的一部分,或to参数太小(小于sockaddr结构大小);
    •   WSAENETRESET:由于WINDOWS套接口实现放弃了连接,故该连接必需被复位;
    •   WSAENOBUFS:WINDOWS套接口实现报告一个缓冲区死锁;
    •   WSAENOTCONN:套接口未被连接;
    •   WSAENOTSOCK:描述字不是一个套接口;
    •   WSAEOPNOTSUPP:已设置了MSG_OOB,但套接口非SOCK_STREAM类型;
    •   WSAESHUTDOWN:套接口已被关闭。一个套接口以1或2的how参数调用shutdown()关闭后,无法再用sned()函数;
    •   WSAEWOULDBLOCK:套接口被标志为非阻塞, 但该调用会产生阻塞;
    •   WSAEMSGSIZE:套接口为SOCK_DGRAM类型,且数据报大于WINDOWS套接口实现所支持的最大值;
    •   WSAECONNABORTED:由于超时或其他原因引起虚电路的中断;
    •   WSAECONNRESET:虚电路被远端复位;
    •   WSAEADDRNOTAVAIL:所指地址无法从本地主机获得;
    •   WSAEAFNOSUPPORT:所指定地址族中地址无法与本套接口一切使用;
    •   WSAEDESADDRREQ:需要目的地址;
    •   WSAENETUNREACH:当前无法从本主机联上网络;

    4. UDP回射程序实例

    4.1 server.c

    <pre name="code" class="cpp">#include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/ip.h>
    
    const int SERV_PORT = 6000;
    const int MAXLINE = 2048;
    void dg_echo(int sockfd , struct sockaddr *pcliaddr , socklen_t clilen)
    {
    	int n;
    	socklen_t len;
    	char mesg[MAXLINE];
    	for( ; ;)
    	{
    		len = clilen;
    		if((n = recvfrom(sockfd , mesg , MAXLINE , 0 , pcliaddr , &len))<0)
    		{
    			perror("recvfrom error");
    			exit(1);
    		}//if
    
    		if((n = sendto(sockfd , mesg , n , 0 , pcliaddr , len)) < 0)
    		{
    			perror("sendto error");
    			exit(1);
    		}//if
    	}//for
    }
    int main(int argc , char **argv)
    {
    	int sockfd;
    	struct sockaddr_in servaddr , cliaddr;
    	bzero(&servaddr , sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	servaddr.sin_port = htons(SERV_PORT);
    	if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0)
    	{
    		perror("socket error");
    		exit(1);
    	}//if
    
    	if(bind(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)))
    	{
    		perror("bind error");
    		exit(1);
    	}//if
    	dg_echo(sockfd , (struct sockaddr *)&cliaddr , sizeof(cliaddr));	
    }
    
    

    4.2 client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    const int SERV_PORT = 6000;
    const int MAXLINE = 2048;
    
    void dg_cli(FILE *fp , int sockfd , const struct sockaddr *pservaddr , socklen_t servlen)
    {
    	int n;
    	char sendline[MAXLINE] , recvline[MAXLINE+1];
    
    	while(fgets(sendline , MAXLINE , fp) != NULL)
    	{
    		if(sendto(sockfd , sendline , strlen(sendline) , 0 , pservaddr ,  servlen) < 0)
    		{
    			perror("sendto error");
    			exit(1);
    		}//if
    
    		if( ( n = recvfrom(sockfd , recvline , MAXLINE , 0 , NULL , NULL)) < 0)
    		{
    			perror("recvfrom error");
    			exit(1);
    		}//if
    		recvline[n] = '';
    		fputs(recvline , stdout);
    	}//while
    }
    
    int main(int argc , char **argv)
    {
    	int sockfd , t;
    	struct sockaddr_in servaddr;
    	if(argc != 2)
    	{
    		perror("usage: udpcli <IPaddress>");
    		exit(1);
    	}//if
    	bzero(&servaddr , sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(SERV_PORT);
    	if((t = inet_pton(AF_INET , argv[1], &servaddr.sin_addr)) <= 0)
    	{
    		perror("inet_pton error");
    <span style="white-space:pre">	</span>	exit(1);
    	}//if
    
    	if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0)
    	{
    		perror("socket error");
    		exit(1);
    	}//if
    
    	dg_cli(stdin , sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
    	exit(0);
    } 

    4.3 运行结果




  • 相关阅读:
    Best Practices for Using Alpha
    Android手机 Fildder真机抓包
    Android调用MediaScanner进行新产生的媒体文件扫描
    读书笔记-----Java并发编程实战(二)对象的共享
    项目经验谈---IM新消息界面刷新异常处理记录
    读书笔记-----Java并发编程实战(一)线程安全性
    OnScroll与OnTouchEvent方法的区别与联系
    View的getLeft, getRight, getTop, getBottom
    如何用DNS+GeoIP+Nginx+Varnish做世界级的CDN
    更改ubuntu mysql data目录位置
  • 原文地址:https://www.cnblogs.com/shine-yr/p/5214700.html
Copyright © 2020-2023  润新知