【Linux网络编程五】Tcp套接字编程(四个版本服务器编写)
【Linux网络编程五】Tcp套接字编程(四个版本服务器编写)
[Tcp套接字编程]一.服务器端进程:1.创建套接字2.绑定网络信息3.设置监听状态4.获取新连接5.根据新连接进行通信
二.客户端进程:1.创建套接字2.连接服务器套接字3.连接成功后进行通信
三.version1:单进程版四.version2:多进程版五.version3:多线程版六.version4:线程池版七.解决细节问题:完善服务器和客户端1.write写入会存在偶发性失败,进程被信号杀死2.客户端每次请求都需要重新发起连接3.服务器端出现问题,客户端需要尝试自救4.复用ip地址和端口号
[Tcp套接字编程]
一般使用网络套接字编程时都需要引用以上头文件。
一.服务器端进程:
我们首先先进行服务器端的编写:服务器端肯定是需要知道有自己的ip地址和端口号的。
1.创建套接字
跟Udp套接字编程一样,通信前需要创建套接字,创建套接字的本质就是打开一个网络文件。
要注意的是Tcp是面向字节流的,创建套接字时,按照字节流形式创建。
//服务器端启动之前创建套接字,绑定。
//一开始的这个套接字是属于监听套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
lg(Fatal,"sock create errno:%d errstring:%s",errno,strerror(errno));
exit(SockError);
}
//创建套接字成功
lg(Info,"sock create sucess listensock:%d",_listensock);
2.绑定网络信息
创建完套接字后,也就是打开一个网络文件,我们需要绑定该服务器的网络信息,比如ip地址和端口号等。这样该套接字才能找到服务器端。客户端往该套接字写入时,服务器端就能从该套接字里读取到。 服务器往该套接字里写入时,连接该套接字的客户端就能接收到。
//创建成功后就要绑定服务器的网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
//填充信息
local.sin_family=AF_INET;
local.sin_port=htons(_port);
inet_aton(_ip.c_str(),&local.sin_addr);
//填充完毕,真正绑定
if((bind(_listensock,(struct sockaddr*)&local,sizeof(local)))<0)
{
lg(Fatal,"bind errno:%d errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"bind socket success listensock:%d",_listensock);//绑定成功
将字符粗类型转int类型使用inet_aton()调用,简单的那个存在线程安全。
3.设置监听状态
Tcp与Udp不同之处在于,Tcp是面向连接的,什么意思呢?就是在真正通信前,需要先建立连接才能正式通信。比如Udp在绑定完网络信息后,就可以直接进行通信了,但Tcp不行,Tcp在绑定完网络信息后,还需要建立连接。
Tcp相比较Udp比较被动,在通信之前,需要随时随地地等待别人连接上去才能进行通信。所以服务器要一直处于一个等待到来连接的状态。 而等待到新连接需要一种能力,这个能力就是监听!只有我们将套接字设置为监听状态,该套接字才能获取到新连接。不然获取不到别人的连接。
//udp中绑定成功后就可以进行通信了,但tcp与udp不同。tcp是面向连接的,在通信之前
//需要先获取新连接,获取到新连接才能进行通信。没有获取连接那么就要等待连接,等待新连接的过程叫做监听,监听有没有新连接。
//需要将套接字设置成监听状态
listen(_listensock,backlog);//用来监听,等待新连接,只有具备监听状态才能识别到连
4.获取新连接
以上都是服务器的一些网络信息初始化步骤,接下来才是启动服务器。服务器启动服务呢? 首先服务器在通信之前需要获取到客户端的连接。这样才可以和客户端进行通信。那么如何获取到客户端的连接呢?
1.前提是该服务器的套接字已经设置为监听状态,只有套接字设置为监听状态才可以获取到客户端发起的连接。 2.利用accept函数就可以获取到客户端发起的连接。 3.accept函数和recvfrom函数类似,它从套接字里获取到连接,不仅仅可以获取到客户端发起的连接,还可以获取到客户端的网络信息。而获取到客户端的网络信息是通过两个输出型参数带出来的。 4.注意accept函数的返回值是一个文件描述符。为什么呢?也就是accept返回的也是一个套接字文件。accept从一个套接字文件里获取到连接然后再返回套接字文件?什么意思呢?
【问题】:为什么会有两个套接字? accept从我们创建的套接字里获取连接,结果又返回一个套接字文件,为什么呢?这个新的套接字是干啥的?
1.也就是曾经被创建的,被绑定网络信息的,设置为监听状态的套接字是专门用来获取客户端发起的连接的,然后将该连接返回给一个新的套接字,让服务器通过新的套接字与客户端通信,而原来的套接字就又处于监听状态,继续来监听客户端发起的新连接。 2.其实就是为了提高通信效率,一个套接字既去获取连接,又去通信,效率比较低,而让一个套接字一直处于监听状态,获取到新连接后,就转让给另一个套接字,服务器其实是通过accept返回的套接字与连接端的客户进行通信的。 3.未来,获取的连接会越来越多,也就是返回的套接字会越来越多,而监听的套接字只有一个。
lg(Info,"tcpserver is running");
while(true)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//将套接字设置成监听状态后,就可以获取新连接
int sockfd=accept(_listensock,(struct sockaddr*)&client,&len);
//获取从监听套接字那里监听到的连接。然后返回一个新套接字,通过这个套接字与连接直接通信,而监听套接字继续去监听。
if(sockfd<0)
{
lg(Fatal,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
exit(AcceptError);
}
//获取新连接成功
//将客户端端网络信息带出来
uint16_t clientport=ntohs(client.sin_port);
char clientip[32];
inet_ntop(AF_INET,&client.sin_addr,clientip,sizeof(clientip));
}
将客户端网络信息带出来注意需要将网络字节序转换成用户字节序。 还有我们的传输的内容会自动转换的。
5.根据新连接进行通信
一旦服务器端获取到新连接成功后,就可以与对端的客户通过返回的套接字进行通信。
而Tcp是面向字节流的,面向字节流就可以直接使用文件操作,因为文件操作也是面向字节流的,也就是文件操作里的read,write就可以往套接字里读取和写入。
1.我们就可以根据获取到的套接字与客户端进行通信。 2.我们可以将该工作分离成一个函数service。与客户端通信需要知道连接的套接字和客户端的网络信息。
void Service(int &sockfd,const std::string &clientip,uint16_t &clientport)
{
char inbuffer[1024];
while(true)
{
ssize_t n=read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client say# "<
0)
{
outbuffer[n]=0;
std::cout<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "TASK.hpp"
#include "ThreadPool.hpp"
Log lg;
const std::string defaultip="0.0.0.0";
const int defaultfd=-1;
int backlog=10;//一般不要设置太大
enum
{
SockError=2,
BindError,
AcceptError,
};
class Tcpserver
{
public:
Tcpserver(const uint16_t &port,const std::string &ip=defaultip):_listensock(-1),_port(port),_ip(ip)
{}
void Init()
{
//服务器端启动之前创建套接字,绑定。
//一开始的这个套接字是属于监听套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
lg(Fatal,"sock create errno:%d errstring:%s",errno,strerror(errno));
exit(SockError);
}
//创建套接字成功
lg(Info,"sock create sucess listensock:%d",_listensock);
//创建成功后就要绑定服务器的网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
//填充信息
local.sin_family=AF_INET;
local.sin_port=htons(_port);
inet_aton(_ip.c_str(),&local.sin_addr);
//填充完毕,真正绑定
if((bind(_listensock,(struct sockaddr*)&local,sizeof(local)))<0)
{
lg(Fatal,"bind errno:%d errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"bind socket success listensock:%d",_listensock);//绑定成功
//udp中绑定成功后就可以进行通信了,但tcp与udp不同。tcp是面向连接的,在通信之前
//需要先获取新连接,获取到新连接才能进行通信。没有获取连接那么就要等待连接,等待新连接的过程叫做监听,监听有没有新连接。
//需要将套接字设置成监听状态
listen(_listensock,backlog);//用来监听,等待新连接,只有具备监听状态才能识别到连接
}
void START()
{
lg(Info,"tcpserver is running");
while(true)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//将套接字设置成监听状态后,就可以获取新连接
int sockfd=accept(_listensock,(struct sockaddr*)&client,&len);
//获取从监听套接字那里监听到的连接。然后返回一个新套接字,通过这个套接字与连接直接通信,而监听套接字继续去监听。
if(sockfd<0)
{
lg(Fatal,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
exit(AcceptError);
}
//获取新连接成功
//将客户端端网络信息带出来
uint16_t clientport=ntohs(client.sin_port);
char clientip[32];
inet_ntop(AF_INET,&client.sin_addr,clientip,sizeof(clientip));
//根据新连接进行通信
lg(Info,"get a new link...sockfd: %d,clientip: %s,clientport: %d",sockfd,clientip,clientport);
//-----------version1 单进程版本
Service(sockfd,clientip,clientport);
close(sockfd);//不用了就关闭
}
}
void Service(int &sockfd,const std::string &clientip,uint16_t &clientport)
{
char inbuffer[1024];
while(true)
{
ssize_t n=read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client say# "<
#include
#include
void Usage(std::string proc)
{
std::cout<<"\n\rUsage: "< tcpsvr(new Tcpserver(port));
tcpsvr->Init();
tcpsvr->Run();
}
【存在问题】上面属于单进程版本,只有一个进程在执行,只能服务一个客户端,其他客户端要想再使用服务器需要等当前客户端使用完服务器退出才能使用。这也太low了。 所以我们想改进为多进程版本。 单进程就是因为当前进程获取到连接后,该进程就去服务客户端端了,其他客户端就无法再使用该进程了。所以我们可以这样改进:让父进程获取新连接,然后创建子进程,让子进程去提供服务。子进程在服务的过程中,父进程就一直在等待新的连接。
四.version2:多进程版
【处理文件描述符】 多进程版我们首先需要处理一下文件描述符,也就是被打开的套接字。 当获取到新连接后,我们的进程就可以创建子进程,让子进程去跟客户端进行通信。而父进程继续去等待新连接。
因为创建子进程,子进程会继承父进程的文件描述符表。也就是父子进程具有相同的文件描述符表。 1.父进程的主要任务是获取新连接,然后将获取到的套接字给子进程使用。所以它是不需要获取到的套接字的,所以父进程需要将获取到的新套接字关闭掉。 2.而子进程的主要任务是根据新连接服务客户端,它是需要监听套接字的,所以需要将监听套接字关闭掉。父子进程相互关闭文件描述符是不会影响对方的。
【父子进程并发执行】 因为单进程中就是因为进程只能在处于要么在监听中,要么在服务中,不能处于既在监听中,又在服务中。所以我们需要让父进程去获取连接,子进程去服务客户端。而如何做到呢? 父进程是需要等待子进程的,如果子进程不退出,那么它就要一直在阻塞等待。那么它就无法再去获取新连接了。这就跟单进程一样了。 所以我们需要让父进程非阻塞等待子进程。或者通过这样的方法: 让子进程再创建一个子进程,当创建成功时,就直接退出,那么父进程就会等待成功。就会继续往下执行回去新的连接。 而让孙子进程去服务客户端。
Tcpserver.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "TASK.hpp"
#include "ThreadPool.hpp"
Log lg;
const std::string defaultip="0.0.0.0";
const int defaultfd=-1;
int backlog=10;//一般不要设置太大
enum
{
SockError=2,
BindError,
AcceptError,
};
class Tcpserver
{
public:
Tcpserver(const uint16_t &port,const std::string &ip=defaultip):_listensock(-1),_port(port),_ip(ip)
{}
void Init()
{
//服务器端启动之前创建套接字,绑定。
//一开始的这个套接字是属于监听套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
lg(Fatal,"sock create errno:%d errstring:%s",errno,strerror(errno));
exit(SockError);
}
//创建套接字成功
lg(Info,"sock create sucess listensock:%d",_listensock);
//创建成功后就要绑定服务器的网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
//填充信息
local.sin_family=AF_INET;
local.sin_port=htons(_port);
inet_aton(_ip.c_str(),&local.sin_addr);
//填充完毕,真正绑定
if((bind(_listensock,(struct sockaddr*)&local,sizeof(local)))<0)
{
lg(Fatal,"bind errno:%d errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"bind socket success listensock:%d",_listensock);//绑定成功
//udp中绑定成功后就可以进行通信了,但tcp与udp不同。tcp是面向连接的,在通信之前
//需要先获取新连接,获取到新连接才能进行通信。没有获取连接那么就要等待连接,等待新连接的过程叫做监听,监听有没有新连接。
//需要将套接字设置成监听状态
listen(_listensock,backlog);//用来监听,等待新连接,只有具备监听状态才能识别到连
}
void START()
{
lg(Info,"tcpserver is running");
while(true)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//将套接字设置成监听状态后,就可以获取新连接
int sockfd=accept(_listensock,(struct sockaddr*)&client,&len);
//获取从监听套接字那里监听到的连接。然后返回一个新套接字,通过这个套接字与连接直接通信,而监听套接字继续去监听。
if(sockfd<0)
{
lg(Fatal,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
exit(AcceptError);
}
//获取新连接成功
//将客户端端网络信息带出来
uint16_t clientport=ntohs(client.sin_port);
char clientip[32];
inet_ntop(AF_INET,&client.sin_addr,clientip,sizeof(clientip));
//根据新连接进行通信
lg(Info,"get a new link...sockfd: %d,clientip: %s,clientport: %d",sockfd,clientip,clientport);
//-----------version1 单进程版本
//Service(sockfd,clientip,clientport);
//close(sockfd);//不用了就关闭
//-----------version2 多进程版本
pid_t id=fork();
if(id==0)//子进程,用来处理服务,父进程用来获取新连接
{
close(_listensock);//子进程不需要该文件关闭
if(fork()>0)exit(0);//再创建一个子进程,然后让该进程退出,让孙子进程执行下面的服务,子进程就退出,父进程就等待成功就会重新获取连接
Service(sockfd,clientip,clientport);
close(sockfd);
exit(0);
}
//父进程只负责用来获取新连接,获取完毕后就交给子进程,自己是不用的,所以关闭
close(sockfd);
pid_t rid=waitpid(id,nullptr,0);//阻塞等待
(void)rid;
}
}
void Service(int &sockfd,const std::string &clientip,uint16_t &clientport)
{
char inbuffer[1024];
while(true)
{
ssize_t n=read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client say# "<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "TASK.hpp"
#include "ThreadPool.hpp"
Log lg;
const std::string defaultip="0.0.0.0";
const int defaultfd=-1;
int backlog=10;//一般不要设置太大
enum
{
SockError=2,
BindError,
AcceptError,
};
class Tcpserver;
//构建线程所需要的参数数据:获取连接的套接字,客户端端的网络信息,服务器类的指针
class ThreadData
{
public:
ThreadData(int &fd,const std::string& ip,uint16_t &port,Tcpserver* svr):_sockfd(fd),_ip(ip),_port(port),_svr(svr)
{}
public:
int _sockfd;
std::string _ip;
uint16_t _port;
Tcpserver* _svr;//通过该类指针可以找到类内部函数
};
class Tcpserver
{
public:
Tcpserver(const uint16_t &port,const std::string &ip=defaultip):_listensock(-1),_port(port),_ip(ip)
{}
void Init()
{
//服务器端启动之前创建套接字,绑定。
//一开始的这个套接字是属于监听套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
lg(Fatal,"sock create errno:%d errstring:%s",errno,strerror(errno));
exit(SockError);
}
//创建套接字成功
lg(Info,"sock create sucess listensock:%d",_listensock);
//创建成功后就要绑定服务器的网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
//填充信息
local.sin_family=AF_INET;
local.sin_port=htons(_port);
inet_aton(_ip.c_str(),&local.sin_addr);
//填充完毕,真正绑定
if((bind(_listensock,(struct sockaddr*)&local,sizeof(local)))<0)
{
lg(Fatal,"bind errno:%d errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"bind socket success listensock:%d",_listensock);//绑定成功
//udp中绑定成功后就可以进行通信了,但tcp与udp不同。tcp是面向连接的,在通信之前
//需要先获取新连接,获取到新连接才能进行通信。没有获取连接那么就要等待连接,等待新连接的过程叫做监听,监听有没有新连接。
//需要将套接字设置成监听状态
listen(_listensock,backlog);//用来监听,等待新连接,只有具备监听状态才能识别到连接
}
static void* Routine(void *args)//静态成员函数无法使用成员函数,再封装一个服务器对象
{
//子线程要和主线程分离,主线程不需要等待子线程,直接回去重新获取新连接
pthread_detach(pthread_self());
ThreadData* td=static_cast(args);
//子线程用来服务客户端
td->_svr->Service(td->_sockfd,td->_ip,td->_port);
delete td;
return nullptr;
}
void Run()
{
//一启动服务器,就将线程池中的线程创建
ThreadPool::GetInstance()->Start();//单例对象
//静态函数,通过类域就可以使用
lg(Info,"tcpserver is running");
while(true)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//将套接字设置成监听状态后,就可以获取新连接
int sockfd=accept(_listensock,(struct sockaddr*)&client,&len);
//获取从监听套接字那里监听到的连接。然后返回一个新套接字,通过这个套接字与连接直接通信,而监听套接字继续去监听。
if(sockfd<0)
{
lg(Fatal,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
exit(AcceptError);
}
//获取新连接成功
//将客户端端网络信息带出来
uint16_t clientport=ntohs(client.sin_port);
char clientip[32];
inet_ntop(AF_INET,&client.sin_addr,clientip,sizeof(clientip));
//根据新连接进行通信
lg(Info,"get a new link...sockfd: %d,clientip: %s,clientport: %d",sockfd,clientip,clientport);
//-----------version3 多线程版本
ThreadData *td=new ThreadData(sockfd,clientip,clientport,this);//首先将线程所需要的参数数据初始化好
pthread_t tid;
pthread_create(&tid,nullptr,Routine,td);//Routine要设置成静态成员函数
}
}
void Service(int &sockfd,const std::string &clientip,uint16_t &clientport)
{
char inbuffer[1024];
while(true)
{
ssize_t n=read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client say# "<
#include
#include
#include
#include
#include
struct ThreadInfo
{
pthread_t tid;
std::string name;
};
static const int defalutnum = 10;
template
class ThreadPool
{
public:
void Lock()
{
pthread_mutex_lock(&mutex_);
}
void Unlock()
{
pthread_mutex_unlock(&mutex_);
}
void Wakeup()
{
pthread_cond_signal(&cond_);
}
void ThreadSleep()
{
pthread_cond_wait(&cond_, &mutex_);
}
bool IsQueueEmpty()
{
return tasks_.empty();
}
std::string GetThreadName(pthread_t tid)
{
for (const auto &ti : threads_)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
static void *HandlerTask(void *args)
{
ThreadPool *tp = static_cast *>(args);
std::string name = tp->GetThreadName(pthread_self());
while (true)
{
tp->Lock();
while (tp->IsQueueEmpty())
{
tp->ThreadSleep();
}
T t = tp->Pop();
tp->Unlock();
t();
}
}
void Start()
{
int num = threads_.size();
for (int i = 0; i < num; i++)
{
threads_[i].name = "thread-" + std::to_string(i + 1);
pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
}
}
T Pop()
{
T t = tasks_.front();
tasks_.pop();
return t;
}
void Push(const T &t)
{
Lock();
tasks_.push(t);
Wakeup();
Unlock();
}
static ThreadPool *GetInstance()
{
if (nullptr == tp_) // ???
{
pthread_mutex_lock(&lock_);
if (nullptr == tp_)
{
std::cout << "log: singleton create done first!" << std::endl;
tp_ = new ThreadPool();
}
pthread_mutex_unlock(&lock_);
}
return tp_;
}
private:
ThreadPool(int num = defalutnum) : threads_(num)
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
~ThreadPool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
ThreadPool(const ThreadPool &) = delete;
const ThreadPool &operator=(const ThreadPool &) = delete; // a=b=c
private:
std::vector threads_;
std::queue tasks_;
pthread_mutex_t mutex_;
pthread_cond_t cond_;
static ThreadPool *tp_;
static pthread_mutex_t lock_;
};
template
ThreadPool *ThreadPool::tp_ = nullptr;
template
pthread_mutex_t ThreadPool::lock_ = PTHREAD_MUTEX_INITIALIZER;
构建任务,准备投递到线程池里的任务队列里
#pragma once
#include "Log.hpp"
#include
#include "Init.hpp"
Init init;//刚加载程序时,文件里的内容就加载到map里了。
extern Log lg;
class TASK//构建任务,就是一旦获取到连接后,就将客户端的网络信息存到任务里,让服务器根据这个信息去服务客户端
{
public:
TASK(int &sockfd,const std::string& ip,uint16_t& port):_sockfd(sockfd),_clientip(ip),_clientport(port)
{}
void run()
{
char inbuffer[1024];
// while(true)
ssize_t n=read(_sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client key: "<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "TASK.hpp"
#include "ThreadPool.hpp"
Log lg;
const std::string defaultip="0.0.0.0";
const int defaultfd=-1;
int backlog=10;//一般不要设置太大
enum
{
SockError=2,
BindError,
AcceptError,
};
class Tcpserver;
class ThreadData
{
public:
ThreadData(int &fd,const std::string& ip,uint16_t &port,Tcpserver* svr):_sockfd(fd),_ip(ip),_port(port),_svr(svr)
{}
public:
int _sockfd;
std::string _ip;
uint16_t _port;
Tcpserver* _svr;
};
class Tcpserver
{
public:
Tcpserver(const uint16_t &port,const std::string &ip=defaultip):_listensock(-1),_port(port),_ip(ip)
{}
void Init()
{
//服务器端启动之前创建套接字,绑定。
//一开始的这个套接字是属于监听套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
lg(Fatal,"sock create errno:%d errstring:%s",errno,strerror(errno));
exit(SockError);
}
//创建套接字成功
lg(Info,"sock create sucess listensock:%d",_listensock);
//创建成功后就要绑定服务器的网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
//填充信息
local.sin_family=AF_INET;
local.sin_port=htons(_port);
inet_aton(_ip.c_str(),&local.sin_addr);
//填充完毕,真正绑定
if((bind(_listensock,(struct sockaddr*)&local,sizeof(local)))<0)
{
lg(Fatal,"bind errno:%d errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"bind socket success listensock:%d",_listensock);//绑定成功
//udp中绑定成功后就可以进行通信了,但tcp与udp不同。tcp是面向连接的,在通信之前
//需要先获取新连接,获取到新连接才能进行通信。没有获取连接那么就要等待连接,等待新连接的过程叫做监听,监听有没有新连接。
//需要将套接字设置成监听状态
listen(_listensock,backlog);//用来监听,等待新连接,只有具备监听状态才能识别到连接
}
static void* Routine(void *args)//静态成员函数无法使用成员函数,再封装一个服务器对象
{
//子线程要和主线程分离,主线程不需要等待子线程,直接回去重新获取新连接
pthread_detach(pthread_self());
ThreadData* td=static_cast(args);
//子线程用来服务客户端
td->_svr->Service(td->_sockfd,td->_ip,td->_port);
delete td;
return nullptr;
}
void Run()
{
//一启动服务器,就将线程池中的线程创建
ThreadPool::GetInstance()->Start();//单例对象
//静态函数,通过类域就可以使用
lg(Info,"tcpserver is running");
while(true)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//将套接字设置成监听状态后,就可以获取新连接
int sockfd=accept(_listensock,(struct sockaddr*)&client,&len);
//获取从监听套接字那里监听到的连接。然后返回一个新套接字,通过这个套接字与连接直接通信,而监听套接字继续去监听。
if(sockfd<0)
{
lg(Fatal,"accept error,errno: %d, errstring: %s",errno,strerror(errno));
exit(AcceptError);
}
//获取新连接成功
//将客户端端网络信息带出来
uint16_t clientport=ntohs(client.sin_port);
char clientip[32];
inet_ntop(AF_INET,&client.sin_addr,clientip,sizeof(clientip));
//根据新连接进行通信
lg(Info,"get a new link...sockfd: %d,clientip: %s,clientport: %d",sockfd,clientip,clientport);
//----------version4 线程池版本
//当获取到新连接时,就构建任务
TASK t(sockfd,clientip,clientport);
//将任务放进线程池里,线程就会到线程池里去执行任务。
ThreadPool::GetInstance()->Push(t);
}
}
void Service(int &sockfd,const std::string &clientip,uint16_t &clientport)
{
char inbuffer[1024];
while(true)
{
ssize_t n=read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client say# "<翻译字典】
服务器端的服务功能是交给线程池里的线程执行的,所以最好是短服务,如果是长服务的话,那么就会存在很多线程不退出,客户端不主动退出,线程就不退出的场景。我们想要让服务器只服务一次,服务完后,就将对应的服务的套接字关闭,不再服务。那么线程服务完一次就结束了。
所以我们可以设计一个翻译字典的功能,让客户端用户发送字母,服务器端给客户端翻译字母,再将字母的中文发送回去。服务器端服务完一次后,就将监听套接字获取到的新的套接字关闭,不再服务。 如果客户端想要再次请求服务,就需要重新发起连接,让服务器端的监听套接字重新获取到新的套接字。因为原来获取到的套接字已经被关闭。
翻译字典功能
#pragma once
#include
#include
#include
#include
#include "Log.hpp"
const std::string dictname="./dict.txt";
const std::string sep=":";//默认的分割符
extern Log lg;
class Init//相当于加载配置文件的动作,当创建出来,文件内容就已经被加载到map里了
{
public:
void Spilt(std::string& line,std::string *part1,std::string* part2)
{
auto pos=line.find(sep);
if(pos==std::string::npos)return ;
*part1=line.substr(0,pos);
*part2=line.substr(pos+1);
return ;
}
Init()
{
std::ifstream in(dictname);//定义一个文件流,成功就打开该文件
if(!in.is_open())
{
lg(Fatal,"ifstream open %s error",strerror(errno));
exit(1);
}
//读取文件里的内容
std::string line;
//按行读取。读取到line里
while(std::getline(in,line))
{
std::string part1,part2;
Spilt(line,&part1,&part2);
//然后分割到map里
dic.insert({part1,part2});
}
in.close();
}
std::string translation(const std::string& key)
{
auto iter=dic.find(key);//返回对应的该值的的迭代器
if(iter==dic.end())return "Unkonwn";
else return iter->second;
}
private:
std::unordered_map dic;
};
dict。txt对应的配置文件
apple:苹果
yellow:黄色
red:红色
bule:蓝色
man:男人
woman:女人
hello:你好
让服务器端的翻译字典只服务服务一次,就将对应的套接字关闭:
#pragma once
#include "Log.hpp"
#include
#include "Init.hpp"
Init init;//刚加载程序时,文件里的内容就加载到map里了。
extern Log lg;
class TASK//构建任务,就是一旦获取到连接后,就将客户端的网络信息存到任务里,让服务器根据这个信息去服务客户端
{
public:
TASK(int &sockfd,const std::string& ip,uint16_t& port):_sockfd(sockfd),_clientip(ip),_clientport(port)
{}
void run()
{
char inbuffer[1024];
// while(true)
ssize_t n=read(_sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<"client key: "<
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout<<"\n\rUsage: "<0)
{
outbuffer[n]=0;
std::cout<
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout<<"\n\rUsage: "<0)
{
outbuffer[n]=0;
std::cout<