UDP(user datagram protocol)用户数据报协议,属于传输层。
UDP是面向非连接的协议,它不与对方建立连接,而是直接把数据报发给对方。UDP无需建立类如三次握手的连接,使得通信效率很高。因此UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景。
UDP通信的过程:
服务端:
(1)使用函数socket(),生成套接字文件描述符;
(2)通过struct sockaddr_in 结构设置服务器地址和监听端口;
(3)使用bind() 函数绑定监听端口,将套接字文件描述符和地址类型变量(struct sockaddr_in )进行绑定;
(4)接收客户端的数据,使用recvfrom() 函数接收客户端的网络数据;
(5)向客户端发送数据,使用sendto() 函数向服务器主机发送数据;
(6)关闭套接字,使用close() 函数释放资源;
客户端:
(1)使用socket(),生成套接字文件描述符;
(2)通过struct sockaddr_in 结构设置服务器地址和监听端口;
(3)向服务器发送数据,sendto() ;
(4)接收服务器的数据,recvfrom() ;
(5)关闭套接字,close() ;
sockaddr 与 sockaddr_in 区别
一、sockaddr
sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:
/* POSIX.1g specifies this type name for the `sa_family' member. */
typedef unsigned short int sa_family_t;
/* This macro is used to declare the initial common members
of the data types used for socket addresses, `struct sockaddr',
`struct sockaddr_in', `struct sockaddr_un', etc. */
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
相关视频推荐
C++网络面试题:TCP/UDP应用场景分析,UDP如何实现可靠性设计
大厂面试复盘-UDP协议常见面试问题分享
7道面试题打通C/C++后端开发的技术脉络
免费学习地址:c/c++ linux服务器开发/后台架构师
需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
二、sockaddr_in
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};
sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。
三、总结
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
例子如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc,char **argv)
{
int sockfd = 0;
struct sockaddr_in addr_in;
struct sockaddr * addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); //获得fd
bzero(&addr_in,sizeof(addr_in)); // 初始化结构体
/*
8008的主机字节序 小端字节序 0001 1111 0100 1000 = 8008
8008的网络字节序 大端字节序 0100 1000 0001 1111 = 18463
*/
addr_in.sin_port = htons(8008);
addr_in.sin_family = AF_INET; // 设置地址家族
addr_in.sin_addr.s_addr = inet_addr("192.168.3.30"); //设置地址
printf("sockaddr_in.sin_addr.s_addr = %d \n", addr_in.sin_addr.s_addr);
printf("addr = %s \n", inet_ntoa(addr_in.sin_addr));
// addr_in.sin_addr.s_addr = htonl(INADDR_ANY); //设置地址
printf("struct sockaddr size = %ld \n", sizeof (addr));
printf("struct sockaddr_in size = %ld \n", sizeof (addr_in));
addr = (struct sockaddr *)&addr_in;
// bind(sockfd, (struct sockaddr *)&addr_in, sizeof(struct sockaddr)); /* bind的时候进行转化 */
bind(sockfd, addr, sizeof(struct sockaddr));
... ...
return 0;
}
题外话,两个函数 htons() 和 inet_addr()。
htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)
inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。
inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。比如:
printf("%s",inet_ntoa(mysock.sin_addr));
htonl()作用和htons()一样,不过它针对的是32位的(long),而htons()针对的是两个字节,16位的(short)。
与htonl()和htons()作用相反的两个函数是:ntohl()和ntohs()。
sendto()
1 1 int sendto(int s, const void *buf, int len, unsigned int flags,
2 const struct sockaddr *to, int tolen);
返回值说明:
成功则返回实际传送出去的字符数,失败返回-1,错误原因会存于errno 中。
参数说明:
-
s: socket描述符;
-
buf:UDP数据报缓存区(包含待发送数据);
-
len: UDP数据报的长度;
-
flags:调用方式标志位(一般设置为0);
-
to: 指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换);
-
tolen:to所指结构体的长度;
recvfrom()
1 int recvfrom(int s, void *buf, int len, unsigned int flags,
2 struct sockaddr *from, int *fromlen);
返回值说明:
成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中。
参数说明:
-
s: socket描述符;
-
buf: UDP数据报缓存区(包含所接收的数据);
-
len: 缓冲区长度。
-
flags: 调用操作方式(一般设置为0)。
-
from: 指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换);
-
fromlen:指针,指向from结构体长度值。
示例代码
服务端
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAXLINE 4096
#define UDPPORT 8001
#define SERVERIP "192.168.255.129"
using namespace std;
int main(){
int serverfd;
unsigned int server_addr_length, client_addr_length;
char recvline[MAXLINE];
char sendline[MAXLINE];
struct sockaddr_in serveraddr , clientaddr;
// 使用函数socket(),生成套接字文件描述符;
if( (serverfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ){
perror("socket() error");
exit(1);
}
// 通过struct sockaddr_in 结构设置服务器地址和监听端口;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(UDPPORT);
server_addr_length = sizeof(serveraddr);
// 使用bind() 函数绑定监听端口,将套接字文件描述符和地址类型变量(struct sockaddr_in )进行绑定;
if( bind(serverfd, (struct sockaddr *) &serveraddr, server_addr_length) < 0){
perror("bind() error");
exit(1);
}
// 接收客户端的数据,使用recvfrom() 函数接收客户端的网络数据;
client_addr_length = sizeof(sockaddr_in);
int recv_length = 0;
recv_length = recvfrom(serverfd, recvline, sizeof(recvline), 0, (struct sockaddr *) &clientaddr, &client_addr_length);
cout << "recv_length = "<< recv_length <<endl;
cout << recvline << endl;
// 向客户端发送数据,使用sendto() 函数向服务器主机发送数据;
int send_length = 0;
sprintf(sendline, "hello client !");
send_length = sendto(serverfd, sendline, sizeof(sendline), 0, (struct sockaddr *) &clientaddr, client_addr_length);
if( send_length < 0){
perror("sendto() error");
exit(1);
}
cout << "send_length = "<< send_length <<endl;
//关闭套接字,使用close() 函数释放资源;
close(serverfd);
return 0;
}
客户端
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAXLINE 4096
#define UDPPORT 8001
#define SERVERIP "192.168.255.129"
using namespace std;
int main(){
int confd;
unsigned int addr_length;
char recvline[MAXLINE];
char sendline[MAXLINE];
struct sockaddr_in serveraddr;
// 使用socket(),生成套接字文件描述符;
if( (confd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ){
perror("socket() error");
exit(1);
}
//通过struct sockaddr_in 结构设置服务器地址和监听端口;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(SERVERIP);
serveraddr.sin_port = htons(UDPPORT);
addr_length = sizeof(serveraddr);
// 向服务器发送数据,sendto() ;
int send_length = 0;
sprintf(sendline,"hello server!");
send_length = sendto(confd, sendline, sizeof(sendline), 0, (struct sockaddr *) &serveraddr, addr_length);
if(send_length < 0 ){
perror("sendto() error");
exit(1);
}
cout << "send_length = " << send_length << endl;
// 接收服务器的数据,recvfrom() ;
int recv_length = 0;
recv_length = recvfrom(confd, recvline, sizeof(recvline), 0, (struct sockaddr *) &serveraddr, &addr_length);
cout << "recv_length = " << recv_length <<endl;
cout << recvline << endl;
// 关闭套接字,close() ;
close(confd);
return 0;
}