Linux 基本UDP套接字編程

UDP(User Datagram Protocol) : 用戶數據報協議,是一種無連接,不可靠的數據傳輸服務。與TCP不同的是,它不需要建立連接就可以直接傳輸數據,也就不存在關閉連接之類的問題。
常見的UDP程序有:DNS,NFS,SNMP。

一、recvfrom 和 sendto 函數

  recvfrom 和 sendto 函數主要用於UDP數據的讀寫操作,一個用於接收網絡數據,一個用於向對方發生數據。雖然都可以用於TCP,但一般不這麼做。

/* recvfrom 
 * 返回值:成功則爲讀到的字節數(可以爲0),錯誤則爲-1;
 * 函數定義:
 */
# include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
                 struct sockaddr *from, socklen_t *addrlen);

/* 說明
 * sockfd:要接收數據的套接字描述符;
 * buff:接收數據緩衝區;
 * nbytes:請求接收數據字節個數(緩衝區大小);
 * flag:設置一些參數,一般爲0;
 * from:數據發送者的套接字地址結構;
 * addrlen:from的長度/字節數(值-結果參數);
 * recvfrom的返回值可以是0,即沒有數據只有數據報頭部的UDP報文;
 * 若是不關心數據發送者地址,from可以爲空,相應addrlen也要爲空;
 */


/* sendto
 * 返回值:成功則爲發送的字節數(可以爲0),錯誤則爲-1;
 * 函數定義:
 */
# include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
               const struct sockaddr *to, socklen_t addrlen);

/* 說明
 * sockfd:要發送數據的套接字描述符;
 * buff:發送數據緩衝區;
 * nbytes:可以發送數據字節個數;
 * flag:設置一些參數,一般爲0;
 * from:數據接收者的套接字地址結構;
 * addrlen:from的長度/字節數,注意是整數不是指針;
 * sendto的返回值也可以是0,即寫一個空的數據報;
 */

二、簡單的udp回射客戶/服務器程序

  基於UDP協議的客戶/服務器程序與基於TCP的不同,不需要建立連接,也不需要分別對不同的客戶端進行分別處理,只是簡單的進行數據發送和接收。一般來說大多數TCP服務器都是併發的,因爲它需要與多個客戶端保持連接,而UDP服務器多是迭代的,即只需要不停的循環進行接收數據就行。UDP層中隱含有排隊發生,每一個UDP套接字都有一個接收緩衝區,到達的數據報都進入這個緩衝區,而recvfrom函數就從這個緩衝區按照順序讀取數據報。因此不管是哪一個客戶端發來的數據都放在緩衝區,由recvfrom函數來讀取,不需要類似tcp中建立子進程來處理每一個客戶端。

/* 服務器 */
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>   //INADDR_ANY

void udp_echo(int sockfd, struct sockaddr* pcliaddr, socklen_t clilen);

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666);

    bind(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
    udp_echo(sockfd, (struct sockaddr*) &cliaddr, sizeof(cliaddr));
    return 0;

}

void udp_echo(int sockfd, struct sockaddr* pcliaddr, socklen_t clilen)
{
    int n;
    char buf[256];
    for(;;)
    {

        n = recvfrom(sockfd, buf, 256, 0, pcliaddr, &clilen);
        write(fileno(stdout), buf, n);
        sendto(sockfd, buf, n, 0, pcliaddr, clilen);
    }
}
/* 客戶端 */
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>

int main(int argc, char** argcv)
{
    int sockfd;
    struct sockaddr_in servaddr;
    char *ipaddr = "192.168.110.128";
    int n, m;

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    inet_pton(AF_INET, ipaddr, &servaddr.sin_addr);
    //ip address need to be transfromed from char* to bytes

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    char sendbuf[256], recvbuf[256];
    while((n = read(fileno(stdin), sendbuf, 256)) != 0)
    {
        sendto(sockfd, sendbuf, n, 0, (struct sockaddr*) &servaddr, sizeof(servaddr));
        m = recvfrom(sockfd, recvbuf, 256, 0, NULL, NULL);
        write(fileno(stdout), recvbuf, m);
    }
}

對於客戶端來說,進程並沒有爲套接字sockfd綁定一個本地端口地址,與TCP一樣,內核會給它分配一個臨時端口,可以在代碼中顯示的調用bind,但是一般不會這樣做。
需要注意的是,對於IO來說,一般都存在緩衝區,因此有時最好不要連續調用讀取函數,不然可能不會快速輸出,需要強行輸入緩衝區內容可以用fflush函數。

三、UDP connect

  udp中可以調用connect函數,不過結果與tcp中不大一樣,沒有3路握手過程,內核只是檢查是否存在立即可知的錯誤,記錄對端的IP地址和端口號,然後立即返回到調用進程。
  調用connect之後,不再使用sendto和recvfrom來進行數據的讀寫,一般用read,write,send等函數;當然一定要用sendto也是可以的,但是不需要再指定目的地址,因爲連接之後,套機字已經保存了目的端地址,也因爲如此,連接的套接字只能與固定端進行數據交換,需要更改目的地址可以多次調用connect;
  再次調用connect有兩個目的:指定新的IP地址和端口號(tcp套接字connect只能調用一次),斷開套接字(套接字地址結構的地址族設置爲AF_UNSPEC)。
  調用connect的好處:已連接的套接字能夠接收到返回的異步錯誤,未連接的不能,提高傳輸效率,增加系統穩定性(不會把非目的地址發來的數據當作返回數據)。
  UDP 調用 connect的作用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章