网络编程(三)
基于UDP的网络客户端和服务端模型
服务端
- socket
- bind
- IO函数(recvfrom/sendto)
socket:创建网络软通道
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);//参数1:协议域(AF_INET)ipv4
//参数2:套接字类型-->SOCK_DGRAM 数据报套接字
//参数3:其他协议-->0:自动匹配其他需要的协议
//返回值:成功返回文件描述符,标识socket网络软通道;失败返回-1,更新errno
bind:给socket套接字绑定网络终端主机信息
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);//参数1:文件描述符-->socket返回值
//参数2:指向网络终端主机信息的结构体(协议域,IP地址,端口号)
//参数3:struct sockaddr的大小
//返回值:成功返回0,失败返回-1,更新errno
//原结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
//替换的结构体
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_adrr sin_addr;
};
struct in_addr{
__bs32 s_addr;
}
该结构体总共占16字节,两个结构体可以强转的前提是所占空间大小相同,借用struct sockaddr_in结构体存储,之后强转为struct sockaddr
recvfrom:接收数据报通道中的数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);//参数1:文件描述符-->socket返回值
//参数2:存放接收数据的BUF
//参数3:接收数据大小
//参数4:阻塞&非阻塞的标志
//参数5:用来存放对方主机的相关信息(客户端地址)
//参数6:struct sockaddr 大小的指针
//返回值:成功返回接收到字节个数;失败返回-1,并更新errno
sendto:发数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);//参数1:文件描述符-->socket返回值
//参数2:存放发送数据的BUF
//参数3:接收数据大小
//参数4:阻塞&非阻塞的标志
//参数5:用来存放对方主机的相关信息(客户端地址)
//参数6:struct sockaddr 大小
//返回值:成功返回发送的字节个数;失败返回-1,并更新errno
server:
c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//udpserver
int main()
{
//socket
int serverfd = socket(AF_INET, SOCK_DGRAM, 0);
//判断socket返回值
if(-1 == serverfd)
{
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok----\r\n");
//bind
struct sockaddr_in stserver;
stserver.sin_family = AF_INET;
stserver.sin_port = htons(6666);
stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
//判断的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("bind error");
return -1;
}
//绑定服务端主机成功
printf("bind ok-----\r\n");
//接受客户端的信息
struct sockaddr_in stclient;
//结构体大小
socklen_t len = sizeof(struct sockaddr);
//创建收发数据的缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE);
//接收客户端的信息
ret = recvfrom(serverfd, buf, BUF_SIZE, 0, (struct sockaddr*)&stclient, &len);
//判断返回值
if(ret <= 0)
{
//接收数据失败或者没有数据,不退出,直接进行下一次客户端连接
perror("recvfrom error or recvfrom end");
continue;
}
//打印输出接收到实际的数据
printf("recvfrom data:%s\r\n", buf);
//清空缓冲区,为发送数据做准备
memset(buf, 0, BUF_SIZE);
printf("please write:\r\n");
//从标准输入输入数据
fgets(buf, BUF_SIZE, stdin);
//发送数据
ret = sendto(serverfd, buf, BUF_SIZE, 0, (struct sockaddr*)&stclient, len);
//返回值判断
if(ret <= 0)
{
//发送完毕或者发送失败,不退出,进行下一次客户端连接
perror("sendto error or sendto end");
continue;
}
}
return 0;
}
客户端
- socket
- IO函数(sendto/recvfrom)
client:
c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//udpclient
#define BUF_SIZE 20
int main()
{
//socket
int clientfd = socket(AF_INET, SOCK_DGRAM, 0);
//判断socket返回值
if(-1 == clientfd)
{
//返回值为-1,代表创建软通道出错,打印出错原因并返回
perror("socket error \r\n");
return -1;
}
//sendto
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(6666);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t len = sizeof(struct sockaddr);
//接收数据的缓冲区
char buf[BUF_SIZE] = {0};
printf("please write:\r\n");
//从标准输入接收数据
fgets(buf, BUF_SIZE, stdin);
//发送数据
int ret = sendto(clientfd, buf, BUF_SIZE,0,(struct sockaddr *)&serveraddr, len);
if(-1 == ret)
{
//失败发送
perror("sendto error");
}
//recvfrom
memset(buf, 0, BUF_SIZE);
//接收客户端的信息
ret = recvfrom(clientfd, buf, BUF_SIZE,0,(struct sockaddr *)&serveraddr, &len);
//返回值判断
if(-1 == ret)
{
//接收失败,返回并关闭数据报套接字
perror("recvfrom error");
close(clientfd);
return -1;
}
//否则,打印接收的数据,并关闭数据报套接字
printf("recvfrom:%s\r\n", buf);
close(clientfd);
return 0;
}
TCP与UDP的网络通信模型区别
常见套接字类型:
-
流式套接字(SOCK_STREAM) 提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设 置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
-
数据报套接字(SOCK_DGRAM) 提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复, 顺序发送,可能乱序接收。
-
原始套接字(SOCK_RAW)--ping.c 可以对较低层次协议如IP、ICMP
-
直接访问 unix域套接字--》本地通信
tcp:面向连接的可靠的传输协议,通过三次握手保证可靠传送。
udp:面向无连接的不可靠的传输协议,没有三次握手,可以采用数据重传。
TCP(传输控制协议):
- TCP是一个可靠的,全双工的,有序的,面向链接的字节流通信的协议。
- 为什么可靠:
- 丢失的数据包重发 (能保证拿到数据)
- 错误的数据包重发 (保证能拿到正确的数据)
- 数据的有序到达(因为对每个数据包进行了编号)(拆包,编号)
- 有较为健全的校验机制(为了保证数据的正确性)
- 支持面向连接(保证通信线路的畅通)-->三次握手
- 有信道拥堵控制(通过一种对于信道拥堵解决的方案,来提高转发效率)产生的原因,是中继设备中(接 收的速度 >> 发送的速度)
- 为什么有序(有序列号):
- 保证数据都能传输给对端,不至于当传输的数据 > 信道带宽 ,导致数据丢弃。
- 通过序号,在对端主机上可以拼接成原本的数据包
- 保证数据传输的可靠性
- 如何面向链接: 三次握手和四次挥手
UDP(The User Datagram Protocol):
无连接的数据报协议,别名"不可靠的协议"
- 使用校验和来实现错误侦测
- UDP常用于媒体流的传输(音频、视频、等),在这种情况下,实时性比可靠性更重要
- UDP也常用于简单的查询/回应程序,例如DNS查找,在这种情况下,建立可靠传输的资源消耗太大
- UDP是一种实时传输协议(Real-time Transport Protocol),这种协议通常用来传输实时数据例如:音 视频流
- 不可靠的原因:
- 非面向连接(不关心接收端是否在线)--》没有三次握手
- 丢包不重发
- 错误的包不重发
- 没有信道拥堵控制
- 有一个最大传输长度限制
- 没有严格的校验机制
- 如何抉择使用TCP还是UDP
- 可靠性
- 实时性
- 可靠性 > 实时性: TCP 可靠性 < 实时性: UDP