目录
[1.UDP通信(User Datagram Protocol用户数据报协议)](#1.UDP通信(User Datagram Protocol用户数据报协议))
[1.1 UDP协议概述](#1.1 UDP协议概述)
[1.3. UDP 通信流程](#1.3. UDP 通信流程)
1.UDP通信(User Datagram Protocol用户数据报协议)
1.1 UDP协议概述
UDP协议:用户数据报协议(数据报:一包一包地传递 问题:易丢包,对方有没有收到,这个地址对不对,都无反馈)
特点:①UDP资源开销比较小
②UDP传输机制简单
③UDP传输不安全、不可靠
图示: 发送数据:逐层加包头接收数据:逐层拆包头
1.2套接字
套的是协议和接口,把不同的协议都套到这一组函数接口上完成功能。这是Linux系统下提供的一种为协议去解决通信问题的一套函数接口,任何协议都能套(套接字就是网络通信的"端点",
就像电话机是通话的端点一样。有了它不同设备之间才能"打电话"(通信),而IP地址是"电话号码",端口号是"分机号")
1.3. UDP 通信流程
服务端(收端)步骤:
socket():创建套接字
bind():绑定 IP 和端口
recvfrom():接收客户端数据
sendto():给客户端回发数据
close():关闭套接字
客户端(发端)步骤:
socket():创建套接字
(bind():绑定 IP 和端口。可以绑定也可以不绑定,系统会帮你随机绑定一个端口)
sendto():给服务端发数据
recvfrom():接收服务端回复
close():关闭套接字
1.4函数接口
socket
Socket 是 "网络文件描述符",打开网络设备后获得它,就能通过它收发数据。
- 标识一个网络连接:IP+端口(IP 定位主机,端口定位主机上的应用程序)
- 端口范围:1~65535
原型:int socket(int domain,int type,int protocol);头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
功能:创建一个用来进行套接字通信的终端节点
参数:
domain:AF_INET IPv4协议族(PF_INET= 网络程序)
type:SOCK_DGRAM 数据报套接字
protocol:0 UDP通信
返回值:
成功返回新的文件描述符
失败返回-1
示例:
sendto
原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr,socklen_t addrlen);
头文件:#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
功能:向另一个套接字发送信息
参数:
sockfd:使用socket创建获得的套接字
buf:发送内容的空间的首地址
len:发数据的长度
flags:发送的属性,默认为0
dest_addr:目的IP的空间的首地址
addrlen:目的IP的大小
返回值:
成功返回发送的字节数
失败返回-1
示例:
htons
原型:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
功能:
本地字节序转换成网络字节序
h:host 本地
n:net网络
l:long 长整型 IPV6用它(因为IPV6更长)
s:short 短整型 IPV4用它
htons:将本地字节序转换为网络字节序
inet_addr
原型:in_addr_t inet_addr(const char *cp);
功能:
将字符串IP地址转换 为32位的IP地址
参数:
cp:字符串IP地址
返回值:
32位结构体IP地址
bind
原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
将地址与套接字完成绑定
参数:
sockfd:套接字文件描述符fd
addr:要绑定的地址信息(IPv4 用 struct sockaddr_in)
cppstruct sockaddr_in { u_short sin_family; // 地址族(AF_INET) u_short sin_port; // 端口(要转网络字节序) struct in_addr sin_addr; // IP地址 };addrlen:地址长度
返回值:
成功返回0
失败返回-1
注意:
1.只能绑定自己的IP地址
2.端口号不能重复绑定
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:想要接收的地址的大小,接收完毕后返回实际接收到地址的大小
返回值:
成功返回接收字节数
失败返回-1
注意:
1.只能绑定自己的IP地址
2.端口号不能重复绑定
测试------编写程序实现UDP发端,从终端接收字符串发送给接收端
接收软件:
wltszs4.3.29.exe
前提:
1.关闭防火墙/杀毒软件
2.关闭windows Defencer
3.关闭windows 防火墙
代码:
结果:
注意:①主机中用小端存储,网络中用大端存储(因为接收数据更容易管理)
②UDP包头字段
UDP包头占8个字节,包含:
源端口(2个字节)
目的端口(2个字节)
长度(2个字节)
校验值(2个字节)
2.UDP通信应用示例
2.1基于UDP协议的文件传输程序
发端代码:send.c
cpp
#发端:send.c
#include "head.h"
int main(void)
{
int sockfd = 0;
int fd = 0;
char filename[256] = {0};
char tmpbuff[1300] = {0};
ssize_t nret = 0;
struct sockaddr_in recvaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
printf("请输入要发送文件:\n");
gets(filename);
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
nret = sendto(sockfd, filename, strlen(filename), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return -1;
}
fd = open(filename, O_RDONLY);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
nret = read(fd, tmpbuff, sizeof(tmpbuff));
if (nret <= 0)
{
break;
}
nret = sendto(sockfd, tmpbuff, nret, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return -1;
}
usleep(10000);
}
close(fd);
sprintf(tmpbuff, "____QUIT____");
nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return -1;
}
close(sockfd);
return 0;
}
收端代码:recv.c
cpp
#收端:recv.c
#include "head.h"
int main(void)
{
int fd = 0;
int ret = 0;
int sockfd = 0;
struct sockaddr_in recvaddr;
ssize_t nret = 0;
char tmpbuff[1300] = {0};
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nret)
{
perror("fail to recvfrom");
return -1;
}
fd = open(tmpbuff, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (-1 == fd)
{
perror("fail to open");
return -1;
}
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nret)
{
perror("fail to recvfrom");
return -1;
}
if (0 == strcmp(tmpbuff, "____QUIT____"))
{
break;
}
write(fd, tmpbuff, nret);
}
close(fd);
close(sockfd);
printf("接收成功!\n");
return 0;
}
makefile:
bash
all:send recv
send:send.c
gcc $^ -o $@
recv:recv.c
gcc $^ -o $@
.PHONY:
clean:
rm send recv
注意:
①测试流程是,将需要发送的文件存于发送端可执行文件所在的目录下,程序会先发送你输入的文件名,再发文件数据;收端先收到文件名,再在收端可执行程序所在的文件夹下建立以该文件名命名的文件,再打卡此文件,将接收的数据存入,再关闭文件。所以,使用时不要将将发端可执行程序与收端可执行程序放于同一个文件夹下,否则会清理需要发送的文件
②两个代码的IP需要手动更改,使用ifconfig命令查看IP地址填入
两个程序的端口为50000,可以更改也可以不更改;
2.2基于UDP协议的双向聊天程序
clientA 代码:
cpp
#clientA:
#include "head.h"
pthread_t tid_send;
pthread_t tid_recv;
int sockfd = 0;
void *sendfun(void *arg)
{
ssize_t nret = 0;
char tmpbuff[4096] = {0};
struct sockaddr_in recvaddr;
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
while (1)
{
gets(tmpbuff);
nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return NULL;
}
if (0 == strcmp(tmpbuff, ".quit"))
{
break;
}
}
pthread_cancel(tid_recv);
return NULL;
}
void *recvfun(void *arg)
{
ssize_t nret = 0;
char tmpbuff[4096] = {0};
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nret)
{
perror("fail to recvfrom");
return NULL;
}
if (0 == strcmp(tmpbuff, ".quit"))
{
break;
}
printf("RECV:%s\n", tmpbuff);
}
pthread_cancel(tid_send);
return NULL;
}
int main(void)
{
int ret = 0;
struct sockaddr_in sendaddr;
//socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//bind
sendaddr.sin_family = AF_INET;
sendaddr.sin_port = htons(40000);
sendaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
ret = bind(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
pthread_create(&tid_send, NULL, sendfun, NULL);
pthread_create(&tid_recv, NULL, recvfun, NULL);
pthread_join(tid_send, NULL);
pthread_join(tid_recv, NULL);
return 0;
}
clintB代码:
cpp
#clientB代码
#include "head.h"
pthread_t tid_send;
pthread_t tid_recv;
int sockfd = 0;
void *sendfun(void *arg)
{
ssize_t nret = 0;
char tmpbuff[4096] = {0};
struct sockaddr_in recvaddr;
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(40000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
while (1)
{
gets(tmpbuff);
nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if (-1 == nret)
{
perror("fail to sendto");
return NULL;
}
if (0 == strcmp(tmpbuff, ".quit"))
{
break;
}
}
pthread_cancel(tid_recv);
return NULL;
}
void *recvfun(void *arg)
{
ssize_t nret = 0;
char tmpbuff[4096] = {0};
while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
if (-1 == nret)
{
perror("fail to recvfrom");
return NULL;
}
if (0 == strcmp(tmpbuff, ".quit"))
{
break;
}
printf("RECV:%s\n", tmpbuff);
}
pthread_cancel(tid_send);
return NULL;
}
int main(void)
{
int ret = 0;
struct sockaddr_in sendaddr;
//socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("fail to socket");
return -1;
}
//bind
sendaddr.sin_family = AF_INET;
sendaddr.sin_port = htons(50000);
sendaddr.sin_addr.s_addr = inet_addr("192.168.0.151");
ret = bind(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if (-1 == ret)
{
perror("fail to bind");
return -1;
}
pthread_create(&tid_send, NULL, sendfun, NULL);
pthread_create(&tid_recv, NULL, recvfun, NULL);
pthread_join(tid_send, NULL);
pthread_join(tid_recv, NULL);
return 0;
}
makefile代码
bash
all:clientA clientB
clientA:clientA.c
gcc $^ -o $@ -lpthread
clientB:clientB.c
gcc $^ -o $@ -lpthread
.PHONY:
clean:
rm clientA clientB
注意:①两个代码的IP需要手动更改,使用ifconfig命令查看IP地址填入
②发端代码要使用bind绑定自己的IP,接收端的IP和端口号要传给发端的sendto函数



