一、下图是典型的UDP客户端/服务器通讯过程

下面依照通信流程,我们来实现一个UDP回射客户/服务器:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

当套接字处于“已连接”的状态时,才可以使用send,当flags = 0 时 send 与 write 一致。

且 send(sockfd, buf, len, flags);  即  sendto(sockfd, buf, len, flags, NULL, 0);

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

recv 与 recvfrom 的关系与 send 与 sendto 的关系一致。

recvfrom的最后两个参数类似于accept的最后两个参数:返回时其中套接字地址结构的内容告诉我们是谁发送了数据报(UDP情况下)或是谁发起了连接(TCP情况下)。sendto的最后两个参数类似于connect的最后两个参数:调用时其中套接字地址结构被我们填入数据报将发往(UDP情况下)或与之建立连接(TCP情况下)的协议地址。

服务器代码serv.c:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0) void echo_ser(int sock)
{
char recvbuf[1024] = {0};
struct sockaddr_in peeraddr;
socklen_t peerlen;
int n; while (1)
{ peerlen = sizeof(peeraddr);
memset(recvbuf, 0, sizeof(recvbuf));
n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *)&peeraddr, &peerlen);
if (n == -1)
{ if (errno == EINTR)
continue; ERR_EXIT("recvfrom error");
}
else if(n > 0)
{ fputs(recvbuf, stdout);
sendto(sock, recvbuf, n, 0,
(struct sockaddr *)&peeraddr, peerlen);
}
}
close(sock);
} int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket error"); struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind error"); echo_ser(sock); return 0;
}

客户端代码cli.c:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) void echo_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{ sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
if (ret == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
} fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
} close(sock); } int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket"); echo_cli(sock); return 0;
}

编译运行server,在两个终端里各开一个client与server交互,可以看到server具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,此时client还能和server联系上。和前面TCP程序的运行结果相比较,我们可以体会无连接的含义。

二、UDP编程注意点

1、UDP报文可能会丢失、重复
2、UDP报文可能会乱序
3、UDP缺乏流量控制
4、UDP协议数据报文截断
5、recvfrom返回0,不代表连接关闭,因为udp是无连接的。
6、ICMP异步错误
7、UDP connect
8、UDP外出接口的确定

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,即123点所提到的。

对于第4点,可以写个小程序测试一下:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind"); sendto(sock, "ABCD", 4, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); char recvbuf[1];
int n;
int i;
for (i = 0; i < 4; i++)
{
/* udp是报式协议,即若一次性接收的空间小于发来的数据,有可能造成报文截断,
* 但一定没有tcp的粘包问题 */
n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
if (n == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
}
else if(n > 0)
printf("n=%d %c\n", n, recvbuf[0]);
}
return 0;
}

上述程序是自己发送数据给自己,发送了4个字节,但我们只提供1个字节的缓冲区recvbuf,第一次recvfrom 读取一个字节,但接下去循环却读不到剩下的数据了,因为udp 是报式协议,如果一次性接收的缓冲区小于发来的数据,有可能造成报文截断反观tcp流式协议,可以一次读取一个数据包的一部分,也可以一次性读取多个数据包,但这也正是其会造成粘包问题的来源,所以也说udp 协议不会有粘包问题,因为一次就接收一个消息。输出如下:

huangcheng@ubuntu:~$ ./a.out
n=1 A

接收了一个字符之后,再次recvfrom 就阻塞了。

对于第5点,如果我们使用sendto 发送的数据大小为0,则发送给对方的是只含有各层协议头部的数据帧,recvfrom 会返回0,但并不代表对方关闭连接,因为udp 本身没有连接的概念。

第678点合起来一起讲,可以看到我们的客户端程序现在没有调用connect,不运行服务器程序,直接运行客户端程序(上面的客户端程序:cli.c),查看现象:

huangcheng@ubuntu:~$ ./cli
huangcheng

当我们在键盘敲入几个字符,sendto只是把Buf的数据拷贝到sock对应的缓冲区中,此时服务器未开启,协议栈返回一个ICMP异步错误,但因为前面没有调用connect“建立”一个连接,则recvfrom时不能收到这个错误而一直阻塞。

现在我们在while 循环的外面添加一句:

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

再次测试一下:

huangcheng@ubuntu:~$ ./cli
huangcheng
recvfrom: Connection refused

此时recvfrom 就能接收到这个错误而返回了,并打印错误提示。

其实connect 并没有真正建立一个连接,即没有3次握手过程,只是维护了一种状态,绑定了远程地址,因为如此在调用sendto 时也可以不指定远程地址了,如 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);  甚至也可以使用send 函数

send(sock, sendbuf, strlen(sendbuf), 0);

