TCP/IP网络编程学习(5):实现基于UDP的服务端/客户端

UDP套接字的特点

UDP通信与传统的信件通信类似。寄件人填写好地址(IP)和收信人姓名(Port)后将信件投递到邮筒(交付给网络层)。不保证信件的的准确到达。提供的是不可靠的传输服务。考虑可靠性TCP肯定好于UDP。但是UDP相对于TCP具有以下优点。

不用连接,无连接时延,实时性高
不用维护连接状态,如确认号,序列号,流量控制,拥塞控制
头部开销小

流控制是区分UDP和TCP的最重要的标志 。 但若从TCP中除去流控制,所剩内容也屈指可数 。也就是说,TCP的生命在于流控制 。
在这里插入图片描述
IP的作用就是让离开主机B的UDP数据包准确传递到主机A。但把UDP包最终交给主机A的某一UDP套接字的过程则是由UDP完成的 。UDP最重要的作用就是根据端口号将传到主机的数据包交付给最终的UDP套接字 。、

TCP比UDP慢的原因通常有以下两点 。

  1. 收发数据前后进行的连接设置及清除过程 。
  2. 收发数据过程中为保证可靠性而添加的流控制

UDP 中的服务器端和客户端没有连接

UDP服务器端/客户端不像TCP那样在连接状态下交换数据,因此与TCP不同,无需经过连接过程。 也就是说,不必调用TCP连接过程中调用的 listen函数和accept函数 。 UDP中只有创建套接字的过程和数据交换过程 。

UDP 服务器端和客户端均只需 1 个套接字

在这里插入图片描述
因此UDP可以实现1对1,1对多,多对1,多对多传输。多个UDP投递到某一主机的数据都可以通过该主机的UDP套接字获取。

基于 UDP 的数据 I/O 函数

创建好TCP套接字后,传输数据时无需再添加地址信息 。 因为l’CP套接字将保持与对方套接字的连接。 换言之 , TCP套接字知道目标地址信息 。 但UDP套接字不会保持连接状态 ( UDP套接字只有简单的邮筒功能),因此每次传输数据都要添加 目标地址信息 。 这相当于寄信前在信件中填写地址 。 接下来介绍填写地址并传输数据时调用的UDP相关函数 。

ssize_t sendto(int sock, void *buff,size_t nbyte,int flags,struct sockaddr *to, socklen_t addrlen);
//成功时返回传输的字节损,失败时返回-1 0
//sock: 用于传输数据的 UDP套接字文件描述符。
//buf: 保存待传输数据的缓冲地址值。
//nbγtes: 待传输的数据长度,以字节为单位。
//flags :可选项参数,若没有则传递0。
//to: 存有目标地址信息的sockaddr结构体变量的地址值。
//addrlen: 传递给参数to的地址值结构体变量长度。

上述函数与之前的TCP输出雨数最大的区别在于 . 此函数需要向它传递目标地址信息 。 接下来介绍接收UDP数据的函数 。 UDP数据的发送端并不固定,因此该函数定义为可接收发送端信息的形式,也就是将同时返回UDP数据包中的发送端信息。

ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags,
struct sockaddr * from, socklen_t *addrlen);
//成功时返回接收的字节数,失败时返回-1
//sock :用于接收数据的UDP套接字文件描述符。
//buf :保存接收数据的缓冲地址值。
//nbytes: 可接收的最大字节数,故无法超过参数buf所指的缓冲大小 。
//flags: 可选项参数,若没有则传入0 。
//from: 存有发送端地址信息的 sockaddr结构体变量的地址值。
//addrlen: 保存参数from的结构体变量长度的变量地址值。

编写UDP程序时最核心的部分就在于上述两个函数 , 这也说明二者在UDP数据传输巾 的地位 。

UDP不同于TCP ,不存在请求连接和受理过程,因此在某种意义上无法明确区分服务器端和客户端。

#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;
	socklen_t clnt_adr_sz;
	
	struct sockaddr_in serv_adr, clnt_adr;
	if(argc!=2){
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	if(serv_sock==-1)
		error_handling("UDP socket creation 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]));
	
	//确定服务器UDP套接字的端口号和网络地址
	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	
	while(1) 
	{
		clnt_adr_sz=sizeof(clnt_adr);
		//接受客户端数据
		str_len=recvfrom(serv_sock, message, BUF_SIZE, 0, 
								(struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		//发送到客户端
		sendto(serv_sock, message, str_len, 0, 
								(struct sockaddr*)&clnt_adr, clnt_adr_sz);
	}	
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

客户端

#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);
	fputc('\n', stderr);
	exit(1);
}

TCP客户端套接字在调用 connect函数时自动分配IP地址和端口号,既然如此, UDP客户端何时分配IP地址和端口号?
调用sendto函数时自动分配IP和端口号,因此,UDP客户端中通常无需额外的地址分配过程 。而且此时分配的地址一直保留到程序结束为止,因此也可用来与其他UDP套接字进行数据交换 。 所以之前示例中省略了该过程 ,这也是普遍的实现方式 。

UDP 的数据传输特性和调用 connect 函数

前面说过TCP数据传输中不存在边界,这表示"数据传输过程中调用 I10函数的次数不具有任何意 。"相反UDP是具有数据边界的协议,传输中调用I/O函数的次数非常重要 。 因此,输入函数的调用次数应和输出函数的调用次数完全一致,这样才能保证接收全部已发送数据 。
在这里插入图片描述

已连接 (connected)UDP套接字与未连接 (unconnected)UDP套接字

通过sendto函数传输数据的过程大致可分为以下3个阶段 。
第1阶段:向UDP套接字注册目标E和端口号 。
第2阶段:传输数据 。
第3阶段:删除UDP套接字中注册的目标地址信息 。

每次调用 sendto函数时重复上述过程。 每次都变更目标地址,因此可以重复利用同一UDP套接字向不同目标传输数据。这种未注册目标地址信息的套接字称为未连接套接字。反之,注册了目标地址的套接字称为连接connected套接字。 显然, UDP套接字默认属于未连接套接字。

创建己连接 UDP 套接字

创建已连接UDP套接字的过程格外简单,只需针对UDP套接字调用 connect函数 。

sock = socket(PF_INET, SOCK_DGRAM,,0);
memset(&adr, 0sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = ...
adr.sin_ port =...
connect(sock, (struct sockaddr *) &adr) sizeof(adr));

当然,针对UDP套接字调用connect函数并不意味着要与对方UDP套接字连接 , 这只是向UDP套接字注册目 标E和端 口信息 。之后就与TCP套接字一样,每次调用 sendto函数时只需传输数据 。 因为已经指定了收发对象,所以不仅可以使用sendto 、 recvfrom函数 , 还可以使用write 、 read函数进行通信。

#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]));
	
	connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
	//创建连接UDP,使用read和write交换数据。此时已经自动分配了客户端的IP和端口号。
	//可以用此udp(sock)和其余主机UDP通信
	while(1)
	{
		fputs("Insert message(q to quit): ", stdout);
		fgets(message, sizeof(message), stdin);     
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))	
			break;
		write(sock, message, strlen(message));
		str_len=read(sock, message, sizeof(message)-1);
		message[str_len]=0;
		printf("Message from server: %s", message);
	}	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值