网络编程--UDP

 

说明:只供学习交流,转载请注明出处

 

面向连接的套接字使用的通信协议是TCPTCP协议可以保证信息的可靠传输,但是在建立连接前需要进行3次握手过程。网络通信在很多时候只是传输很少的数据,并且对数据传输的可靠性没有很高的要求,在这种情况下,可以使用UDP协议。UDP协议是无连接的数据通信协议。由于UDP协议没有建立连接的过程,UDP协议通信效率要高于TCP协议。

 

一,工作流程

无连接套接字通信不需要服务器与客户机之间建立连接,因此也就没有使用listen函数实现监听和调用accept函数建立服务器与客户机之间连接的过程。要在服务器和客户机之间实现通信必须指定另一方的IP地址。

无连接的套接字通信具体工作流程如下图所示。首先服务器和客户机都需要建立用于通信的套接字,并将其地址信息进行绑定。在完成这些操作之后,就可以通过sendtorecvfrom函数进行通信了。

二,recvfrom函数

recvfrom函数用于接收通过套接字发送来的消息,该函数的具体信息如下表:

 

头文件

#include <sys/types.h>

#include <sys/socket.h>

函数原型

ssize_t recvfrom(int s, void *buf, size_t  len, int flags, struct sockaddr *from, socklen_t fromlen);

返回值

成功

失败

是否设置errno

接收到的字节数

-1

说明:recvfrom函数可用于无连接或面向连接的套接字通信中。参数s为套接字文件描述符。参数buf为接收缓冲区。参数len为接收缓冲区长度。flags为控制参数,用于控制是否接收带外数据,通常设为0,常用的值还有:

MSG_PEEK:返回的数据不会在系统系统内删除。

MSG_WAITALL:强迫接收到len大小的数据后才返回。

MSG_NOSIGNAL:不会被SIGPIPE信号中断。

from用于记录发送端的地址结构。fromlen为发送段地址结构的长度。

 

错误信息:

EAGAIN:套接字处于非阻塞状态。

EBADF:非法的文件描述符。

ECONNABORTED:远程主机拒绝网络连接。

EFAULT:指向接收数据的缓冲区指针指向了非法地址空间。

EINTR:系统调用被信号中断。

EINVAL:非法参数。

ENOTCONN:套接字使用了面向连接的协议,但是并没有建立连接。

ENOTSOCK:文件描述符为文件的文件描述符。

 

 

 

三,sendto函数

sendto函数用于发送信息给指定的主机,该函数的具体信息如下表:

 

头文件

#include <sys/types.h>

#include <sys/socket.h>

函数原型

ssize_t sendto(int s, const void *buf,  size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

返回值

成功

失败

是否设置errno

发送的字节数

-1

说明:sendto函数可用于无连接或面向连接的套接字通信中。当用于面向连接的通信(套接字类型为SOCK_STREAMSOCK_SEQPACKET),参数totolen将被忽略。

sendto函数中参数s为套接字文件描述符。参数buf为指向要发送信息地址空间的指针。参数len为要发送的字节数。参数flags为相关控制参数,用于控制是否接收数据以及是否预览报文。参数from为指向存放接收方地址信息的指针。fromlen为接收端的地址结构的长度。

 

错误信息:

EBADF:非法的文件描述符。

ECONNRESET:连接重置。

EDESTADDRREQ:在套接字操作中没有指定目标地址。

EFAULT:参数指向了非法的地址空间。

EINTR:数据发送前,捕获到信号。

EINVAL:非法参数。

ENOTSOCK:参数非套接字的文件描述符。

ENOMEM:内存不足。

 

UNIX domain中面向无连接的通信实例:

Server.c

 

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define UNIX_DOMAIN "UNIX.domain"

int main(void)
{
	socklen_t addr_len;
	int listen_fd;
	int com_fd;
	int ret;
	int i;
	static char recv_buf[1024];
	int len;

	struct sockaddr_un clt_addr;
	struct sockaddr_un srv_addr;

	com_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
	if (com_fd < 0)
	{
		perror("Cannot create listening socket");
		return (1);
	}

	srv_addr.sun_family = AF_UNIX;
	strcpy(srv_addr.sun_path, UNIX_DOMAIN);
	unlink(UNIX_DOMAIN);

	ret = bind(com_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr.sun_family)+
								strlen(srv_addr.sun_path));
	if (ret == -1)
	{
		perror("Cannot bind server socket");
		close(com_fd);
		unlink(UNIX_DOMAIN);
		return (1);
	}

	for (i = 0; i < 4; i++)
	{
		memset(recv_buf, 0, 1024);
		int num = recvfrom(com_fd, recv_buf, 1024, 0, (struct sockaddr*)&clt_addr, &addr_len);
		printf("Message from client (%d) : %s\n", num, recv_buf);
	}

	close(com_fd);

	unlink(UNIX_DOMAIN);

	return (0);
}


 

