目录
功能:接收数据的同时可以获取到该数据包从哪里来,即可以知道发送方的地址
功能:发送数据给指定该数据包应该发给谁,即指定接收方的地址,必须指定清楚这个包该发给谁
1.UDP流程图
2.UDP搭建函数
1>socket详情见TCP(函数功能参数一样)
2>bind详情见TCP(函数功能参数一样)
3>recvfrom函数
功能:接收数据的同时可以获取到该数据包从哪里来,即可以知道发送方的地址
参数:
问题:recvfrom函数能否替换成其他函数
a.可以
b.当recvfrom后面两个参数填NULL的时候,可以替换成recv函数
c.当recv函数最后一个参数填0,可以替换成read函数
4>sendto函数
功能:发送数据给指定该数据包应该发给谁,即指定接收方的地址,必须指定清楚这个包该发给谁
参数:![](https://i-blog.csdnimg.cn/blog_migrate/a5c2d6e5117969fc07c4d32b58316271.png)
3.UDP服务器代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define PORT 8888 //端口号的网络字节序,1024~49151
#define IP "192.168.125.5" //本机IP,ifconfig
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n", sfd);
//填充地址信息结构体给bind函数使用
//真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT); //端口号的网络字节序,1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //本机IP,ifconfig
//绑定服务器自身的地址信息
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
struct sockaddr_in cin; //存储发送放的地址信息
socklen_t addrlen =sizeof(cin);
char buf[128] = "";
ssize_t res = 0;
while(1)
{
bzero(buf, sizeof(buf));
//接收数据 --->同时存储这个数据包是从哪里来的,即发送的地址
res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
//res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
//res = recv(sfd, buf, sizeof(buf), 0);
//res = read(sfd, buf, sizeof(buf));
if(res < 0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s:%d] : %s\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
//发送数据, 谁发给我,我发还给谁
strcat(buf, "*_*");
if(sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, sizeof(cin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
}
//关闭套接字
close(sfd);
return 0;
}
4.UDP客户端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define SER_PORT 8888 //服务器绑定的端口号
#define SER_IP "192.168.125.5" //服务器绑定的IP
int main(int argc, const char *argv[])
{
//创建报式套接字
int cfd = socket(AF_INET, SOCK_DGRAM, 0);
if(cfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success cfd=%d\n", cfd);
//绑定客户端自身的地址信息---》非必须绑定
//若不绑定则操作系统会给客户端绑定本机IP及随机端口
//填充地址信息结构体给sendto函数使用,想发给谁就填谁的地址信息
//真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(SER_PORT); //服务器绑定的端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器绑定的IP
struct sockaddr_in rcvaddr; //存储数据包是从哪里来的
socklen_t addrlen = sizeof(rcvaddr);
char buf[128] = "";
ssize_t res = 0;
while(1)
{
bzero(buf, sizeof(buf));
printf("请输入>>> ");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0;
//发送数据, 主动发送给指定接收放,例如这里可以主动发给服务器
if(sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
bzero(buf, sizeof(buf));
//接收数据 --->同时存储这个数据包是从哪里来的,即发送的地址
res = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&rcvaddr, &addrlen);
//res = recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
//res = recv(cfd, buf, sizeof(buf), 0);
//res = read(cfd, buf, sizeof(buf));
if(res < 0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s:%d] : %s\n", \
inet_ntoa(rcvaddr.sin_addr), ntohs(rcvaddr.sin_port), buf);
}
//关闭套接字
close(cfd);
return 0;
}
5.UDP中重点函数connect
UDP可以调用connect函数
1.TCP的connect会产生三次握手,将client和serv连接
UDP的connect函数不会产生连接,仅仅是将对端的IP和端口号记录到内核中,此时UDP只能与记录的对端进行通信
2.TCP的connect函数只能成功调用一次
UDP中的connect可以被调用多次,刷新内核中对端的IP和端口
如果想清空内核中对端的信息,可以将地址族设置为AF_UNSPEC. 即将sin_family成员设置为AF_UNSPEC
3.当udp采用connect函数的方式收发报文后,可以调用
- recvfrom recv read
- sendto send write
if(sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, sizeof(cin)) < 0)
if(sendto(sfd, buf, sizeof(buf), 0, NULL, 0) < 0)
if(send(sfd, buf, sizeof(buf), 0) < 0)
res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
res = recv(sfd, buf, sizeof(buf), 0);
res = read(sfd, buf, sizeof(buf))
6.udp调用connect函数的优点:
- 提升传输效率
- 不调connect函数:将对端的地址信息填充到内核中----》发送报文---》清空内核中对端信息---》将对端的地址信息填充到内核中----》发送报文---》清空内核中对端信息--》将对端的地址信息填充到内核中----》发送报文---》清空内核中对端信息
- 调用connect函数:将对端的地址信息填充到内核中----》发送报文---》发送报文---》发送报文---》....---》清空内核中对端信息
- 增加传输稳定性
- 调用connect函数的udp通信可以防止AB进程在做大量数据传输的过程中收到C进程的数据,造成错误
问题:
- udp能否使用connect函数。使用有什么优点
- 答:可以, 对比一下udp和tcp的connect函数的区别
- udp中sendto函数能否替换成其他函数?
- 答:可以,比如可以替换成send, write。但是有前提条件,必须要先调用connect函数。
示例代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "__%d__ ", __LINE__);\
perror(msg);\
}while(0)
#define PORT 8888 //端口号的网络字节序,1024~49151
#define IP "192.168.125.5" //本机IP,ifconfig
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n", sfd);
//填充地址信息结构体给bind函数使用
//真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET;
sin.sin_port = htons(PORT); //端口号的网络字节序,1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //本机IP,ifconfig
//绑定服务器自身的地址信息
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
struct sockaddr_in cin; //存储发送放的地址信息
socklen_t addrlen =sizeof(cin);
struct sockaddr_in saveAddr;
char buf[128] = "";
ssize_t res = 0;
while(1)
{
bzero(buf, sizeof(buf));
//接收数据 --->同时存储这个数据包是从哪里来的,即发送的地址
res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
if(res < 0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s:%d] : %s\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
if(strcmp(buf, "quit") != 0)
{
if(memcmp(&cin, &saveAddr, sizeof(cin)) != 0) //内存比较,若不相等
{
saveAddr = cin;
//一旦接收到数据包,此时只能与这个对端进行通信
if(connect(sfd, (struct sockaddr*)&cin, sizeof(cin)) < 0)
{
ERR_MSG("connect");
return -1;
}
printf("connect [%s:%d] success\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
}
}
else
{
//清空内核中对端信息
memset(&saveAddr, 0, sizeof(saveAddr));
cin.sin_family = AF_UNSPEC;
if(connect(sfd, (struct sockaddr*)&cin, sizeof(cin)) < 0)
{
ERR_MSG("connect");
return -1;
}
printf("connect 清空内核中对端信息 success\n");
continue;
}
//发送数据, 谁发给我,我发还给谁
strcat(buf, "*_*");
//if(sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, sizeof(cin)) < 0)
//if(sendto(sfd, buf, sizeof(buf), 0, NULL, 0) < 0)
//if(send(sfd, buf, sizeof(buf), 0) < 0)
if(write(sfd, buf, sizeof(buf)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("sendto success\n");
}
//关闭套接字
close(sfd);
return 0;
}