UDP网络编程


前言

本期主要分享的时UDP网络编程的相关内容,主要依赖于SOCKET套接字编程,UDP通信虽然是一种不可靠的传输方式,但是在我们平时用的也是非常多的,因此各位小伙伴们可以掌握这种编程方法,以便于在后续的工业控制中进行运用;


一、UDP通信的函数接口

   套接字:通信对象的抽象

1.socket

int socket(int domain, int type, int protocol);
功能:
	创建一个用来通信的套接字文件描述符
参数:
	domain:通信域		AF_INET		IPv4协议
	type:
		SOCK_DGRAM		用户数据报套接字		UDP		
		SOCK_STREAM		流式套接字		    TCP
		SOCK_RAW		原始套接字		
	protocol:
			默认为0 
返回值:
			成功返回新文件描述符
			失败返回-1 

2.sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);
功能:
	发送消息
参数:
			sockfd:套接字文件描述符
			buf:发送数据空间首地址
			len:发送数据的长度
			flags:发送属性(默认为0)
			dest_addr:目的地址
			addrlen:目的地址的长度
返回值:
			成功返回发送字节数,失败返回-1 
		IPV4地址类型可以通过man 7 ip查看

2.1 存放ip地址和端口号的结构体

		struct sockaddr_in {
			sa_family_t    sin_family; /* address family: AF_INET */协议族
			in_port_t      sin_port;   /* port in network byte order */端口
			struct in_addr sin_addr;   /* internet address */ip地址
		};

		/* Internet address. */
		struct in_addr {
		   uint32_t       s_addr;     /* address in network byte order */
		};

3. htons

	   uint16_t htons(uint16_t hostshort);
	   功能:
		将本地字节序转换为网络字节序

3. inet_addr

	   in_addr_t inet_addr(const char *cp);
	   功能:
		将字符串IP地址转换为二进制IP地址

3. bind

	   int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	   功能:
			将IP地址和套接字绑定
	   参数:
			sockfd:套接字文件描述符
			addr:IP地址空间首地址
			addrlen:IP地址长度
	   返回值:
			成功返回0
			失败返回-1 

3. recvfrom

	   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen);
	   功能:
			接收数据
	   参数:
			sockfd:套接字文件描述符
			buf:存放数据空间首地址
			len:最多存放数据的个数
			flags:属性默认为0 
			src_addr:存放发送方IP地址空间首地址
			addrlen:想要接收IP地址的长度
	   返回值:
			成功返回实际读取字节数
			失败返回-1 

二、 UDP编程模型:

发送端:
	(1)socket
	(2)sendto 
	(3)recvfrom
			
接收端:
	(1)socket
	(2)bind
	(3)recvfrom
	(4)sendto 5

三、UDP通信实例(使用UDP实现聊天室)

3.1.引入库

以下是代码的头文件,其中包括了自定义的消息结构体,代码如下:

#ifndef __HEAD_H__
#define __HEAD_H__

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

typedef struct msg
{
	char name[32];
	char mytext[1024];
}msg_t;

#endif

以下注释中将发送和接收方抽象为A,B两方;

3.2.具体代码实现过程

本项目主要包括两个代码文件,每个文件的实现过程都是既有发送和接受消息的功能,并且都是通过创建线程来实现的;

3.2.1 A代码

#include "head.h"

pthread_t tid1;
pthread_t tid2;
int sockfd = -1;		//socket通信文件描述符
ssize_t nsize = 0;
char name[32];			//存储用户姓名
void *sendfun(void *arg)
{
	msg_t tmpmsg;
	struct sockaddr_in recvaddr;		//存放协议族端口号以及ip地址的结构体

	recvaddr.sin_family = AF_INET;		//IPV4协议族
	recvaddr.sin_port = htons(50001);	//讲整形变量的端口号转变成网络字节序(因为整数一般存储方式为小端存储,但是IPV4有它特定的网络字节序)
	recvaddr.sin_addr.s_addr = inet_addr("192.168.209.129");  //将点分十进制转化为IPV4能够识别的二进制数
	
	while(1)
	{
		memset(&tmpmsg, 0, sizeof(tmpmsg.mytext));
		strcpy(tmpmsg.name, name);					//拷贝主进程中的用户昵称
		gets(tmpmsg.mytext);						//接收需要发送的消息

		nsize = sendto(sockfd, &tmpmsg, sizeof(tmpmsg), 0, (struct sockaddr *)(&recvaddr), sizeof(recvaddr));  //发送消息
		if (-1 == nsize)
		{
			perror("fail to sendto");
			return NULL;
		}

		if (!strcmp(tmpmsg.mytext, "q"))	//发送q结束聊天
		{
			break;
		}

	}
	close(sockfd);

	pthread_cancel(tid2);

	return NULL;
}

