《Linux網路程式設計》: UDP程式設計
UDP程式設計
1. UDP 程式設計的 C/S 架構
2. UDP 客戶端程式
ssize_t sendto( int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to,socklen_t addrlen );
功能:向 to 結構體指標中指定的 ip,傳送 UDP 資料,可以傳送 0 長度的 UDP 資料包
引數:
sockfd:套接字
buf:傳送資料緩衝區
nbytes:傳送資料緩衝區的大小
flags:一般為 0
to:指向目的主機地址結構體的指標
addrlen:to 所指向內容的長度
返回值:
成功:傳送資料的長度
失敗: -1
這裡通過 Windows 的網路除錯助手和虛擬機器中的 ubuntu 客戶端程式進行通訊。
Windows 的網路除錯助手作為伺服器,接收客戶端的請求,除錯助手配置如下:
對於 UDP客戶端程式設計流程, 有點類似於寫信過程:找個郵政工作人員(socket() ) ->信封上寫上地址同時裡面裝上信件內容並且投遞(sendto() )-> ……還可以繼續寫信,或者,接收對方的回信(recvfrom()
)……-> 打完收工( close() )。
虛擬機器中 ubuntu 的 UDP 客戶端程式:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { unsigned short port = 8080; //伺服器埠 char *server_ip = "10.221.20.10"; //伺服器ip地址 if( argc > 1 ) // main函式傳參,伺服器ip地址 { server_ip = argv[1]; } if( argc > 2 ) // main函式傳參,伺服器埠 { port = atoi(argv[2]); } int sockfd; sockfd = socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字 if(sockfd < 0) { perror("socket"); exit(-1); } // 套接字地址 struct sockaddr_in dest_addr; bzero(&dest_addr, sizeof(dest_addr)); // 清空內容 dest_addr.sin_family = AF_INET; // ipv4 dest_addr.sin_port = htons(port); // 埠轉換 inet_pton(AF_INET, server_ip, &dest_addr.sin_addr); // ip地址轉換 printf("send data to UDP server %s:%d!\n", server_ip, port); while(1) { char send_buf[512] = ""; fgets(send_buf, sizeof(send_buf), stdin);//獲取輸入 send_buf[strlen(send_buf)-1] = '\0'; //傳送資料 int len = sendto(sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); printf("len = %d\n", len); } close(sockfd); return 0; }
執行結果如下:
UDP 客戶端注意點:
1)本地IP、本地埠(我是誰)
2)目的IP、目的埠(發給誰)
3)在客戶端的程式碼中,我們只設置了目的IP、目的埠
4)客戶端的本地 ip、本地 port 如果不指定那麼我們呼叫 sendto 的時候 linux 系統底層自動給客戶端分配的;分配埠的方式為隨機分配,即每次執行系統給的 port 不一樣。
3. UDP 伺服器程式
UDP網路程式想要收取資料需什麼條件?
1)確定的 ip 地址
2)確定的埠(port)
這正如,我要收到別人寄過來的信,我必須告訴別人我的地址(ip),同時告訴別人我我的公寓信箱號(埠)。
接收端使用 bind() 函式,來完成地址結構與 socket 套接字的繫結,這樣 ip、port 就固定了,傳送端在 sendto 函式中指定接收端的 ip、port,就可以傳送資料了。
需要標頭檔案:#include <sys/socket.h>
int bind( int sockfd,const struct sockaddr *myaddr,socklen_t addrlen );
功能:將本地協議地址與 sockfd 繫結,這樣 ip、port 就固定了
引數:
sockfd:socket 套接字
myaddr: 指向特定協議的地址結構指標
addrlen:該地址結構的長度
返回值:
成功:返回 0
失敗:-1
使用例項如下:
// 本地網路地址
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr)); // 清空結構體內容
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(port); // 埠轉換
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 繫結網絡卡所有ip地址,INADDR_ANY為通配地址,值為0
printf("Binding server to port %d\n", port);
int err_log;
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 繫結
if(err_log != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
繫結埠有些需要注意的問題,請看《繫結( bind )埠需要注意的問題》。
ssize_t recvfrom( int sockfd, void *buf, size_t nbytes,int flags,struct sockaddr *from, socklen_t *addrlen );
功能:
接收 UDP 資料,並將源地址資訊儲存在 from 指向的結構中,預設的情況下,如果沒有接收到資料,這個函式會阻塞,直到有資料到來。
引數:
sockfd:套接字
buf:接收資料緩衝區
nbytes:接收資料緩衝區的大小
flags:套接字標誌(常為 0)
from:源地址結構體指標,用來儲存資料的來源
addrlen:from 所指內容的長度
返回值:
成功:接收到的長度
失敗: -1
對於 UDP 伺服器程式設計流程, 有點類似於收信過程:找個郵政工作人員( socket() ) -> 確定信箱的位置:地址+信箱號(bind() )-> 等待對方的來信( recvfrom() )-> ……還可以回信( write() ),或者,繼續等待對方的來信……
ubuntu 中的伺服器程式如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short port = 8000; // 本地埠
if(argc > 1)
{
port = atoi(argv[1]);
}
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 建立套接字
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
// 本地網路地址
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr)); // 清空結構體內容
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(port); // 埠轉換
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 繫結網絡卡所有ip地址,INADDR_ANY為通配地址,值為0
printf("Binding server to port %d\n", port);
int err_log;
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 繫結
if(err_log != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
printf("receive data...\n");
while(1)
{
int recv_len;
char recv_buf[512] = "";
struct sockaddr_in client_addr;
char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
socklen_t cliaddr_len = sizeof(client_addr);
// 接受資料
recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
printf("data(%d):%s\n",recv_len,recv_buf);
}
close(sockfd);
return 0;
}
Windows 的網路除錯助手作為客戶端,給 ubuntu 中的伺服器傳送資料,除錯助手配置如下:
執行結果如下: