赞
踩
目录
这些先在Xshell上创建,后续直接使用VScode来进行编码
- #pragma once
-
- #include <iostream>
- #include <string>
-
- namespace Server
- {
- class udpServer
- {
- public:
- udpServer()
- {
- }
- void initServer() //初始化
- {
- }
- void start() //启动
- {
- }
- ~udpServer() //析构
- {
- }
- };
-
- }

- #include "udpServer.hpp"
- #include <memory>
-
-
- using namespace std;
- using namespace Server;
-
- int main()
- {
- std::unique_ptr<udpServer> usvr(new udpServer());
-
- usvr->initServer();
- usvr->start();
-
-
- return 0;
- }

参数
返回值
读写网络就像读写文件一样
初始化代码写到构造里面了,后面修改
这里的填充涉及到内存对齐方面,知道就好
对类型进行了很多层的封装
从上面看到,我们可以得到一个问题:为什么库里面使用的是整型的ip地址,而我们是用string的ip地址的呢?
点分十进制和整数风格互转
往一段空间中填 0
记得包含头文件
getopt
附:当我们想要做命令行解析的时候是可以用下面这个接口的,这里我们不使用(参数太少了,没必要使用)
至此服务器已经可以正常启动
- #pragma once
-
- #include <iostream>
- #include <string>
- #include <strings.h>
- #include <cerrno>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
-
- namespace Server
- {
- using namespace std;
- static const string defaultIp = "0.0.0.0"; //TODO
-
- enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR}; //1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
- class udpServer
- {
- public:
- udpServer(const uint16_t &port, const string& ip = defaultIp): _port(port),_ip(ip),_sockfd(-1)
- {
- //注意这里是直接写在构造里面的,是写错地方了,虽然运行是没有错的,由于修改图片太麻烦,下面统一进行了修改
- //1.创建套接字
- _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if(_sockfd == -1)
- {
- cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
- exit(SOCKET_ERR); //创建套接字失败直接终止进程
- }
- //2.绑定套接字(port, ip)
- struct sockaddr_in local; //这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
- bzero(&local, sizeof(local)); //先填 0 再修正
- //注意这下面几个名字是拼接出来的,就是那个##拼接而来的
- local.sin_family = AF_INET; //这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
- local.sin_port = htons(_port);//你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
- local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1. string->unit32_t 2. htonl(); -> inet_addr
- int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
- if(n == -1)
- {
- cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
- exit(BIND_ERR);
- }
- //UDP Server 的预备工作完成
-
- }
- void initServer() // 初始化
- {
- }
- void start() // 启动
- {
- //服务器的本质其实就是一个死循环,
- for(;;)
- {
- sleep(1);
- }
- }
- ~udpServer() // 析构
- {
- }
-
- private:
- uint16_t _port;
- string _ip; // TODO
- int _sockfd;
- };
- }

- #include "udpServer.hpp"
- #include <memory>
-
- using namespace std;
- using namespace Server;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " local_ip local_port\n\n"; //命令提示符
- }
-
- int main(int argc, char *argv[])
- {
- if(argc != 3) //这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
- uint16_t port = atoi(argv[2]); //使用atoi强转,因为argv里面放置的都是字符串,类型需要转换
- string ip = argv[1];
-
- std::unique_ptr<udpServer> usvr(new udpServer(port, ip));
-
- usvr->initServer();
- usvr->start();
-
-
- return 0;
- }

认识接口2
刚开始测试的地址是直接使用这一个
127.0.0.1
查看网络的接口 netstat
假如绑定其他的公网IP地址需要注意
所以一个服务器真实情况下是要接受任意ip发过来的通信,因此我们修改ip的,不需要传ip号了
- #pragma once
-
- #include <iostream>
- #include <string>
- #include <strings.h>
- #include <cerrno>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
-
- namespace Server
- {
- using namespace std;
- static const string defaultIp = "0.0.0.0"; // 直接使用这个缺省值
-
- enum
- {
- USAGE_ERR = 1,
- SOCKET_ERR,
- BIND_ERR
- }; // 1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
- class udpServer
- {
- public:
- udpServer(const uint16_t &port, const string &ip = defaultIp) : _port(port), _ip(ip), _sockfd(-1)
- {}
- void initServer() // 初始化
- {
- // 1.创建套接字
- _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (_sockfd == -1)
- {
- cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
- exit(SOCKET_ERR); // 创建套接字失败直接终止进程
- }
- // 2.绑定套接字(port, ip)
- struct sockaddr_in local; // 这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
- bzero(&local, sizeof(local)); // 先填 0 再修正
- // 注意这下面几个名字是拼接出来的,就是那个##拼接而来的
- local.sin_family = AF_INET; // 这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
- local.sin_port = htons(_port); // 你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
- local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->unit32_t 2. htonl(); -> inet_addr
- // local.sin_addr.s_addr = htonl(INADDR_ANY); //可以主机转网络,不够也可以不处理,直接赋值也行
- int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
- if (n == -1)
- {
- cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
- exit(BIND_ERR);
- }
- // UDP Server 的预备工作完成
- }
- void start() // 启动
- {
- // 服务器的本质其实就是一个死循环,
- for (;;)
- {
- sleep(1);
- }
- }
- ~udpServer() // 析构
- {
- }
-
- private:
- uint16_t _port;
- // 实际上,一款网络服务器,不建议指明一个IP,因为一个服务器可以能有多个ip,万一用户使用其他的ip地址来访问该端口号(这里是8080,就收不到了),这也是我们为什么使用0.0.0.0的IP缺省值
- string _ip;
- int _sockfd;
- };
- }

