初学网络编程之UDP

458 阅读3分钟

UDP:

用户数据报协议

特点:

    是无连接的协议,不可靠,数据漏包,传输速度快

用途:

     (1)发送小尺寸数据:比如DNS服务器进行IP地址查询

     (2)在接收数据,给出应答比较困难的网络中使用UDP,比如:一些无线网络

     (3)QQ,飞秋,MSN,等即时通信工具的音视频数据传输和视频会议等相关的应用

     (4)流媒体、IPTV等网络多媒体服务中

     (5)广播和组播通信

UDP服务器与客户端设计思路

image.png

UDP与TCP的区别

(1)socket的第二个参数

     TCP:流式套接字    SOCK_STREAM

     UDP:数据报套接字 SOCK_DGRAM

(2)流程不一样,UDP不需要listen,accept,connect

(3)收发数据不一样,UDP:recvfrom,sendto

示例代码1

 服务器、客户端互发信息

//服务器
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

int main(int argc, char const *argv[])
{
	//1、创建通信套接字		//域       数据报套接字 
	int s_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (s_socket == -1)
	{
		perror("socket");
		return -1;
	}
	//套接字设置端口重用
	int reuse = -1;
	if(setsockopt(s_socket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse)) < 0)
	{
		perror("setsockopt error");
		return -1;
	}

	//2、初始化地址结构体
	struct sockaddr_in s_addr;
	memset(&s_addr, 0, sizeof(s_addr));

	s_addr.sin_family = AF_INET;   //地址族
	s_addr.sin_port = 55628;		//端口号
	// s_addr.sin_addr.s_addr = inet_addr("192.168.1.235");
	s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	//3、绑定服务器本机IP地址结构体和套接字
	if(bind(s_socket, (struct sockaddr *)&s_addr, sizeof(struct sockaddr)))
	{
		perror("bind");
		return -1;
	}
	printf("服务器地址绑定成功\n");

	//4、数据接收
	char r_buf[512];
	char w_buf[512];	
	//定义一个结构体用来存放客户端的地址
	struct sockaddr_in c_addr;
	int len = sizeof(c_addr);

	while(1)
	{
		memset(r_buf, 0, sizeof(r_buf));
		memset(w_buf, 0, sizeof(w_buf));
		memset(&c_addr, 0, sizeof(c_addr));
		recvfrom(s_socket, r_buf, sizeof(r_buf), 0, (struct sockaddr *)&c_addr, &len);
		printf("buf:%s\n", r_buf);
		printf("[%s][%d]\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
		
		scanf("%[^\n]", w_buf);
		getchar();
		sendto(s_socket, w_buf, sizeof(w_buf), 0, (struct sockaddr *)&c_addr, sizeof(c_addr));

	}

	//5、关闭套接字
	close(s_socket);
	return 0;
}
//客户端
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

int main(int argc, char const *argv[])
{
	//1、创建通信套接字		//域       数据报套接字 
	int c_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (c_socket == -1)
	{
		perror("socket");
		return -1;
	}
	//套接字设置端口重用
	int reuse = -1;
	if(setsockopt(c_socket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse)) < 0)
	{
		perror("setsockopt error");
		return -1;
	}

	//2、初始化地址结构体
	struct sockaddr_in c_addr;
	memset(&c_addr, 0, sizeof(c_addr));

	c_addr.sin_family = AF_INET;   //地址族
	c_addr.sin_port = 55628;		//端口号
	c_addr.sin_addr.s_addr = inet_addr("192.168.1.11");

	//3、绑定服务器本机IP地址结构体和套接字(可选)
	// if(bind(c_socket, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)))
	// {
	// 	perror("bind");
	// 	return -1;
	// }
	// printf("服务器地址绑定成功\n");

	//4、数据发送
	char w_buf[512];
	char r_buf[512];

	//定义一个服务器的地址结构体,用来存储IP地址和端口号
	struct sockaddr_in s_addr;
	int len = sizeof(s_addr);

	while(1)
	{
		memset(w_buf, 0, sizeof(w_buf));
		memset(r_buf, 0, sizeof(r_buf));
		scanf("%[^\n]", w_buf);
		getchar();
		sendto(c_socket, w_buf, sizeof(w_buf), 0, ( struct sockaddr *)&c_addr, sizeof(c_addr));	

		recvfrom(c_socket, r_buf, sizeof(r_buf), 0, (struct sockaddr *)&s_addr, &len);
		printf("buf:%s\n", r_buf);
		printf("[%s][%d]\n", inet_ntoa(s_addr.sin_addr), ntohs(s_addr.sin_port));
	}

	//5、关闭套接字
	close(c_socket);
	return 0;
}

运行结果

image.png

示例代码2

设计一个UDP服务器,实现:

(1)初始化绑定服务器IP,端口号,套接字

(2)循环接收消息

     1、客户端上线消息,显示当前上线的客户端IP和端口号,并记录

     2、客户端聊天信息,直接转发给对方客户端

设计一个UDP客户端程序,实现:

(1)上线要发送一条上线消息

(2)线程1:发送消息(聊天消息标记,对方IP,消息内容)

(3)线程2:接收消息,显示

//服务器
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

//设计一个存放地址信息的链表节点结构体
typedef struct node
{
	struct sockaddr_in client_addr;
	struct node *next;
}Node, *List;