void *recvfun(void *arg)
{
	msg_t tmpmsg;

	while (1)
	{
		memset(&tmpmsg, 0, sizeof(msg_t));
		nsize = recvfrom(sockfd, &tmpmsg, sizeof(msg_t), 0, NULL, NULL); //接收消息
		if (-1 == nsize)
		{
			perror("fail to recvfrom");
			return NULL;
		}
		if (!strcmp(tmpmsg.mytext, "q"))
		{
			break;
		}

		printf("%s:%s\n", tmpmsg.name, tmpmsg.mytext);
	}

	close(sockfd);
	pthread_cancel(tid1);
	return NULL;
}

int main(int argc, const char *argv[])
{
	int ret = 0;
	struct sockaddr_in senaddr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == sockfd)
	{
		perror("fail to sockfd");
		return -1;
	}

	senaddr.sin_family = AF_INET;
	senaddr.sin_port = htons(50000);
	senaddr.sin_addr.s_addr = inet_addr("192.168.209.128");

	ret = bind(sockfd, (struct sockaddr *)&senaddr, sizeof(senaddr));	//绑定自己的端口号和IP(清楚本进程是谁)
	if (-1 == ret)
	{
		perror("fail to bind");
		return -1;
	}	

	printf("请输入您的昵称:");
	gets(name);
	putchar('\n');

	pthread_create(&tid1, NULL, sendfun, NULL);
	pthread_create(&tid2, NULL, recvfun, NULL);

	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	return 0;
}

3.2.2 B代码

#include "head.h"

pthread_t tid1;
pthread_t tid2;
int sockfd = -1;
struct sockaddr_in recvaddr;
char tmpbuff[1024] = {0};
int ret = 0;
ssize_t nsize = 0;
char name[32];
void *sendfun(void *arg)
{
	msg_t tmpmsg;
	struct sockaddr_in senaddr;
	
	senaddr.sin_family = AF_INET;
	senaddr.sin_port = htons(50000);
	senaddr.sin_addr.s_addr = inet_addr("192.168.209.128");
	while(1)
	{
		memset(&tmpmsg, 0, sizeof(msg_t));
		strcpy(tmpmsg.name, name);
		gets(tmpmsg.mytext);

		nsize = sendto(sockfd, &tmpmsg, sizeof(msg_t), 0, (struct sockaddr *)(&senaddr), sizeof(senaddr));
		if (-1 == nsize)
		{
			perror("fail to sendto");
			return NULL;
		}
		if (!strcmp(tmpmsg.mytext, "q"))
		{
			break;
		}
	}

	close(sockfd);
	pthread_cancel(tid2);

	return NULL;
}


void *recvfun(void *arg)
{
	msg_t tmpmsg;

	while (1)
	{
		memset(&tmpmsg, 0, sizeof(tmpmsg));
		nsize = recvfrom(sockfd, &tmpmsg, sizeof(msg_t), 0, NULL, NULL);
		if (-1 == nsize)
		{
			perror("fail to recvfrom");
			return NULL;
		}
		if (!strcmp(tmpmsg.mytext, "q"))
		{
			break;
		}

		printf("%s:%s\n", tmpmsg.name, tmpmsg.mytext);
	}

	close(sockfd);
	pthread_cancel(tid1);
	return NULL;
}
	
int main(int argc, const char *argv[])
{

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == sockfd)
	{
		perror("fail to sockfd");
		return -1;
	}

	recvaddr.sin_family = AF_INET;
	recvaddr.sin_port = htons(50001);
	recvaddr.sin_addr.s_addr = inet_addr("192.168.209.129");

	ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
	if (-1 == ret)
	{
		perror("fail to bind");
		return -1;
	}

	printf("请输入昵称:");
	gets(name);
	putchar('\n');

	pthread_create(&tid1, NULL, sendfun, NULL);
	pthread_create(&tid2, NULL, recvfun, NULL);

	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	return 0;
}


总结

1.必须在了解UDP基本通信特点以及通信流程的前提下进行UDP通信编程,这也是本次分享的关键,虽然只有简单的几个接口,但是每个接口的具体使用都需要使用者认真剖析,在经过若干项目的练习后就会熟能生巧;
2.必须了解线程的基本应用,本次的分享使用的是线程实现的,小伙伴们也可以尝试一下通过fork进程进行实现;
最后,各位小伙伴们如果喜欢我的分享可以点赞收藏哦,你们的认可是我创作的动力,一起加油!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值