天天看點

UNP學習筆記3——基本UDP套接字程式設計

1 概述

TCP和UDP網絡程式設計存在一些本質的差異,主要是由于傳輸層的差别:UDP是無連接配接的不可靠的資料報協定,而TCP是面向連接配接的位元組流協定。

下圖是典型的UDP用戶端和伺服器之間的通信流程。客戶不與伺服器建立連接配接,而是隻管使用sendto函數。伺服器不接受來自客戶的連接配接,而是隻管調用recvfrom函數,等待某個客戶的資料到達。

UNP學習筆記3——基本UDP套接字程式設計

本章學習用于UDP套接字的兩個新函數recvfrom和sendto,并使用UDP重寫ECHO程式。還将學習connect函數在UDP套接字中的用法,同時了解異步錯誤的概念

2 recvfrom和sendto函數

這兩個函數類似标準的read、write函數,不過需要額外的三個參數。

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);
/*以上函數傳回:成功傳回讀或寫到的位元組數,出錯傳回-1*/      

前三個參數sockfd、buff和nbytes分别代表:描述符、指向輸入或寫出緩沖區的指針和讀寫的位元組數。

flags參數在讨論recv、send、recvmsg和sendmsg函數時再介紹,這裡預設設定為0。

sendto的to參數指向一個含有資料報接受者的協定位址(IP位址及端口号)的套接字位址結構,其大小由addrlen指定。recvfrom的from參數指向一個含有資料報發送者的協定位址(IP位址及端口号)的套接字位址結構,其大小由addrlen指定。注意sendto的最後一個參數是一個整數值,而recvfrom的最後一個參數是一個指向整形的指針(即值-結果函數)。

寫一個長度位(0)的資料報是可行的(TCP不允許),這會發送一個隻有IP首部和UDP首部,沒有資料的資料報。類似的,recvfrom傳回0值也是可以的(TCP則代表對端已關閉連接配接)。如果recvfrom的from參數是一個空指針,相應的addrlen為0,說明我們不關心發送者的協定位址。recvfrom和sendto函數都可以用于TCP,盡管通常不這麼做。

3 最原始的UDP Echo程式

 udp_echo_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void echo_server(int sockfd, struct sockaddr *p_client_addr, socklen_t cli_len) {
    
    int n;
    socklen_t len;
    char buf[1024];
    
    while(1) {
        len = cli_len;
        n = recvfrom(sockfd, buf, 1024, 0, p_client_addr, &len);
        sendto(sockfd, buf, n, 0, p_client_addr, len);
    }
}


int main(int argc, char **argv) {
    
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    
    sockfd=socket(AF_INET, SOCK_DGRAM, 0);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    server_addr.sin_port=htons(12345);
    
    if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr))<0)
        perror("bind error!");
    
    echo_server(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr));
        
}      

該程式存在一些問題:

  1. 函數永遠不會終止,因為UDP是一個無連接配接的協定,沒有像TCP中EOF之類的東西
  2. 該函數提供的是一個疊代伺服器,而不是并發服務。是以單個伺服器程序就得處理所有客戶
  3. 對于本套接字,UDP層隐含有排隊的發生

udp_echo_client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void echo_client(FILE *fp, int sockfd, const struct sockaddr *p_server_addr, socklen_t serv_len) {
    
    int n;
    char send[1024], recv[1024];
    while(fgets(send, 1024, fp) !=NULL) {
        sendto(sockfd, send, strlen(send), 0, p_server_addr, serv_len);
        n=recvfrom(sockfd, recv, 1024, 0, NULL, NULL);
        recv[n]='\0';
        fputs(recv, stdout);
    }
}

int main(int argc, char **argv) {
    int sockfd;
    struct sockaddr_in server_addr;
    
    if(argc!=2) {
        printf("usage: udp_client <IP Address>\n");
        exit(0);
    }
    
    sockfd=socket(AF_INET, SOCK_DGRAM, 0);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(12345);
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);    
    
    echo_client(stdin, sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
}      

該程式也隐含一個問題:

  1. 用戶端尚未請求核心給它的套接字指派一個臨時端口
  2. recvfrom第5個和第6個參數是空指針,表示不關心資料是由誰發送。是以任何程序的資料包都會被當成是伺服器的應答。
  3. UDP是不可靠的。如果一個資料報丢失,客戶将永遠阻塞在recvfrom函數上。

假設在伺服器不啟動的情況下啟動用戶端,資料報由客戶發出,伺服器主機響應一個端口不可達的ICMP消息,而這個ICMP消息不傳回給客戶程序,客戶将永遠阻塞于recvfrom調用,等待伺服器的應答。我們稱這個錯誤為異步錯誤(asynchronous error)。該錯誤由sendto引起,但sendto本身卻成功傳回。UDP輸出成功僅僅代表在接口隊列中有存放IP資料報的空間。改ICMP直到後來才傳回,這就是稱其為異步的原因。

4 UDP程式例子小結

轉載于:https://www.cnblogs.com/alwayswangzi/p/6595310.html

繼續閱讀