UDP&TCP Linux網路應用程式設計詳解

巴斯比男孩發表於2020-11-28

轉載自韋東山部落格

1.目標

暫時想不出什麼好的應用場景,
目前想到目標就是實現讓兩個裝置通過網路傳輸資料,
比如開發板和Linux主機之間傳資料,
以後就可以實現開發板通過網路上報資料或者主機通過網路控制開發板。

此外,暫時不想關心具體的網路模型,更注重於網路相關函式的直接使用。

2.Linux網路程式設計基礎

2.1 巢狀字
多個TCP連線或者多個應用程式程式 可能需要同一個TCP埠傳輸資料。
為了區分不同應用程式程式和連線,許多計算機作業系統為應用程式與TCP/IP互動提供了稱為**巢狀字(Socket)**的介面。
Linux中的網路程式設計正是通過Socket介面實現的,Socket是一種檔案描述符。

常用的TCP/IP有以下三種型別的巢狀字:

  • 流式巢狀字(SOCK_STREAM)
    用於提供面向連線的、可靠的資料傳輸服務,即使用TCP進行傳輸。

  • 資料包巢狀字(SOCK_DGRAM)
    用於提供無連線的服務,即使用UDP進行傳輸。

  • 原始巢狀字(SOCK_RAW)
    可以讀寫核心沒有處理的IP資料包,而流式巢狀字只能讀取TCP的資料,資料包巢狀字只能讀取UDP的資料。

因此,如果要訪問其它協議傳送的資料必須使用原始巢狀字,它允許對底層協議(如IP或ICMP)直接訪問。

2.2 埠
TCP/IP協議中的埠,埠號的範圍從0~65535。
一類是由網際網路指派名字和號碼公司ICANN負責分配給一些常用的應用程式固定使用的“周知的埠”,其值一般為0~1023。例如http的埠號是80,FTP為21,SSH為22,Telnet為23等。
還有一類是使用者自己定義的,通常是大於1024的整型值。

2.3 網路地址
網路通訊,歸根到底還是程式間的通訊(不同計算機上的程式間通訊)。在網路中,每一個節點(計算機或路由)都有一個網路地址,如192.168.1.4,也就是IP地址。
兩個程式通訊時,首先要確定各自所在的網路節點的網路地址。

但是,網路地址只能確定程式所在的計算機,而一臺計算機上很可能同時執行著多個程式,所以僅憑網路地址還不能確定到底是和網路中的哪一個程式進行通訊,因此套介面中還需要包括其他的資訊,也就是埠號(PORT)。
在一臺計算機中,一個埠號一次只能分配給一個程式,也就是說,在一臺計算機中,埠號和程式之間是一一對應關係。

所以,使用埠號和網路地址的組合可以唯一的確定整個網路中的一個網路程式

例如,如網路中某一臺計算機的IP為192.168.1.4,作業系統分配給計算機中某一應用程式程式的埠號為1500,則此時192.168.1.4 1500就構成了一個套介面。

網路地址的格式

在Socket程式設計中,struct sockaddr用於記錄網路地址,其格式如下:

struct sockaddr
{
     unsigned short sa_family; /*協議族,採用AF_XXX的形式,例如AF_INET(IPv4協議族)*/
     char sa_data[14]; /*14位元組的協議地址,包含該socket的IP地址和埠號。*/
};

但在實際程式設計中,並不針對**sockaddr**資料結構進行操作,而是用與其等價的sockaddr_in資料結構:

struct sockaddr_in
{
     short int sa_family; /*地址族*/
     unsigned short int sin_port; /*埠號*/
     struct in_addr sin_addr; /*IP地址*/
     unsigned char sin_zero[8]; /*填充0 以保持與struct sockaddr同樣大小*/
};

其中struct in_addr結構體原型為:

struct in_addr
{
in_addr_t s_addr; // 無符號 32 位網路地址
};

sin_port 為2 個位元組,sin_addr為4個位元組,sin_zero[8]8個位元組,剛好加起來就是14個位元組

2.3.1 網路地址的轉換

