进程通信之Udp
目录
- Ucp的概念
- UDPclient.c, UDPserver.c–这里是客户端给服务器发数据,反过来自己改改
- 编程流程图
- 输出
注意:udp提供无连接服务的,所以没有像tcp的listen(),accept(),connect(),创建好套接字后,就直接通信:sendto(),recvfrom()
(一)udp的概念–User Datagram Protocol(用户数据报协议)
- TCP协议和UDP协议是5层网络协议传输层最重要的协议
- TCP是面向连接的传输控制协议
- UDP提供了无连接的数据报服务
- 熟知协议及端口号:DNS–53; TFTP----69; SNMP—161
(二)UDPclient.c
写在前面:建议参考一定要阅读man 7 ip
(1)建立socket服务
//creates an endpoint for communication and returns a descriptor
int socket(int domain, int type, int protocol);
参数分析:
-
domain:使用的协议,下面是所有取值
- type:套接口的类型描述,下面是所有取值
- 返回值: On success, a file descriptor for the new socket is returned. On error, -1 is returned
上面简单的阐释了下,下面重点看ipv4的实现。 man 7 ip中详细描述了ipv4的实现
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //TCP--这么写就对了,这个组合是固定的
udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //UDP--这么写就对了,这个组合是固定的
raw_socket = socket(AF_INET, SOCK_RAW, protocol); //原始套接字
细心地同学会发现这里用的PF_INET,而不是AF_INET,其实两个在数值上是一样的。
- AF = Address Family—地址族,用在sockaddr_in中指定address family时一般设置为AF_INET
- PF = Protocol Family—协议族,socket使用的协议是PF_INET
- 混用其实没问题的,数值是一样的,上面是标准写法,指定协议用PF_INET
文件路径:/usr/include/bits/socket.h
(2)sockaddr_in 结构体–*man 7 ip
struct sockaddr_in {
sa_family_t sin_family; //address family:AF_INET--always set to AF_INET. This is requiredz(讲的很清楚,必须是这个值,这是规定)
in_port_t sin_port; //port in network byte order--要用honts转换
struct in_addr sin_addr; //internet address
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; //address in network byte order
};
- sin_family: ipv4的tcp/udp取AF_INET
- sin_port:端口号—必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字
-
sin_addrs._addr:IP地址—必须要采用网络数据格式,用到了我再说
补充,涉及格式转换,通信内部不识我们平常的一些术语,需要转化成网络字节顺序
//一段内存置零
extern void bzero(void *s, int n);
s 要置零的数据的起始地址;
n 要置零的数据字节个数
如:bzero(&s_add,sizeof(struct sockaddr_in));
//将一个点分十进制的IP转换成一个长整数型数(u_long类型)
in_addr_t inet_addr(const char *cp)
如:inet_addr(argv[1])
//将主机的无符号短整形数转换成网络字节顺序
uint16_t htons(uint16_t hostshort);
hostshort:主机字节顺序表达的16位数
如:s_add.sin_port=htons(portnum);
(3)发送数据
char sendline[100]; //待发送的数组被写入 Hello, world! 了
sprintf(sendline, "Hello, world!");
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即可
- dest_addr:目的套接口的地址
- addrlen :所指地址的长度
(三)UDPserver.c
前面和客户端差不多一样的,获取本机ip地址是INADDR_ANY哈
也是先建立socket服务----初始化struct sockaddr_in结构体。这里需要注意的是:
INADDR_ANY:(0.0.0.0)可以监听的所有地址
- 0.0.0.0/8可以表示本网络中的所有主机
- 0.0.0.0/32可以用作本机的源地址
- 0.0.0.0/0表示默认路由
- 0.0.0.0/0已经不是一个真正意义上的IP地址了。它表示的是一个集合:所有未知的主机和目的网络。路由表中无法查询的包都将送到全零网络的路由中去。
INADDR_LOOPBACK: (127.0.0.1)环回地址,数据报不会发出去的地址
INADDR_BROADCAST:(255.255.255.255)广播地址
(1)捆绑(主机地址INADDR_ANY–自动填本机地址/本机上的一个端口号–不指定随机获取)
//bind a name to a socket--分配一个本地名字
//It is normally necessary to assign a local address using bind() before a SOCK_STREAM socket may receive connections (see accept(2)).
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(2)接收客户端发送的数据
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 :源套接口的地址
- addrlen :所指地址的长度
代码:
UDPclient.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
/****************************************************************************************
** Udp(客户端)
** socket():建立socket服务
** int socket(int domain, int type, int protocol);
** domain :使用的协议
** type :套接口的类型描述
** protocol:0
** RETURN VALUE
** On success, a file descriptor for the new socket is returned. On error, -1 is returned
** sprintf():将字符串写进str所指向的数组指针
** int sprintf(char *str, const char *format, ...);
** str :字符数数组
** sendto():发送数据
** 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即可
** dest_addr:目的套接口的地址
** addrlen :所指地址的长度
** inet_addr():将一个点分十进制的IP转换成一个长整数型数(u_long类型)
** in_addr_t inet_addr(const char *cp)
** htons():将主机的无符号短整形数转换成网络字节顺序
** uint16_t htons(uint16_t hostshort);
** hostshort:主机字节顺序表达的16位数
****************************************************************************************/
int main(int argc, char **argv)
{
int i = 10;
int sockfd; //udp返回套接字描述符
struct sockaddr_in servaddr;
if(argc != 2){
printf("usgae: ./client [ip]\n");
return -1;
}
/* 创建一个UDP的socket连接 */
sockfd = socket(PF_INET, SOCK_DGRAM, 0); //#define AF_INET PF_INET(数值是一样的)
/* 变量servaddr清零 */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(50001); //在临时端口,给用户进程使用的49152-65535
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
char sendline[100];
sprintf(sendline, "Hello, world!");
for(; i>0;--i){
/* 发送数据 */
sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
}
/* 关闭socket连接 */
close(sockfd);
return 1;
}
UDPserver.c
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
/****************************************************************************************
** Udp(服务器)
** socket():建立socket服务
** int socket(int domain, int type, int protocol);
** domain :使用的协议
** type :套接口的类型描述
** protocol:0
** RETURN VALUE
** On success, a file descriptor for the new socket is returned. On error, -1 is returned
** bind():捆绑(主机地址/端口号)
** int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
** sockfd :socket的返回值
** addr :struct sockaddr结构体指针
** addrlen :结构体大小
** RETURN VALUE
** If the connection or binding succeeds, zero is returned. On error, -1 is returned
** recvfrom():接收数据
** 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 :源套接口的地址
** addrlen :所指地址的长度
** inet_addr():将一个点分十进制的IP转换成一个长整数型数(u_long类型)
** in_addr_t inet_addr(const char *cp)
** htons():将主机的无符号短整形数转换成网络字节顺序
** uint16_t htons(uint16_t hostshort);
** hostshort:主机字节顺序表达的16位数
****************************************************************************************/
int main(int argc, char **argv)
{
int n;
char recvline[1024] = {0};
int sockfd; //udp返回套接字描述符
struct sockaddr_in servaddr;
/* 创建一个UDP连接的socket */
sockfd = socket(PF_INET, SOCK_DGRAM, 0); //#define AF_INET PF_INET(数值是一样的)
/* 变量servaddr清零 */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(50001); //在临时端口,给用户进程使用的49152-65535
/* 绑定servaddr到创建的socket上 */
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while(1){
/* 接收客户端发送的数据 */
recvfrom(sockfd, recvline, 1024, 0, NULL, NULL);
printf("%s\n", recvline);
}
/* 关闭socket连接 */
close(sockfd);
}
(四)输出
- 客户端是Arm开发板,用Arm的编译器
- 服务器选择运行在X86的虚拟机(刚装的虚拟机,没有配置),其实其他的都可以,你要有对应编译器就可以了
- 客户端给服务器发送10次数据。