113人参与 • 2024-08-01 • udp
udp,即用户数据报协议,是一种面向无连接的传输层协议。相比于tcp协议,udp具有以下特点:
udp应用:
udp协议被广泛应用于各种网络应用中,包括但不限于以下几种:
此外,一些实时性要求较高的应用,如在线游戏和voip通话,也会选择使用udp协议来减少延迟,提高用户体验
socket,也被称为"套接字",是网络编程中用于实现不同主机上进程间通信的一种技术。它提供了一种将网络通信抽象为文件操作的接口,使得程序员可以通过简单的函数调用来实现复杂的网络通信功能。
socket的特点
socket的分类
根据使用的协议和通信方式的不同,socket可以分为以下几种类型:
udp网络编程流程:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:
创建一个套接字,返回一个文件描述符
参数:
domain:通信域,协议族
af_unix 本地通信
af_inet ipv4网络协议
af_inet6 ipv6网络协议
af_packet 底层接口
type:
套接字的类型
sock_stream 流式套接字(tcp)
sock_dgram 数据报套接字(udp)
sock_raw 原始套接字(用于链路层)
protocol:
附加协议,如果不需要,则设置为0
返回值:
成功:文件描述符
失败:-1
特点
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
// 使用socket函数创建套接字
// 创建一个用于udp网络编程的套接字
int sockfd;
if ((sockfd = socket(af_inet, sock_dgram, 0)) == -1) {
perror("fail to socket");
exit(1);
}
printf("sockfd = %d\n", sockfd);
return 0;
}
执行结果
在网络编程中经常使用的结构体
头文件:#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr;//ip地址 4字节
};
struct sockaddr_in
{
sa_family_t sin_family;//协议族 2字节
in_port_t sin_port;//端口号 2字节
struct in_addr sin_addr;//ip地址 4字节
char sin_zero[8]//填充,不起什么作用 8字节
};
为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构,原因是因为不同场合所使用的结构体不一样,但是调用的函数却是同一个,所以定义一个通用结构体,当在指定场合使用时,在根据要求传入指定的结构体即可
通用结构体 sockaddr
头文件:#include
struct sockaddr
{
sa_family_t sa_family; // 2字节
char sa_data[14] //14字节
};
在定义源地址和目的地址结构的时候,选用struct sockaddr_in;
当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换
#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);
功能: sendto
函数用于通过套接字发送数据。
参数:
sockfd
:文件描述符,由socket
函数返回。buf
:指向要发送数据的缓冲区的指针。len
:要发送的数据的长度。flags
:标志位,用于指定发送操作的行为。常见的标志位有:
0
:阻塞模式,函数会一直等待直到数据发送完成。msg_dontwait
:非阻塞模式,如果数据不能立即发送,函数会立即返回错误。dest_addr
:指向目的网络信息结构体的指针,用于指定数据发送的目标地址。addrlen
:目的网络信息结构体的长度。返回值:
errno
以指示错误原因。sendto
函数是网络编程中常用的函数之一,它允许我们向指定的目标地址发送数据。在使用该函数时,我们需要正确地指定目的地址和地址长度,以确保数据能够准确地发送到目标。同时,我们也需要注意选择合适的标志位,以满足不同的发送需求。
我们用一个“网络调试助手”的软件来模拟服务器,给服务器发送消息。
客户端的代码编写
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <sys/types.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <string.h>
#define n 128
int main(int argc, char const *argv[])
{
//./a.out 192.168.3.78 8080
if(argc < 3)
{
fprintf(stderr, "usage:%s ip port\n", argv[0]);
exit(1);
}
//第一步:创建套接字
int sockfd;
if((sockfd = socket(af_inet, sock_dgram, 0)) == -1)
{
perror("fail to socket");
exit(1);
}
printf("sockfd = %d\n", sockfd);
//第二步:填充服务器网络信息结构体 sockaddr_in
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = af_inet; //协议族,af_inet:ipv4网络协议
serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //ip地址
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:发送数据
char buf[n] = "";
while(1)
{
fgets(buf, n, stdin);
buf[strlen(buf) - 1] = '\0'; //把buf字符串中的\n转化为\0
if(sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
perror("fail to sendto");
exit(1);
}
}
//第四步:关闭套接字文件描述符
close(sockfd);
return 0;
}
执行结果
udp网络程序想要收取数据需什么条件?
怎样完成上面的条件呢?
由于服务器是被动的,客户端是主动的,所以一般先运行服务器,后运行客户端,所以服务 器需要固定自己的信息(ip地址和端口号),这样客户端才可以找到服务器并与之通信,但 是客户端一般不需要bind绑定,因为系统会自动给客户端分配ip地址和端口号
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体
通用结构体(一般不用)
struct sockaddr
网络信息结构体
sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
addrlen:addr的长度
返回值:
成功:0
失败:-1
//第二步:将服务器的网络信息结构体绑定前进行填充
struct sockaddr_in serveraddr;
serveraddr.sin_family = af_inet;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:将网络信息结构体与套接字绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)
{
perror("fail to bind");
exit(1);
}
#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:buf的长度
flags:标志位
0 阻塞
msg_dontwait 非阻塞
src_addr:源的网络信息结构体(自动填充,定义变量传参即可)
addrlen:src_addr的长度
返回值:
成功:接收的字节数
失败:-1
此时我们把刚刚也用到过的网络调试助手作为客户端,ubuntu的程序作为服务器
设置服务器(ubuntu程序)
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <sys/types.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <string.h>
#define n 128
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "usage: %s ip port\n", argv[0]);
exit(1);
}
//第一步:创建套接字
int sockfd;
if((sockfd = socket(af_inet, sock_dgram, 0)) == -1)
{
perror("fail to socket");
exit(1);
}
//第二步:将服务器的网络信息结构体绑定前进行填充
struct sockaddr_in serveraddr;
serveraddr.sin_family = af_inet;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.103
serveraddr.sin_port = htons(atoi(argv[2])); //9999
//第三步:将网络信息结构体与套接字绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)
{
perror("fail to bind");
exit(1);
}
//接收数据
char buf[n] = "";
struct sockaddr_in clientaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
while(1)
{
if(recvfrom(sockfd, buf, n, 0, (struct sockaddr *)&clientaddr, &addrlen) == -1)
{
perror("fail to recvfrom");
exit(1);
}
//打印数据
//打印客户端的ip地址和端口号
printf("ip:%s, port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
//打印接收到数据
printf("from client: %s\n", buf);
}
return 0;
}
执行结果
接下来我们就可以自己实现服务器与客户端互发消息了
1、本地ip、本地端口(我是谁)
2、目的ip、目的端口(发给谁)
3、在客户端的代码中,我们只设置了目的ip、目的端口
客户端的本地ip、本地port是我们调用sendto的时候linux系统底层自动给客户端分配 的;分配端口的方式为随机分配,即每次运行系统给的port不一样
//udp客户端的实现
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <sys/types.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <string.h>
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd; //文件描述符
struct sockaddr_in serveraddr; //服务器网络信息结构体
socklen_t addrlen = sizeof(serveraddr);
//第一步:创建套接字
if((sockfd = socket(af_inet, sock_dgram, 0)) < 0)
{
perror("fail to socket");
exit(1);
}
//客户端自己指定自己的ip地址和端口号,一般不需要,系统会自动分配
#if 0
struct sockaddr_in clientaddr;
clientaddr.sin_family = af_inet;
clientaddr.sin_addr.s_addr = inet_addr(argv[3]); //客户端的ip地址
clientaddr.sin_port = htons(atoi(argv[4])); //客户端的端口号
if(bind(sockfd, (struct sockaddr *)&clientaddr, addrlen) < 0)
{
perror("fail to bind");
exit(1);
}
#endif
//第二步:填充服务器网络信息结构体
//inet_addr:将点分十进制字符串ip地址转化为整形数据
//htons:将主机字节序转化为网络字节序
//atoi:将数字型字符串转化为整形数据
serveraddr.sin_family = af_inet;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:进行通信
char buf[32] = "";
while(1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("fail to sendto");
exit(1);
}
char text[32] = "";
if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&serveraddr, &addrlen) < 0)
{
perror("fail to recvfrom");
exit(1);
}
printf("from server: %s\n", text);
}
//第四步:关闭文件描述符
close(sockfd);
return 0;
}
1、服务器之所以要bind是因为它的本地port需要是固定,而不是随机的
2、服务器也可以主动地给客户端发送数据
3、客户端也可以用bind,这样客户端的本地端口就是固定的了,但一般不这样做
//udp服务器的实现
#include <stdio.h> //printf
#include <stdlib.h> //exit
#include <sys/types.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <string.h>
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd; //文件描述符
struct sockaddr_in serveraddr; //服务器网络信息结构体
socklen_t addrlen = sizeof(serveraddr);
//第一步:创建套接字
if((sockfd = socket(af_inet, sock_dgram, 0)) < 0)
{
perror("fail to socket");
exit(1);
}
//第二步:填充服务器网络信息结构体
//inet_addr:将点分十进制字符串ip地址转化为整形数据
//htons:将主机字节序转化为网络字节序
//atoi:将数字型字符串转化为整形数据
serveraddr.sin_family = af_inet;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:将套接字与服务器网络信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0)
{
perror("fail to bind");
exit(1);
}
while(1)
{
//第四步:进行通信
char text[32] = "";
struct sockaddr_in clientaddr;
if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&clientaddr, &addrlen) < 0)
{
perror("fail to recvfrom");
exit(1);
}
printf("[%s - %d]: %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), text);
strcat(text, " *_*");
if(sendto(sockfd, text, sizeof(text), 0, (struct sockaddr *)&clientaddr, addrlen) < 0)
{
perror("fail to sendto");
exit(1);
}
}
//第四步:关闭文件描述符
close(sockfd);
return 0;
}
执行的结果
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论