1. 程式人生 > >網路程式設計——6. 基於UDP的伺服器端/客戶端

網路程式設計——6. 基於UDP的伺服器端/客戶端

6.1 理解UDP

UDP套接字的特點

跟寄信一樣,我寫好名字和地址,貼上郵票寄出去就好了。 郵寄過程的丟失或者損壞我都沒辦法保證,是一種不可靠的傳輸方式。但相比TCP,雖然可靠性差一些,但比TCP簡潔一些,速度也更快一些(在每次交換的資料量越小的情況下)。

TCP和UDP的差異只在於流控制機制:TCP在不可靠的IP層進行流控制,而UDP缺少這種流控制機制。

6.2 實現基於UDP的伺服器端/客戶端

UDP中的伺服器端和客戶端沒有連線

與TCP不同,無需經過連線過程即可交換資料 (UDP中只有建立套接字過程資料交換過程

UDP伺服器端和客戶端都只需1個套接字

TCP中,套接字之間是1對1的關係。若要向10個客戶端提供服務,除了守門的伺服器套接字外,還需要10個伺服器端套接字

把郵筒比作套接字,只要附近有1個郵筒,就能通過它向任意地址寄出信件。 也就是說,只要1個UDP套接字就能向任意主機傳輸資料。 在這裡插入圖片描述

基於UDP的資料IO函式

建立好TCP套接字後,傳輸資料時就不用再新增地址資訊。因為TCP套接字將保持與對方套接字的連線。

UDP套接字不會保持連線狀態,因此每次傳輸資料都需要新增目標地址資訊(相當於寄信前在信件中填地址)

可以看到,sendto函式與TCP輸出函式的最大區別在於,此函式需要向它傳遞目標地址資訊。 在這裡插入圖片描述 在這裡插入圖片描述 上述就是UDP程式最核心的部分。

基於UDP的回聲伺服器端/客戶端

UDP不同於TCP,不存在請求連線和受理過程,因此在某種意義上沒辦法去明確區分伺服器端和客戶端。

1)伺服器端

// uecho_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 	30

void error_handling(char *message);

int main(int argc, char *argv[]){
	int serv_sock;
	char message[
BUF_SIZE]; int str_len; struct sockaddr_in serv_adr; struct sockaddr_in clnt_adr; socklen_t clnt_addr_sz; if(argc != 2){ printf("Usage : %s <port>\n",argv[0]); exit(1); } // 建立UDP套接字,向socket函式第二個引數傳遞SOCK_DGRAM serv_sock = socket(PF_INET,SOCK_DGRAM,0); if(serv_sock == -1){ error_handling("socket() error"); } memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family=AF_INET; serv_adr.sin_addr.s_addr=htonl(INADDR_ANY); serv_adr.sin_port=htons(atoi(argv[1])); if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1){ error_handling("bind() error"); } while(1){ clnt_addr_sz = sizeof(clnt_adr); // 利用bind分配的地址接收資料,不限制資料傳輸物件 str_len = recvfrom(serv_sock,message,BUF_SIZE,0,(struct sockaddr*)&clnt_adr,&clnt_addr_sz); // 通過上面獲得的資料傳輸端的地址,來將接收的資料逆向重傳 sendto(serv_sock,message,str_len,0,(struct sockaddr*)&clnt_adr,clnt_addr_sz); } close(serv_sock); return 0; } void error_handling(char *message){ fputs(message,stderr); fputs("\n",stderr); exit(1); }

2)客戶端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 	30

void error_handling(char *message);

int main(int argc, char *argv[]){
	int sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t adr_sz;
	struct sockaddr_in serv_adr, from_adr;

	if(argc != 3){
		printf("Usage : %s <IP> <port>\n",argv[0]);
		exit(1);
	}

	sock = socket(PF_INET,SOCK_DGRAM,0);
	if(sock == -1){
		error_handling("socket() error");
	}

	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));

	while(1){
	
		fputs("Insert message(q to quit):",stdout);
		fgets(message,sizeof(message),stdin);
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;
		// 向伺服器端一次性發送完畢(不保證完全接收)
		sendto(sock,message,strlen(message),0,(struct sockaddr *)&serv_adr,sizeof(serv_adr));
		adr_sz = sizeof(from_adr);
		str_len = recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr *)&from_adr,&adr_sz);
		message[str_len] = 0;

		printf("Message from server: %s",message);
	}

	close(sock);

	return 0;

}

void error_handling(char *message){

	fputs(message,stderr);
	fputs("\n",stderr);
	exit(1);
}

UDP客戶端套接字的地址分配

仔細觀察UDP客戶端會發現,缺少把IP地址和埠分配給套接字的過程。 TCP客戶端呼叫connect函式自動完成IP地址和埠分配給套接字的過程。 UDP客戶端通常無需額外的地址分配過程。呼叫sendto函式時自動分配IP和埠號

6.3 UDP的資料傳輸特性和呼叫connect函式

UDP是具有資料邊界的協議,傳輸中呼叫IO函式的次數非常重要。輸入函式的呼叫次數應和輸出函式的呼叫次數一致,這樣才能保證接收全部已傳送資料。