概念学习
TCP概念:
TCP(Transmission Control Protocol)协议指的是传输控制协议,是一个面向连接的传输协议,他是一个能提供高可靠性的通信协议,所谓高可靠性指的是数据无丢失、数据无误、数据无失序、数据无重到达。(打电话)
适用场景:
适用于对传输质量要求较高,以及传输大量数据的通信。
在需要传输可靠数据的场合通常会选择使用TCP通信协议。
比如QQ/微信/支付宝等通信软件的账户登录和支付相关功能是通常采用可靠的TCP通信协议来实现。
UDP概念:
UDP(User Datagram Protocol)指的是用户数据报协议,是一种不可靠无连接的协议,在数据发送前,不需要提前建立连接,所以可以更高效地传输数据。(发邮件)
适用场景:
发送小尺寸地数据(例如对DNS服务器进行地址查询或路游器更新路由表)
在收到数据,给出应答比较困难地网络中适用UDP(比如无线网络)
适用于广播/组播式通信。
QQ/微信等即时通信软件地点对点文件通讯以及音视频通话时。
流媒体、VoIOP、IPTV等网络多媒体服务中(直播间)
相关API函数
UDP相关API
创建一个待连接套接字socket
1 #include <sys/socket.h>
2 int socket(int domain, int type, int protocol);
3 参数:
4 domain:域。
5 AF_INET/PF_INET: 网际协议
6 AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCAL
7 type:类型。
8 SOCK_STREAM:流式套接字 TCP协议
9 SOCK_DGRAM:数据报套接字 UDP协议
10 protocol:协议。
11 一般为 0
12 返回值:
13 成功:待连接套接字
14 失败:-1
绑定地址:
#include <sys/types.h>
#include <sys/socket.h>
1 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2 参数:
3 sockfd:待连接套接字
4 addr:包含本地地址(IP+PORT)的通用地址结构体的指针
5 addrlen:地址结构体大小
6 返回值:
7 成功:0
8 失败:-1
地址结构体
我们要么用IPV4或IPV6,因为直接使用通用结构体来配置不方便,再将IPV4或IPV6的结构体强转为通用结构体。
struct sockaddr // 通用IP信息结构体
{
sa_family_t sa_family;
char sa_data[14];
}
struct sockaddr_in // IPV4地址结构体
{
u_short sin_family;// 地址族(网际协议/本地协议)
u_short sin_port;// 端口(65000 以上)
struct in_addr sin_addr;// IPV4 地址(ifconfig可以查看)
char sin_zero[8];//暂时不管不知道干嘛的
};
struct in_addr // IP地址结构体
{
in_addr_t s_addr;// 无符号 32 位网络地址
};
发送数据到UDP:
1 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
2 const struct sockaddr *dest_addr, socklen_t addrlen);
3 参数:
4 sockfd:UDP 套接字
5 buf:即将发送的数据
6 len:数据的长度
7 flags:发送标志,与函数 send 的 flags 完全一致
8 dest_addr:对端网络地址
9 addr_len:地址长度
10 返回值:
11 成功:已发送字节数
12 失败:-1
从UDP中接收数据:
1 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
2 struct sockaddr *src_addr, socklen_t *addrlen);
3 参数:
4 sockfd:UDP 套接字
5 buf:储存数据缓冲区
6 len:缓冲区大小
7 flags:接收标志,与函数 send 的 flags 完全一致
8 src_addr:对端网络地址
9 addrlen:地址长度
10 返回值:
11 成功:已接收字节数
12 失败:返回-1
UDP通信过程:
程序实现
UDP通讯
服务端
服务端实现思路:就好比寄邮件
第一步:我们需要一个放邮件的邮箱(创建套接字,socket()函数)
第二步:我们要标注将这个邮件发往什么地方(用IPV4地址结构体标注这个套接字的去向信息比如:IP地址、端口号、那种协议)
第三步:将邮件和标注信息绑定起来(bind()函数)
第四部:将邮件进行发送 (recvfrom()函数)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h> //这个结构体包含了地址结构体的声明
#include <netinet/in.h>
int main()
{
//1、创建信箱(套接字)
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == socket_fd)
{
perror("socket err");
return -1;
}
//2、设置信箱地址和端口号
//设置信箱地址
/*
struct sockaddr_in // IPV4地址结构体
{
u_short sin_family;// 地址族(网际协议/本地协议)
u_short sin_port;// 端口(65000 以上)
struct in_addr sin_addr;// IPV4 地址(ifconfig可以查看)
char sin_zero[8];//暂时不管不知道干嘛的
};
*/
struct sockaddr_in my_sockaddr = {0};
my_sockaddr.sin_family = AF_INET;
my_sockaddr.sin_port = htons(65000);//用函数将主机字节序转换为网络字节序才能进行茶传输
//in_addr_t inet_addr(const char *cp);
my_sockaddr.sin_addr.s_addr = inet_addr("192.168.80.128");
int len = sizeof(struct sockaddr_in);
//3.把设置好的信息与信箱进行绑定
int ret_val = bind(socket_fd, (struct sockaddr *)&my_sockaddr, len);
if(-1 == ret_val)
{
perror("bind error");
return -1;
}
//4.等待客户端来信
//先让一直等待接受
char * msg = calloc(128,1);//存储接收到的信息
struct sockaddr_in src_addr;//将接收到的套接字存放在此处
while(1)
{
ssize_t ret_val = recvfrom(socket_fd, msg, 128, 0,
(struct sockaddr *)&src_addr, &len);
if(-1 == ret_val)
{
perror("recvfrom error");
continue;
}
else
{
printf("recvfrom succeed,msg:%s\n",msg);
//如果需要打印别的信息src_addr,这个结构体里将数据进行转换显示
printf("addr:%s\n",inet_ntoa( src_addr.sin_addr ));//显示发送者的IP地址
printf("addr:%s\n",inet_ntoa( src_addr.sin_addr ));
}
}
return 0;
}
客户端
客户端不要将信件和信件的去向信息进行绑定,客户端是发送信息
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h> //这个结构体包含了地址结构体的声明
#include <netinet/in.h>
int main()
{
//1、创建信箱(套接字)
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == socket_fd)
{
perror("socket err");
return -1;
}
//2、设置信箱地址和端口号
//设置信箱地址
/*
struct sockaddr_in // IPV4地址结构体
{
u_short sin_family;// 地址族(网际协议/本地协议)
u_short sin_port;// 端口(65000 以上)
struct in_addr sin_addr;// IPV4 地址(ifconfig可以查看)
char sin_zero[8];//暂时不管不知道干嘛的
};
*/
struct sockaddr_in my_sockaddr = {0};
my_sockaddr.sin_family = AF_INET;
my_sockaddr.sin_port = htons(65000);//用函数将主机字节序转换为网络字节序才能进行茶传输
//in_addr_t inet_addr(const char *cp);
my_sockaddr.sin_addr.s_addr = inet_addr("192.168.80.128");
int len = sizeof(struct sockaddr_in);
//4.等待客户端来信
//先让一直发送
char * msg = calloc(128,1);
int ret_val = -1;
while(1)
{
printf("请输入的消息为:\n");
fgets(msg,128,stdin);
ret_val = sendto(socket_fd, msg, strlen(msg), 0,
(struct sockaddr *)&my_sockaddr, len);
if(-1 == ret_val)
{
perror("sendto error");
continue;
}
else
{
printf("sendto succeed %d byte! \n",ret_val);
}
}
return 0;
}
实验结果
两个端口之间可以进行通讯