基本UDP套接口编程 - fanqiang.com

来源:百度文库 编辑:神马文学网 时间:2024/04/29 10:00:45
概述
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);
}
(http://www.fanqiang.com)