假设现在客户端有多个ip地址,由connect 或 sendto 函数提供的远程地址的参数,系统会选择一个合适的出口,比如远程ip 是192.168.2.10, 而客户端现在的ip 有 192.168.1.32 和 192.168.2.75 那么会自动选择192.168.2.75 这个ip 出去。

UNIX网络编程——基于UDP协议的网络程序的更多相关文章

  1. 网络编程(基于udp协议的套接字/socketserver模块/进程简介)

    一.基于UDP协议的套接字 TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就 ...

  2. JAVA基础知识之网络编程——-基于UDP协议的通信例子

    UDP是一种不可靠的协议,它在通信两端各建立一个socket,这两个socket不会建立持久的通信连接,只会单方面向对方发送数据,不检查发送结果. java中基于UDP协议的通信使用DatagramS ...

  3. 网络编程: 基于UDP协议的socket

    udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接 UDP协议的通信优势: 允许一个服务器同时和多个客户端通信, TCP不行 服务端 import socket sk = socket ...

  4. 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程

    Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...

  5. 基于UDP协议的网络编程

    UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送.接收数据报的对象. Java使用DatagramSock ...

  6. 网络编程(UDP协议-聊天程序)

    网络编程中的UDP协议中聊天程序,发送端口,和接受端口. 发送端口(Send): <span style="font-size:18px;">package cn.it ...

  7. java 网络编程基础 UDP协议的Socket:DatagramSocket;广播Socket:MulticastSocket

    什么是UDP协议: UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket 但这两个 Socket之间并没有虚拟链路,这两个Socket只是发送.接收数据报的对象.Java 提供了 ...

  8. 基于UDP协议的网络程序

    一.下图是典型的UDP客户端/服务器通讯过程 下面依照通信流程,我们来实现一个UDP回射客户/服务器 #include <sys/types.h>  #include <sys/so ...

  9. 网络编程——基于UDP的网络化CPU性能检测

    网络化计算机性能检测软件的开发,可对指定目标主机的CPU利用率进行远程检测,并自动对远程主机执行性能指标进行周期性检测,最终实现图形化显示检测结果. 网络通信模块:(客户端类似,因为udp是对等通信) ...

随机推荐

  1. rasa_core:基于机器学习的对话引擎

    用机器学习管理你的对话,让它提升每一个对话.Rasa Core引导对话,考虑对话的历史和外部环境. 而不是成千上万的规则,Rasa 从真正的对话中挑选模式. 现在是扔掉你的状态机的时候了! Manag ...

  2. Servlet init()

    有时候希望在servlet首次载入时,执行复杂的初始化任务,但并不想每个请求都重复这些任务的时候,用init()方法他在servlet初次创建时被调用,之后处理每个用户的请求时,则不在调用这个方法.因 ...

  3. linux修改root账户的用户名所得的教训

    之前linux服务器的密码被别人改过, 然后叫服务器相关的负责人重置了root账户(服务器负责人在客户所在公司), 重置好之后, 领导叫更改下root 用户名和密码, 于是我二话不说就开始找方法, 找 ...

  4. JS运行机制之 Event Loop 的思考

    先举个栗子,如下: for (var i = 0; i < 5; i++) { setTimeout(function() { console.log('i: ',i); //一秒之后输出几乎没 ...

  5. JAVA反射之Class类的练习

    package zhang; /** * JAVA反射之CLass类的练习 * * 在面向对象的语言里,万事万物皆对象,那么类是谁的对象呢? * 类的类型是CLass * * */ class Tes ...

  6. ubuntu15.10 opencv3.1 安装配置codeblocks

    安装codeblocks: sudo add-apt-repository ppa:damien-moore/codeblocks-stable // 添加codeblocks的ppa sudo ap ...

  7. ABP文档笔记 - 通知

    基础概念 两种通知发送方式 直接发送给目标用户 用户订阅某类通知,发送这类通知时直接分发给它们. 两种通知类型 一般通知:任意的通知类型 "如果一个用户发送一个好友请求,那么通知我" ...

  8. spring cloud 入门系列四:使用Hystrix 实现断路器进行服务容错保护

    在微服务中,我们将系统拆分为很多个服务单元,各单元之间通过服务注册和订阅消费的方式进行相互依赖.但是如果有一些服务出现问题了会怎么样? 比如说有三个服务(ABC),A调用B,B调用C.由于网络延迟或C ...

  9. 无忧代理免费ip爬取(端口js加密)

    起因 为了训练爬虫技能(其实主要还是js技能-),翻了可能有反爬的网站挨个摧残,现在轮到这个网站了:http://www.data5u.com/free/index.shtml 解密过程 打开网站,在 ...

  10. Linux设置文件读写权限

    设置文件夹的读写权限: sudo chmod -R 777 /data 权限码描述 sudo chmod 600 ××× (只有所有者有读和写的权限)sudo chmod 644 ××× (所有者有读 ...