欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【Linux】socket套接字
前言
下面的编程代码中,一些socket接口需要参考【Linux】socket套接字
目录
- 👉🏻新的网络接口函数(待会编程会用到)
- inet_pton
- inet_ntop
- read和write函数
- 👉🏻实现TCP通信
- TcpServer.hpp
- TcpClient.cc
- 👉🏻TCP实现并发通信(服务端一对多服务)
- 1.多进程
- 2.多进程:父进程不等待子进程
- 3.多线程
- 4.线程池(引入多模式服务)
- 补充翻译服务
👉🏻新的网络接口函数(待会编程会用到)
inet_pton
inet_pton
是一个用于将字符串形式的 IP 地址转换为网络地址结构的函数,其中 “pton” 指的是 “presentation to network”。这个函数通常用于将 IPv4 或 IPv6 地址的字符串表示形式转换为适当的网络地址结构,以便网络编程中的套接字操作。
函数原型如下:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
其中:
af
参数指定了地址族(address family),可以是AF_INET
表示 IPv4 地址族,或者AF_INET6
表示 IPv6 地址族。src
参数是一个指向以 NULL 结尾的字符串的指针,表示待转换的 IP 地址字符串。dst
参数是一个指向用于存储结果的缓冲区的指针,该缓冲区应该足够大以容纳对应的网络地址结构。
函数的返回值是一个整数,如果转换成功,返回值为 1(表示成功转换了一个有效的 IP 地址字符串),如果转换失败,返回值为 0(表示输入的字符串不是有效的 IP 地址),并且 errno
被设置为相应的错误码。
例如,下面是一个使用 inet_pton
的简单示例:
#include <stdio.h>
#include <arpa/inet.h>
int main() {
const char *ip_str = "192.0.2.1";
struct in_addr addr;
if (inet_pton(AF_INET, ip_str, &addr) > 0) {
printf("IPv4 address: %s\n", inet_ntoa(addr));
} else {
perror("inet_pton");
return 1;
}
return 0;
}
这个示例将字符串形式的 IPv4 地址 “192.0.2.1” 转换为网络地址结构,并打印出转换后的地址。
inet_ntop
inet_ntop
是一个用于将网络地址结构表示的 IP 地址转换为字符串形式的函数,其中 “ntop” 指的是 “network to presentation”。这个函数通常用于将 IPv4 或 IPv6 地址的网络地址结构转换为人类可读的字符串表示形式,以便网络编程中的套接字操作。
函数原型如下:
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
其中:
af
参数指定了地址族(address family),可以是AF_INET
表示 IPv4 地址族,或者AF_INET6
表示 IPv6 地址族。src
参数是一个指向包含网络地址结构的内存区域的指针。dst
参数是一个指向用于存储结果的缓冲区的指针,这个缓冲区应该足够大以容纳转换后的字符串表示形式。size
参数指定了dst
缓冲区的大小,以防止发生缓冲区溢出。
函数的返回值是一个指向转换后的字符串的指针,如果转换成功,返回值指向 dst
缓冲区中的字符串,如果转换失败,返回值为 NULL
,并且 errno
被设置为相应的错误码。
例如,下面是一个使用 inet_ntop
的简单示例:
#include <stdio.h>
#include <arpa/inet.h>
int main() {
struct in_addr addr;
const char *ip_str = "192.0.2.1";
char buffer[INET_ADDRSTRLEN];
if (inet_pton(AF_INET, ip_str, &addr) > 0) {
const char *ip_str_converted = inet_ntop(AF_INET, &addr, buffer, INET_ADDRSTRLEN);
if (ip_str_converted != NULL) {
printf("IP address: %s\n", ip_str_converted);
} else {
perror("inet_ntop");
return 1;
}
} else {
perror("inet_pton");
return 1;
}
return 0;
}
这个示例将字符串形式的 IPv4 地址 “192.0.2.1” 转换为网络地址结构,然后再将该结构转换为字符串形式,并打印出转换后的地址。
read和write函数
当你在网络编程中处理数据时,read
和 write
函数是你的好帮手。它们用于从文件描述符(包括套接字)读取数据和向文件描述符写入数据。让我来详细解释一下它们的作用。
- read 函数:
read
函数用于从文件描述符中读取数据。在网络编程中,通常用于从套接字读取数据。其函数原型如下:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- `fd` 是文件描述符,指向要读取的数据流。
- `buf` 是一个指针,指向用于存储读取数据的缓冲区。
- `count` 是要读取的字节数。
read
函数返回实际读取的字节数,如果读取成功,则返回非负整数;如果到达文件末尾(或连接关闭),则返回0;如果出现错误,则返回-1,并设置 errno
变量来指示错误的类型。
- write 函数:
write
函数用于向文件描述符中写入数据。在网络编程中,通常用于向套接字写入数据。其函数原型如下:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
- `fd` 是文件描述符,指向要写入数据的目标。
- `buf` 是一个指针,指向要写入的数据。
- `count` 是要写入的字节数。
write
函数返回实际写入的字节数,如果写入成功,则返回非负整数;如果出现错误,则返回-1,并设置 errno
变量来指示错误的类型。
这两个函数是网络编程中最常用的函数之一。当你需要从套接字读取数据或向套接字写入数据时,就会使用它们。想象一下,当你在网络上发送消息时,你使用 write
函数将消息写入套接字,而接收消息时,你使用 read
函数从套接字中读取数据。这就是它们的基本用法。
read,write和recvfrom,sendto有什么不同
read 和 write 函数主要用于简单的文件 I/O 操作和部分套接字数据传输,而 recvfrom 和 sendto 函数主要用于 UDP 协议中的套接字数据传输,特别是在需要指定数据来源和目标地址的情况下。
👉🏻实现TCP通信
TcpServer.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<cerrno>
#include"nocopy.hpp"
#include"Log.hpp"
#include"CommErr.hpp"
const int default_backlog = 5;
class TcpServer:public nocopy
{
public:
TcpServer(uint16_t port)
:_port(port),_isrunning(false)
{}
void Init()
{
//创建套接字sockfd
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock == -1)
{
lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Debug,"create success,sockfd:%d\n",_listensock);
//初始化本地网络信息
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);
//将本地网络信息Bind到内核当中
if(bind(_listensock,CONV(&local),sizeof(local))==-1)
{
lg.LogMessage(Fatal,"Bind fail,the error code :%d,error string: %s\n",errno,strerror(errno));
exit(Bind_Err);
}
lg.LogMessage(Debug,"Bind success,sockfdL%d\n",_listensock);
//往事俱备,接下来服务端进行监听就可以了
if(listen(_listensock,default_backlog)==-1)
{
lg.LogMessage(Fatal,"Listen fail,the error code :%d,error string: %s\n",errno,strerror(errno));
exit(Listen_Err);
}
lg.LogMessage(Debug,"Listen success,sockfdL%d\n",_listensock);
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
//接收客户端信息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listensock,CONV(&peer),&len);
if(sockfd==-1)
{
lg.LogMessage(Fatal,"accept fail,the error code :%d,error string: %s\n",errno,strerror(errno));
continue;//失败了也不退出,不放弃继续等待下一位有缘人
}
lg.LogMessage(Debug,"accept success,sockfdL%d\n",sockfd);
//与客户端建立连接后,可以为其提供服务了
Serveice(sockfd);
close(sockfd);//关闭文件描述符
}
}
void Serveice(int sockfd)
{
//先读取客户端发过来的消息,因为通信用的是文件描述符,所以这里用read和write
char buffer[1024];
while(true)
{
ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = 0;
cout<<"client say:"<<buffer<<endl;
string echo_string = "server say:";
echo_string+=buffer;
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if(n==0)
{
lg.LogMessage(Info,"client quit...\n");
break;
}
else
{
lg.LogMessage(Error,"read socket the error code :%d,error string: %s\n",errno,strerror(errno));
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _isrunning;
};
TcpClient.cc
#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include<sys/types.h>
//包含网络头文件
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
//包含外部头文件
#include"Log.hpp"
#include"CommErr.hpp"
void Usage(const string & process)
{
cout<<"Usage:"<<process<<" server_ip server_port"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
return 1;
}
//将命令行的ip和port记录
string server_ip = argv[1];
uint16_t server_port = stoi(argv[2]);
//创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Debug,"create success,sockfd:%d\n",sockfd);
//初始化本地网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(server_port);
// p:process(进程), n(网络) -- 不太准确,但是好记忆
inet_pton(AF_INET,server_ip.c_str(),&local.sin_addr);
//发起连接的时候,client会被OS自动进行本地绑定。所以我们不用显示bind!
//接下来开始主动去连接服务端
int n = connect(sockfd,CONV(&local),sizeof(local));
if(n==-1)
{
lg.LogMessage(Error,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
exit(Connect_Err);
}
//连接成功后,去发消息给服务端,并接收服务端发来的消息
while(true)
{
string inbuffer;
cout<<"Please Enter:";
getline(cin,inbuffer);
ssize_t ret = write(sockfd,inbuffer.c_str(),inbuffer.size());
if(ret>0)
{
//发送成功,此时我们可以顺便读取服务端发送来的消息
char buffer[1024];
int n = read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
//读到了
buffer[n] = 0;
cout<<buffer<<endl;
}
else
break;
}
else
break;
}
close(sockfd);//关闭文件描述符
return 0;
}
实现效果:
我们目前实现的通信代码因为是单进程的,所以客户端和服务端只能进行串行执行,也就是说服务端一次只能应付对待一个客户端,如果再来一个客户端,服务端并不会马上响应,得等另一个客户端退出了,服务端才会过来执行这个新来的客户端。
接下来,我们就会去写多个版本的代码去实现客户端和服务端的并发执行。
👉🏻TCP实现并发通信(服务端一对多服务)
代码主要在服务端代码中进行优化
1.多进程
//v2:支持多进程,实现并发
pid_t id = fork();
if(id<0)
{
close(sockfd);
continue;
}
else if(id==0)
{
//child
close(_listensock);//关闭子进程不会再使用的文件描述符,使得文件描述符表的空间得到有效释放
if(fork()>0)
{
//此时再创建进程,如果还是子进程本程则退出
exit(0);//子进程退出后进入下一个循环进行accept,让孙子进程对accept的新的sockfd进行处理,就能实现并发
}
//孙子进程,孤儿进程(此时子进程已经退了),孙子进程退出不用担心僵尸状态,因为被系统领养(资源系统自动回收),正常处理
Serveice(sockfd);//孙子进程来处理
close(sockfd);
exit(0);
}
else
{
close(sockfd);//关闭父进程不会再使用的文件描述符
pid_t rid = waitpid(id,nullptr,0);//父进程等待子进程的终止进行资源回收,避免子进程僵尸状态
if(rid == id)
{
//do nothing
}
}
2.多进程:父进程不等待子进程
#include<signal.h>
signal(SIGCHLD,SIG_IGN);//在Linux环境中,如果对SIG_IGN进行忽略,子进程退出的时候,会自动释放资源
pid_t id = fork();
if(id<0)
{
close(sockfd);
continue;
}
else if(id==0)
{
//child
close(_listensock);//关闭子进程不会再使用的文件描述符,使得文件描述符表的空间得到有效释放
Serveice(sockfd);//子进程来处理
close(sockfd);
exit(0);
}
else
{
close(sockfd);//关闭父进程不会再使用的文件描述符
//do nothing
}
3.多线程
class TcpServer;//先声明TcpServer
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;
};
//v5:多线程,相比于进程池,多线程可以直接共享当前进程下的文件描述符
ThreadData *td = new ThreadData(sockfd, this, peer);
pthread_t tid;
pthread_create(&tid, nullptr, HandlerRequest, td);
//主线程和新线程因为共享同一张文件描述符表,所以没有多余不用的文件描述符
//主线程和新线程如何join回收?
//如果直接pthread_join,这会直接包括主线程一起被回收,而我主线程还要一直去accept新的sockfd呢
//所以为了只让新线程进行join,主线程不join,可以进行线程detach分离
//线程在结束执行时能够自动释放其所占用的资源,而无需其他线程调用pthread_join函数等待其结束。
//之所以要static是因为pthread_create的函数方法参数只能有一个void*,而成员函数默认还有一个this参数
static void *HandlerRequest(void *args) //static的话就没有默认的this参数,所以要在ThreadData中自己传入
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData*>(args);
td->GetServer()->Service(td->SockFd(), td->addr);
delete td;
return nullptr;
}
void Serveice(int sockfd,InetAddrtoLocal addr)
{
//先读取客户端发过来的消息,因为通信用的是文件描述符,所以这里用read和write
char buffer[1024];
while(true)
{
ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = 0;
cout<<addr.Info()<<"say:"<<buffer<<endl;
string echo_string = "server say:";
echo_string+=buffer;
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if(n==0)
{
lg.LogMessage(Info,"client quit...\n");
break;
}
else
{
lg.LogMessage(Error,"read socket the error code :%d,error string: %s\n",errno,strerror(errno));
break;
}
}
}
4.线程池(引入多模式服务)
🍎TcpServer.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<unistd.h>
#include<cerrno>
#include<sys/wait.h>
#include<sys/types.h>
#include<signal.h>
#include<pthread.h>
#include <functional>
#include <unordered_map>
//包含外部头文件
#include"nocopy.hpp"
#include"Log.hpp"
#include"CommErr.hpp"
#include"InetAddrtoLocal.hpp"
#include"ThreadPool.hpp"
#include"Task.hpp"
using task_t = std::function<void()>;
using callback_t = std::function<void(int, InetAddrtoLocal &)>; // 可能要调整
const int default_backlog = 5;
class TcpServer;
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:
InetAddrtoLocal addr;
};
class TcpServer:public nocopy
{
public:
TcpServer(uint16_t port)
:_port(port),_isrunning(false)
{}
void Init()
{
//创建套接字sockfd
_listensock = socket(AF_INET,SOCK_STREAM,0);
if(_listensock == -1)
{
lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Debug,"create success,sockfd:%d\n",_listensock);
//解决一些少量的bind失败的问题 TODO
int opt = 1;
setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));
//初始化本地网络信息
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);
//将本地网络信息Bind到内核当中
if(bind(_listensock,CONV(&local),sizeof(local))==-1)
{
lg.LogMessage(Fatal,"Bind fail,the error code :%d,error string: %s\n",errno,strerror(errno));
exit(Bind_Err);
}
lg.LogMessage(Debug,"Bind success,sockfd%d\n",_listensock);
//往事俱备,接下来服务端进行监听就可以了
if(listen(_listensock,default_backlog)==-1)
{
lg.LogMessage(Fatal,"Listen fail,the error code :%d,error string: %s\n",errno,strerror(errno));
exit(Listen_Err);
}
lg.LogMessage(Debug,"Listen success,sockfd%d\n",_listensock);
//启动线程池
ThreadNs::ThreadPool<task_t>::GetInstance()->Start();
//_funcs.insert(make_pair("defaultService",std::bind()))
}
void Serveice(int sockfd,InetAddrtoLocal addr)
{
//先读取客户端发过来的消息,因为通信用的是文件描述符,所以这里用read和write
char buffer[1024];
while(true)
{
ssize_t n = read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n] = 0;
cout<<addr.Info()<<"say:"<<buffer<<endl;
string echo_string = "server say:";
echo_string+=buffer;
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if(n==0)
{
lg.LogMessage(Info,"client quit...\n");
break;
}
else
{
lg.LogMessage(Error,"read socket the error code :%d,error string: %s\n",errno,strerror(errno));
break;
}
}
}
void Start()
{
_isrunning = true;
signal(SIGCHLD,SIG_IGN);//在Linux环境中,如果对SIG_IGN进行忽略,子进程退出的时候,会自动释放资源
while(_isrunning)
{
//接收客户端信息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listensock,CONV(&peer),&len);
if(sockfd==-1)
{
lg.LogMessage(Fatal,"accept fail,the error code :%d,error string: %s\n",errno,strerror(errno));
continue;//失败了也不退出,不放弃继续等待下一位有缘人
}
lg.LogMessage(Debug,"accept success,sockfdL%d\n",sockfd);
//v6:线程池,解决来了才创建线程的效率低下的问题
task_t t = std::bind(&TcpServer::Route,this,sockfd,InetAddrtoLocal(peer));
ThreadNs::ThreadPool<task_t>::GetInstance()->Push(t);//将任务推送到线程池中执行
}
}
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 quiy...\n");
}
else
{
lg.LogMessage(Error, "read socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
}
return type;
}
void Route(int sockfd,InetAddrtoLocal addr)
{
//根据客户端发来的信息路由与之对应的服务
//_funcs["defaultService"](sockfd,addr);
string type = Read(sockfd);//读取客户端发来的消息
lg.LogMessage(Debug,"%s select %s\n",addr.Info().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 RegisterFunc(const std::string &name, callback_t func)
{
//注册服务
_funcs[name] = func;
}
void DefaultService(int sockfd, InetAddrtoLocal &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());
}
//之所以要static是因为pthread_create的函数方法参数只能有一个void*,而成员函数默认还有一个this参数
static void *HandlerRequest(void *args) //static的话就没有默认的this参数,所以要在ThreadData中自己传入
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData*>(args);
td->GetServer()->Serveice(td->SockFd(), td->addr);
delete td;
return nullptr;
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
bool _isrunning;
//构建业务逻辑
unordered_map<string,callback_t> _funcs;
};
🍎TcpClient.cc
#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include<sys/types.h>
//包含网络头文件
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
//包含外部头文件
#include"Log.hpp"
#include"CommErr.hpp"
#define Retry_Count 5
void Usage(const string & process)
{
cout<<"Usage:"<<process<<" server_ip server_port"<<endl;
}
bool visitServer(string& server_ip,uint16_t& server_port,int* cnt)
{
//创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
lg.LogMessage(Fatal,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
return false;
}
lg.LogMessage(Debug,"create success,sockfd:%d\n",sockfd);
//初始化本地网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(server_port);
// p:process(进程), n(网络) -- 不太准确,但是好记忆
inet_pton(AF_INET,server_ip.c_str(),&local.sin_addr);
//发起连接的时候,client会被OS自动进行本地绑定。所以我们不用显示bind!
//接下来开始主动去连接服务端
int n = connect(sockfd,CONV(&local),sizeof(local));
if(n==-1)
{
lg.LogMessage(Error,"create socket fail,the error code :%d,error string :%s\n",errno,strerror(errno));
return false;
}
*cnt = 0;
//连接成功后,去发消息给服务端,并接收服务端发来的消息
string inbuffer;
cout<<"Please select service or quit:";//挑选服务模式
getline(cin,inbuffer);
if(inbuffer == "quit") return true;
ssize_t ret = write(sockfd,inbuffer.c_str(),inbuffer.size());
if(ret>0)
{
//服务端收到我们发送的挑选服务信息了
while(true)
{
//发送成功,此时我们可以顺便读取服务端发送来的消息
char buffer[1024];
int n = read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
//读到了
buffer[n] = 0;
cout<<"server say:"<<buffer<<endl;
//接下来继续与服务端对话
string inbuffer;
cout<<"Please enter:";
getline(cin,inbuffer);
write(sockfd,inbuffer.c_str(),inbuffer.size());
}
else if(n==0)
{
break;
}
else
return false;//读取失败
}
}
else
{
return false;
}
close(sockfd);//关闭文件描述符
return true;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
return 1;
}
//将命令行的ip和port记录
string server_ip = argv[1];
uint16_t server_port = stoi(argv[2]);
int cnt = 1;
while(cnt <= Retry_Count)
{
bool result = visitServer(server_ip,server_port,&cnt);
if(result)
{
break;//这是正常退出情况
}
else
{
sleep(1);
cout<<"server offline,retrying connect...count = "<<cnt<<endl;
cnt++;
}
}
if(cnt >= Retry_Count)
{
cout<<"server offline"<<endl;
}
return 0;
}
🍎Main.cc
#include "TcpServer.hpp"
#include "CommErr.hpp"
#include <memory>
void Usage(std::string proc)
{
std::cout << "Usage : \n\t" << proc << " local_port\n" << std::endl;
}
void Interact(int sockfd)
{
while(true)
{
char buffer[1024];
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);//读取客户端信息
if (n > 0)
{
buffer[n] = 0;
cout<<"clinet say:"<<buffer<<endl;
string message = buffer;
if(write(sockfd, message.c_str(), message.size())<0)//发送回客户端
cout<<"send fail"<<endl;
}
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;
}
}
}
void Ping(int sockfd, InetAddrtoLocal addr)
{
lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "ping", sockfd);
// 一直进行IO
std::string message = "Ping Service Start...";
write(sockfd, message.c_str(), message.size());//先回应来自客户端的消息
Interact(sockfd);
}
void Translate(int sockfd,InetAddrtoLocal addr)
{
lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "Translate", sockfd);
}
// 改成大写,字符串改成大写
void Transform(int sockfd, InetAddrtoLocal addr)
{
lg.LogMessage(Debug, "%s select %s success, fd : %d\n", addr.Info().c_str(), "Transform", sockfd);
}
// ./tcp_server 8888
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
return Usage_Err;
}
uint16_t port = stoi(argv[1]);
std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);
//在内核中注册服务类型
tsvr->RegisterFunc("ping", Ping);
tsvr->RegisterFunc("translate", Translate);
tsvr->RegisterFunc("transform", Transform);
//初始化和启动服务端
tsvr->Init();
tsvr->Start();
return 0;
}
实现效果如下: 👇🏻👇🏻👇🏻
至于还有更多的服务模式可以根据自己的需求去增设,这里只写了ping对话服务
补充翻译服务
🍎Translate.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
const std::string unknown = "unknown";
const std::string mydict = "./resouce/dict.txt";
const std::string sep = " ";
class Wold
{
// 单词, 音节, 中文解释,类型, 相近
};
class Translater
{
public:
Translater(std::string dict_path = mydict) : _dict_path(dict_path)
{
LoadDict();
Parse();
}
void LoadDict()
{
std::ifstream in(_dict_path);
std::string line;
while(std::getline(in, line))//边读取一行
{
lines.push_back(line);//边写入一行
}
in.close();
lg.LogMessage(Debug, "Load dict txt success, path: %s\n", _dict_path.c_str());
}
void Parse()
{
for(auto &line : lines)
{
auto pos = line.find(sep); // XXXX YYY
if(pos == std::string::npos) continue;
else
{
std::string word = line.substr(0, pos);//空格前的英文
std::string chinese = line.substr(pos+sep.size());//空格后的中文
_dict.insert(std::make_pair(word, chinese));
}
}
lg.LogMessage(Debug, "Parse dict txt success, path: %s\n", _dict_path.c_str());
}
void debug()//打印字典
{
// for(auto &line : lines)std::cout << line << std::endl;
for(auto &elem : _dict)
{
std::cout << elem.first << " : " << elem.second << std::endl;
}
}
std::string Excute(const std::string &word)//翻译转换
{
auto iter = _dict.find(word);
if (iter == _dict.end())
return unknown;
else
return _dict[word];
}
~Translater()
{
}
private:
std::string _dict_path;
std::unordered_map<std::string, std::string> _dict;
std::vector<std::string> lines;
};
效果如下: 👇🏻👇🏻👇🏻
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长