Unix网络编程之UDP常见缺陷与实例

UDP与TCP相比,各有优缺点,下来来列举一下UDP的缺点:

1.UDP是一种不可靠的协议(缺乏流量控制)

实例代码:

//server.c

#include <stdlib.h>

#include <stdio.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <string.h>

int main()

{

int sockfd;

struct sockaddr_in servaddr,clientaddr;

char recv[1024];

socklen_t len;

int count = 0;

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

memset(&servaddr, ‘\0‘, sizeof(servaddr));

memset(recv, ‘\0‘, 1024);

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons( 5000 );

bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

for( ; ; )

{

recvfrom(sockfd, recv, 1024, 0, (struct sockaddr *)&clientaddr, &len);

memset(recv, ‘\0‘, 1024);

count++;

printf("count = %d\n",count);

}

close(sockfd);

}

//client.c

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <sys/socket.h>

#include <netinet/in.h>

int main()

{

int sockfd;

struct sockaddr_in servaddr;

char buff[1024];

int count = 0;

memset(&servaddr, ‘\0‘, sizeof(servaddr));

memset(buff, ‘\0‘, 1024);

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons( 5000 );

servaddr.sin_addr.s_addr = inet_addr("192.168.59.129");

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

for(count; count < 4000; count++)

{

sendto(sockfd, buff, 1024, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

}

close(sockfd);

return 0;

}

客户端快速给服务器发送4000个数据报,然后在服务器端统计接收到的数据报的个数,发现接收到的数据报数量小于4000,这是由于UDP并不是一种可靠的协议,一旦发生丢包等情况,没有重发等机制来应对这种情况。上面程序中出现的情况主要是由于服务器端的接收区缓冲区溢出导致的,一旦缓冲区溢出,后续的数据报就被丢弃。

针对上面这种情况,我们有两种方式来处理:

1.通过socket选项来修改接收缓冲区的大小,以容纳更多的数据报(通过setsockopt函数来设置)

2.发送端每次发送一个数据报以后,暂停一段时间(即不要连续发送大量的数据报,可以使用usleep,sleep等函数来挂起进程)

但是上面两种方式只能用来缓解这种丢包的情况,而不能消除,这是UDP本身的机制决定的。

2.UDP是一种无连接协议

由于UDP是无连接的,因此即使服务器端没有程序在运行,客户端仍然可以运行客户端程序:

//client.c

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <sys/socket.h>

#include <netinet/in.h>

int main()

{

int sockfd;

struct sockaddr_in servaddr;

char recv[1024];

char send[1024];

int len;

memset(&servaddr, ‘\0‘, sizeof(servaddr));

memset(recv, ‘\0‘, 1024);

memset(send, ‘\0‘, 1024);

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons( 5000 );

servaddr.sin_addr.s_addr = inet_addr("192.168.59.129");

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

sendto(sockfd, send, 1024, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

printf("stop at sendto\n");

recvfrom(sockfd, recv, 1024, 0, (struct sockaddr *)&servaddr, &len);

printf("stop at recvfrom\n");

close(sockfd);

return 0;

}

我们打开tcpdump工具来查看数据报的发送情况,然后运行客户端程序,可以看到客户端程序正常运行,但是阻塞在了recvfrom函数,并且通过tcpdump看到了如下的情况:

也就是说,数据在传输的过程中其实发生了错误(上例中是端口不可达错误,这是一个ICMP报文),但是客户端程序并不知道有错误发生,仍然阻塞在recvfrom等待数据返回,这样就不利于我们快速定位错误。

那么有没有什么办法来得到错误信息呢?

—使用已连接的UDP套接口能够得到错误信息。

Unix网络编程这本书上面说对于已连接的UDP套接口,不能给输出操作指定IP地址和端口号,即有如下两种方式:

1.使用write函数代替sendto函数

2.仍然使用sendto函数,但是不能指定IP地址与端口,即sendto函数的第五个参数必须为空指针(个人觉得这种说法太绝对)

但实际上经过试验,sendto中的第五个参数仍然可以指定IP地址与端口,只不过需要与connect函数的第一个参数保持一致(即由connect中的参数为准)。

//client.c

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <sys/socket.h>

#include <netinet/in.h>

int main()

{

int sockfd;

struct sockaddr_in servaddr;

char recv[1024];

char send[1024];

int len;

memset(&servaddr, ‘\0‘, sizeof(servaddr));

memset(recv, ‘\0‘, 1024);

memset(send, ‘\0‘, 1024);

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons( 5000 );

servaddr.sin_addr.s_addr = inet_addr("192.168.59.129");

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

sendto(sockfd, send, 1024, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

printf("stop at sendto\n");

recvfrom(sockfd, recv, 1024, 0, (struct sockaddr *)&servaddr, &len);

printf("stop at recvfrom\n");

printf("the imformation of error is %s\n", recv);

close(sockfd);

return 0;

}

该例与上一个例子相比多出了两条语句,但是在Ubuntu下运行发现,这个程序并没有阻塞在recvfrom函数上,因为通过connect建立连接,应用程序已经能够得到错误了,但是最后打印recv并没有打印出任何错误信息,这个与内核是否把ICMP消息反送给UDP套接口有关。我在Ubuntu下进行试验发现并没有返回这个ICMP消息。

注意:

如果是在TCP程序中调用connect函数,那么服务器端未运行程序,connect会直接返回错误(因为三次握手在此时建立),但是在UDP中则不会返回错误。

几个要注意的问题:

1.一般来说,在客户端我们并不指定ip和端口号,在这种情况下,端口号有内核临时分配(分配端口在第一次调用sendto函数时即确定,随后不能改变)。但是如果客户端有多个IP地址,那么客户端发送的每个UDP数据报的源IP地址可能就会不同。

2.如果客户端通过bind函数绑定了一个ip地址,但是内核决定使用另外一个数据链路将数据报发出,那么IP数据报将包含一个不同于外出链路IP地址的源IP地址。

3.在实际应用中,如果存在这样源IP地址变化的IP数据报,要如何进行识别他们属于同一个客户端?

时间: 2024-10-10 12:40:02

Unix网络编程之UDP常见缺陷与实例的相关文章

Unix网络编程之UDP实例

首先先看一下UDP客户/服务器交互的典型情形,如下图: 由上图可知,客户端程序不需要与服务器端建立连接,只需要调用sendto函数向服务器发送数据,或者调用recvfrom函数从服务器接收数据即可.最明显地特点就是服务器端只是比客户端多调用了一个bind函数来显式绑定一个端口.事实上客户端也可以通过bind来绑定端口,只不过没有那个必要. 实例代码: //server.c #include <stdlib.h> #include <stdio.h> #include <sys

java网络编程之UDP实例

package Socket; import java.net.DatagramPacket; import java.net.InetAddress; public class Dgram { public static DatagramPacket toDatagram(String s, InetAddress destIA, int destPort) { byte[] buf = new byte[s.length() + 1]; s.getBytes(0, s.length(), b

黑马程序员——Java网络编程之UDP传输

网络编程 网络模型 通讯要素:InetAddress(对象):ip地址,网络中设备的标识,不可记忆,可用主机名,本地回环地址:127.0.0.1主机名localhost 端口号 传输协议:UDP,将数据的源及目的封装成数据包中,不需要建立连接,每个数据包的大小限制在64K内,无连接,是不可靠协议,不需要建立连接,速度快.力求速度,不求数据的准确性.比如聊天软件,网络会议. TCP:建立连接,形成传输数据的通道,在连接中进行大数据量传输,通过三次握手完成连接,是可靠协议,必须建立连接效率稍低. S

网络编程之UDP编程

网络编程之UDP编程 UDP协议是一种不可靠的网络协议,它在通信的2端各建立一个Socket,但是这个Socket之间并没有虚拟链路,这2个Socket只是发送和接受数据的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送和接受数据报.值得注意的是:UDP编程必须先由客户端发出信息.一个客户端就是一封信,Socket相当于美国式邮筒(信件的收发都在一个邮筒中).端口与协议相关,所以TCP的30

Unix网络编程之IO复用

上篇存在的问题 在上一篇TCP套接字中,还存在着一些问题. 当客户端连接上服务器后,阻塞于从标准输入读入信息的状态,若此时服务器进程被杀死,即使给客户TCP发来一个 FIN结束分节,但是由于客户处于阻塞状态,它将看不到这个EOF,直到读取之后,此时可能已经过去了很长时间. 因此进程需要一种能力,让内核同时检测多个IO口是否就绪,这个能力就称为IO复用.这是由select和poll两个函数 支持的. select函数 作用 允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或

linux/unix网络编程之 select

转自http://www.cnblogs.com/zhuwbox/p/4221934.html linux 下的 select 知识点 unp 的第六章已经描述的很清楚,我们这里简单的说下 select 的作用,并给出 select 的客户端实例.我们知道 select 是IO 多路复用的一个最简单支持,poll 和 epoll 是 select 的升级版.在 UNIX 网络编程第五章读书笔记 我们遇到这样一个问题:当客户端阻塞在 fgets() 等待客户输入的时候,服务器端断开连接.而客户端却

unix网络编程之listen()详解

Unix网络编程描述如下: #include <sys/socket.h> int listen(int sockfd,  int backlog); 返回:若成功则为0,  若出错则为-1: 本函数通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用: 为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列: (1)未完成连接队列(incomplete connection queue),

UNIX网络编程之epoll的 accept , read , write

本文转载自:http://www.it165.net/os/html/201308/5868.html 非阻塞模式下的网络编程,非阻塞模式常常需要不停地进行轮询,大量耗费CPU资源,这种方式并不可取. 在一个非阻塞的socket上调用read/write函数,返回EAGAIN或者EWOULDBLOCK(注:EAGAIN就是EWOULDBLOCK). 从字面上看,意思是: EAGAIN: 再试一次 EWOULDBLOCK:如果这是一个阻塞socket, 操作将被block perror输出:Res

浅析Java网络编程之UDP传输(一)

网络传输方式一般有两种--TCP和UDP.本文用于简单了解一下UDP传输方式. 一.UDP有何特点? 1.面向无连接的: 2.面向报文的,且每一份报文不超过64KB: 3.不可靠但高效(不用建立连接): 4.支持一对一.一对多.多对一.多对多的交互通讯: 对于这些特点,没有什么为什么,事实而已啦! 二.在Java中怎么玩UDP? 在Java中,将建立UDP服务相关操作封装到了DatagramSocket类中,将数据报相关操作封装到了DatagramPacket类中. 三.具体玩的思路是什么? U