套接字编程(二)UDP服务端与客户端的通信模拟实现
目录
一、前言
二、UDP客户端流程信息
1、创建套接字
2、为套接字绑定地址信息(不推荐)
3、发送数据(将数据放入发送缓冲区中)
4、接收数据(从socket结构体接收缓冲区中取出数据)
5、关闭套接字
三、UDP客户端通信简单模拟实现
1、创建套接字
2、为套接字绑定地址信息
3、发送数据
4、接收数据
5、功能函数代码
6、主main函数
7、makefile
四、UDP通信具体操作
1、使用ifconfig了解虚拟机ip 端口推荐选择10000以上
2、创建俩个会话(一个用来运行服务端程序,一个用来运行客户端程序)
3、创建服务端
4、创建客户端
5、客户端发送问候hello
6、服务端收到hello 回复hi
7、客户端收到服务端回复hi
一、前言
上一篇学习UDP服务端搭建的博文,也都是近一个周前写的博客了,当时对UDP通信展开了初步学习,清楚了网络通信的基本原理,练习掌握了关于网络通信的部分接口,应用实现了关于UDP服务端的搭建
关于服务端大致流程分为
1、创建套接字(在内核中创建一个socket结构体)
2、为套接字绑定地址信息(给创建的套接字内部描述源端IP和端口)
3、从socket结构体中的接收缓冲区中取出数据
4、将数据写入socket结构体中的发送缓冲区中
5、关闭套接字
这一篇博文大概就是完成UDP通信的另一阶段,对于客户端通信的创建与模拟实现。
二、UDP客户端流程信息
具体流程如下:
1、创建套接字
建立内核与网卡之间的联系,在内核中创建一个socket结构体(struct socket)
2、为套接字绑定地址信息(不推荐)
一个端口只能被一个网络进程所占用,所以如果进行信息绑定那么这一个主机就只能进行这一个进程的通信了,如果在绑定信息之后又想访问其他进程信息,就会发出端口冲突,因此并不主动推荐客户端这边主动绑定地址信息。
3、发送数据(将数据放入发送缓冲区中)
客户端与服务器的一个小的区别就是客户端总是先进行发送请求在进行接收回复,而服务端肯定是更主动的先进行数据接收,如果有主机发出请求那么才会进行请求的回应。
4、接收数据(从socket结构体接收缓冲区中取出数据)
5、关闭套接字
三、UDP客户端通信简单模拟实现
在UDP服务端通信的搭建过程中发现,所有的实现都贯穿着套接字描述符。所以将套接字描述符sockfd作为私有成员
1、创建套接字
接口中使用到的参数(地址域信息、套接字类型、协议类型)都为默认的IPV4相关的UDP参数,(AF_INET、SOCK_DGRAM、IPPROTO_UDP)
2、为套接字绑定地址信息
需要提供ip地址参数以及端口号信息,套接字描述符作为私有成员
下方代码对应ipv4结构体进行各项初始化
3、发送数据
① ssize_t sendto(int sockfd, void *buf, size_t dlen,
int flag, struct sockaddr *peer, socklen_t alen);
② sockfd socket返回的套接字描述符
③ buf 发送数据的起始地址
④ dlen 发送数据的长度,从buf地址开始,发送dlen长度的数据
⑤ flag 默认为0,阻塞发送(发送缓冲区满了就等着, 当发送缓冲区有空间了进行发送)
⑥ peer 对端地址信息,描述了数据要发送给谁
描述了信息送往哪里 (这里存放的是接收方的信息)
⑦ alen 对端地址信息长度
⑧ 返回值: 成功返回实际发送数据字节长度,失败返回-1;
4、接收数据
① ssize_t recvfrom(int sockfd, void *buf, size_t dlen,
int flag, struct sockaddr *peer, socklen_t *alen);② sockfd socket创建的套接字描述符
③ 一块地址空间, 里面用来存放我接收得到的数据
④ dlen 接收数据的字节长度
⑤ flag 默认为0,当socket接收缓冲区为空的时候进行等待,有数据了就进行接收
⑥ peer 对端地址信息,描述了接收的数据来自哪里。(这里存放的是来自发送方的地址信息)
⑦ alen 地址信息长度,里面即存放了想要接收的数据长度也存放了实际接收的数据长度
⑧ 成功返回实际接收的数据长度,失败返回-1
5、功能函数代码
1 #include<iostream>
2 #include<string>
3 #include<unistd.h>
4 #include<arpa/inet.h> // 字节序转换接口头文件
5 #include<netinet/in.h>// 地址结构类型以及协议类型头文件
6 #include<sys/socket.h>// 套接字接口文件
7
8 class UdpSocket
9 {
10 private:
11 int _sockfd;
12 public:
13 UdpSocket():_sockfd(-1){}
14 ~UdpSocket(){Close();}
15 bool Sockte() // 创建套接字
16 {
17 _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
18 if(_sockfd<0)
19 {
20 perror("socket error\n");
21 return false;
22 }
23 return true;
24 }
25 bool Bind(const std::string& ip, uint16_t port)
26 {
27 struct sockaddr_in addr; // 定义一个ipv4socket结构体
28 addr.sin_family = AF_INET;// 初始化addr结构体中的sin_family
29 addr.sin_port = htons(port);
30 addr.sin_addr.s_addr = inet_addr(ip.c_str());
31 socklen_t len = sizeof(struct sockaddr_in);
32 int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
33 if(ret<0){
34 perror("bind error\n");
35 return false;
36 }
37 return true;
38 }
39 bool Recv(std::string *body, std::string *peer_ip, uint16_t *peer_port)
40 {
41 struct sockaddr_in peer;
42 socklen_t len = sizeof(struct sockaddr_in);
43 char tmp[4096]={0};
44 ssize_t ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer, &len);
45 if(ret<0)
46 {
47 perror("recv error");
48 return false;
49 }
50 *peer_ip = inet_ntoa(peer.sin_addr);
51 *peer_port = ntohs(peer.sin_port);
52 body->assign(tmp, ret); // 从tmp中取出ret长度数据,放入body中
53 return true;
54 }
55 bool Send(const std::string &body, const std::string &peer_ip, uint16_t peer_port)
56 {
57 struct sockaddr_in addr;
58 addr.sin_family = AF_INET;
59 addr.sin_port = htons(peer_port);
60 addr.sin_addr.s_addr = inet_addr(peer_ip.c_str());
61 socklen_t len = sizeof(struct sockaddr_in);
62 ssize_t ret = sendto(_sockfd, body.c_str(), body.size(), 0, (struct sockaddr*)&addr, len);
63 if(ret < 0){
64 perror("send error");
65 return false;
66 }
67 return true;
68 }
69 bool Close()
70 {
71 if(_sockfd != -1)
72 {
73 close(_sockfd);
74 _sockfd = -1;
75 }
76 return true;
77 }
78 };
6、主main函数
81 int main(int argc, char* argv[])
82 {
83 if(argc != 3)
84 {
85 std::cout<<"usage: ./client.cpp 192.168.2.2 9000\n";
86 return -1;
87 }
88 std::string srv_ip = argv[1];
89 uint16_t srv_port = std::atoi(argv[2]);
90 UdpSocket socket;
91 // 1、创建套接字
92 assert(socket.Sockte() == true);
93 // 2、绑定套接字(不推荐)
94 while(1){
95 // 3、发送数据
96 std::string data;
97 std::cout<<"client say:";
98 fflush(stdout);
99 std::cin>>data;
100 assert(socket.Send(data, srv_ip, srv_port) == true);
101
102 // 4、接收数据
103 data.clear();
104 //assert(socket.Recv(&data, &srv_ip, &srv_port) == true);
105 assert(socket.Recv(&data) == true);
106 }
107 socket.Close();
108 return 0;
109 }
7、makefile
四、UDP通信具体操作
1、使用ifconfig了解虚拟机ip 端口推荐选择10000以上
使用ens33所标记的inet
绑定端口时,推荐给出10000以上
端口范围uint16_t 0~65535
0~1023这些端口已经被知名服务器使用了,SSH(22号端口)、HTTP(80号端口)