16-基於udp的客戶端/服務端通信

1. 使用udp協議通信

在此之前,我們實現的程序是基於TCP協議有連接方式的,客戶端會先調用connect發起連接,然後服務端調用accept接受客戶端的連接,通過套接字開始通信。

UDP協議是無連接不可靠的,沒有被動套接字和主動套接字之分,客戶端和服務端之間通信不需要提前建立好連接,而是直接調用sendto或recvfrom函數收發數據,如下圖所示:

圖1-使用udp協議通信過程

 

關於udp協議以及udp數據報格式請參考:41-udp協議

 

2. recvfrom和sendto函數

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

這兩個函數的前三個參數相當於read和write的三個參數:文件描述符,讀緩衝區或寫緩衝區,讀或寫的字節數。關於參數flags一般設置爲0即可,我們將在後面詳細討論flags參數的用法。

recvfrom函數的參數src_addr用於指向數據報發送者的套接字地址,參數addrlen則是用於指定套接字地址大小。sendto函數的參數dest_addr用於指向數據報接收者的套接字地址,參數addrlen指定套接字地址大小。

返回值說明:sendto函數調用成功返回實際寫入的字節數,recvfrom函數調用成功返回實際讀取的字節數,如果兩個函數出錯則返回-1。

需要注意的是,recvfrom函數會一直阻塞,直到有數據到來(即直到接收緩衝區有數據),sendto函數也一樣,如果緩衝區滿了(這裏說的是發送緩衝區),同樣也會阻塞,直到緩衝區的數據被讀走。

另外,sendto函數可以發送長度爲0的數據報,即一個只包含IP首部(對於IPv4來說通常是20字節)和一個8字節的UDP首部的數據報。同理,recvfrom也可以返回0,這並不意味着像tcp一樣表示對端已關閉,因爲對於UDP來說沒有所謂的關閉連接之類的事情。

 

3. 服務端程序

#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>

#define MAXLINE 1024
#define SERV_PORT 10001

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int sockfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;

	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(SERV_PORT);
	bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	printf("Accepting connections ...\n");

	while (1) {
		cliaddr_len = sizeof(cliaddr);
		n = recvfrom(sockfd, buf, MAXLINE,0, (struct sockaddr *)&cliaddr, &cliaddr_len);
		if (n == -1)
			perror("recvfrom error");
		printf("received from %s PORT %d\n", 
				inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
				ntohs(cliaddr.sin_port));
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);

		n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
		if (n == -1)
			perror("sendto error");
	}
	close(sockfd);
	return 0;
}

 

4. 客戶端程序

爲了防止在一臺機器上啓動多個客戶端導致出錯,客戶端不需要調用bind函數顯式綁定ip地址和端口,因爲客戶端在第一次調用sendto函數時,系統會選擇當前可用的ip地址和一個隨機的端口號綁定到套接字。

對於客戶端程序來說,如果並不關心數據報發送者的協議地址,可以將recvfrom函數的src_addr和addrlen參數設置爲NULL,這樣做會帶來一個風險就是任何進程都可以向該客戶端發送數據報,並且這些數據報會被客戶端接收作爲服務端的應答

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>

#define MAXLINE 1024
#define SERV_PORT 10001

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	int sockfd, n;
	char buf[MAXLINE];

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
		if (n == -1)
			perror("sendto error");
		n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
		if (n == -1)
			perror("recvfrom error");
		write(STDOUT_FILENO, buf, n);
	}
	close(sockfd);
	return 0;
}

 

先啓動server,然後再啓動client,程序執行結果如下:

 

tcpdump抓取lo本地網卡的數據包:

從上圖我們可以看到使用udp協議通信時無需建立連接和關閉連接,udp數據報的格式相比tcp協議來說更簡單一些。

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