该函数提供的是一个迭代服务器,而不是像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. [BZOJ]3243 向量内积(Noi2013)

    小C做了之后很有感觉的题目之一,但因为姿势不对调了很久. Description 两个d 维向量A=[a1,a2,...,ad]与B=[b1,b2,...,bd]的内积为其相对应维度的权值的乘积和,即 ...

  2. bzoj2669[cqoi2012]局部极小值 容斥+状压dp

    2669: [cqoi2012]局部极小值 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 774  Solved: 411[Submit][Status ...

  3. 镜像文件、光盘、iso文件、启动盘

    刚入大学,有一门计算机硬件维修课程,韩国彬老师(学生们公认的好老师).当时韩老师教给了我们好多实用的好东西,例如装系统,做镜像文件,装虚拟机,ghost版本系统,计算机组装等等.由于高中刚刚过度到大学 ...

  4. c# &sqlserver

    sqlserver CHARINDEX函數返回字符或者字符串在另一個字符串中的起始位置 eg: select bh,left(djxl,CHARINDEX('A',djxl)) as ,sum(rs) ...

  5. jquery easyui datagrid动态改变title的值

    title:'<input type="text" id="txtTitle1" style="background:none;border:n ...

  6. 一个页面从输入url到页面加载显示完成,中间都经历了什么

    第一种解释: 一般会经历以下几个过程: 1.首先,在浏览器地址栏中输入url 2.浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容.若没有,则跳到第三步操作. 3 ...

  7. 实践详细篇-Windows下使用VS2015编译安装Caffe环境(CPU ONLY)

    学习深度学习背景 最近在做一款抢票软件,由于12306经常检测账号状态,抢票抢着抢着就需要重新登录了,然后登录是需要验证码的.所以我最开始是想到了使用java基于感知哈希算法pHash做相似度匹配识别 ...

  8. day0203 XML 学习笔记

    day02, 03 1. xml语言和作用 2. xml语法详解 2.1 xml 语法声明 2.1.1 encoding 属性 2.1.2 standalone 属性 2.2 xml 元素(Eleme ...

  9. MYSQL 索引类型、什么情况下用不上索引、什么情况下不推荐使用索引

    mysql explain的使用: http://blog.csdn.net/kaka1121/article/details/53394426 索引类型 在数据库表中,对字段建立索引可以大大提高查询 ...

  10. Android超精准计步器开发-Dylan计步

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/52868803 本文出自[DylanAndroid的博客] Android超精准 ...