udp
用户数据报协议-无连接,不可靠,面向数据报
面向数据报:每条数据有长度标记,整条发,整条收,传输不够灵活,但是不会存在粘包问题
视使用场景:对数据实时要求高,输出视频——保证传输速度
优点:传输速度快 ,无粘包情况
缺点:不可靠
如果使用场景对文件的安全性要求比较高(文件传输),尽量使用tcp,保证数据的可靠;像音乐视频这种对安全性要求不是很高(视频传输),但是实时性,速度快的用udp
socket套接字编程:
网络编程涉及到对网卡的操作,而我们用户无法直接操作,因此操作系统就提供了一套接口来供我们操作——socket接口
网络编程中分了两个端:客户端程序 / 服务端程序
在网络编程中,客户端是主动的一方,并且客户端必须知道服务端的地址信息(ip+port)
并且服务端还必须得在这个制指定的地址上等着别人
udp编程步骤:
服务端:
1.创建套接字 :在内核中创建socket结构体;向进程返回一个操作句柄;通过这个内核中的socket结构体与网卡建立联系
2.为套接字绑定地址信息:向内核中的socket结构体添加各种地址描述信息(IP地址和端口)为套接字绑定地址端口,就是为了声明,去网卡接受数据的时候,接收的是哪一部分数据局(因为数据上有可能会有很多的数据)若目的地址信息和我绑定的地址信息相同,则将数据交给我处理
3.接收数据:从socket结构体中的接受缓冲区中取出数据(每个数据中都包含源地址和目的地址,因此获取数据也就获取了对端是谁)
4.发送数据:把数据拷贝到内核中的socket结构体的发送缓冲区中
操作系统这时候会在合适的时候从发送缓冲区中取出数据,然后进行数据的层层封装,最终通过网卡发送出去
5.不通信了,关闭套接字,释放资源
客户端永远都是主动发送数据的一方,意味着客户端必须知道服务端的信息才可以在发送数据的时候,将数据能够层层封装完成(网络传输的数据都应该包括:源IP地址/目的IP地址/源端口/目的端口/协议)
客户端所知道的服务端地址,都是服务端告诉它的——服务端的地址通常都是永久不变的
客户端:
1.创建套接字
2.为套接字绑定地址(不主动绑定) 通常客户端不推荐用户手动帮东地址信息
3.发送数据
4.接收数据‘
5.关闭套接字**
服务端:
1.创建套接字socket()
2. 为套接字绑定地址 bind()
3. 接收数据 recvform
4. 发送数据sendto()
5.关闭套接字 close()//销毁内核中的数据
ip地址动态分配——DHCP
NAT服务——地址替换
IPV6(缺陷:不向下兼容)
IPV4
1、创建套接字
int socket(int domain,int type,int protocol)
domain:地址域——不同的网络地址结构 AF_INET—IPV4
type: 套接字类型
SOCK_STREAM 流式套接字 (一种有序的,可靠的,双向的,基于连接的字节流传输),默认协议tcp 不支持udp
SOCK_DGRAM 数据报套接字 (无连接的,不可靠的,有最大长度限制的传输 )默认协议udp 不支持tcp
protocol:协议类型 0:使用套接字默认协议(流式套接字默认是TCP/数据报套接字默认是UDP)
IPPR0TO_TCP=6 tcp协议
IPPROTO_UDP=17 udp协议
返回值:返回套接字的操作句柄——文件描述符 非负整数(套接字描述符) 失败:-1
socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
2、为套接字绑定地址
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
sockfd:创建套接字返回的操作句柄
addr:要绑定的地址信息
addrlen:地址信息长度
返回值:成功:0 失败:-1
用户先定义sockaddr_in的IPV地址结构,强转之后传入binf之中
bind(sockaddr*){
if(sockaddr ->sin_family== AF_INET){
这是IPV4地址结构的解析
}else if(sockaddr->sin_family = =AF_INET6)
}
unit16_t htons(uint16_t hostshort)
将一个短整型(16位)数据从主机字节序转位网络字节序
in_addr_t inet_addr(const char *cp);
将点分十进制字符串的ip地址转换为网络字节序ip地址
3.接收数据
int recvfrom( int sockfd,char *buf,int len, int flags, struct sockaddr *dest_addr,socklen_t *addr_len)
sockfd: 套接字操作句柄
buf:缓冲区首地址,用buf存储要接收的数据,从内核socket接收缓冲区中取出数据放入这个buf用户缓冲区中
len:想要接收的 长度
flags:发送标志,0-默认阻塞操作(若缓冲区中没有数据则一直等待) MSG_DONTWAIT-非阻塞
MSG_PEEK 接收数据后并不会从缓冲区删除
dest_addr:接收到的数据的源端地址——表示这个数据是谁发的,从哪来的,回复的时候就是对这个地址进行回复
addr_len:输入输出型参数,用于指定指定想要获取多长的地址信息,用于返回地址信息的实际长度
返回值:实际读取到的数据字节长度 失败返回:-1
4.发送数据
i
nt sendto( int sockfd,char *data,int data_len, int flag, struct sockaddr *dest_addr,socklen_t addrlen)
socked:套接字操作句柄,发送数据就是将数据拷贝到内核的socket发送缓冲区中
data:要发送的数据首地址
len:要发送的数据长度
flags:选项参数 0-默认阻塞发送 MSG_DONTWAIT-设置为非阻塞
若发送数据的时候,socket发送缓冲区已经满了,则0默认阻塞等待;MSG_DONTWAIT就是立即报错返回了
daddr:目的段地址信息结构——表示数据要发送到哪里去
每一条数据都要描述源端信息(绑定的地址信息)和对端信息(当前赋予的信息)
addrlen:地址信息结构长度
返回值:成功返回实际发送的数据字节数 失败:-1
5.关闭socket
int close(int fd);
字节序转换:
assign: 从一个字符串中拷贝指定长度数据到string中
buf->assign(tmp,rlen)
udp下服务端编程代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<netinet/in.h>
6 #include<arpa/inet.h>
7 #include<sys/socket.h>
8
9 int main(int argc,char*argv[]){
10 //argc表示参数个数,通过argv向程序传递端口参数
11 if (argc!=3){
12 printf("./udp_srv ip port em:./udp_srv 192.168.85.128 9000\n");
13 return -1;
14
15 }
16 const char*ip_addr=argv[1];
17 uint16_t port_addr=atoi(argv[2]);
18 //socket(地址域,套接字类型,协议类型)
19 int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
20 if(sockfd<0){
perror("socket error");
22 return -1;
23 }
24 //bind(套接字描述符,地址结构,地址长度)
25 //struct sockaddr_in IPV4地址结构;
26 //struct in_addr{uint 32_t s_addr};
27 struct sockaddr_in addr;
28 addr.sin_family=AF_INET;
29 //htons-将两个字节的主机字节序整数转转换成网络字节序的整数
30 addr.sin_port=htons(port_addr);
31 //inet_addr 将一个点分十进制的字符串IP地址转换成网络字节序的整数IP地址
32 addr.sin_addr.s_addr=inet_addr(ip_addr);
33 socklen_t len=sizeof(struct sockaddr_in);
34 int ret=bind(sockfd,(struct sockaddr*)&addr,len);
35 if(ret<0){
36 perror("bind error");
37 return -1;
38 }
39 while(1){
40
41 char buf[1024]={0};
42 struct sockaddr_in cliaddr;
43 socklen_t len=sizeof(struct sockaddr_in);
44
45 //recvfrom(描述符,缓冲区,长度,参数,客户端地址信息,地址信息长度)
46 //阻塞接收数据,将数据放入buf中,将发送的地址放入cliaddr中
47 int ret=recvfrom(sockfd,buf,1023,0,(struct sockaddr*)&cliaddr,&len);
48 if(ret<0){
49 perror("recvform error");
50 close(sockfd);//关闭套接字
51 return -1;
52 }
53 printf("client say:%s\n",buf);
54 printf("server say:");
55 fflush(stdout);//让用户输入数据,发送给客户端
56 memset(buf,0x00,1024);//清空buf中的数据
57 scanf("%s",buf);
58 //通过sockfd将buf中的数据发送到cliaddr客户端
59 ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&cliaddr,len);
60 if(ret <0){
61 perror("sendto error");
62 close(sockfd);//关闭套接字
63 return -1;
64 }
UDP下客户端编程代码:
1/*封装一个udpsocket类,向外提供简单的接口实现套接字编程*/
2#include<*iostream*>
3 #include<*cstdio*>//stdio.h
4 #include<*string*>//std::string
5 #include<unistd.h>//close接口
6 #include<stdlib.h>//atoi接口
7 #include<netinet/in.h>//地址结构定义
8 #include<arpa/inet.h>//字节序转换接口
9 #include<sys/socket.h>//套接字接口
10
11 class UdpSocket{
12 public:
13 UdpSocket():_sockfd(-1){
14 }
15 //1.创建套接子
16 //socket(地址域,套接字类型,协议类型)
17 //
18 bool Socket(){
19 _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
20 if(_sockfd<0){
21 perror("socket error");
22 return false;
23 }
24 return true;
25 }
26
27 //2.为套接字绑 定地址信息
28 //bind(描述符,统一地址信息,地址信息长度)
29 bool Bind(const std::string&ip,uint32_t port){
30 struct sockaddr_in addr;
31 addr.sin_family=AF_INET;//地址域,用于向bind接口表明这是一个IPV4 地址结构
32 addr.sin_port=htons(port);//网络字节序的端口信息
33 addr.sin_addr.s_addr=inet_addr(ip.c_str());//网络字节序的IP地址> 信息
34 socklen_t len=sizeof(struct sockaddr_in);
35 int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
36 if (ret<0){
37 perror("bind error");
38 return false;
39 }
40 return true;
41 }
42 //3.发送数据
43
44 bool Send(const std::string &data,const std::string &ip,uint16_t port){
45 //sendto(描述符,数据,长度,选项,对端地址,地址长度)
46 struct sockaddr_in addr;
47 addr.sin_family=AF_INET;
48 addr.sin_port=htons(port);
49 addr.sin_addr.s_addr=inet_addr(ip.c_str());
50 int ret;
51 socklen_t len=sizeof(struct sockaddr_in);
52 ret=sendto(_sockfd,data.c_str(),data.size(),0,(struct sockaddr*)&addr,len);
53 if(ret<0){
54 perror("sendto error");
55 return false;
56 }
57 return true;
58 }
59 //4.接收数据
60 //
61 //输入型参数使用const 引用
62 //输出型参数使用指针
63 //输入输出型参数使用引用
64 bool Recv(std::string *buf,std::string *ip=NULL,uint16_t*port=NULL){
65 struct sockaddr_in addr;
66 socklen_t len=sizeof(struct sockaddr_in);
67 int ret;
68 char tmp[4096]={0};
69 ret=recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)&addr,&len);
70 if(ret<0){
71 perror("recvfrom error");
72 return -1;
73 }
74 buf->assign(tmp,ret);//给buf申请ret大小的空间,从tmp中拷贝ret长度的数据进去
75 if(ip!=NULL){
76 *ip=inet_ntoa(addr.sin_addr);//将网络字节序整数ip地址转化为字符串地址
77 }
78 if(port!=NULL){
79 *port=ntohs(addr.sin_port);
80 }
81 return true;
82
83 //recvfrom(描述符,缓冲区,数据长度,选项,对端地址,地址长度)
84
85 }
86
87 //5.关闭套接字
88 void Close(){
89 close(_sockfd);
90 _sockfd=-1;
91 ;
92 }
93 private:
94 //贯穿全文的套接字描述符
95 int _sockfd;
96 };
97 //客户端要给服务端发送数据,那么就需要知道服务端的地址信息
98 //因此通过程序运行参数传入服务器的地址信息
99 #define CHECK_RET(q) if((q)==false){return -1;}
100 int main(int argc,char*argv[])
101 {
102 if(argc!=3){//argc在这里的作用是判断用户输入的变量是否是下面三个变量("./ udp_cli""192.168.85.128""9000")
103 printf("em: ./udp_cli 192.168.85.128 9000\n");
104 return -1;
105 }
106 std::string ip_addr=argv[1];
107 uint16_t port_addr=atoi(argv[2]);
108 UdpSocket sock;
109 CHECK_RET(sock.Socket());//创建套接子
110 //CHECK_RET(sock.Bind());//绑定地址信息
111 while(1){
112 //获取一个标准输入的数据,进行发送
113 std::cout<<"client say:";
114 fflush(stdout);
115 std::string buf;
116 std::cin>>buf;//获取标准输入的数据
117 sock.Send(buf,ip_addr,port_addr);
118 buf.clear();//清空buf缓冲区
119 sock.Recv(&buf);//因为本身客户端就知道服务端的地址,因此不需要再获取
了
120 std::cout<<"server say:"<<buf<<std::endl;
121 }
122 sock.Close();
123 return 0;
124 }
125
126
客户端建议最好不要手动绑定地址信息,因为绑定了有可能绑定失败(端口被占用),因此,最好把绑定的过程交给操作系统完成,因为操作系统会选择一个当前合适的地址信息进行绑定,这样做可以将失败的可能性降至最低
但是服务器必须绑定地址,因为服务器是被动的一端,必须告诉别人需要将数据发送到哪个地址哪个端口,因此自己就必须接收这个地址这个端口的数据
每个程序绑定的都是自己的网卡地址信息
客户端发送的对端地址信息一定是服务端绑定的地址信息
服务端绑定的地址信息,一定是自己主机上的网卡IP地址