该函数提供的是一个迭代服务器,而不是像TCP服务器那样可以提供一个并发服务器。其中没有对fork的调用,因此单个服务器进程就得处理所有客户。一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。

对于本套接字,UDP层中隐含有排队发生。事实上每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。当进程调用recvfrom时,缓冲区中的下一个数据报以FIFO(先入先出)顺序返回给进程。

服务器程序:

#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 SERV_PORT 3333
#define MAXLINE 1024 #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0) typedef struct sockaddr SA;
void dg_echo(int sockfd, SA *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;
struct sockaddr_in servaddr, cliaddr; sockfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT); bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}

客户端程序:

#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 SERV_PORT 3333
#define MAXLINE 1024
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) typedef struct sockaddr SA;
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1]; while (fgets(sendline, MAXLINE, fp) != NULL) { sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
} int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr; if (argc != 2)
ERR_EXIT("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0);
}

1.数据报的丢失

我们的UDP客户/服务器例子是不可靠的。如果一个客户数据报丢失(譬如说,被客户主机与服务器主机之间的某个路由器丢失),客户将永远阻塞于dg_cli函数中的recvfrom调用,等待一个永远不会达到的服务器应答。类似的,如果客户数据报到达服务器,但是服务器的应答丢失了,客户也将永远阻塞于recvfrom调用。防止这样永远阻塞的一般方法是给客户的recvfrom调用设置一个超时。

仅仅给recvfrom调用设置超时并不是完整的解决办法。举例来说,如果确实超时了,我们将无从判定超时原因是我们的数据报没有到达服务器,还是服务器的应答没有回到客户。所以我们可以增加UDP客户/服务器程序的可靠性。(后面会有讲解)

2.服务器进程未运行

我们下一个要检查的情形是在不启动服务器的前提下启动客户。如果我们这么做后在客户上键入一行文本,那么什么也不发生。客户永远阻塞于它的recvfrom调用,等待一个永远不出现的服务器应答。

经过抓包分析,服务器主机响应的是一个“port unreachable”(端口不可达)ICMP消息。不过这个ICMP错误不返回给客户进程。我们称这个ICMP错误为异步错误。该错误由sendto引起,但是sendto本身却成功返回。我们知道从UDP输出操作成功返回仅仅表示在输出队列中具有存放所形成IP数据报的空间。该ICMP错误直到后来才返回,这就是称其为异步的原因。

一个基本规则是:对于一个UDP套接字,由它引发的异步错误却并不返回给它,除非它已连接。仅在进程已将其UDP套接字连接到恰恰一个对端后,这些异步错误才返回给进程。

注:只要SO_BSDCOMPAT 套接字选项没有开启,linux甚至对未连接的套接字也返回大多数ICMP(目的地不可达)错误。

3.验证接收到的响应

知道客户临时端口号的任何进程都可往客户发送数据报,而且这些数据报会与正常的服务器应答混杂。

我们的解决办法是修改recvfrom调用以返回数据报发送者的IP地址和端口号,保留来自数据报所发往服务器的应答,而忽略任何其他数据报。

void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr_in *preply_addr; preply_addr = malloc(servlen); while (fgets(sendline, MAXLINE, fp) != NULL) { sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); len = servlen;
n = recvfrom(sockfd, recvline, MAXLINE, 0, (SA*)preply_addr, &len);
if (len != servlen || memcmp(pservaddr, (SA*)preply_addr, len) != 0) {
printf("reply from %s (ignored)\n",inet_ntoa(preply_addr->sin_addr));
continue;
} recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
}

然而这样做照样存在一些缺陷,如果服务器运行在一个只有单个IP地址的主机上,那么这个版本的客户工作正常。然而如果服务器主机是多宿的(多个IP地址),该客户就有可能失败。例如服务器有2个IP地址(172.24.37.94和135.197.17.100),客户连接服务器(135.197.17.100),但是服务器响应的IP地址是(172.24.37.94),这样我们的程序就出问题。

一个解决办法是:得到由recvfrom返回的IP地址后,客户通过在DNS中查找服务器主机的名字来验证该主机的域名(而不是它的IP地址)。

另一个解决办法是:UDP服务器给服务器主机上配置的每个IP地址创建一个套接字,用bind捆绑每个IP地址到各自的套接字,然后再所有这些套接字上使用select(等待其中任何一个变得可读),再从可读的套接字给出应答。既然用于给出应答的套接字上绑定的IP地址就是客户请求的目的IP地址(否则该数据报不会被投递到达该套接字),这就保证应答的源地址与请求的目的地址相同。

