当前位置:首页 > 技术文章 > 正文内容

linux网络编程之socket:基于UDP协议的网络程序

arlanguage3个月前 (01-31)技术文章25

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

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

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 的关系一致。

echoser_udp.c:

/*************************************************************************

> File Name: echoser_udp.c

> Author: Simba

> Mail: dameng34@163.com

> Created Time: Sun 03 Mar 2013 06:13:55 PM CST

************************************************************************/

#include

#include

#include

#include

#include

#include

#include

#include

#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;

}

echocli_udp.c:

/*************************************************************************

> File Name: echocli_udp.c

> Author: Simba

> Mail: dameng34@163.com

> Created Time: Sun 03 Mar 2013 06:13:55 PM CST

************************************************************************/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#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 协议来说,server与client 的界限更模糊了,只要知道对等方地址(ip和port) 都可以主动发数据。

二、UDP编程注意点

1、UDP报文可能会丢失、重复

2、UDP报文可能会乱序

3、UDP缺乏流量控制

4、UDP协议数据报文截断

5、recvfrom返回0,不代表连接关闭,因为udp是无连接的。

6、ICMP异步错误

7、UDP connect

8、UDP外出接口的确定

9、太大的UDP包可能出现的问题

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,即123点所提到的。比如 如果发送端速度较快,而接收端较慢,很可能会产生 ICMP Source Quench Error,丢弃一些数据包。

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

#include

#include

#include

#include

#include

#include

#include

#include

#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 协议不会有粘包问题,因为一次就接收一个消息。输出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./trunc

n=1 A

............

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

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

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

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_udp

dfsaf

................

当我们在键盘敲入几个字符,sendto只是把Buf的数据拷贝到sock对应的缓冲区中,此时服务器未开启,协议栈返回一个ICMP异步错误,但因为前面没有调用connect“建立”一个连接,则recvfrom时不能收到这个错误而一直阻塞。现在我们在while 循环的外面添加一句:connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)); 再次测试一下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_udp

dfsaf

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 出去。

关于第9点。假设现在我们发送一个8192B 的IP数据报,必须分片传输,如果此时目的地址arp 并没有缓存,那么每一片都会发起arp 请求,此时会造成 arp flooding(RFC 建议的最大发送速率是每秒一次)。此时每一片都在等待arp reply,系统实现只会将最后一片发送到目的地(ARP input queue was LIFO ),也就是说,其他片都被丢弃了。对等方的IP层当接收到第一个到来的片时(不一定是偏移为0的片)会启动定时器,如果在30~60s 内的超时时间内没有接收到所有的片,则会丢弃所有接收到的片。但需要注意的是不一定会产生 ICMP "time exceeded during reassembly" error (ICMP 超时错误类型为11,code为0表示是TTL为0超时,code为1表示对方重组分片超时),只有在已经接收到偏移为0的片,即包含udp头部的片时才会产生此种错误,因为这个时候ICMP报文的接收方通过头部(源端口号,如下ICMP超时报文的payload)才知道是哪个进程发送的这个IP报文被丢弃了。实际上有没有产生ICMP超时报文并不是那么重要,因为系统假设TCP层 或者使用UDP的应用程序最终会timeout 导致重传。


需要C/C++ Linux服务器开发学习资料私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

扫描二维码推送至手机访问。

版权声明:本文由AR编程网发布,如需转载请注明出处。

本文链接:http://www.arlanguage.com/post/1899.html

标签: nginx 截断
分享给朋友:

“linux网络编程之socket:基于UDP协议的网络程序” 的相关文章

nginx做限流设置

一、限流nginx设置nginx限流使用模块 upstream放置在http模块#限流并发upstream node{ server 127.0.0.1:8080 max_conns=1; }#超出的请求会返回502状态码放置在server模块#测试地址,访问服务器py路径会转发到本机的8080端口...

什么是Nginx?为什么使用Nginx?《Nginx的作用及优点》

前言 为毛要用nginx服务器代理,不直接用tomcat 7.0,还做多了一次接请求? 这个是我想问的,公司的新项目是要用Nginx+tomcat7+jdk开发的,用户命名可以直接访问tomcat,为啥还要用Nginx?这货是个啥玩意?什么是Nginx? 根据前面的对比,我们可以了解到Nginx是一...

nginx限制php程序“跨站”访问 nginx限制只能域名访问

我秀站外合作有一个需求:需在一台web服务器上增加一个虚拟主机用来做图片资源站,所用程序为第三方,担心有后门程序,因此希望最好隔断与原机器其他服务的关系。思考了一下,确实有一些风险存在。目前我们服务器上都统一使用nobody用户启动nginx和php,包括web目录,这些机器上部分有多个域名在一起运...

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.For online documentation and suppo...

给你的Nginx加个防火墙

引言朋友的一个 WordPress 站经常访问慢。看了一下日志,发现整天被扫描网站目录,如phpmyadmin 或者 SQL 文件,和被 CC攻击。找了一下,发现 ngx_lua_waf 是个不错的方案,但是太久不更新了,而且代码我看不懂,猝最后找到 oneinstack 一键包内置的 ngx_lu...

面试常问知识点:Nginx设置代理的一个注意点

前几天,重启了下Nginx代理服务,发现报错了,以下是本次的思考。1:先解决问题查看Nginx错误日志:40 SSL_do_handshake() failed (SSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handsha...