UDP Socket編程基礎

概述
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) ”, //忽略回射行,並輸出對方地址
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(" received %d datagrams ", 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 ", Sock_ntop((SA *) &cliaddr, len));
exit(0);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章