[ 永遠的UNIX::UNIX技術資料的寶庫 ]   GB | BIG5

首頁 > 編程技術 > 網絡編程 > 正文
基本UDP套接口編程
本文出自:http://sunsland.top263.net 作者: (2001-10-23 10:00:00)


概述

UDP
無連接的 connectionless不可靠的 unreliable數據報協議 datagram

應用:DNS, NFS, SNMP, ICQ

TCP
面向連接的 connection-oriented可靠的 reliable字節流協議 byte stream

應用:www, telnet ,ftp


UDP 客戶-服務器程序的套接口函數

recvfrom 和 sendto 函數

#include <sys/socket.h>
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 <IPaddress>");
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 <IPaddress>");
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)
    進入【UNIX論壇

相關文章
基本UDP套接口編程 (2001-10-23 10:00:00)
套接口選項 (2001-10-23 09:00:00)
基本TCP套接口編程二 (2001-10-22 15:00:01)
基本TCP套接口編程一 (2001-10-22 12:00:00)
套接口編程基礎 (2001-10-22 10:00:00)
UNIX的套接口(Socket)編程簡介 (2001-04-19 12:48:30)
 

★  樊強制作 歡迎分享  ★