前言
前面我们学习socket的udp通信,了解到了socket的概念与udp的实现方法,今天我们来学习一下面向连接的tcp通信。
一、tcp套接字创建
UDP和TCP都是通过套接字(socket)来实现通信的,因此TCP也得使用socket()接口创建套接字。
socket() ->创建套接字
- 参数domain:代表协议家族,输入AF_INET为IPv4协议,该参数告诉操作系统如何解释后面的type和protocol参数
- 参数type:指定了套接字的类型,即指定了套接字的通信方式和数据传输方式,输入SOCK_STREAM面向字节流
- 参数protocol:代表所使用的具体协议,输入0让系统自动选择合适的协议
二、填充网络信息并bind
创建好了基于tcp的IPv4套接字,我们还需要填充本地网络信息,知道了本地ip地址和端口号,和socket创建的sockfd进行绑定,后面就可以通过sockfd进行通信了。
如下是 IPv4 地址的结构体sockaddr_in 。
struct sockaddr_in {
short int sin_family; // 地址族(Address Family),一般为 AF_INET
unsigned short int sin_port; // 端口号(Port),使用网络字节顺序(big-endian)
struct in_addr sin_addr; // IPv4 地址
unsigned char sin_zero[8]; // 未使用的填充字段,通常设置为 0
};
填充完毕就可以开始bind了。
bind() ->让socket信息与sockfd进行绑定
- 参数sockfd:套接字文件描述符 传入之前创建的socket返回值
- 参数addr:创建好的sockaddr结构体的地址
- 参数addrlen:网络信息结构体的长度的长度
三、建立连接与监听
由于tcp是面向连接的,因此客户端和服务器双方要进行数据通信,必须要先建立连接。一般都是客户端去发起连接请求,想让服务端为他进行服务。比如你想刷抖音,那么你得打开抖音,申请去刷抖音,而不是说抖音舔着脸求你刷我,虽然他也想要日活,但是主动权还是在你手上的。
那服务端也需要去监听连接的到来,方便为客户端服务,因此有连接和监听两种状态。
connect() 建立连接
参数与bind一样
listen() 监听连接
- 参数sockfd:套接字描述符
- 参数backlog:指定连接请求的最大排队数量,即在队列中等待接受连接的最大数量。他只是用于控制等待连接队列的长度,并不是服务器的最大并发连接数。
- 返回值,成功返回0
listen返回值不为0证明listen失败。
四、获取连接
客户端使用connect与服务端进行连接,服务端listen将自己设置为监听状态,代表能接受连接,但是你只是说自己能接受,并不会创建与客户端的连接。
正在的连接需要服务端既要listen进行监听,又要accept进行获取连接。
accept() 从处于监听状态的套接字中接受一个传入的连接请求,并创建一个新的套接字来与客户端进行通信。
- 参数sockfd:处于监听状态的套接字描述符
- 参数addr:创建好的sockaddr结构体的地址
- 参数addrlen:创建好的sockaddr结构体的长度的地址
- 返回值:返回一个sockfd,使用返回的这个sockfd与客户端进行通信。
也就是说,在tcp通信中,是有两个sockfd的,之前我们socket、bind、listen、accept使用的都是同一个sockfd,但是链接已经完全建立好之后,我们要与客户端进行通信,需要使用accept返回的sockfd。
这就类似于在街边拉客的美容店,张三在外面寻找客人,带到美容店去消费,进入美容店后张三就不再管你了,而是让美容店里的员工对你进行服务,张三转头又去拉客去了。
五、tcp通信的实现
有了这些预备知识,我们就可以编写代码了,具体代码如下,注释写的比较详细
Comm.hpp (错误码)
#pragma once
//错误码
enum{
Usage_Err = 1,
Socket_Err,
Bind_Err,
Listen_Err,
Connect_Err,
};
InetAddr.hpp (封装sockaddr_in结构体)
#pragma once
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
class InetAddr
{
public:
InetAddr(struct sockaddr_in& peer):_addr(peer)
{
_port = ntohs(peer.sin_port); // ntohs网络转主机short
// inet_ntoa 多线程下不安全 使用静态缓冲区来存储结果,并返回指向这个静态缓冲区的指针
// 每次调用都会覆盖这个静态缓冲区的内容
// _ip = inet_ntoa(peer.sin_addr); // inet_ntoa sin_addr转点分十进制字符串ip
// 现在我们自己维护空间ipbuff,让inet_ntop把数据写到ipbuff里 这样线程安全
char ipbuff[64];
inet_ntop(AF_INET,&peer.sin_addr,ipbuff,sizeof(ipbuff));
_ip = ipbuff;
}
string GetIp()
{
return _ip;
}
uint16_t GetPort()
{
return _port;
}
struct sockaddr_in& GetAddr()
{
return _addr;
}
string PrintDebug()
{
string info = _ip;
info+=":";
info+=to_string(_port);
return info;
}
~InetAddr()
{
}
private:
string _ip;
uint16_t _port;
struct sockaddr_in _addr;
};
LockGuard.hpp (锁的守护者)就是C++11里的lock_guard
#pragma once
#include <pthread.h>
// 不定义锁,外部会传递锁
class Mutex
{
public:
Mutex(pthread_mutex_t *lock)
: _lock(lock)
{
}
void Lock()
{
pthread_mutex_lock(_lock);
}
void UnLock()
{
pthread_mutex_unlock(_lock);
}
~Mutex()
{
}
private:
pthread_mutex_t *_lock;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t *lock)
: _mutex(lock)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.UnLock();
}
private:
Mutex _mutex;
};
Log.hpp (日志类)
#pragma once
#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{
Debug = 0,
Info,
Warning,
Error,
Fatal
};
enum{
Screen = 10,
OneFile,
ClassFile
};
string LevelToString(int level)
{
switch (level)
{
case Debug:
return "Debug";
case Info:
return "Info";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "Unkonw";
}
}
const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";
class Log
{
public:
Log(int style = default_style,string filename = default_filename)
:_style(style),_filename(filename)
{
if(_style != Screen)
mkdir(logdir.c_str(),0775);
}
//更改打印方式
void Enable(int style)
{
_style = style;
if(_style != Screen)
mkdir(logdir.c_str(),0775);
}
//时间戳转化为年月日时分秒
string GetTime()
{
time_t currtime = time(nullptr);
struct tm* curr = localtime(&currtime);
char time_buffer[128];
snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",
curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);
return time_buffer;
}
//写入到文件中
void WriteLogToOneFile(const string& logname,const string& message)
{
FILE* fp = fopen(logname.c_str(),"a");
if(fp==nullptr)
{
perror("fopen filed");
exit(-1);
}
fprintf(fp, "%s\n", message.c_str());
fclose(fp);
}
//打印日志
void WriteLogToClassFile(const string& levelstr,const string& message)
{
string logname = logdir;
logname+="/";
logname+=_filename;
logname+=levelstr;
WriteLogToOneFile(logname,message);
}
void WriteLog(const string& levelstr,const string& message)
{
switch (_style)
{
case Screen:
cout<<message<<endl;//打印到屏幕中
break;
case OneFile:
WriteLogToClassFile("all",message);//给定all,直接写到all里
break;
case ClassFile:
WriteLogToClassFile(levelstr,message);//写入levelstr里
break;
default:
break;
}
}
//打印日志
void LogMessage(int level,const char* format,...)
{
char rightbuffer[1024];//处理消息
va_list args; //va_list 是指针
va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
//现在args指向了可变参数部分
vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
va_end(args);
char leftbuffer[1024];//处理日志等级、pid、时间
string levelstr = LevelToString(level);
string currtime = GetTime();
string idstr = to_string(getpid());
snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
,currtime.c_str(),idstr.c_str());
string loginfo = leftbuffer;
loginfo+=rightbuffer;
WriteLog(levelstr,loginfo);
}
//提供接口给运算符重载使用
void _LogMessage(int level,char* rightbuffer)
{
char leftbuffer[1024];
string levelstr = LevelToString(level);
string currtime = GetTime();
string idstr = to_string(getpid());
snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
,currtime.c_str(),idstr.c_str());
string messages = leftbuffer;
messages+=rightbuffer;
WriteLog(levelstr,messages);
}
//运算符重载
void operator()(int level,const char* format,...)
{
char rightbuffer[1024];
va_list args; //va_list 是指针
va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
va_end(args);
_LogMessage(level,rightbuffer);
}
~Log()
{}
private:
int _style;
string _filename;
};
Log lg;
class Conf
{
public:
Conf()
{
lg.Enable(Screen);
}
~Conf()
{}
};
Conf conf;
nocopy.hpp (让服务器类继承nocopy,达到不可拷贝的作用)
#pragma once
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy& n) = delete;
nocopy& operator=(const nocopy& n) = delete;
~nocopy(){}
};
Thread.hpp Thread库封装
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
// 设计方的视角
//typedef std::function<void()> func_t;
namespace kky
{
template<class T>
using func_t = std::function<void(T&)>;
template<class T>
class Thread
{
public:
Thread(const std::string &threadname, func_t<T> func, T &data)
:_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data)
{}
// 不加static会有this指针,无法调用pthread_creadte
static void *ThreadRoutine(void *args) // 类内方法,
{
// (void)args; // 仅仅是为了防止编译器有告警
Thread *ts = static_cast<Thread *>(args);
ts->_func(ts->_data);
return nullptr;
}
//运行线程
bool Start()
{
int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);
if(n == 0)
{
_isrunning = true;
return true;
}
else return false;
}
//等待线程
bool Join()
{
if(!_isrunning) return true;
int n = pthread_join(_tid, nullptr);
if(n == 0)
{
_isrunning = false;
return true;
}
return false;
}
std::string ThreadName()
{
return _threadname;
}
bool IsRunning()
{
return _isrunning;
}
~Thread()
{}
private:
pthread_t _tid;
std::string _threadname;
bool _isrunning;
func_t<T> _func;
T _data;
};
}
Threadpool.hpp (线程库的封装)
#pragma once
#include <pthread.h>
#include <vector>
#include <functional>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace std;
namespace kky
{
static const int default_num = 5;
class ThreadData
{
public:
ThreadData(string name)
: thread_name(name)
{
}
string thread_name;
};
template <class T>
class ThreadPool
{
private:
ThreadPool(int thread_num = default_num)
: _thread_num(thread_num)
{
pthread_mutex_init(&_mutex, nullptr); // 初始化
pthread_cond_init(&_cond, nullptr);
// 创建指定个数的线程
for (int i = 0; i < _thread_num; i++)
{
string thread_name = "thread_";
thread_name += to_string(i + 1);
ThreadData td(thread_name); // ThreadData为线程数据类型
Thread<ThreadData> t(thread_name, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td);
_threads.emplace_back(t);
lg(Info, "%s 被创建...", thread_name.c_str()); // 写入
}
}
ThreadPool(const ThreadPool<T> &tp) = delete;
const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;
public:
static ThreadPool<T> *GetInstance()
{
if(instance == nullptr)
{
LockGuard lockguard(&sig_lock);
if (instance == nullptr)
{
instance = new ThreadPool<T>();
lg.LogMessage(Info, "创建单例成功...");
}
}
return instance;
}
// 线程运行
bool Start()
{
for (auto &thread : _threads)
{
thread.Start();
lg.LogMessage(Info, "%s 正在运行!", thread.ThreadName().c_str());
}
}
// 线程条件变量等待
void ThreadWait(ThreadData &td)
{
lg.LogMessage(Debug, "没有任务,%s休眠了", td.thread_name.c_str());
pthread_cond_wait(&_cond, &_mutex);
}
// 线程条件变量唤醒
void ThreadWakeUp()
{
pthread_cond_signal(&_cond);
}
// 执行任务
void ThreadRun(ThreadData &td)
{
while (1)
{
T t;
// 取出任务
{
LockGuard lockguard(&_mutex); // 代码块中自动加锁与解锁
while (_q.empty())
{
ThreadWait(td);
lg.LogMessage(Debug, "有任务了,%s去执行任务了", td.thread_name.c_str());
}
t = _q.front();
_q.pop();
}
t();
// 处理任务 我们通过打印消息来模拟任务
// cout<<t<<endl;
// lg.LogMessage(Debug, "%s 计算结果为:%d", td.thread_name.c_str(), t);
}
}
// 将任务放到队列中
void Push(const T &in)
{
{
LockGuard lockguard(&_mutex);
_q.push(in);
}
lg.LogMessage(Debug, "任务push成功");
ThreadWakeUp();
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex); // 销毁
pthread_cond_destroy(&_cond);
}
// 进程等待
void Wait()
{
for (auto &thread : _threads)
{
thread.Join();
}
}
private:
queue<T> _q;
vector<Thread<ThreadData>> _threads;
int _thread_num;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
static ThreadPool<T> *instance;
static pthread_mutex_t sig_lock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
template<class T>
pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;
}
TcpServer.hpp (服务端的封装)
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unordered_map>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <thread>
#include "Log.hpp"
#include "Comm.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Threadpool.hpp"
class ThreadData;
static const int default_backlog = 5;
using func_t = function<void(ThreadData *)>;
using task_t = function<void()>;
using callback_t = function<void(int sockfd, InetAddr &addr)>;
class ThreadData
{
public:
ThreadData(int sock, struct sockaddr_in &peer)
: _sockfd(sock), _addr(peer)
{
}
~ThreadData()
{
close(_sockfd);
}
public:
int _sockfd;
InetAddr _addr;
};
class TcpServer : public nocopy
{
public:
TcpServer(uint16_t port)
: _port(port), _isrunning(false)
{
}
void HandlerRequest(ThreadData *td)
{
Service1(td->_sockfd);
delete td;
}
void Init()
{
// 1.创建套接字 得到文件描述符
_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0); // SOCK_STREAM 代表TCP字节流
if (_listen_sockfd < 0)
{
lg.LogMessage(Fatal, "create socket error, \
errno code: %d,error string: %s",
errno, strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Debug, "create socket success, sockfd: %d", _listen_sockfd);
// 固定写法,解决一些少量bind失败的问题
int opt = 1;
setsockopt(_listen_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
// 2.填充网络信息
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;
// 3.bind
int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n != 0)
{
lg.LogMessage(Fatal, "bind socket error, \
errno code: %d,error string: %s",
errno, strerror(errno));
exit(Bind_Err);
}
lg.LogMessage(Debug, "bind socket success, sockfd: %d", _listen_sockfd);
// 4.设置监听状态,TCP特有的
if (listen(_listen_sockfd, default_backlog) != 0)
{
lg.LogMessage(Fatal, "listen socket error, \
errno code: %d,error string: %s",
errno, strerror(errno));
exit(Listen_Err);
}
lg.LogMessage(Debug, "listen socket success, sockfd: %d", _listen_sockfd);
kky::ThreadPool<task_t>::GetInstance()->Start();
funcs.insert(make_pair("defaultService",std::bind(&TcpServer::DefaultService
,this,placeholders::_1,placeholders::_2)));
}
// accept获取的sockfd是全双工的,因此我们read和write都可以用这个sockfd
void Service1(int sockfd)
{
char buff[1024];
while (true)
{
ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
buff[n] = 0;
cout << "client say# " << buff << endl;
string echo_string = "server echo# ";
echo_string += buff;
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0) // 表示对面关闭了连接
{
lg.LogMessage(Info, "client quit...");
break;
}
else
{
lg.LogMessage(Error, "read socket error, \
errno code: %d,error string: %s",
errno, strerror(errno));
break;
}
}
}
void Service2(int sockfd, InetAddr addr)
{
char buff[1024];
while (true)
{
ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
buff[n] = 0;
cout << "[" << addr.PrintDebug() << "]#" << buff << endl;
string echo_string = "server echo# ";
echo_string += buff;
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0) // 表示对面关闭了连接
{
lg.LogMessage(Info, "client quit...");
break;
}
else
{
lg.LogMessage(Error, "read socket error, \
errno code: %d,error string: %s",
errno, strerror(errno));
break;
}
}
}
void Start()
{
_isrunning = true;
// 忽略SIGCHLD信号,那么子进程退出时内核会自动回收其资源,并且不会产生僵尸进程
// 与下面v3版本多线程一起使用,如果不是V3版本,这句代码无用
signal(SIGCHLD, SIG_IGN);
while (_isrunning)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
// 5.获取连接
int sockfd = accept(_listen_sockfd, (struct sockaddr *)&peer, &len);
if (sockfd < 0)
{
lg.LogMessage(Warning, "accept socket error");
continue;
}
lg.LogMessage(Debug, "accept socket success,get a new sockfd: %d", sockfd);
// 6.提供服务
// 6.v1版本 只能一个为一个进程服务
// Service1(sockfd);
// close(sockfd);
// v2 多进程 fork()孙子进程版本
// pid_t id = fork();
// if(id<0)
// {
// close(sockfd);
// continue;
// }
// else if(id ==0)
// {
// //子进程不用关心_listen_sockfd,因为父进程在listen。子进程只服务就好
// close(_listen_sockfd);
// if(fork()>0)
// exit(0);
// //孙子进程 父进程退出了,他变成孤儿进程,
// //被操作系统领养 死亡自动回收,不需要wait了
// Service1(sockfd);
// close(sockfd);
// exit(0);
// }
// else
// {
// //父进程
// close(sockfd); //sockfd交给子进程去服务了
// //nullptr代表不需要子进程的退出信息,0代表阻塞等待
// pid_t rid = waitpid(id,nullptr,0);
// if(rid == id)
// {
// //等待成功 //去干你想干的事情
// }
// }
// v3 多进程 信号版本 //与104行signal(SIGCHLD,SIG_IGN);一起使用
// 通过信号忽略的方式让父进程不再管子进程
// pid_t id = fork();
// if(id < 0)
// {
// close(sockfd);
// continue;
// }
// else if(id == 0)
// {
// //子进程
// close(_listen_sockfd);
// Service1(sockfd);
// close(sockfd);
// exit(0);
// }
// else
// {
// //父进程
// close(sockfd);
// }
// v4 多线程版本
// ThreadData *td = new ThreadData(sockfd,peer);
// //不直接传sockfd,是担心主线程while循坏去将sockfd覆盖,因此传递对象指针
// func_t f = bind(&TcpServer::HandlerRequest,this,placeholders::_1);
// thread t1(f,td);
// t1.detach();
// v5 线程池版本
task_t f = bind(&TcpServer::Routine, this, sockfd, peer);
kky::ThreadPool<task_t>::GetInstance()->Push(f);
}
}
//服务器去读取客户端的输入信息,依据输入提供服务
string Read(int sockfd)
{
char buff[1024];
ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
buff[n] = 0;
}
else if (n == 0) // 表示对面关闭了连接
{
lg.LogMessage(Info, "client quit...");
}
else
{
lg.LogMessage(Error, "read socket error, \
errno code: %d,error string: %s",
errno, strerror(errno));
}
return buff;
}
void Routine(int sockfd,InetAddr addr)
{
funcs["defaultService"](sockfd,addr);
string type = Read(sockfd);
lg.LogMessage(Debug,"%s select %s",addr.PrintDebug().c_str(),type.c_str());
if(type == "ping")
funcs[type](sockfd,addr);
else if(type == "translate")
funcs[type](sockfd,addr);
else
funcs["defaultService"](sockfd,addr);
close(sockfd);
}
void DefaultService(int sockfd,InetAddr& addr)
{
std::string service_list = " | ";
for(auto& func : funcs)
{
service_list += func.first;
service_list +=" | ";
}
write(sockfd,service_list.c_str(),service_list.size());
}
// 添加string->callback_t 方法的映射
void RegisterFunc(const string &name, callback_t func)
{
funcs[name] = func;
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listen_sockfd;
bool _isrunning;
// 输入"xxx" 就去执行 xxx 任务
unordered_map<string, callback_t> funcs;
};
Main.cc (服务端的入口)
#include "TcpServer.hpp"
#include <memory>
using namespace std;
string unknown = "unknown";
class Dictionary
{
public:
Dictionary()
{
dict.insert(make_pair<string,string>("banana","香蕉"));
dict.insert(make_pair<string,string>("apple","苹果"));
dict.insert(make_pair<string,string>("monkey","猴子"));
dict.insert(make_pair<string,string>("love","爱"));
}
string Excute(const string& word)
{
if(dict.find(word)!=dict.end())
return dict[word];
return unknown;
}
~Dictionary()
{}
private:
unordered_map<string,string> dict;
};
Dictionary ts;
void Usage(string proc)
{
cout << "Usage \n\t" << proc << "local_port\n"
<< endl;
}
//判断服务器是否正常运行————>心跳机制 客户ping,就会获得pong 证明服务端没问题
void Ping(int sockfd, InetAddr addr)
{
lg.LogMessage(Debug, "%s select %s success, fd : %d",
addr.PrintDebug().c_str(), "ping", sockfd);
char buffer[1024];
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
buffer[n] = 0;
string echo_string = "Pong";
write(sockfd,echo_string.c_str(),echo_string.size());
}
void Translate(int sockfd, InetAddr addr)
{
lg.LogMessage(Debug, "%s select %s success, fd : %d", addr.PrintDebug().c_str(), "translate", sockfd);
char wordbuf[128];
ssize_t n = read(sockfd, wordbuf, sizeof(wordbuf) - 1);
if (n > 0)
wordbuf[n] = 0;
string chinese = ts.Excute(wordbuf);
write(sockfd,chinese.c_str(),chinese.size());
lg.LogMessage(Debug,"%s Translate service,%s->%s",addr.PrintDebug().c_str(),wordbuf,chinese.c_str());
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return Usage_Err;
}
uint16_t port = stoi(argv[1]);
unique_ptr<TcpServer> tsvr(new TcpServer(port));
tsvr->RegisterFunc("ping", Ping);
tsvr->RegisterFunc("translate", Translate);
tsvr->Init();
tsvr->Start();
}
TcpClient.cc (客户端的入口)
#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include "Comm.hpp"
using namespace std;
#define Retry_Count 5
void Usage(string proc)
{
cout << "Usage \n\t" << proc << " local_ip local_port\n"
<< endl;
}
bool VisitServer(const string &ip, const uint16_t port, int &cnt)
{
int n = 0;
string inbuffer;
char service_list[1024];
ssize_t w = 0; // write 返回值,由于goto原因 需要放在前面
ssize_t r = 0; // read 返回值,由于goto原因 需要放在前面
bool ret = true;
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
cerr << "socket error" << endl;
ret = false;
goto END;
}
// 2.填写sockaddr_in信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
// inet_pton 类似于 inet_addr 都是让点分十进制 IP 转网络字节序的二进制序列
inet_pton(AF_INET, ip.c_str(), &server.sin_addr.s_addr);
// 3.connect进行连接,他会自动绑定,不需要手动绑定
n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
cerr << "connect error" << endl;
ret = false;
goto END;
}
//先读取操作列表
r = read(sockfd, service_list, sizeof(service_list) - 1);
if (r > 0)
{
service_list[r] = 0;
cout << "服务器提供的服务列表是:" << service_list << endl;
}
cnt = 1; // 让cnt又从1-5再次链接
cout << "Please Select Service# ";
getline(cin, inbuffer);
// connect并没有产生新的sockfd,只用这一个sockfd进行通信即可。
w = write(sockfd, inbuffer.c_str(), inbuffer.size());
if (w > 0)
{
char buffer[1024];
cout << "Please Enter# ";
getline(cin, inbuffer);
write(sockfd, inbuffer.c_str(), inbuffer.size());
r = read(sockfd, buffer, sizeof(buffer) - 1);
if (r > 0)
{
buffer[r] = 0;
cout << buffer << endl;
}
else if (r == 0) // read返回值为0,代表读端关闭,算是正常结束
{
goto END;
}
else
{
ret = false;
goto END;
}
}
else if (w == 0)
{
cout << "你并没有输入" << endl;
}
else
{
cout << "write时服务器关闭" << endl;
ret = false;
}
END:
close(sockfd);
return ret;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(Usage_Err);
}
signal(SIGPIPE, SIG_IGN);
string ip = argv[1];
uint16_t port = stoi(argv[2]);
// 断线重连
int cnt = 1;
while (cnt <= Retry_Count)
{
int result = VisitServer(ip, port, cnt);
if (result)
break;
else
{
sleep(1);
cout << "server offline, retrying..., count: " << cnt++ << endl;
}
}
if (cnt > Retry_Count)
{
cout << "server offline" << endl;
}
}
Makefile (一键构建代码)
.PHONY:all
all:tcp_server tcp_client
tcp_server:Main.cc
g++ -o $@ $^ -std=c++11 -lpthread
tcp_client:TcpClient.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f tcp_server tcp_client
代码链接
运行结果如下