- #include "udpServer.hpp"
- #include <memory>
-
- using namespace std;
- using namespace Server;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " local_port\n\n"; //命令提示符
- }
-
- int main(int argc, char *argv[])
- {
- if(argc != 2) //这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
- uint16_t port = atoi(argv[1]); //使用atoi强转,因为argv里面放置的都是字符串,类型需要转换
- //string ip = argv[1];
-
- std::unique_ptr<udpServer> usvr(new udpServer(port));
-
- usvr->initServer();
- usvr->start();
-
-
- return 0;
- }

注意这里是服务端是不需要IP,可以接受任意IP地址发来的请求,但是客户端是需要的,这一点后面再细谈,所以并不会造成淘宝的请求跑到京东去了
这里为什么使用8080的因为服务器可以有很多端口号,当服务器收到了大量的数据,并不是全部都是由一个端口号来进行处理的,也可能是8081之类的端口号,这时候的端口号是没有意义的,后序会详谈,其实不同的端口号是有指定的绑定的不能任意绑定,这是因为只有我自己使用
且无论是UDP还是TCP都是采取这样的形式,接受任意IP的数据,通过端口号来确定谁是谁处理
读取数据
socket_t
将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)
- void start() // 启动
- {
- // 服务器的本质其实就是一个死循环
- char buffer[gnum]; // 定义一个数组来充当缓冲区
- for (;;)
- {
- // 读取数据
- struct sockaddr_in peer;
- socklen_t len = sizeof(peer); // 必须设置成这个结构体的大小,当作为输入时,告诉recvfrom的长度的多少
- ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
- // 关系两件事情
- // 1.数据是什么? 2. 谁发的?
- if (s > 0)
- {
- buffer[s] = 0;
- // 因为是从网络上读取的,所以一定要转,可以使用接口
- string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.整数 -> 点分十进制的ip
- uint16_t clientport = ntohs(peer.sin_port);
- string message = buffer;
-
- cout << clientip << "[" << clientport << "]# " << message << endl;
- }
- }
- }

至此服务器端基本完成,停下来处理客户端
sendto告诉客户端要发给谁
0~1023在云服务器上已经被绑定了
现在无法跨主机发送消息,权限问题,后续解决
sz:下载到本地
rz:上传到服务器
chmod:修改权限
注意一台新的云服务器
至于如何打开端口后续文章介绍 -- 未完持续
我们可以添加function来对业务逻辑进行解耦操作,融入下面代码
function对server通信和业务逻辑解耦!
- #pragma once
-
- #include <iostream>
- #include <string>
- #include <strings.h>
- #include <cerrno>
- #include <functional>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
-
- namespace Server
- {
- using namespace std;
- static const string defaultIp = "0.0.0.0"; // 直接使用这个缺省值
- static const int gnum = 1024;
- enum
- {
- USAGE_ERR = 1,
- SOCKET_ERR,
- BIND_ERR
- }; // 1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
-
- typedef function<void(string, uint16_t, string)> func_t;
-
- class udpServer
- {
- public:
- udpServer(const func_t &cd, const uint16_t &port, const string &ip = defaultIp)
- : _callback(cd), _port(port), _ip(ip), _sockfd(-1)
- {
- }
- void initServer() // 初始化
- {
- // 1.创建套接字
- _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (_sockfd == -1)
- {
- cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
- exit(SOCKET_ERR); // 创建套接字失败直接终止进程
- }
- cout << "udpServer success: "
- << " : " << _sockfd << endl;
-
- // 2.绑定套接字(port, ip)
- // 未来服务器要明确的port, 不能随意改变 -- 变了别人就找不到了
- struct sockaddr_in local; // 这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
- bzero(&local, sizeof(local)); // 先填 0 再修正
- // 注意这下面几个名字是拼接出来的,就是那个##拼接而来的
- local.sin_family = AF_INET; // 这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
- local.sin_port = htons(_port); // 你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
- local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->unit32_t 2. htonl(); -> inet_addr
- // local.sin_addr.s_addr = htonl(INADDR_ANY); //可以主机转网络,不够也可以不处理,直接赋值也行
- int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
- if (n == -1)
- {
- cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
- exit(BIND_ERR);
- }
- // UDP Server 的预备工作完成
- }
- void start() // 启动
- {
- // 服务器的本质其实就是一个死循环
- char buffer[gnum]; // 定义一个数组来充当缓冲区
- for (;;)
- {
- // 读取数据
- struct sockaddr_in peer;
- socklen_t len = sizeof(peer); // 必须设置成这个结构体的大小,当作为输入时,告诉recvfrom的长度的多少
- ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
- // 关系两件事情
- // 1.数据是什么? 2. 谁发的?
- if (s > 0)
- {
- buffer[s] = 0;
- // 因为是从网络上读取的,所以一定要转,可以使用接口
- // inet_ntoa 将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)
- string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.整数 -> 点分十进制的ip
- uint16_t clientport = ntohs(peer.sin_port);
- string message = buffer;
-
- cout << clientip << "[" << clientport << "]# " << message << endl;
-
- //我们只把数据读上来就完了吗? 我们要对数据进行处理 -- 所以我们用回调函数的方式来解决
- _callback(clientip, clientport, message);
- }
- }
- }
- ~udpServer() // 析构
- {
- }
-
- private:
- uint16_t _port;
- // 实际上,一款网络服务器,不建议指明一个IP,因为一个服务器可以能有多个ip,万一用户使用其他的ip地址来访问该端口号(这里是8080,就收不到了),这也是我们为什么使用0.0.0.0的IP缺省值
- string _ip;
- int _sockfd;
- func_t _callback; // 回调函数,用以处理数据
- };
- }