IP地址通常用數字加點(如192.168.1.a)表示,而在struct in_addr中使用的式32位整數表示。因此,Linux提供如下函式進行兩者之間的轉換:

  • inet_aton()函式
  • 即:ascill to net
    所需要標頭檔案
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

函式格式:

int inet_aton(const char *cp, struct in_addr *inp);

函式功能
將a.b.c.d字串形式的IP地址轉換成32位網路序號IP地址;
*cp:存放字串形式的IP地址的指標
*inp:存放32位的網路序號IP地址
返回值
轉換成功,返回非0,否則返回0;

  • inet_ntoa()函式:客戶機端:
  • net to ascill
    所需要標頭檔案
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

函式格式:

char *inet_ntoa(struct in_addr in);

函式功能:
將32位網路序號IP地址轉換成a.b.c.d字串形式的IP地址;
in:Internet主機地址的結構
返回值
轉換成功,返回一個字元指標,否則返回NULL;

2.4 位元組序

不同的CPU採用對變數的位元組儲存順序可能不同。
常用的X86結構是小端模式,很多的ARM,DSP都為小端模式,即記憶體的低地址儲存資料的低位元組,高地址儲存資料的高位元組。
而KEIL C51則為大端模式,記憶體的高地址儲存資料的低位元組,低地址儲存資料高位元組。

對於網路傳輸來說,資料順序必須是一致的,網路位元組順序採用大端位元組序方式。
下面是四個常用的轉換函式:
主機轉網路:

  • htons()函式
  • host to net short
    所需要的標頭檔案
#include <netinet/in.h>

函式格式

unsigned short int htons(unsigned short int hostshort)

函式功能:
將引數指定的16位主機(host)字元順序轉換成網路(net)字元順序;
hostshort:待轉換的16位主機字元順序數

返回值
返回對應的網路字元順序數;

  • htonl()函式
  • host to net long
    所需要標頭檔案:
#include <netinet/in.h>

函式格式

unsigned long int htons(unsigned long int hostlong)

函式功能
將引數指定的32位主機(host)字元順序轉換成網路(net)字元順序;
hostlong:待轉換的32位主機字元順序數

返回值
返回對應的網路字元順序數;

網路轉主機:

  • ntohs()函式
    所需要標頭檔案
#include <netinet/in.h>

** 函式格式**

unsigned short int ntohs(unsigned short int netshort)

函式功能
將引數指定的16位網路(net)字元順序轉換成主機(host)字元順序;
netshort:待轉換的16位網路字元順序數
返回值
返回對應的主機字元順序數;

  • ntohl()函式
    所需要標頭檔案
#include <netinet/in.h>

函式格式

unsigned long int ntohl(unsigned long int netlong)

函式功能
將引數指定的32位網路(net)字元順序轉換成主機(host)字元順序;
netshort:待轉換的32位網路字元順序數
返回值
返回對應的主機字元順序數;

3.TCP

TCP有專門的傳遞保證機制,收到資料時會自動傳送確認訊息,傳送方收到確認訊息後才會繼續傳送訊息,否則繼續等待。
這樣的好處是傳輸的資料是可靠的,此外它是有連線的傳輸,大多數網路傳輸都是用的TCP。

3.1 TCP流程圖

在這裡插入圖片描述

3.2 TCP步驟分析

程式分為伺服器端和客戶機端,先從伺服器端開始分析。

  • 伺服器端
    a.建立socket
if (-1 == sock_fd)
{
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
}

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式

int socket(int domain, int type, int protocol);

函式功能
建立一個套接字;
domain:協議域(族),決定了套接字的地址型別,例如AF_INET決定了要用IPv4地址(32位)與埠號(16位)的組合。常見的協議族有:AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX)、AF_ROUTE等;
type:指定套接字型別SOCK_STREAM(TCP)SOCK_DGRAM(UDP)、SOCK_RAW
protocol:指定socket所使用的傳輸協議編號,通常為0

返回值
若成功,返回一個套接字描述符,否則返回-1;

Socket就是一種檔案描述符,和普通的開啟檔案一樣,需要檢測其返回結果。

b.設定socket

memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
server_addr.sin_port = htons(PORT_NUMBER);

設定何種協議族,設定本機IP和埠,也就有了唯一性。

c.繫結socket

ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
    if(-1 == ret)
    {
        fprintf(stderr,"bind error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
}

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式

int bind(int sockfd, struct sockaddr *addr, int addrlen);

函式功能
把套接字繫結到本地計算機的某一個埠上;
sockfd:待繫結的套接字描述符
addr:一個struct sockaddr *指標,指定要繫結給sockfd的協議地址。內容結構由前面的協議族決定。
addrlen:地址的長度

d.開始監聽

ret = listen(sock_fd, BACKLOG);
    if (-1 == ret)
    {
        fprintf(stderr,"listen error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }

所需要標頭檔案

int listen(int sockfd, int backlog);

函式格式

int listen(int sockfd, int backlog);

函式功能
使伺服器的這個埠和IP處於監聽狀態,等待網路中某一客戶機的連線請求,最大連線數量為backlog≤128;
sockfd:待監聽的套接字描述符
backlog:最大可監聽和連線的客戶端數量

返回值
若成功,返回0,否則返回-1;

e.阻塞,等待連線

addr_len = sizeof(struct sockaddr);
        new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len);
        if (-1 == new_fd)
        {
            fprintf(stderr,"accept error:%s\n\a", strerror(errno));
            close(sock_fd);
            exit(1);
        }

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式

int accept(int sockfd, struct sockaddr *addr, int *addrlen);

函式功能
接受連線請求,建立起與客戶機之間的通訊連線。伺服器處於監聽狀態時,如果某時刻獲得客戶機的連線請求,此時並不是立即處理這個請求,而是將這個請求放在等待佇列中,當系統空閒時再處理客戶機的連線請求;
當accept函式接受一個連線時,會返回一個新的socket識別符號,以後的資料傳輸和讀取就要通過這個新的socket編號來處理,原來引數中的socket也可以繼續使用,繼續監聽其它客戶機的連線請求;

accept連線成功時,引數addr所指的結構體會填入所連線機器的地址資料;
sockfd:待監聽的套接字描述符
addr:指向struct sockaddr的指標,用於返回客戶端的協議地址
addrlen:協議地址的長度

返回值
若成功,返回一個由核心自動生成的一個全新描述字,代表與返回客戶的TCP連線,否則返回-1,錯誤資訊存在errno中;

f.接收資料

recv_len = recv(new_fd, recv_buf, 999, 0);
    if (recv_len <= 0)
    {
        fprintf(stderr, "recv error:%s\n\a", strerror(errno))close(new_fd);	
        exit(1);
    }
    else
    {
        recv_buf[recv_len] = '\0';
        printf("Get msg from client%d: %s\n", client_num, recv_buf);
    }

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式

int recv(int sockfd, void *buf, size_t len, int flags);

函式功能
用新的套接字來接收遠端主機傳來的資料,並把資料存到由引數buf指向的記憶體空間;
sockfd:sockfd為前面accept的返回值,即new_fd,也就是新的套接字
buf:指明一個緩衝區
len:指明緩衝區的長度
flags:通常為0

返回值
若成功,返回接收到的位元組數,另一端已關閉則返回0,否則返回-1,錯誤資訊存在errno中;

g.關閉socket

    exit(0); 

為了應對多個連線,並保證它們之間相互獨立,實際程式設計中往往還要加入多程式fork()。讓子程式接收資料,父程式繼續監聽新的連線。

客戶端

a.建立socket

sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
    if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }`

b.設定socket

memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT_NUMBER);

其中注意的是,這裡設定的socket內容是指 希望連線的伺服器IP和埠號資訊,IP地址來自使用者的輸入,並轉換格式得到。因此,這裡的設定和伺服器的設定,要保持內容上的一致。

ret = inet_aton(argv[1], &server_addr.sin_addr);
    if(0 == ret)
    {
        fprintf(stderr,"server_ip error.\n");
        close(sock_fd);
        exit(1);
    }

c. 連線

ret = connect(sock_fd, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));	
    if (-1 == ret)
    {
        fprintf(stderr,"connect error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式:

int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);

函式功能:
用來請求連線遠端伺服器,將引數sockfd的socket連至引數serv_addr所指定的伺服器IP和埠號上去;

sockfd:客戶端的socket套接字
serv_addr:一個struct sockaddr型別的結構體指標變數,儲存著遠端伺服器的IP與埠號資訊

addrlen:結構體變數的長度

返回值:
若成功,返回0,否則返回-1,錯誤資訊存在errno中;

d.傳送

send_buf = send(sock_fd, send_buf, strlen(send_buf), 0);
			if (send_buf <= 0)
			{
				fprintf(stderr,"send error:%s\n\a", strerror(errno));
				close(sock_fd);
				exit(1);
			}

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式:

int send(int sockfd, const void *buf, int len, int flags);

函式功能:
用來傳送資料給指定的遠端主機;
sockfd:客戶端的socket套接字
buf:指明一個緩衝區
len:指明緩衝區的長度
flags:通常為0

返回值:
若成功,返回傳送的位元組數,否則返回-1,錯誤資訊存在errno中

d.關閉socke

 close(sock_fd);
    exit(0);

3.3 TCP完整程式碼

/*
* tcp_server.c
# Copyright (C) 2017 hceng, <huangcheng.job@foxmail.com>
# Licensed under terms of GPLv2
#
# This program is used for TCP / UDP learning.
# https://hceng.cn/
*/
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

#define PORT_NUMBER 8888
#define BACKLOG     10

/* socket->bind->listen->accept->send/recv->close*/

int main(int argc, char **argv)
{
    int sock_fd, new_fd;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int ret;
    int addr_len;
    int recv_len;
    unsigned char recv_buf[1000];
    int client_num = -1;
    
    signal(SIGCHLD,SIG_IGN);

    /* socket */
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
    if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }
    
    /* set server sockaddr_in */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
    server_addr.sin_port = htons(PORT_NUMBER);

    /* bind */
    ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
    if(-1 == ret)
    {
        fprintf(stderr,"bind error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }
    
    /* listen */
    ret = listen(sock_fd, BACKLOG);
    if (-1 == ret)
    {
        fprintf(stderr,"listen error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }
    
    /* accept */
    while(1)
    {
        addr_len = sizeof(struct sockaddr);
        new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len);
        if (-1 == new_fd)
        {
            fprintf(stderr,"accept error:%s\n\a", strerror(errno));
            close(sock_fd);
            exit(1);
        }
        
        client_num++;
        fprintf(stderr, "Server get connetion form client%d: %s\n", client_num, inet_ntoa(client_addr.sin_addr));
        
        if (!fork())
		{
            /* Child process */
            while (1)
            {
                /* recv */
                recv_len = recv(new_fd, recv_buf, 999, 0);
                if (recv_len <= 0)
                {
                    fprintf(stderr, "recv error:%s\n\a", strerror(errno));
                    close(new_fd);	
                    exit(1);
                }
                else
                {
                    recv_buf[recv_len] = '\0';
                    printf("Get msg from client%d: %s\n", client_num, recv_buf);
                }
            }	
            close(new_fd);			
        }	   
    }
    
    /* close */
    close(sock_fd);
    exit(0); 
}

/*
* tcp_client.c
# Copyright (C) 2017 hceng, <huangcheng.job@foxmail.com>
# Licensed under terms of GPLv2
#
# This program is used for TCP / UDP learning.
# https://hceng.cn/
*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT_NUMBER 8888

/* socket->connect->send->close*/
int main(int argc, char *argv[])
{
    int sock_fd;
    struct sockaddr_in server_addr;
    int ret;
    unsigned char send_buf[1000];
    int send_len;
    
    if(argc != 2)
    {
        fprintf(stderr, "Usage:%s hostname\n\a", argv[0]);
        exit(1);
    }
    
    /* socket */
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
    if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }
    
    /* set sockaddr_in parameter*/
    memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT_NUMBER);
    ret = inet_aton(argv[1], &server_addr.sin_addr);
    if(0 == ret)
    {
        fprintf(stderr,"server_ip error.\n");
        close(sock_fd);
        exit(1);
    }
    
    /* connect */
    ret = connect(sock_fd, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));	
    if (-1 == ret)
    {
        fprintf(stderr,"connect error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }
    
    while (1)
    {
        if (fgets(send_buf, 999, stdin))
        {
            /* send */
            send_len = send(sock_fd, send_buf, strlen(send_buf), 0);
            if (send_len <= 0)
            {
                fprintf(stderr,"send error:%s\n\a", strerror(errno));
                close(sock_fd);
                exit(1);
            }
        }
    }
    
    /* close */
    close(sock_fd);
    exit(0);
}

3.4 測試結果

先在Ubuntu主機上交叉編譯伺服器端程式碼,再在Ubuntu主機上編譯客戶端程式碼。
在開發板上執行伺服器端程式碼,在Ubuntu主機先啟動tmux分屏,再分別執行客戶端程式碼。

  • 伺服器端

  • 客戶機端

4.UDP

UDP沒有傳遞保證機制,如果傳輸中資料丟失,協議不會有任何的檢測或提示。
這樣的好處是傳輸的資料是持續的,此外它是無連線的傳輸,比如實時視訊時,如果採用TCP,中途有一點點資料出錯都會卡住,進行等待,產生延時。加入使用UDP,儘管有少量的丟幀,但資料是實時的。

4.1 UDP流程圖

在這裡插入圖片描述

4.2 UDP步驟分析
從流程圖可以看出UDP比TCP的步驟少多了。

  • 伺服器端

a.建立socket

sock_fd = socket(AF_INET, SOCK_DGRAM, 0);//AF_INET:IPV4;SOCK_DGRAM:UDP
    if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }

協議族改成SOCK_DGRAM。

b. 設定socket

memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
    server_addr.sin_port = htons(PORT_NUMBER);

和前面的TCP設定還是一樣的。
c.繫結socket

ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
    if(-1 == ret)
    {
        fprintf(stderr,"bind error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }

繫結的操作也沒有變。

d.接收資料

addr_len = sizeof(struct sockaddr);
    recv_len = recvfrom(sock_fd, recv_buf, 999, 0, (struct sockaddr *)&server_addr, &addr_len);
    if (recv_len <= 0)
    {
        fprintf(stderr, "recvfrom error:%s\n\a", strerror(errno));
        close(sock_fd);	
        exit(1);
    }
    else
    {
        recv_buf[recv_len] = '\0';
        printf("Get msg from client%d: %s\n", client_num, recv_buf);
    }

所需要的標頭檔案

#include <sys/types.h>
#include <sys/socket.h>

函式格式:

int recvfrom(int sockfd, char FAR *buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);

函式功能:
從套接字上接收一個資料包並儲存源地址;
sockfd:標識一個已連線套接字的描述符
buf:接收資料緩衝區
len:接收資料緩衝區長度
flags:呼叫操作方式,由以下零個或多個組成

在這裡插入圖片描述

from:(可選)指標,指向裝有源地址的緩衝區
fromlen:(可選)指標,指向from緩衝區長度值
返回值:
若成功,返回讀入的位元組數,否則返回0;
e.關閉

exit(0); 

客戶機端:
a. 建立socket

 if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }

協議族改成SOCK_DGRAM。

b. 設定socket

memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT_NUMBER);
    ret = inet_aton(argv[1], &server_addr.sin_addr);
    if(0 == ret)
    {
        fprintf(stderr,"server_ip error.\n");
        close(sock_fd);
        exit(1);
    }

c. 傳送資料

addr_len = sizeof(struct sockaddr);
    send_len = sendto(sock_fd, send_buf, strlen(send_buf), 0, (const struct sockaddr *)&server_addr, addr_len);
    if (send_len <= 0)
    {
        fprintf(stderr,"send error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }

所需要標頭檔案:

#include <sys/types.h>
#include <sys/socket.h>

函式格式:

int sendto(int sockfd, char FAR *buf, int len, int flags, struct sockaddr FAR *to, int FAR *tolen);

函式功能:
向一指定目的地傳送資料;
sockfd:一個標識套接字的描述字
buf:傳送資料緩衝區
len:傳送資料緩衝區長度
flags:呼叫方式標誌位
to:(可選)指標,指向目的的套接字的地址
tolen:目的套接字地址的長度

返回值:
若成功,返回傳送的位元組數,如果連線已中止,返回0,如果發生錯誤,返回-1;

d. 關閉

close(sock_fd);
exit(0);

UDP傳輸的客戶端少了connect(),原本該在connect()函式裡傳入伺服器地址相關資訊,現在變成了在sendto()裡傳入。

4.3 UDP完整程式碼

/*
* udp_server.c
# Copyright (C) 2017 hceng, <huangcheng.job@foxmail.com>
# Licensed under terms of GPLv2
#
# This program is used for TCP / UDP learning.
# https://hceng.cn/
*/
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

#define PORT_NUMBER 8888

/* socket->bind->recvfrom/sendto->close */

int main(int argc, char **argv)
{
    int sock_fd;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int ret;
    int addr_len;
    int recv_len;
	unsigned char recv_buf[1000];
    
    /* socket */
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);//AF_INET:IPV4;SOCK_DGRAM:UDP
    if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }
    
    /* set sockaddr_in parameter*/
    memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
    server_addr.sin_port = htons(PORT_NUMBER);

    /* bind */
    ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
    if(-1 == ret)
    {
        fprintf(stderr,"bind error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }

    while (1)
    {
        /* recvfrom */
        addr_len = sizeof(struct sockaddr);
        recv_len = recvfrom(sock_fd, recv_buf, 999, 0, (struct sockaddr *)&client_addr, &addr_len);
        if (recv_len <= 0)
        {
            fprintf(stderr, "recvfrom error:%s\n\a", strerror(errno));
            close(sock_fd);	
            exit(1);
        }
        else
        {
            recv_buf[recv_len] = '\0';
            printf("Get msg from client:%s: %s\n", inet_ntoa(client_addr.sin_addr), recv_buf);
        }
    }			

    /* close */
    close(sock_fd);
    exit(0); 
}

/*
* udp_server.c
# Copyright (C) 2017 hceng, <huangcheng.job@foxmail.com>
# Licensed under terms of GPLv2
#
# This program is used for TCP / UDP learning.
# https://hceng.cn/
*/
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

#define PORT_NUMBER 8888

/* socket->bind->recvfrom/sendto->close */

int main(int argc, char **argv)
{
    int sock_fd;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int ret;
    int addr_len;
    int recv_len;
	unsigned char recv_buf[1000];
    
    /* socket */
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);//AF_INET:IPV4;SOCK_DGRAM:UDP
    if (-1 == sock_fd)
    {
        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
        exit(1);
    }
    
    /* set sockaddr_in parameter*/
    memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
    server_addr.sin_port = htons(PORT_NUMBER);

    /* bind */
    ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
    if(-1 == ret)
    {
        fprintf(stderr,"bind error:%s\n\a", strerror(errno));
        close(sock_fd);
        exit(1);
    }

    while (1)
    {
        /* recvfrom */
        addr_len = sizeof(struct sockaddr);
        recv_len = recvfrom(sock_fd, recv_buf, 999, 0, (struct sockaddr *)&client_addr, &addr_len);
        if (recv_len <= 0)
        {
            fprintf(stderr, "recvfrom error:%s\n\a", strerror(errno));
            close(sock_fd);	
            exit(1);
        }
        else
        {
            recv_buf[recv_len] = '\0';
            printf("Get msg from client:%s: %s\n", inet_ntoa(client_addr.sin_addr), recv_buf);
        }
    }			

    /* close */
    close(sock_fd);
    exit(0); 
}

3.4 測試結果

先在Ubuntu主機上交叉編譯伺服器端程式碼,再在Ubuntu主機上編譯客戶端程式碼。
在開發板上執行伺服器端程式碼,在Ubuntu主機先啟動tmux分屏,再分別執行客戶端程式碼。

  • 伺服器端
  • 客戶機端

相關文章