UDP服务器/客户端Linux下C代码实现以及广播、组播

UDP

UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。常用于以下情况:

  1. 发送小尺寸数据( 如对DNS服务器进行IP地址查询时)。
  2. 在接收到数据, 给出应答较困难的网络中使用UDP。(如: 无线网络)
  3. 适合于广播/组播式通信中。
  4. MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议。
  5. 流媒体、 VOD、 VoIP、 IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输。

UDP的服务器客户端通信流程:
在这里插入图片描述
UDP常用的数据接收和发送函数:recvfrom(),sendto()

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

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
    sockfd :数据报套接字,socket函数返回值
    buf :内存地址
    len :接收数据的大小
    flags :标志位 0
    src_addr :发送端的addr结构体地址
    addrlen :addr结构体的长度的地址
返回值:成功则返回接收的字节数,出错返回-1
#include <sys/types.h>
#include <sys/socket.h>
       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:接收数据
参数:
    sockfd :数据报套接字,socket函数返回值
    buf :内存地址
    len :接收数据的大小
    flags :标志位 0
    src_addr :接收端的addr结构体地址
    addrlen :addr结构体的长度的地址
返回值:成功则返回发送的字节数,出错返回-1

服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	
	//2. 绑定服务器的IP地址和端口号
	struct sockaddr_in serveraddr ={0};
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(7777);
	//serveraddr.sin_addr.s_addr = inet_addr("0");
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	int len = sizeof(serveraddr);
	int ret = bind(sockfd,  (struct sockaddr *)&serveraddr, len );
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}

	//3.收发数据	
	struct sockaddr_in clientaddr={0};
	int l=sizeof(clientaddr);
	char buf[64] = {0};
	while(1)
	{
		int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&l);
		if(n<0)
		{
			perror("recvfrom");
			return -1;
		}
		printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
		
		printf("message:%s\n", buf);
		if(strcmp(buf,"quit")==0)break;
		memset(buf, 0, 64); //数组清零
	}


	close(sockfd);
	
	return 0;
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
	if(argc<3)
	{
		printf("usage ./可执行文件 服务器IP地址 端口号");
		return -1;
	}
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	
	//服务器的IP地址和端口号
	struct sockaddr_in serveraddr = {0},clientaddr={0};
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(atoi(argv[2]));
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	int len = sizeof(serveraddr);

	//2.收发数据
	char buf[64] = {0};
	int l=sizeof(clientaddr);
	printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
	while(1)
	{
		fgets(buf,1024,stdin);
		buf[strlen(buf)-1]='\0';		
		int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);
		if(n<0)
		{
			perror("sendto");
			return -1;
		}
		if(strcmp(buf,"quit")==0)break;
		
		//printf("message:%s\n", buf);
	
		memset(buf, 0, 64); //数组清零
	}


	close(sockfd);
	
	return 0;
}

补充:
TCP和UDP无好坏之分,只是适用场景不同

TCP:

发送小包裹:可能会产生粘包,可通过让发送和接收字节数一致或者设置协议来解决

发送大包裹:会拆包传输,可能会数据混乱,需要协议来解决

UDP:

发送小包裹:一个一个发,不会产生粘包

发送大包裹:会丢包,不会拆包

广播

  • 如果同时发给局域网中的所有主机,称为广播

  • 只有用户数据报(使用UDP协议)套接字才能广播

以192.168.1.0 (255.255.255.0) 网段为例, 最大的主机地址192.168.1.255代表该网段的广播地址发到该地址的数据包被所有的主机接收

  • 255.255.255.255在所有网段中都代表广播地址,但有权限,普通用户无法发送广播数据

流程

发送广播消息:(client)
1.socket(AF_INET, SOCK_DGRAM, 0);
2.struct sockaddr_in argv[1] //向广播地址发送
//192.168.2.255
3.setsockopt //开广播权限
4.sendto (如果不开权限,会报错)

接收广播消息:(server)
1.socket(AF_INET, SOCK_DGRAM, 0);
2.struct sockaddr_in serveraddr;
3.bind //"0"地址 or 广播地址(只能收广播消息)
4.recvfrom

socket设置函数:      
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,void *optval, socklen_t
optlen);
参数:
	level : 选项级别( 例如SOL_SOCKET)
	optname : 选项名( 例如SO_BROADCAST)
	optval : 存放选项值的缓冲区的地址
	optlen : 缓冲区长度
返回值: 成功返回0 失败返回-1并设置errno
        
        
属性设置表:level
				SOL_SOCKET
------------------------------------------------
参数optname 		宏的作用 			对应参数optaval的类型
SO_BROADCAST 	允许发送广播数据 		 int
SO_DEBUG 		允许调试 			    int
SO_DONTROUTE 	不查找路由 			   int
SO_ERROR 		获得套接字错误 		  int
SO_KEEPALIVE 	保持连接 				int
SO_LINGER 		延迟关闭连接 			   struct linger
SO_OOBINLINE 	带外数据放入正常数据流 	int
SO_RCVBUF 		接收缓冲区大小 		   int
SO_SNDBUF 		发送缓冲区大小 		   int
SO_RCVLOWAT 	接收缓冲区下限 		   int
SO_SNDLOWAT 	发送缓冲区下限 		   int
SO_RCVTIMEO 	接收超时 				struct timeval
SO_SNDTIMEO 	发送超时 				struct timeval
SO_REUSEADDR 	允许重用本地地址和端口 	int
SO_TYPE 		获得套接字类型 		   int
SO_BSDCOMPAT 	与BSD系统兼容 			int
======================================================
					IPPROTO_IP