Client.c

与服务器类似,程序也是首先创建通信的套接字,然后使用bind函数将文件信息与套接字绑定。值得注意的是,为了保证客户端的用于通信的文件的唯一性,使用了mkstemp函数。使用mkstemp函数将获得文件名唯一的临时文件。

之所以要保证文件名的唯一性,是因为服务器进程没有向客户端进程发送消息,而只是读取了客户端发送来的消息。对服务器而言,可以通过recvfrom中获得的值来判断是哪个客户端发送的消息。但是,如果客户端使用相同的文件的话,在多客户端的情况下,服务器就无法判断要将消息发送给哪个客户端了。通过文件名的唯一性,这一个问题得到了解决。

具体代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define UNIX_DOMAIN "UNIX.domain"

int main(void)
{
	int com_fd;
	int ret;
	char snd_buf[1024] = {'\0'};
	int i;

	static struct sockaddr_un srv_addr;
	static struct sockaddr_un clt_addr;
	char clt_file[] = "xxxx";

	srv_addr.sun_family = AF_UNIX;
	strcpy(srv_addr.sun_path, UNIX_DOMAIN);

	com_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
	if ( com_fd < 0 )
	{
		perror("Cannot create communication socket");
		return (1);
	}

	mkstemp(clt_file);
	clt_addr.sun_family = AF_UNIX;
	strcpy(clt_addr.sun_path, clt_file);
	unlink(clt_file);

	ret = bind(com_fd, (struct sockaddr*)&clt_addr, sizeof(clt_addr.sun_family)+strlen(clt_addr.sun_path));
	if (ret == -1)
	{
		perror("Cannot bind server socket");
		return (1);
	}

	memset(snd_buf, 0, 1024);
	sprintf(snd_buf, "%ld : message from client", (long)getpid());

	for (i = 0; i < 4; i++)
	{
		sleep(1);
		int num = sendto(com_fd, snd_buf, sizeof(snd_buf), 0, (struct sockaddr*)&srv_addr,
							sizeof(struct sockaddr));
		printf("send %d characters\n", num);
	}

	close(com_fd);
	unlink(clt_file);

	return (0);
}


 

 

 

ternet domain中面向无连接通信实现实例:

 

Server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
	int sock;
	int length;
	int fromlen;
	int n;
	struct sockaddr_in server;
	struct sockaddr_in from;
	char buf[1024] = {'\0'};

	if (argc != 2)
	{
		printf("Usage: %s port_num\n", argv[0]);
		return (1);
	}

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0)
	{
		perror("Cannot create communicating socket");
		return (1);
	}

	length = sizeof(server);
	bzero(&server, length);
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port=htons(atoi(argv[1]));

	if (bind(sock, (struct sockaddr*)&server, length) < 0)
	{
		perror("Cannot bind the socket");
		close(sock);
		return (1);
	}

	fromlen = sizeof(struct sockaddr_in);

	while ( 1 )
	{
		n = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &fromlen);
		if ( n < 0 )
		{
			perror("Cannot receive date from client");
			break;
		}

		write(STDOUT_FILENO, "server: Received a datagram: ", 29);
		write(STDOUT_FILENO, buf, n);

		n = sendto(sock, "send message to client\n", 22, 0, (struct sockaddr*)&from, fromlen);
		if (n < 0)
		{
			perror("Cannot send data to the client");
			break;
		}
	}

	close(sock);

	return (0);
}


 

 

Client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	int sock;
	int length;
	int n;
	struct sockaddr_in server;
	struct sockaddr_in from;
	struct hostent *hp;
	char buffer[256] = {'\0'};

	if (argc != 3)
	{
		printf("Usage : %s server_ip port_num\n", argv[0]);
		return (1);
	}

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0)
	{
		perror("Cannot create communicating socket");
		return (1);
	}

	server.sin_family = AF_INET;
	hp = gethostbyname(argv[1]);
	if (hp == 0)
	{
		perror("cannot get the server ip address");
		return (1);
	}

	bcopy((char *)hp->h_addr, (char *)&server.sin_addr, hp->h_length);
	server.sin_port = htons(atoi(argv[2]));
	length = sizeof(struct sockaddr_in);

	printf("(client) enter the message: ");
	bzero(buffer, 256);
	fgets(buffer, 255, stdin);

	n = sendto(sock, buffer, strlen(buffer), 0, &server, length);
	if (n < 0)
	{
		perror("Cannot send message to the server");
		return (1);
	}

	bzero(buffer, 256);
	n = recvfrom(sock, buffer, 256, 0, &from, &length);
	if ( n < 0 )
	{
		perror("Cannot get message from server");

	}

	printf("Client get message : %s\n", buffer);

	close(sock);

	return (0);
}


 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值