文章目录
前言
本期主要分享的时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进程进行实现;
最后,各位小伙伴们如果喜欢我的分享可以点赞收藏哦,你们的认可是我创作的动力,一起加油!