文章目录
- 网络通信的本质
- tcp 和 udp 协议
- 网络字节序
- 网络主机数据转化接口
- udp 通信
- 服务端逻辑
- 客户端逻辑
- TCP 通信
- 服务端程序编写步骤
- 客户端程序编写步骤
- 两种通信程序代码
- udp服务端程序编写
- udp 客户端程序编写
- tcp 服务端程序编写
- tcp 客户端程序编写
网络通信的本质
网络之间的通信,本质上就是进程间通信
对双方主机的两个进程而言,需要先将数据发送到对方的主机(ip地址),再找到指定的进程(port:端口号),就能实现通信
ip地址用来标识互联网中唯一的一台主机;port端口号用来标识该指定机器中进程的唯一性
那么(ip, port) 则可以用来表示互联网中唯一一个进程,ip + port 也叫网络套接字 socket
如何理解port:
一个端口号和一个进程相绑定,一个进程可以绑定多个端口号,反之则不可以。
那么为什么不用进程pid来表示网络中进程的唯一性呢?
为了其他的进程模块和网络进行解耦(万一pid的规则变化,网络部分也不受影响),port是专门用于网络通信的
tcp 和 udp 协议
tcp 协议常用于可靠通信,适用于对数据要求比较高的场景(如游戏,传输重要文件等),复杂
udp 协议用于不可靠通信,适用于允许数据偶尔出现差错的场景(如体育赛事直播等),简单,快
这两个协议没有好坏之分,只是应用场景不同,如果不确定使用哪个的时候,就要 tcp,毕竟复杂一点比丢包好
网络字节序
机器有大小端之分,大小端机器存储数据方式不同。大端是“正着存储”的,可读性较好,因此在网络传输时规定,所以到达网络的数据,必须时大端存储的,因此,如果是小端机,收发数据到网络时需要先转化为大端
网络主机数据转化接口
ip 地址为4个字节,使用 uint32_t,port 为2个字节,使用 uint_16
htonl、htons 是转网络,ntohl、ntohs 是转主机数据,使用这些接口可以自动识别机器的大小端,并将数据转化为需要的大小端数据。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uintl6_t ntohs(uint16_t netshort);
socket 创建 udp 套接字接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
int socket(AF_INET, SOCK_DGRAM, 0); // 创建网络套接字, 成功返回文件描述符, 失败返回 -1
// AF_INET 说明协议家族使用的是ipv4, SOCK_DGRAM表示当前创建的是 udp套接字,本质是创建文件
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
填充 sockaddr_in 结构(套接字信息)
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INEF; // 设置协议家族
local.sin_port = htons(_port); // 将端口号转化为网络字节序列并赋值给 sockaddr_in 参数
local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 使用 inet_addr 接口,变为4字节网络序列
服务器收发消息
recvform(); //收消息
sendto(); //发消息
查看网络状态 可以查看启动的服务器的信息
sudo netstat -anup
udp 通信
首先,要有一个客户端,一个服务端。这两台机器的通信可以看作是两个在这台机器上的进程进行
通信,udp 是全双工通信,因此两个进程可以互发消息,不会相互影响。
服务端逻辑
服务端的主体逻辑可以封装成一个类对象,主函数中通过调用类中的 Init 函数和 Start 函数来实现服务器的初始化和启动。
首先,Init 类内函数中,先创建 socket 套接字,再填充 sockaddr_in 结构,sockaddr_in是网络通信相关的结构体,有 ip 地址,端口号,协议家族这几个字段,然后将 socket 套接字和 sockaddr_in 结构体 bind 绑定(注意,不是C++那个bind),绑定后,所谓的互联网中唯一的进程就出现了!
然后 Start 类内函数中,设置一个死循环,定义一个缓冲区,并定义一个 sockaddr_in 结构体变量,sockaddr_in 结构体变量用来存储收到的消息的网络地址,缓冲区用于存放收到的消息。通过 recvfrom 函数收到来自另一个互联网中唯一进程的消息后,如果 recvfrom 函数接受成功,就可以对消息做处理,然后通过 sendto 函数,将消息转发给对面的主机,对面主机的网络地址,就是 recvfrom 函数收到的存储在 sockaddr_in 结构体中的网络地址!
客户端逻辑
客户端的逻辑比较简单,就是拿到服务端的 ip 地址和端口号后,和服务端进行通信。如果通信的形式是收->发->收->发->收…这种收到消息后再发消息,发消息后才能收消息的情况,那么只需要定义一个死循环即可,先发消息,然后检测是否发送成功,成功了才收消息这种的
![[Pasted image 20240608100519.png]]
也可以使用多线程,一个线程只负责死循环发消息,一个线程只负责死循环收消息,即可实现 qq、微信式通信
![[Pasted image 20240608100201.png]]
这大概就是udp通信写程序的基本逻辑
TCP 通信
TCP 套接字
Tcp 通信与 Udp不同的是,Tcp要先建立连接
才能通信,因此与 dup 服务端不同的是,tcp要有一个 listensock与本地网络信息绑定,还要有一个普通socket 来与 client 通信
服务端程序编写步骤
- 创建
listensocket
, 它是 file fd, 本质是文件描述符 - 填充本地网络信息(初始化 struct sockaddr_in)并bind
listensocket
和本地网络信息 - 设置socket为监听状态,tcp特有 listen(_listensock, default_backlog)
- 获取连接,用 listensocket 来获取连接,定义一个 socket 用来接收客户端的信息,再定义一个网络套接字信息,accept 接收成功后,socket 将自动与网络信息绑定
- 提供服务,建立连接成功后,用 read 读 socket,即可接收客户端信息,用 write 往 socket 中写,即可向客户端发送信息
- 规定一个端口号,就能启动服务器
// 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listensock, struct sockaddr*(&peer), &len);
// 填充网络信息时,ip地址的处理优化:
// p:process(进程), n(网络) -- 不太准确,但是好记忆
inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // 1. 字符串ip->4字节IP 2. 网络序列
客户端程序编写步骤
- 先用服务端的ip 地址和它开放的端口号启动客户端
- 创建 socket 并初始化
- 填写 struct sockaddr_in 套接字信息
- 用 sockfd 建立连接 connect
- 收发信息用 read 和 write 向 sockfd 读写就行
如果要与多个客户端建立通信,则可使用多进程的方式:每检测到和一个客户端建立连接,就 fork 一个子进程,让子进程去与这个新建立的客户端通信(子进程继承父进程的文件描述符,但每个进程的文件描述符之间是独立的)
当然,也可以采用多线程,只需要将次级进程设置为分离即可!
两种通信程序代码
udp服务端程序编写
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include <functional>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#include "nocopy.hpp"
#include "Comm.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
using task_t = std::function<void()>;
const static int defaultport = 8888;
const static int defaultfd = -1;
// using func_t = std::function<std::string(std::string)>; // 定义了一个参数为 string, 返回值也为 string 的函数
// 继承nocopy, 防止udpserver 被拷贝,因为基类中已经删除了拷贝构造和赋值
class UdpServer : public nocopy
{
public:
UdpServer(u_int16_t port = defaultport)
: _port(port), _sockfd(defaultfd)
{
pthread_mutex_init(&_user_mutex, nullptr);
}
void Init() // 初始化服务器
{
// 1. 创建 socket 文件细节
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
std::cout << "sockfd: " << _sockfd << std::endl;
if (_sockfd < 0)
{
lg.LogMessage(Fatal, "sock errr: %d %s\n", errno, strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Info, "sock success, sockfd: %d\n", _sockfd);
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY; // ip 设置为 0
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr 可以将点分十进制的ip地址转化为四字节网络字节序列
int n = ::bind(_sockfd, (struct sockaddr*)(&local), (socklen_t)sizeof(local)); // 绑定到内核中, 成功就返回 0
if (n != 0)
{
lg.LogMessage(Fatal, "bind errr: %d %s\n", errno, strerror(errno));
exit(Bind_Err);
}
ThreadPool<task_t>::GetInstance()->Start();
}
void Map()
{
// 添加用户即可 ..
}
void AddOnlineUser(InetAddr addr) // 添加用户
{
LockGuard lockguard(&_user_mutex);
for (auto& user : _online_user)
{
if (addr == user) return;
}
_online_user.push_back(addr);
lg.LogMessage(Debug, "%s : %d is add onlineusers!\n", addr.Ip().c_str(), addr.Port());
}
void Route(int sockfd, const std::string& message)
{
// 先加锁,然后转化给所有人
LockGuard lockguard(&_user_mutex);
for (auto& user : _online_user)
{
lg.LogMessage(Debug, "send to %s %d success!\n", user.Ip().c_str(), user.Port());
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&user.GetAddr(), (socklen_t)sizeof(user.GetAddr()));
}
}
void Start()
{
char buffer[1024];
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if (n > 0)
{
InetAddr addr(peer);
buffer[n] = 0;
AddOnlineUser(addr);
std::string message;
// 检测是否加入
if (_hash.count(addr.Ip()) > 0) message = _hash[addr.Ip()] + buffer;
else message = "[" + addr.Ip() + ":" + std::to_string(addr.Port()) + "]# " + buffer;
std::cout << "message:" << message << std::endl;
task_t task = std::bind(&UdpServer::Route, this, _sockfd, message);
ThreadPool<task_t>::GetInstance()->Push(task);
}
}
}
~UdpServer()
{
pthread_mutex_destroy(&_user_mutex);
}
private:
u_int16_t _port;
int _sockfd;
// func_t _OnMessage;
std::vector<InetAddr> _online_user;
pthread_mutex_t _user_mutex;
std::unordered_map<std::string, std::string> _hash;
};
udp 客户端程序编写
#include <string>
#include <iostream>
#include <cstring>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Comm.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Pthread.hpp"
void Usage(std::string proc)
{
std::cout << "Usage:" << proc << " server_ip server_port" << std::endl;
}
class ThreadData
{
public:
ThreadData(int sock, struct sockaddr_in& server)
:_sockfd(sock), _serveraddr(server)
{}
~ThreadData()
{}
public:
int _sockfd;
InetAddr _serveraddr;
};
// 负责收消息
void RecverRoutine(ThreadData& td)
{
while (true)
{
char buffer[4096];
struct sockaddr_in tmp;
socklen_t len = sizeof(tmp);
ssize_t m = recvfrom(td._sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &len);
if (m > 0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
}
}
// 负责发消息
void SenderRoutine(ThreadData& td)
{
while (true)
{
std::string inbuffer;
std::getline(std::cin, inbuffer);
auto server = td._serveraddr.GetAddr();
ssize_t n = sendto(td._sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&server, sizeof server);
if (n <= 0) std::cout << "send erro......" << std::endl;
usleep(50);
}
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return Usage_Err;
}
std::string server_ip = argv[1];
u_int16_t port = std::stoi(argv[2]);
// 创建 socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
lg.LogMessage(Fatal, "sock errr: %d %s\n", errno, strerror(errno));
return Socket_Err;
}
lg.LogMessage(Info, "sock success, sockfd: %d\n", sockfd);
// lient 需要bind,但是不需要显示bind,让本地OS自动随机bind,选择随机端口号/
// 填充 server 信息, 方便后面收发信息使用
struct sockaddr_in server;
memset(&server, 0, sizeof server);
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(server_ip.c_str());
server.sin_port = htons(port);
ThreadData td(sockfd, server);
Thread<ThreadData> recver("recver", RecverRoutine, td);
Thread<ThreadData> sender("sender", SenderRoutine, td);
recver.Start();
sender.Start();
recver.Join();
sender.Join();
close(sockfd);
return 0;
}
tcp 服务端程序编写
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unordered_map>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
class ThreadData
{
public:
ThreadData(int sock, TcpServer *ptr, struct sockaddr_in &peer)
:sockfd(sock), svr_ptr(ptr), addr(peer)
{}
int SockFd() {return sockfd;}
TcpServer *GetServer() { return svr_ptr;};
~ThreadData()
{
close(sockfd);
}
private:
int sockfd;
TcpServer* svr_ptr;
public:
InetAddr addr;
};
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port) : _port(port), _isrunning(false)
{
}
// 都是固定套路
void Init()
{
// 1. 创建socket, file fd, 本质是文件
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
lg.LogMessage(Fatal, "create socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
exit(Fatal);
}
lg.LogMessage(Debug, "create socket success, sockfd: %d\n", _listensock);
// 固定写法,解决一些少量的bind失败的问题 -- TODO
int opt = 1;
setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
// 2. 填充本地网络信息并bind
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
// 2.1 bind
if (bind(_listensock, CONV(&local), sizeof(local)) != 0)
{
lg.LogMessage(Fatal, "bind socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
exit(Bind_Err);
}
lg.LogMessage(Debug, "bind socket success, sockfd: %d\n", _listensock);
// 3. 设置socket为监听状态,tcp特有的
if (listen(_listensock, default_backlog) != 0)
{
lg.LogMessage(Fatal, "listen socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
exit(Listen_Err);
}
lg.LogMessage(Debug, "listen socket success, sockfd: %d\n", _listensock);
ThreadNs::ThreadPool<task_t>::GetInstance()->Start();
funcs.insert(std::make_pair("defaultService", std::bind(&TcpServer::DefaultService, this, std::placeholders::_1, std::placeholders::_2)));
}
void Service(int sockfd, InetAddr addr)
{
char buffer[1024];
// 一直进行IO
while (true)
{
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
std::cout << addr.PrintDebug() << "# " << buffer << std::endl;
std::string echo_string = "server echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0) // read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
{
lg.LogMessage(Info, "client quit...\n");
break;
}
else
{
lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
break;
}
}
}
// static void *HandlerRequest(void *args) // this
// {
// pthread_detach(pthread_self());
// ThreadData *td = static_cast<ThreadData*>(args);
// td->GetServer()->Service(td->SockFd(), td->addr);
// delete td;
// return nullptr;
// }
void Start()
{
_isrunning = true;
signal(SIGCHLD, SIG_IGN); // 在Linux环境中,如果对SIG_IGN进行忽略,子进程退出的时候,自动释放自己的资源
while (_isrunning)
{
// 4. 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listensock, CONV(&peer), &len);
if (sockfd < 0)
{
lg.LogMessage(Warning, "accept socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
continue;
}
lg.LogMessage(Debug, "accept success, get n new sockfd: %d\n", sockfd);
// 5. 提供服务啊, v1~v4
// v1 单进程
// Service(sockfd);
// close(sockfd);
// v2 多进程
// pid_t id = fork();
// if (id < 0)
// {
// close(sockfd);
// continue;
// }
// else if (id == 0)
// {
// // child
// close(_listensock);
// if (fork() > 0)
// exit(0);
// // 孙子进程,孤儿进程,被系统领养,正常处理
// Service(sockfd);
// close(sockfd);
// exit(0);
// }
// else
// {
// close(sockfd);
// pid_t rid = waitpid(id, nullptr, 0);
// if (rid == id)
// {
// // do nothing
// }
// }
// v3 多进程 - 信号版
// pid_t id = fork();
// if (id < 0)
// {
// close(sockfd);
// continue;
// }
// else if (id == 0)
// {
// // child
// close(_listensock);
// Service(sockfd);
// close(sockfd);
// exit(0);
// }
// else
// {
// close(sockfd);
// // 父进程不想等待呢?
// }
// v3 进程池 - 课堂不讲了 - 试一试 -- 问题?先创建子进程,然后才获取的连接,子进程看不到新获取的文件fd.
// v4 多线程
// ThreadData *td = new ThreadData(sockfd, this, peer);
// pthread_t tid;
// pthread_create(&tid, nullptr, HandlerRequest, td);
// 主线程和新线程,不需要关闭所谓文件描述符, 将线程设置为分离
// v5 线程池 -- 不能让我们的服务器,提供长服务
task_t t = std::bind(&TcpServer::Routine, this, sockfd, InetAddr(peer));
ThreadNs::ThreadPool<task_t>::GetInstance()->Push(t);
}
}
std::string Read(int sockfd)
{
char type[1024];
ssize_t n = read(sockfd, type, sizeof(type) - 1);
if (n > 0)
{
type[n] = 0;
}
else if (n == 0) // read如果返回值是0,表示读到了文件结尾(对端关闭了连接!)
{
lg.LogMessage(Info, "client quit...\n");
}
else
{
lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
}
return type;
}
void Routine(int sockfd, InetAddr addr)
{
funcs["defaultService"](sockfd, addr);
std::string type = Read(sockfd);
lg.LogMessage(Debug, "%s select %s\n", addr.PrintDebug().c_str(), type.c_str());
if (type == "ping")
funcs[type](sockfd, addr);
else if (type == "translate")
funcs[type](sockfd, addr);
else if (type == "transform")
funcs[type](sockfd, addr);
else
{
}
close(sockfd);
}
void DefaultService(int sockfd, InetAddr& addr)
{
(void)addr;
std::string service_list = " |";
for (auto func : funcs)
{
service_list += func.first;
service_list += "|";
}
write(sockfd, service_list.c_str(), service_list.size());
}
void RegisterFunc(const std::string& name, callback_t func)
{
funcs[name] = func;
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock; // TODO
bool _isrunning;
// 构建业务逻辑
std::unordered_map<std::string, callback_t> funcs;
};
tcp 客户端程序编写
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include "Comm.hpp"
using namespace std;
void handler(int signo)
{
std::cout << "signo: " << signo << std::endl;
exit(0);
}
#define Retry_Count 5
void Usage(const std::string &process)
{
std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}
bool visitServer(std::string &serverip, uint16_t &serverport, int *cnt)
{
// 1. 创建socket
string inbuffer;
char service_list[1024];
ssize_t m = 0;
ssize_t n = 0;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
cerr << "socket error" << endl;
return false;
}
bool ret = true;
// 2. 要不要bind?必须要有Ip和Port, 需要bind,但是不需要用户显示的bind,client系统随机端口
// 发起连接的时候,client会被OS自动进行本地绑定
// 2. connect
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
// p:process(进程), n(网络) -- 不太准确,但是好记忆
inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // 1. 字符串ip->4字节IP 2. 网络序列
n = connect(sockfd, CONV(&server), sizeof(server)); // 自动进行bind哦!
if (n < 0)
{
cerr << "connect error" << endl;
ret = false;
goto END;
}
*cnt = 0;
m = read(sockfd, service_list, sizeof(service_list) - 1);
if (m > 0)
{
service_list[m] = 0;
cout << "服务器提供的服务列表是: " << service_list << endl;
}
// 并没有向server一样,产生新的sockfd.未来我们就用connect成功的sockfd进行通信即可.
cout << "请你选择服务# ";
getline(cin, inbuffer);
write(sockfd, inbuffer.c_str(), inbuffer.size());
cout << "Enter> ";
getline(cin, inbuffer);
if (inbuffer == "quit")
return true;
// std::cout << "echo : " << inbuffer << std::endl;
n = write(sockfd, inbuffer.c_str(), inbuffer.size());
if (n > 0)
{
char buffer[1024];
m = read(sockfd, buffer, sizeof(buffer) - 1);
if (m > 0)
{
buffer[m] = 0;
cout << buffer << endl;
}
else if (m == 0)
{
return true;
}
else
{
ret = false;
goto END;
}
}
else
{
std::cout << "hello write Error" << std::endl;
ret = false;
goto END;
}
END:
close(sockfd);
return ret;
}
// ./tcp_client serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);
signal(SIGPIPE, SIG_IGN);
// for (int i = 1; i <= 31; i++)
// {
// signal(i, handler);
// }
int cnt = 1;
while (cnt <= Retry_Count)
{
bool result = visitServer(serverip, serverport, &cnt);
if (result)
{
break;
}
else
{
sleep(1);
std::cout << "server offline, retrying..., count : " << cnt << std::endl;
cnt++;
}
}
if (cnt >= Retry_Count)
{
std:
cout << "server offline" << std::endl;
}
return 0;
}