------------------------------------------------------
IP_ADD_MEMBERSHIP 加入到组播组中 		   struct ip_mreq
IP_MULTICAST_IF 允许开启组播报文的接口 	struct ip_mreq
    


服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	
	//2. 绑定服务器的IP地址和端口号
	struct sockaddr_in serveraddr ={0};
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(7777);
	//serveraddr.sin_addr.s_addr = inet_addr("0");
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	int len = sizeof(serveraddr);
	int ret = bind(sockfd,  (struct sockaddr *)&serveraddr, len );
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}

	//3.收发数据	
	struct sockaddr_in clientaddr={0};
	int l=sizeof(clientaddr);
	char buf[64] = {0};
	while(1)
	{
		int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&l);
		if(n<0)
		{
			perror("recvfrom");
			return -1;
		}
		printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
		
		printf("message:%s\n", buf);
		if(strcmp(buf,"quit")==0)break;
		memset(buf, 0, 64); //数组清零
	}


	close(sockfd);
	
	return 0;
}

客户端:

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

int main(int argc, const char *argv[])
{
	if(argc<3)
	{
		printf("usage ./可执行文件 服务器IP地址 端口号");
		return -1;
	}
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	//打开广播权限
	int on=1;
	int m=setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
	if(m<0)
	{
		perror("setsockopt");
		return -1;
	}
	//服务器的IP地址和端口号
	struct sockaddr_in serveraddr = {0},clientaddr={0};
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(atoi(argv[2]));
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	int len = sizeof(serveraddr);
	
	//2.收发数据
	char buf[64] = {0};
	int l=sizeof(clientaddr);
	printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
	while(1)
	{
		fgets(buf,1024,stdin);
		buf[strlen(buf)-1]='\0';		
		int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);
		printf("n=%d\n",n);		
		//printf("message:%s\n", buf);
		if(strcmp(buf,"quit")==0)break;
		memset(buf, 0, 64); //数组清零
	}


	close(sockfd);
	
	return 0;
}

组播

  • 广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。

  • 组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。

  • 多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

D类地址(组播地址)
不分网络地址和主机地址, 第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255

流程

发送组播消息:(client)
1.socket
2.struct sockaddr_in argv[1]//向组播地址发送
3.sendto

接收组播消息:(server) man 7 ip
1.socket
2.struct sockaddr_in serveraddr;
3.struct ip_mreqn mreq; // 组播地址 + "0"地址
4.setsockopt //加入多播组
5.bind
6.recvfrom

struct ip_mreqn {
	struct in_addr imr_multiaddr; /* IP multicast group address即多播组IP地址 */
	struct in_addr imr_address; /* IP address of local interface 本地接口的IP地址 */
	int imr_ifindex; /* interface index接口索引 */
};

服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, const char *argv[])
{
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	
	//2. 绑定服务器的IP地址和端口号
	struct sockaddr_in serveraddr ={0};
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(7777);
	//serveraddr.sin_addr.s_addr = inet_addr("0");
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	int len = sizeof(serveraddr);
	int ret = bind(sockfd,  (struct sockaddr *)&serveraddr, len );
	if(ret == -1)
	{
		perror("bind");
		return -1;
	}
	//组播
	struct ip_mreqn mreq;
	mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.10");
	mreq.imr_address.s_addr = inet_addr("0");
	//加入多播组
	ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq) );
	if(ret == -1)
	{
		perror("setsockopt");
		return -1;
	}
	//3.收发数据	
	struct sockaddr_in clientaddr={0};
	int l=sizeof(clientaddr);
	char buf[64] = {0};
	while(1)
	{
		int n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&clientaddr,&l);
		if(n<0)
		{
			perror("recvfrom");
			return -1;
		}
		printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
		
		printf("message:%s\n", buf);
		if(strcmp(buf,"quit")==0)break;
		memset(buf, 0, 64); //数组清零
	}


	close(sockfd);
	
	return 0;
}

客户端:

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

int main(int argc, const char *argv[])
{
	if(argc<3)
	{
		printf("usage ./可执行文件 服务器IP地址 端口号");
		return -1;
	}
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	//服务器的IP地址和端口号
	struct sockaddr_in serveraddr = {0},clientaddr={0};
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(atoi(argv[2]));
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	int len = sizeof(serveraddr);
	
	//2.收发数据
	char buf[64] = {0};
	int l=sizeof(clientaddr);
	printf("server ip:%s server port:%d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
	while(1)
	{
		fgets(buf,1024,stdin);
		buf[strlen(buf)-1]='\0';		
		int n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serveraddr,len);
		printf("n=%d\n",n);		
		//printf("message:%s\n", buf);
		if(strcmp(buf,"quit")==0)break;
		memset(buf, 0, 64); //数组清零
	}

	close(sockfd);
	
	return 0;
}
  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值