一、UDP协议
UDP协议:User Datagrams Protocal,即用户数据报协议。位于传输层
(一)UDP特点
-
无连接
-
面向数据报
-
尽最大努力交付,不安全不可靠(丢包、乱序)
-
机制简单,实时性好,效率高(直播、游戏画面)
(二)网络通信模型
B/S:Browser/Server:浏览器/服务器
-
通用客户端
-
数据资源基本都保存在服务端
-
只需要开发服务端
C/S:Client/Server:客户端/服务端
-
专用客户端
-
客户端和服务端都可以保存资源
-
客户端和服务端都需要开发
(三)UDP通信

1. 客户端
操作流程:
(1)socket():创建socket套接字
套接字:应用层网络通信的端口,本质是文件描述符
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建通信的套接字
参数:
domain:使用的协议簇
AF_INET:IPv4(网络层)
AF_INET6:IPv6
type:传输层的协议类型
SOCK_STREAM:TCP
SOCK_DGRAM:UDP
protocol:0 默认方式
返回值:
成功:文件描述符(套接字)
失败:-1
(2)sendto():发送UDP报文
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送udp数据报文
参数:
sockfd:套接字
buf:要发送的数据首地址
len:发送的数据字节数
flags:0 按照默认方式发送
dest_addr:服务器的地址信息(IP+PORT)
addrlen:地址信息的长度
返回值:
成功:发送的字节数
失败:-1
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};//新定义的,与struct sockaddr兼容
网络字节序:大端(内存高地址存低字节位,低地址存高字节位)
主机字节序:小端(内存高地址存高字节位,低地址存低字节位)

这个是小端存储
网络字节序和主机字节序的转换
host--->net s:short 2字节 l:long 4字节
htons:2字节数据,主机字节序转网络字节序
htonl:4字节数据,主机字节序转网络字节序
ntohs:2字节数据,网络字节序转主机字节序
ntohl:4字节数据,网络字节序转主机字节序
相关函数
in_addr_t inet_addr(const char *cp);
功能:将点分十进制的字符串IP地址转换成32位的二进制IP地址形式
参数:
cp:字符串IP地址
返回值:整型IP地址
(3)recvfrom():接收UDP报文
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收UDP数据报文
参数:
sockfd:套接字
buf:存储网络接收到的数据
lrn:期望接收到的字节数
flags:0 默认方式接收
src_addr:保存发送方地址信息的指针
addrlen:地址长度的指针
返回值:
成功:实际接收到的字节数
失败:-1
0:当对方发送0字节数据包
(4)close():关闭套接字
2. 服务端
操作流程
(1)socket():创建socket套接字
(2)bind():绑定服务端的IP和端口号
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定当前套接字和IP地址及端口号
参数:
sockfd:套接字
addr:要绑定的地址信息指针
addrlen:地址信息的长度
返回值:
成功:0
失败:-1
(3)recvfrom():接收UDP报文
(4)sendto():发送UDP报文
(5)close():关闭套接字
UDP报文头部
| 源端口号(4字节) | 目标端口号(8字节) |
|---|---|
| 数据包长度(2字节) | 检验和(Checksum)(2字节) |
示例:
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <string.h>
int main(void)
{
//socket()
//sendto();
//recvfrom();
//close();
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000); //接收方的端口号
seraddr.sin_addr.s_addr = inet_addr("192.168.0.XXX"); //接收方的IP地址
char buff[1024] = {0};
while (1)
{
fgets(buff, sizeof(buff), stdin);
ssize_t size = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == size)
{
perror("sendto error");
return -1;
}
printf("size = %ld\n", size);
memset(buff, 0, sizeof(buff));
size = recvfrom(sockfd, buff, sizeof(buff), 0, NULL ,NULL);
if (size < 0)
{
printf("recvfrom error");
return -1;
}
printf("ser-->cli : %s\n", buff);
}
close(sockfd);
return 0;
}
server.c
#include "head.h"
int main(void)
{
//socket()
//bind()
//recvfrom()
//sendto()
//close()
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.0.XXX");//接收方IP地址
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
char buff[1024] = {0};
while (1)
{
memset(buff, 0, sizeof(buff));
ssize_t size = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
if (size < 0)
{
perror("recvfrom error");
return -1;
}
printf("[%s : %d][%ld] %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),size, buff);
strcat(buff, "----ok!");
sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&cliaddr, clilen);
}
close(sockfd);
return 0;
}
二、TCP协议
TCP:即传输控制协议(流式套接字),位于传输层
(一)TCP特点
-
有连接通信
-
面向字节流
-
安全可靠的传输协议:三次握手、四次挥手、应答机制、超时重传机制等
-
机制复杂,实时性和效率没有UDP高(HTTPS、MQTT、FTP)
(二)TCP的三次握手和四次挥手机制
1. 三次握手
TCP建立连接时,通过三次握手,来确保双方都已经准备就绪。
三次握手由客户端发起

