Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1255478
  • 博文数量: 404
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 5382
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-03 16:29
文章存档

2010年(40)

2009年(140)

2008年(224)

我的朋友

分类: LINUX

2009-05-25 11:10:18

概述

UDP
无连接的 connectionless不可靠的 unreliable数据报协议 datagram

应用:DNS, NFS, SNMP, ICQ

TCP
面向连接的 connection-oriented可靠的 reliable字节流协议 byte stream

应用:www, telnet ,ftp


UDP 客户-服务器程序的套接口函数

recvfrom 和 sendto 函数

#include
ssize_t recvfrom(int sockfd, void *buff, size nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);

sockfd: 描述字

buff: 指向输入缓冲器的指针

nbytes: 读字节大小

flag: 标志:0

from :对方协议地址

addrlen: 对方协议地址长度

函数返回值: 读入数据的长度,可以为0.

ssize_t sendto(int sockfd, void *buff, size nbytes, int flags, const struct sockaddr *to, socklen_t *addrlen);

TCP的字节流输入输出函数:
ssize_t readn(int sockfd, void *buff, size nbytes) ; 
ssize_t writen (int sockfd, void *buff, size nbytes);


UDP回射服务器程序

//服务器main主程序

#include "unp.h"
Int main(int argc, char **argv)
{
int sockfd; //定义套接字
struct sockaddr_in servaddr, cliaddr; //IPv4套接口地址定义
sockfd = Socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字
bzero(&servaddr, sizeof(servaddr)); //地址结构清零
servaddr.sin_family = AF_INET; //IPv4协议
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//内核指定地址
servaddr.sin_port = htons(SERV_PORT); //9877 端口
/*分配协议地址,绑定端口*/
Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); 
/* 回射子程序*/
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}

回射子程序:

include "unp.h"
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
int n; //读入字节数
socklen_t len; //协议地址长度, 没有这个参数用 clilen也可以
char mesg[MAXLINE];
for ( ; ; ) {
len = clilen;
/* 读入一行 */
n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
/* 回射到对方套接口 */
Sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
}


UDP回射客户程序

//客户 main主程序

include "unp.h"
int main(int argc, char **argv) //命令行的第二个参数代表服务器地址
{ int sockfd; //套接字
struct sockaddr_in servaddr; //服务器地址结构
/* 必须在命令行指定服务器地址*/
if (argc != 2) err_quit("usage: udpcli ");
bzero(&servaddr, sizeof(servaddr)); //地址结构清零
servaddr.sin_family = AF_INET; //IPv4
servaddr.sin_port = htons(SERV_PORT); //9877端口
/*网络字节序的IP地址*/
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); 
/*建立UPD套接口*/
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
/*回射客户端子程序, stdin 为标准输入:键盘*/
dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));
exit(0); //子程序结束后退出程序
}

//客户端回射子程序

#include "unp.h"
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n; //读入字节数
char sendline[MAXLINE], recvline[MAXLINE + 1]; // 1:结束标志占用
/* 从键盘读入一行 */
while (Fgets(sendline, MAXLINE, fp) != NULL) { //如果不是^D结束
/* 将读入行发送到服务器套接口*/
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
/*从读入回射,读入字节数为n, 不关心从何处读入
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0; /* recvline字符串的结束标志*/
Fputs(recvline, stdout); //输出到标准输出:显示器
} //while循环结束:直到从键盘读入结束符^D为止
}


验证收到的响应

void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n; socklen_t len;
char sendline[MAXLINE], recvline[MAXLINE + 1];
struct sockaddr *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, preply_addr, &len);
/*对方套接口地址长度和指定服务器地址长度不相同*/
/*或套接口地址结构也不相同时,*/
if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) {
printf(“reply from %s (ignored)\n”, //忽略回射行,并输出对方地址
Sock_ntop(preply_addr, len) ); 
continue; //下一轮循环
}
recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
}


服务器进程未运行

回射服务器-客户端程序执行的基本步骤:

——启动服务器
——启动客户程序

服务器没有启动,客户端没有回射行,一直等待

用tcpdump观察数据包 tcpdump icmp or arp or port 9877

有ARP请求和应答:端口不可达 port ... unreachable

异步错误:由sendto 引起的ICMP错误, 而sendto本身并不返回该错误

用已连接套接口才能返回到UDP套接口,需要调用connect.



UDP调用CONNECT

在末连接UDP套接口上给两个数据报调用函数sendto导致内核执行下列六步


1.连接套接口;
2.输出第一个数据报
3.断开套接口连接;
4.连接套接口,
5.输出第二个数据报;
6.断开套接口连接

已连接套接口发送两个数据报的导致内核执行如下步骤;


1.连接套接口;
2.输出第一个数据报;
3.输出第二个数据报。

对同一套接口发送时,耗时减少1/3


dg_cli 函数(修订版)

调用connect 函数(有ICMP错误返回)
用read和write代替sendto 和 recvform

/* 调用connect函数的UDP 回射客户子程序*/

void 
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
/* 与对方建立连接 */
Connect(sockfd, (SA *) pservaddr, servlen);

while (Fgets(sendline, MAXLINE, fp) != NULL) {
Write(sockfd, sendline, strlen(sendline));
n = Read(sockfd, recvline, MAXLINE);
recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
}


UPD缺乏流量控制

UDP没有流量控制,它是不可靠的。

如果UDP发送方比UDP接收方运行速度快, 可能导致接收缓冲区满而造成数据报丢失。

对服务器或客户来说,并没有给出任何指示说这些数据报已丢失。

UDP套接口缓冲区

由UDP给特定套接口排队的UDP数据报数目受限于套接口接收缓冲区的大小。

用SO_RCVBUF套接口选项改变此值,可以改善数据报丢失的情况,但并不能从根本

上解决问题。

/*增大套接口接收队列大小的函数*/

static void recvfrom_int(int); //内部函数
static int count;
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen){
int n; socklen_t len;
char mesg[MAXLINE];
Signal(SIGINT, recvfrom_int);
n = 240 * 1024;
Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
for ( ; ; ) {
len = clilen;
Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
count++;
}
}
static void recvfrom_int(int signo) {
printf("\nreceived %d datagrams\n", count);
exit(0);
}


UDP中外出接口的确定

已连接UDP套接口可用来确定用于待定目标的外出接口。

内核选择本地IP地址(假设进程并没有调用bind以明确地指派它)。

这个本地IP地址是通过给目的IP地址按索路由表,然后使用结果接口的主IP地址而选定的。

例程:

int main(int argc, char **argv)
{
int sockfd;
socklen_t len;
struct sockaddr_in cliaddr, servaddr;
if (argc != 2) err_quit("usage: udpcli ");
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
len = sizeof(cliaddr);
Getsockname(sockfd, (SA *) &cliaddr, &len);
printf("local address %s\n", Sock_ntop((SA *) &cliaddr, len));
exit(0);
}

阅读(1132) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~