UDP网络编程基础


来源:《基于UNIX/Linux的C系统编程》

UDP协议采用C/S模式,套接字的全部工作流程如下图:

 

步骤1:服务器端和客户端进程均调用socket函数创建一个基于UDP协议的套接字描述符;

步骤2:服务器端进程调用bind函数命名套接字,将套接字与协议、本地地址和本地端口绑定;

步骤3:客户端与服务器端进行数据通信,调用sendto函数可向对方发送数据,调用recvfrom函数可接收数据;

步骤4:当不需要数据传输时,可以调用close或shutdown函数关闭套接字。

1、创建UDP套接字

socket函数可以用于创建UDP套接字,其调用形式与TCP类似,但须在函数的第二个参数type取值为SOCK_DGRAM。

与TCP协议服务器端套接字的创建过程相比,这里减少了listen和accept过程。

2、UDP数据的发送和接收

在UDP套接字中,使用recvfrom和sendto函数来收发数据,其函数说明如下表:

所需头文件 #include <sys/types.h>
#include <sys/socket.h>
函数原型  int sendto(int sockfd, const void *buff, int nbytes, int flags, struct sockaddr *to, socklen_t *addrlen);
 int recvfrom(int sockfd, void *buff, int nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
参数  sockfd:套接字描述符
 buff:一个指向接收数据缓冲区的指针
 nbytes:指定缓冲区最大可以容纳数据的大小(字节)
 flags:在UDP连接中一般设为0
 to:一个含有数据将发往的协议地址(包含IP地址和端口号)的套接字地址结构,其大小由addrlen来指定
 from:所指向的套接字地址结构装填数据包发送者的协议地址,它的大小也由addrlen所指的整数返回给调用者
 addrlen:地址长度
函数返回值  sendto():若函数调用成功,返回读入的字节数,否则返回-1并将错误码写入errno
 recvfrom():若函数调用成功,返回写出的字节数,否则返回-1并将错误码写入errno

与TCP数据的发送和接收相比,UDP的发送和接收函数中都增加了用于存储对方地址的结构,这主要与UDP无连接的特性相关。

例1:结合UDP编程模型,编写一程序验证上述函数的可行性。需要实现以下功能:

  • UDP服务器程序:守候在特定的套接字地址上,循环接收客户发来的信息,并显示客户IP地址及相应信息。如果服务收到客户的信息为”quit”,则退出循环,并关闭套接字;
  • UDP客户机程序:客户向服务器发信息,然后等待服务器回应。一旦接收到服务器发来的数据,则显示该信息,并关闭套接字。

客户端程序:

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

#define PORT 1234
#define MAXDATASIZE 100

int main(int argc, char *argv[]) {
	int fd, numbytes;      // files descriptors
	char buf[MAXDATASIZE]; // buf will store received text
	struct hostent *he;    // structure that will get infotmation about remote host
	struct sockaddr_in server, reply; // server's address information
	if (argc != 3) {
		printf("Usage: %s <IP Address> <message>\n", argv[0]);
		exit(1);
	}
	if ((he = gethostbyname(argv[1])) == NULL) {
		perror("gethostbyname() error!");
		exit(1);
	}
	if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
		perror("socket() error");
		exit(1);
	}
	memset(&server, 0, sizeof(server));
	server.sin_family = PF_INET;
	server.sin_port = htons(PORT);
	server.sin_addr = *((struct in_addr *)he->h_addr);
	sendto(fd, argv[2], strlen(argv[2]), 0, (struct sockaddr *)&server, sizeof(struct sockaddr));
	while (1) {
		int len;
		if ((numbytes = recvfrom(fd, buf, MAXDATASIZE, 0, (struct sockaddr *)&reply, &len)) == -1) {
			perror("ercvfrom() error!");
			exit(1);
		}
		buf[numbytes] = '\0';
		printf("Server message: %s\n", buf);
		break;
	}
	close(fd);
}

服务器端程序:

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

#define PORT 1234
#define MAXDATASIZE 100

int main() {
	int sockfd;
	struct sockaddr_in server, client;
	int sin_size, num;
	char msg[MAXDATASIZE]; // buffer for message
	/* Creating UDP socket */
	if ((sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
		perror("Creating socket error!");
		exit(1);
	}
	memset(&server, 0, sizeof(server));
	server.sin_family = PF_INET;
	server.sin_port = htons(PORT);
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
		perror("bind() error!");
		exit(1);
	}
	sin_size = sizeof(struct sockaddr_in);
	while (1) {
		num = recvfrom(sockfd, msg, MAXDATASIZE, 0, (struct sockaddr *)&client, &sin_size);
		if (num < 0) {
			perror("recvfrom() error!");
			exit(1);
		}
		msg[num] = '\0';
		printf("You got a message %s from %s\n", msg, inet_ntoa(client.sin_addr)); // Print client's IP
		sendto(sockfd, "Welcome to my server.\n", 22, 0, (struct sockaddr *)&client, sin_size);
		if (strcmp(msg, "quit") == 0)
			break;
	}
	close(sockfd);
}

客户端运行结果如下:

此处留截图。

服务器端运行结果如下:

此处留截图。

Leave a comment

邮箱地址不会被公开。 必填项已用*标注