- #include "udpServer.hpp"
- #include <memory>
-
- using namespace std;
- using namespace Server;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " local_port\n\n"; //命令提示符
- }
-
-
- void handlerMessage(string clientip, uint16_t clientport, string message)
- {
- //这里就可以对message进行特定的业务处理,而不关心message怎么来的 --- 这就是server通信和业务逻辑解耦!
- }
-
- int main(int argc, char *argv[])
- {
- if(argc != 2) //这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
- uint16_t port = atoi(argv[1]); //使用atoi强转,因为argv里面放置的都是字符串,类型需要转换
- //string ip = argv[1];
-
- std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
-
- usvr->initServer();
- usvr->start();
-
-
- return 0;
- }

- #pragma once
-
- #include <iostream>
- #include <string>
- #include <strings.h>
- #include <cerrno>
- #include <cstring>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
-
- namespace Client
- {
- using namespace std;
- class udpClient
- {
- public:
- udpClient(const string &serverip, const uint16_t &serverport)
- : _serverip(serverip), _serverprot(serverport), _sockfd(-1), _quit(false)
- {
- }
- void initClient()
- {
- // 1. 创建socket
- _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (_sockfd == -1)
- {
- cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
- exit(1); // 创建套接字失败直接终止进程
- }
- cout << "socket success: " << " : " << _sockfd << endl; //成功打印出来
-
- // 2. client要不要bind(必须要!)因为bind就是和系统、网络产生联系. client要不要显示的bind -> 需不需要程序员bind? 名字不重要,重要的是唯一性的,和服务端是不一样的
- // 就像宿舍号是几不重要,有就行了。一个端口号只能被一个客户端绑定,就像是服务端是明星,客户端是民众,民众名字不重要
- // 服务端是具体的一家公司,比如抖音是字节的,就像一个手机有很多app比如抖音,快手这样的客户端,不能让它们固定bind端口号,万一其他公司也用了用一个端口就冲突其他不来了
- // 写服务器的是一家公司,写client是无数家公司 -- 有OS自动形成端口进行bind!不需要自己操作,包括ip地址也不需要,OS自己会处理,当然也可以自己写
- // 那么OS在什么时候,如何bind
- }
- void run()
- {
- struct sockaddr_in server;
- memset(&server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_addr.s_addr = inet_addr(_serverip.c_str());
- server.sin_port = htons(_serverprot);
-
- string messages;
- while (!_quit)
- {
- cout << "Please Enter# ";
- cin >> messages;
-
- sendto(_sockfd, messages.c_str(), sizeof(messages), 0, (struct sockaddr *)&server, sizeof(server));
- }
- }
- ~udpClient()
- {
- }
-
- private:
- int _sockfd;
- string _serverip;
- uint16_t _serverprot;
- bool _quit;
- };
-
- }

- #include "udpClient.hpp"
- #include <memory>
-
- using namespace Client;
-
- static void Usage(string proc)
- {
- cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n"; //命令提示符
- }
-
- // ./udpClient server_ip server_port 第一个是运行方式 要知道要发送的服务端的ip地址 和 端口号
- int main(int argc, char *argv[])
- {
- if(argc != 3)
- {
- Usage(argv[0]);
- exit(1);
- }
- string serverip = argv[1];
- uint16_t serverport = atoi(argv[2]);
-
-
- unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
-
- ucli->initClient();
- ucli->run();
-
- return 0;
- }

- cc=g++
-
- .PHONY:all
- all:udpClient udpServer
-
-
- udpClient:udpClient.cc
- $(cc) -o $@ $^ -std=c++11
- udpServer:udpServer.cc
- $(cc) -o $@ $^ -std=c++11
-
- .PHONY:clean
- clean:
- rm -f udpClient udpServer
至于如何打开端口后续文章介绍 -- 简单的UDP网络程序·续写
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。