UDP套接字编程
来源:互联网 发布:雷霆战机网络连接超时 编辑:程序博客网 时间:2024/04/29 00:00
UDP编程简介
TCP和UDP在传输层区别
UDP是无连接不可靠的数据报协议。TCP提供面向连接的可靠字节流。使用UDP常见应用
DNS(域名系统),NFS(网络文件系统),SNMP(简单网络管理协议)
典型的UDP的Client和Server
(1)客户端不和服务器建立连接,只是使用sendto给服务器发送数据,必须指定服务器地址作为参数。(2)服务器不接受来自客户端的连接,而只是使用recvfrom,等待来自客户端的数据达到。recvfrom同时返回客户端的协议地址,用于服务器给客户端响应。 UDP Client UDP服务器 socket() | | bind()众所周知端口 | | socket() recvfrom() <---------- | | | | 数据请求 | | --->sendto() -----------------> 阻塞,直到收到数据 | | | | | | | | | | | 处理请求 | | | | | | | | | | | 数据应答 | | |---recvfrom() <----------------- sendto() ------------ | | | close()
重要函数recvfrom和sendto
recvfrom函数:
#include <sys/socket.h> ssize_t recvfrom(int sockfd,void* buff,size_t nbytes,int flags,struct sockaddr* from,socklen_t *addrlen); 参数:sockfd : 套接字描述符 buff : 用于存放数据的缓冲区 nbytes : 缓冲区大小 flags : 暂时总设置为0 from : 用于存放UDP对端的套接字协议地址(输出参数) addrlen : UDP对端的套接字协议地址字节大小(输出参数) 注意:最后两个参数from和addrlen可以得知该UDP数据是谁发送过来的。 如果设置from和addrlen为NULL,表示我们忽略对端信息。 返回值: 成功返回读取到的字节数,出错返回-1。返回值0是被允许的,不同于TCP中read返回0表示对端已经关闭。
sendto函数:
#include <sys/socket.h> ssize_t sendto(int sockfd,const void* buff,size_t nbytes,int flags,const struct sockaddr* to,socklen_t *addrlen); 参数:sockfd : 套接字描述符 buff : 要发送的数据内存 nbytes : 数据大小 flags : 暂时总设置为0 to : 指向接收者的套接字地址结构(输入参数) addrlen : 上述套接字地址结构to的字节大小(输入参数) 注意:最后两个参数to和addrlen告知该数据要发给谁。 返回值:成功返回写入的字节数,出错返回-1 写入字节长度为0是被允许的,在UDP下,会形成一个只包含20字节的IP首部和一个8字节的UDP首部,没 有数据的IP数据报。
UDP的回射实例
fgets sendto recvfromstdin----------> -----------------------------------> UDP客户端 UDP服务器stdout<--------- <---------------------------------- fputs recvfrom sendto说明:(1)通过socket指定参数SOCK_DGRAM创建UDP套接字。(2)bind指定server端本地地址为INADDR_ANY(0),端口为众所周知。(3)UDP中没有TCP中的EOF,因此recvfrom不会终止。(4)recvfrom是阻塞的,因此提供的是一个迭代服务器,而不是并发服务器。
**//UDPserver代码**#include <iostream>#include <sys/socket.h>#include <sys/types.h>#include <strings.h>#include <arpa/inet.h>#include <unistd.h>using namespace std;#define MAXLINE 1024#define SERV_PORT 29988void dg_echo(int sockfd,struct sockaddr* pcliaddr,socklen_t clilen){ int n; socklen_t len; char mesg[MAXLINE]; for(;;){ len = clilen; n = recvfrom(sockfd,mesg,MAXLINE,0,pcliaddr,&len); sendto(sockfd,mesg,n,0,pcliaddr,len); }}int main(int argc , char** argv) { int sockfd = -1; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; sockfd = socket(AF_INET , SOCK_DGRAM , 0); bzero(&servaddr , sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(0); servaddr.sin_port = htons(SERV_PORT); bind(sockfd , (struct sockaddr*)&servaddr , sizeof(servaddr)); dg_echo(sockfd , (struct sockaddr*)&cliaddr , sizeof(cliaddr)); return 0;}
**//UDPClient代码**#include <iostream>#include <sys/socket.h>#include <sys/types.h>#include <string.h>#include <arpa/inet.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <strings.h>using namespace std;#define MAXLINE 1024#define SERV_PORT 29988void dg_cli(FILE* fp,int sockfd,const SA* pservaddr,socklen_t servlen){ int n; char sendline[MAXLINE]; char recvline[MAXLINE+1]; socklen_t len; struct sockaddr* preply_addr; preply_addr = (struct sockaddr*)malloc(servlen); while(fgets(sendline,MAXLINE,fp)!=NULL){ sendto(sockfd,sendline,strlen(sendline),0,pservaddr,servlen); len = servlen; n = recvfrom(sockfd,recvline,MAXLINE,0,preply_addr,&len); if(len!= servlen || memcmp(pservaddr,preply_addr,len)!=0){ continue; } recvline[n] = 0; fputs(recvline,stdout); }}int main(int argc , char** argv) { int sockfd = -1; struct sockaddr_in servaddr; if(argc != 2){ cout << "usage:udpcli <IPaddress>" << endl; exit(-1); } sockfd = socket(AF_INET , SOCK_DGRAM , 0); bzero(&servaddr , sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET , argv[1],&servaddr.sin_addr); dg_cli(stdin,sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); return 0;}
注意:上述代码可能出现问题:
(1)如果客户端sendto的数据丢失(比如网络问题),则recvfrom一直阻塞。
(2)如果客户端sendto数据达到,但是服务器发出数据没有达到客户端,则客户端也将一直处于阻塞。
UDP产生ICMP异步错误
做如下测试: (1)不启动UDPServer; (2)开启tcpdump抓包:sudo tcpdump -i lo (3)启动UDPClient:./UDPClient 127.0.0.1,发送数据。结果: 此时并没有发生回射,在tcpdump我们发现如下信息: 15:41:39.535289 IP localhost.50872 > localhost.29988: UDP, length 7 15:41:39.535300 IP localhost > localhost: ICMP localhost udp port 29988 unreachable, length 43 第一条显示本地端口50872向本地端口29988服务器端口发送数据,长度为7字节UDP 第二条显示回了一条ICMP包: udp port 29988 unreachable,端口不可达。总结: 服务并没有开启时会响应一个port unreachable的ICMP错误,但是该错误不会返回 给客户端,我们称这样错误为异步错误。产生异步错误原因: 该异步错误由sendto引起,UDP输出操作sendto仅仅表示在接口输出队列中具有存放数据报的空间,并不 代表发送成功。该ICMP错误是之后真正发送时才产生。解决异步错误的方法: 对于一个UDP套接字,由它引起的异步错误并不返回给它,除非它已经连接。在进程将其UDP套接字连接 到恰恰一个对端后,这些异步错误才返回给进程。
UDP的connect函数
udp使用connect函数必要性: 解决udp无法接受异步错误的问题。tcp和udp使用connect区别: (1)没有tcp连接的三次握手协议 (2)内核只是检查是否存在一个立即可知的错误(例如上述的ICMP错误) (3)内核记录对端的IP和port。 (4)立即返回。未连接的UDP套接字:使用socket创建UDP套接字默认如此。已连接的UDP套接字:调用connect结果。udp套接字调用connect影响: (1)发送数据时不能再指定IP和Port。即不能使用sendto,改用write和send 任何写入该套接字的数据会自动发往coonect指定对端。 (2)不必再使用recvfrom接收数据,改用read,recv,recvmsg。 (3)引发的任何异步错误,会立即返回给当前进程。对一个udp多次调用connect: (1)可以指定新的IP和Port (2)把套接字地址结构地址族成员指定为AF_UNSPEC,则断开套接字。如果此时返回一个FAFNOSUPPORT 错误,可以忽略。
客户端connect接收异步错误
void dg_cli2(FILE* fp,int sockfd,const SA* pservaddr,socklen_t servlen){ int n; char sendline[MAXLINE]; char recvline[MAXLINE+1]; connect(sockfd,(SA*)pservaddr,servlen); while(fgets(sendline,MAXLINE,fp)!=NULL){ write(sockfd,sendline,strlen(sendline)); n = read(sockfd,recvline,MAXLINE); if(n<0){ cout << strerror(errno) << endl; return ; } recvline[n] = 0; fputs(recvline,stdout); } }
调用connect指定对端IP和PORT;使用read替换recvfrom,使用write替换sendto。注意:如果服务器没有启动,在connect时并不会出错,当进行read时,则会返回错误:Connection refused。 如果使用的是TCP,则会进行三次握手,Connection refused则会在connect时就返回给进程。
UDP缺乏流量控制
测试实例:客户端不间断的向server发送大量数据,服务器不间断的接收数据。
//客户端发送数据 #define NDG 2000 #define DGLEN 1400 void dg_cli(FILE* fp,int sockfd,const SA* pservaddr,socklen_t servlen){ int i; char sendline[DGLEN]; for(i=0;i<NDG;i++){ sendto(sockfd,sendline,DGLEN,0,pservaddr,servlen); } } //服务器不间断接收数据并统计 static int count; void dg_echo(int sockfd,SA* pcliaddr,socklen_t clilen) { socklen_t len; char mesg[MAXLINE]; signal(SIGINT,recvfrom_int); for(;;){ len = clilen; recvfrom(sockfd,mesg,MAXLINE,0,pcliaddr,&len); count++; } } static void recvfrom_int(int signo){ cout<<"received " << count << " datagrams" << endl; exit(0); }
使用命令netstat -s -udp 可以查看系统网络数据包信息,如下: Udp: 245306 packets received 402051 packets to unknown port received. 3712 packet receive errors 651115 packets sent RcvbufErrors: 3712 IgnoredMulti: 1 可以查看接收到包,没有接收的包,接收错误的包,以及发送的包。测试结果: Client不控制流量的发送,Server不间断的接收,有一定概率接收的包小于发送的包。 也就是说:有包丢失了。结论: 发送或者接收UDP套接字有一定缓冲区,当缓冲区已经满时再发送数据,会导致数据的丢失。 同时一般发送端速度较快,接收端较慢,UDP发送端淹没接收端是轻而易举的。设置UDP缓冲区大小: int n = 220 * 1024; setsockopt(sockfd,SOL_SOCKET,SO_RECBUF,&n,sizeof(n));
0 0
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- UDP套接字编程
- Java UDP套接字编程
- 基本UDP套接字编程
- UDP套接字编程(一)
- UDP套接字编程(二)
- udp套接字编程 C#
- 基本UDP套接字编程
- 基本UDP套接字编程
- TCP / UDP套接字编程
- 机器学习算法思想简单梳理
- 64位win中用PLSQL Developer连接64位Oracle数据库
- UVA-10106 Product
- (4)
- trigger和triggerHander的区别
- UDP套接字编程
- 关于AndroidSDK配置时的tools目录下找不到adb.exe的错误
- UVA 11243 Texas Trip <三分 + 最大中求最小>
- JVM(Java虚拟机)笔记
- Codeforces 691E Xor-sequences【矩阵快速幂,好题】
- 最常用的10个Matlab快捷键,助你编程更高效
- 【arcgis】使用ArcGIS完成空间插值——趋势面分析法
- laravel中数据库迁移
- UVA-465 Overflow