数据收发:

2. 四次挥手
TCP都拿开连接时,通过四次挥手,确保通信双方数据都已经收发结束。
四次挥手可以由客户端发起,也可以由服务端发起

四次挥手机制:
1)应答机制:TCP为发送的数据进行编号,发送数据时,报文头部的序列号是这包数据的第一个数据的编号;将来接收方需要给这包数据发送ACK,ACK报文中确认号是收到的最后一个字节编号+1
2)超时重传机制:TCP每发送一包数据后都要等待应答,如果超时时间之内没有收到应答,则重新发送这包数据
3)滑动窗口机制:缓冲区,保存已发送并收到应答的数据、已发送未收到应答的数据、未发送但在对方处理范围内的数据
4)延迟应答机制:TCP可以发送多组数据,发送的同时等待应答
5)流量控制机制:TCP会根据发送端的数据处理和接收能力调整自己的发送速率,根据ACK中窗口值的大小进行动态调整流量
6)捎带应答机制:ACK可以和应用层发送的数据一起发出,表示对上包数据的响应
标志位
TCP报文头部数据位:
1)SYN:请求建立连接标志位
2)ACK:响应报文标志位
3)PSH:携带数据的报文标志位
4)FIN:请求断开连接标志位
(三)TCP通信

1. 客户端
socket()--->connect()--->send()--->recv()--->close()
(1)connect();
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:请求建立连接
参数:
sockfd:套接字
addr :服务端的地址
addrlen:地址长度
返回值:
成功:0
失败:-1
(2)recv();
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收网络数据
参数:
sockfd:通讯套接字
buf:存放接收到的数据的空间首地址
len:期待接收到的字节数
flags: 0 默认方式
返回值:
成功:返回实际收到的字节数
失败:-1
0:发送断开连接
2. 服务端
socket()--->bind()--->listen()--->accept()--->send()--->close()
(1)listen();
int listen(int sockfd, int backlog);
功能:监听客户端的三次握手
参数:
sockfd:监听套接字
backlog:最多允许监听的客户端的个数
返回值:
成功:0
失败:-1
(2)accept();
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:接收完成三次握手的客户端,并返回一个通讯套接字
参数:
sockfd:监听套接字
addr:保存接入的客户端的地址信息的指针
addrlen:地址信息长度的指针
返回值:
成功:通讯套接字
失败:-1
示例:
client.c
#include "head.h"
int main(void)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.0.XXX"); //接收方IP地址
int ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
perror("connect error");
return -1;
}
send(sockfd, "hello world", 11, 0);
close(sockfd);
return 0;
}
server.c
#include "head.h"
//socket()
//bind()
//listen()
//accept();
//recv();
//send();
//close()
int main(void)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket error");
return -1;
}
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.0.XXX"); //接收方IP地址
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
perror("bind error");
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("listen error");
return -1;
}
int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (-1 == connfd)
{
perror("accept error");
return -1;
}
printf("[%s : %d] get online!\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
char buff[1024] = {0};
ssize_t size = recv(connfd, buff, sizeof(buff), 0);
if (-1 == size)
{
perror("recv error");
return -1;
}
printf("buff: %s\n", buff);
close(connfd);
close(sockfd);
return 0;
}
(四)TCP报文头部

标志位:
TCP报文头部数据位:
1)SYN:请求建立连接标志位
2)ACK :效应报文标志位
3)PSH:携带数据的报文标志位
4)FIN:请求断开连接标志位
5)URG:紧急数据标志位
6)RST:重置标志位
三、网络抓包工具
wireshark:网络抓包工具,可以抓取经过网卡的网络数据,从而分析网络问题
Linux下安装wireshark
sudo apt-get install wireshark
抓包流程:
(1)启动wireshark:sudo wireshark
(2)选择抓取的网卡:any
(3)选择抓取数据包的过滤条件并触发:udp.port == 50000
(4)进行网络通信