UNIX网络编程——UDP回射服务器程序(初级版本)以及漏洞分析的更多相关文章

  1. UNIX网络编程——TCP回射服务器/客户端程序

    下面通过最简单的客户端/服务器程序的实例来学习socket API. serv.c 程序的功能是从客户端读取字符然后直接回射回去: #include<stdio.h> #include&l ...

  2. UNIX网络编程——使用select函数的TCP和UDP回射服务器程序

    服务器程序: #include <sys/wait.h> #include <string.h> #include <string.h> #include < ...

  3. 【Unix网络编程】chapter5TCP回射服务器程序

    chapter5  5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t ...

  4. 《UNIX网络编程》TCP客户端服务器例子

    最近在看<UNIX网络编程>(简称unp)和<Linux程序设计>,对于unp中第一个获取服务器时间的例子,实践起来总是有点头痛的,因为作者将声明全部包含在了unp.h里,导致 ...

  5. UNIX网络编程——UDP 的connect函数(改进版)

    上一篇我们提到,除非套接字已连接,否则异步错误是不会返回到UDP套接字的.我们确实可以给UDP套接字调用connect,然而这样做的结果却与TCP连接大相径庭:没有三次握手.内核只是检查是否存在立即可 ...

  6. UNIX网络编程卷1 时间获取程序server TCP 协议相关性

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 最初代码:  这是一个简单的时间获取server程序.它和时间获取程序client一道工作. ...

  7. Linux下select的用法--实现一个简单的回射服务器程序

    1.先看man手册 SYNOPSIS       /* According to POSIX.1-2001 */       #include <sys/select.h>       / ...

  8. UNIX网络编程——UDP缺乏流量控制(改进版)

    现在我们查看无任何流量控制的UDP对数据报传输的影响.首先我们把dg_cli函数修改为发送固定数目的数据报,并不再从标准输入读.如下,它写2000个1400字节大小的UDP数据报给服务器. 客户端程序 ...

  9. TCP回射服务器程序:str_echo函数

    str_echo函数执行处理每个客户的服务: 从客户读入数据,并把它们回射给客户 读入缓冲区并回射其中内容: read函数从套接字读入数据,writen函数把其中内容回射给客户 如果客户关闭连接,那么 ...

随机推荐

  1. [Educational Codeforces Round#22]

    来自FallDream的博客,未经允许,请勿转载,谢谢. 晚上去clj博客逛来逛去很开心,突然同学提醒了一下,发现cf已经开始40分钟了,慌的一B,从B题开始写,写完了B到E最后收掉了A,结果太着急B ...

  2. [bzoj4821][Sdoi2017]相关分析

    来自FallDream的博客,未经允许,请勿转载,谢谢. Frank对天文学非常感兴趣,他经常用望远镜看星星,同时记录下它们的信息,比如亮度.颜色等等,进而估算出星星的距离,半径等等.Frank不仅喜 ...

  3. 使用jquery.qrcode.js生成二维码

    通常生成二维码的方式有两种:第一种是java代码的形式,第二种是通过Js方式. 在这里我做个记录,用js生成二维码,可以在官网下载源码:http://jeromeetienne.github.io/j ...

  4. MySQl之最全且必会的sql语句

    创建一个名称为mydb1的数据库,如果有mydb1数据库则直接使用,如果无则创建mydb1数据库 create database if not exists mydb1; create databas ...

  5. 【实用】【移动端】Retain屏1px解决方案

    新浪微博HTML5版 微博的实现方式(rem + 小数px) <meta name="viewport" content="width=device-width,i ...

  6. Linux学习之CentOS(十六)-----内存置换空间(swap)之建置(转)

    内存置换空间(swap)之建置 安装时一定需要的两个 partition 啰! 一个是根目录,另外一个就是 swap(内存置换空间), swap 的功能就是在应付物理内存不足的情况下所造成的内存延伸记 ...

  7. GC机制

    java虚拟机中的垃圾回收机制是,一个类,当该对象没有更多的应用指向它时,就会被垃圾回收器给回收,从而释放资源.该机制不可以程序员手动调用去回收某个对象,系统自动会去调用,当然程序员可以建议垃圾回收器 ...

  8. ConcurrentHashMap1.7和1.8的不同实现

    ConcurrentHashMap 在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap,为 ...

  9. nginx 网络模型,cpu亲和等优点

    nginx优点1.IO多路复用epollIO多路复用:多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这里的"复用" 指的是复用同一个线程epollIO多路复用的实现方式 ...

  10. java中的final和volatile详解

    相比synchronized,final和volatile也是经常使用的关键字,下面聊一聊这两个关键字的使用和实现 1.使用 final使用: 修饰类表示该类为终态类,无法被继承 修饰方法表示该方法无 ...