//消息结构体
struct MSG
{
	int flag; 			//1表示上线消息,2表示聊天信息
	char to_IP[20]; 	//表示消息发送给那个用户的IP
	char from_IP[20];	//表示消息来自那个用户IP
	char text[1024];	//消息内容
};

int main(int argc, char const *argv[])
{
	//初始化一个带头结点的空链表
	List caddr_list = malloc(sizeof(Node));
	//1、创建通信套接字		//域       数据报套接字 
	int s_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (s_socket == -1)
	{
		perror("socket");
		return -1;
	}
	//套接字设置端口重用
	int reuse = -1;
	if(setsockopt(s_socket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse)) < 0)
	{
		perror("setsockopt error");
		return -1;
	}

	//2、初始化地址结构体
	struct sockaddr_in s_addr;
	memset(&s_addr, 0, sizeof(s_addr));

	s_addr.sin_family = AF_INET;   //地址族
	s_addr.sin_port = 55558;		//端口号
	// s_addr.sin_addr.s_addr = inet_addr("192.168.1.235");
	s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	//3、绑定服务器本机IP地址结构体和套接字
	if(bind(s_socket, (struct sockaddr *)&s_addr, sizeof(struct sockaddr)))
	{
		perror("bind");
		return -1;
	}
	printf("服务器地址绑定成功\n");

	//4、数据接收
	struct MSG rmsg;
	//定义一个结构体用来存放客户端的地址
	struct sockaddr_in c_addr;
	int len = sizeof(c_addr);

	while(1)
	{
		bzero(&rmsg, sizeof(rmsg));
		recvfrom(s_socket, &rmsg, sizeof(rmsg), 0, (struct sockaddr *)&c_addr, &len);


		//客户端上线信息
		if (rmsg.flag == 1)
		{
			//打印用户上线
			printf("用户【%s:%d】上线\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
			//将用户添加到链表里面
			List new = malloc(sizeof(Node));
			if (new == NULL)
			{
				printf("新节点创建失败\n");
				continue;
			}
			new->client_addr = c_addr;
			new->next = NULL;
			//将这个节点插入到链表中
			new->next = caddr_list->next;
			caddr_list->next = new;
			printf("新用户信息已记录\n");
		}
		//发送信息
		else		
		if (rmsg.flag == 2)//客户端聊天信息
		{
			List p = caddr_list;
			while(p->next != NULL)
			{
				p = p->next;
				if (strcmp(inet_ntoa(p->client_addr.sin_addr), rmsg.to_IP) == 0)
				{
					strcpy(rmsg.from_IP, inet_ntoa(c_addr.sin_addr));
					sendto(s_socket, &rmsg, sizeof(rmsg), 0, 
						(struct sockaddr *)&(p->client_addr), 
						sizeof(struct sockaddr_in));
				}
			}
		}
		// else
		// if (rmsg.flag == 3)//离线
		// {
		// 	//找到消息的源地址,从链表中删除,打印xxx下线
		// }
	}

	//5、关闭套接字
	close(s_socket);

	return 0;
}
//客户端
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

//消息结构体
struct MSG
{
	int flag; 			//1表示上线消息,2表示聊天信息
	char to_IP[20]; 	//表示消息发送给那个用户的IP
	char from_IP[20];	//表示消息来自那个用户IP
	char text[1024];	//消息内容
};

void *recv_msg(void *arg)
{
	int c_socket = *(int *)arg;
	//定义一个服务器的地址结构体,用来存储IP地址和端口号
	struct sockaddr_in s_addr;
	int len = sizeof(s_addr);
	struct MSG rmsg;
	while(1)
	{
		memset(&s_addr, 0, sizeof(s_addr));
		memset(&rmsg, 0, sizeof(rmsg));
		recvfrom(c_socket, &rmsg, sizeof(rmsg), 0, (struct sockaddr *)&s_addr, &len);
		printf("-------------------------------------------\n");
		printf("来自【%s】的消息:%s\n", rmsg.from_IP, rmsg.text);
		printf("-------------------------------------------\n");
	}
}

int main(int argc, char const *argv[])
{
	//1、创建通信套接字		//域       数据报套接字 
	int c_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (c_socket == -1)
	{
		perror("socket");
		return -1;
	}
	//套接字设置端口重用
	int reuse = -1;
	if(setsockopt(c_socket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse)) < 0)
	{
		perror("setsockopt error");
		return -1;
	}

	//2、初始化地址结构体
	struct sockaddr_in c_addr;
	memset(&c_addr, 0, sizeof(c_addr));

	c_addr.sin_family = AF_INET;   //地址族
	c_addr.sin_port = 55558;		//端口号
	c_addr.sin_addr.s_addr = inet_addr("192.168.1.235");

	//先发一条上线消息
	struct MSG one_msg = {1, "", "", ""};
	sendto(c_socket, &one_msg, sizeof(one_msg), 0, ( struct sockaddr *)&c_addr, sizeof(c_addr));

	//4、数据发送
	struct MSG smsg;

	pthread_t pid;
	pthread_create(&pid, NULL, recv_msg, (void *)&c_socket);

	while(1)
	{
		memset(&smsg, 0, sizeof(smsg));
		smsg.flag = 2;
		scanf("%s%s", smsg.to_IP, smsg.text);
		getchar();
		sendto(c_socket, &smsg, sizeof(smsg), 0, ( struct sockaddr *)&c_addr, sizeof(c_addr));	
		printf("发送成功\n");	
	}

	//5、关闭套接字
	close(c_socket);
	return 0;
}