【Unix 网络编程】UDP 客户/服务器简单 Socket 程序

UDP 是无连接不可靠的数据包协议,不同于 TCP 提供的面向连接的可靠字节流。在介绍UDP编程模型前,先介绍UDP协议中两个重要的函数:sendto 和 recvfrom。

#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
	const struct sockaddr *to, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
	struct sockaddr *from, socklen_t *addrlen);

//两者均返回:读写字节数——成功,-1——出错
前面三个参数:sockfd、buff 和 nbytes 等同于 read 和 write 函数的三个参数:套接口描述字、指向读入或写出缓冲区的指针和读写字节数。

sendto :其中 to 参数指向一个含有数据包接收者的协议地址(含 IP 地址和端口号)的套接口地址结构,其大小由 addrlen 参数指定。

recvfrom:from 参数指向一个将由该函数在返回时填写数据包发送者的协议地址(含数据发送端的 IP地址和端口号)的套接口地址结构,而在该套接口地址结构中填写的字节数则放在 addrlen 参数所指的整数中返回给调用者。简单地说就是 from 参数指向的结构体在 recvfrom 函数返回时将被对端(即:数据发送端)地址(含 IP 地址和端口号)所填充,后面的 addrlen 是个整型指针,调用前应填入 from 指向的结构体的大小,调用后将被填入对端地址的实际大小。

上面的 recvfrom 函数最后两个参数类似于 accept 的最后两个参数:函数返回时其中套接口地址结构的内容告诉我们是谁发送了数据包(UDP,通常用于UDP情况下)。

我们看看 UDP 编程模型:

                                                                                 

相比TCP,对于服务器而言,UDP

  • 没有调用 accept ,因为不需要建立连接,一个套接口可以接收不同 IP 发来的数据,对端 socket 地址由 recvfrom 获得。

对于客户端而言

  • 没有调用 connect,因为不需要建立连接,首次发送数据时,由操作系统临时选择本地 IP 地址和端口,由 sendto 指定目标 IP 地址和端口,因此一个套接口可以向不同 IP 发送数据。
程序如下(编程环境:Linux deepin 2014)
服务器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

#define PORT 8888
#define MAX_SIZE 1024

void udp_respon(int sockfd)
{
	struct sockaddr_in cliaddr;
	int n,i;
	unsigned int addrlen;
	char msg[MAX_SIZE];
	while(1)
	{     
		n = recvfrom(sockfd, msg, MAX_SIZE, 0, (struct sockaddr *)&cliaddr, &addrlen);
		msg[n] = '\0';

		fprintf(stdout, "I have recevied %s", msg);

		n = strlen(msg);
		for(i = 0; i < n; ++i)
		{
			msg[i] = msg[i] + 'A' - 'a';
		}
		sendto(sockfd, msg, n, 0, (struct sockaddr *)&cliaddr, addrlen);
	}
}

int main(void)
{
	int sockfd;
	struct sockaddr_in seraddr;

	sockfd = socket(AF_INET, SOCK_DGRAM,0);

	bzero(&seraddr, sizeof(seraddr));
	
	//服务器端套接口结构体初始化
	seraddr.sin_family = AF_INET;
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	seraddr.sin_port = htons(PORT);
	
	//绑定
	bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));

	udp_respon(sockfd);

	return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>

#define PORT 8888
#define MAX_SIZE 1024

void udp_request(int sockfd, const struct sockaddr_in *addr, int len)
{
	char buffer[MAX_SIZE];
	int n;

	while(1)
	{
		fgets(buffer, MAX_SIZE, stdin);
		//向目标端发送数据
		sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)addr, len);
		printf("I have sent to server %s", buffer);
		printf("Waiting respond from server\n");

		bzero(buffer, MAX_SIZE);
		
		//这里不需要了解数据发送端的信息,所以设置NULL
		n = recvfrom(sockfd, buffer, MAX_SIZE, 0, NULL, NULL);
		buffer[n] = 0;
		printf("I have received from server ");
		fputs(buffer, stdout);
		printf("\n");
	}
}

int main()
{
	int sockfd;
	struct sockaddr_in seraddr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	//目标端套接口结构体设置
	bzero(&seraddr, sizeof(struct sockaddr_in));
	seraddr.sin_family = AF_INET;
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	seraddr.sin_port = htons(PORT);

	udp_request(sockfd, &seraddr, sizeof(struct sockaddr_in));

	close(sockfd);
	return 0;
}
对于服务器程序而言,编程步骤:
调用 socket,创建一个不与任何网络连接相对应的套接口(socket),其返回值是一个文件描述符;调用 bind,将刚刚创建的 socket 与本机的 IP地址和端口号绑定,这样一来 socket 就变成了一个 3元组;调用 recvfrom,从套接口接收客户端发送过来的数据(同时能获得客户端的 IP地址和端口号),当然如果此时客户端并未发送数据,那么 recvfrom 将阻塞到有数据到达为止;根据客户端的要求进行数据处理;调用 sendto (将客户端 IP和端口号作为参数传入),向套接口写入处理后的数据,该数据将通过网络到达客户端,当所有数据都已处理完毕,将调用 close 关闭套接口,自此通信结束。
对于客户机程序而言,编程步骤:
调用 socket,创建一个不与任何网络连接相对应的套接口(socket),调用 sendto(将服务器 IP和端口号作为参数传入)向 socket 中写入数据,此时系统将选择一个本机尚未使用的端口号作为本地端口号,并选择本机的 IP地址作为本地 IP,这样,该 socket 就变成了一个 3元组,该数据将到达服务器,被 recvfrom 所读取(服务器端),然后调用
recvfrom(客户端),等待从 socket 获取服务器通过 sendto 回送的数据,当所有数据都接收完毕,调用 close 关闭套接口,自此通信结束。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值