1. 程式人生 > >《Linux網路程式設計》: UDP程式設計

《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 中的伺服器傳送資料,除錯助手配置如下:

執行結果如下: