socket 编程可以说是一个基本的技术掌握,而多个客户端向服务端发送请求又是一个非常常见的场景,因此多线程模式下的socket编程则显得尤为常见与重要。
本文主要利用线程池的技术,来实现多线程的模式,线程池的优点就不多述了,相信大家都能理解,就是减少了线程创建于销毁的时间,提高多线程的性能。
首先,先写个线程池: 下面分别是头文件 和cpp文件
#ifndef __THREAD_H
#define __THREAD_H
/*********************
** Filename: Thread.h
** Dsricbe: 线程池头文件
** Date: 2018.7.18
** @author: xionglei
***/
#include
#include
#include
using namespace std;
/**
* 执行任务的类,设置accept()描述符并执行
*/
class CTask {
protected:
string m_strTaskName; //任务的名称
int connfd; //接收的地址
public:
CTask() = default;
CTask(string &taskName): m_strTaskName(taskName), connfd(NULL) {}
virtual int Run() = 0;
void SetConnFd(int data); //设置接收的套接字连接号。
int GetConnFd();
virtual ~CTask() {}
};
/**
* 线程池类的实现
*/
class CThreadPool
{
private:
static deque m_deqTaskList; /** 任务队列 */
static bool shutdown; /** 线程退出标志 */
int m_iThreadNum; /** 线程池中启动的线程数 */
pthread_t *pthread_id;
static pthread_mutex_t m_pthreadMutex; /** 线程同步锁 */
static pthread_cond_t m_pthreadCond; /** 线程同步的条件变量 */
protected:
static void* ThreadFunc(void * threadData); /** 新线程的线程回调函数 */
static int MoveToIdle(pthread_t tid); /** 线程执行结束后,把自己放入到空闲线程中 */
static int MoveToBusy(pthread_t tid); /** 移入到忙碌线程中去 */
int Create(); /** 创建线程池中的线程 */
public:
CThreadPool(int threadNum = 10);
int AddTask(CTask *task); /** 把任务添加到任务队列中 */
int StopAll(); /** 使线程池中的线程退出 */
int getTaskSize(); /** 获取当前任务队列中的任务数 */
};
#endif
Thread.cpp
/******************
** Fliename: Thread.cpp
** Dscribe: 线程池实现文件
** Date: 2018.7.18
** @author: xionglei
***/
#include "Thread.h"
#include
#include
#include
void CTask::SetConnFd(int data)
{
connfd = data;
}
int CTask::GetConnFd()
{
return connfd;
}
/**
* 初始化数据
*/
deque CThreadPool::m_deqTaskList; //任务列表
bool CThreadPool::shutdown = false;
pthread_mutex_t CThreadPool::m_pthreadMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t CThreadPool::m_pthreadCond = PTHREAD_COND_INITIALIZER;
/**
* 线程池管理类构造函数
*/
CThreadPool::CThreadPool(int threadNum)
{
this->m_iThreadNum = threadNum;
cout << "I will create " << threadNum << " threads" << endl;
Create(); //*创建对象时便创建线程。
}
/**
* 线程回调函数
*/
void* CThreadPool::ThreadFunc(void* threadData)
{
pthread_t tid = pthread_self();
while (1)
{
//* 线程开启时先上锁 */
pthread_mutex_lock(&m_pthreadMutex);
while (m_deqTaskList.size() == 0 && !shutdown)
{
//* 没有任务时,线程等待状态(条件变量)*/
pthread_cond_wait(&m_pthreadCond, &m_pthreadMutex);
}
if (shutdown)
{
pthread_mutex_unlock(&m_pthreadMutex);
printf("thread %lu will exit\n", pthread_self());
pthread_exit(NULL);
}
printf("tid %lu run\n", tid);
/**
* 取任务队列并处理之
*/
//deque::iterator iter = m_deqTaskList.front();
CTask* task = m_deqTaskList.front();
m_deqTaskList.pop_front();
//* 取完任务后释放锁*/
pthread_mutex_unlock(&m_pthreadMutex);
task->Run(); /** 执行任务 */
}
return (void*)0;
}
/**
* 往任务队列里边添加任务并发出线程同步信号
*/
int CThreadPool::AddTask(CTask *task)
{
pthread_mutex_lock(&m_pthreadMutex);
this->m_deqTaskList.push_back(task);
pthread_mutex_unlock(&m_pthreadMutex);
// * 添加任务 条件变量发信号,非阻塞 */
pthread_cond_signal(&m_pthreadCond);
return 0;
}
/**
* 创建线程
*/
int CThreadPool::Create()
{
pthread_id = (pthread_t*)malloc(sizeof(pthread_t) * m_iThreadNum);
for(int i = 0; i < m_iThreadNum; i++)
{
pthread_create(&pthread_id[i], NULL, ThreadFunc, NULL);
}
return 0;
}
/**
* 停止所有线程
*/
int CThreadPool::StopAll()
{
/** 避免重复调用 */
if (shutdown)
{
return -1;
}
printf("Now I will end all threads!!\n");
/** 唤醒所有等待线程,线程池要销毁了 */
shutdown = true;
pthread_cond_broadcast(&m_pthreadCond);
/** 阻塞等待线程退出,否则就成僵尸了 */
for (int i = 0; i < m_iThreadNum; i++)
{
pthread_join(pthread_id[i], NULL);
}
free(pthread_id);
pthread_id = NULL;
/** 销毁条件变量和互斥体 */
pthread_mutex_destroy(&m_pthreadMutex);
pthread_cond_destroy(&m_pthreadCond);
return 0;
}
/**
* 获取当前队列中任务数
*/
int CThreadPool::getTaskSize()
{
return m_deqTaskList.size();
}
好了,有了线程池这个利器,来实现多线程可以说就比较容易了,先来整理一下思路:首先服务端创建socket,绑定、监听后,每接收到一个客户端的连接请求,就创建一个任务,丢到任务队列里去,任务队列有数据后,唤醒等待的线程,执行任务。
服务端的代码:
/**********************
** FileName: Service.cpp
** Dscribe: 服务端程序
** Date:2018.7.19
** @author: xionglei
***/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Thread.h"
//#include "BookInfo.h"
//#include "StrInfo.h"
class CMyTask: public CTask
{
public:
CMyTask() = default;
int Run()
{
//printf("%s\n", (char*)m_ptrData);
//int x = rand()%4 + 1;
//sleep(x);
//return 0;
int connfd= GetConnFd();
while(1)
{
char recvbuf[1024];
char sendbuf[1024];
// Book book= new Book;
//BookInfo bf=book.find_book("茶花女");
//sprintf(sendbuf,"res: %d,%s",bf.book_no,bf.book_name);
memset(recvbuf,0x00,sizeof(recvbuf));
memset(sendbuf,0x00,sizeof(sendbuf));
//printf("function run test.\n");
int len = recv(connfd,recvbuf,sizeof(recvbuf),0);
if(len <= 0)
printf("no buf.\n");
printf("%s \n",recvbuf);
printf("Please input: ");
fflush(stdout);
fgets(sendbuf,1024,stdin);
if(strncmp(sendbuf,"end",3)==0)
{
close(connfd);
break;
}
send(connfd,sendbuf,sizeof(sendbuf),0);
}
close(connfd);
return 0;
}
};
int main(int argc, char* argv[])
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
inet_aton("127.0.0.1",&ser.sin_addr);
ser.sin_port=htons(6500);
int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(sockfd,5);
//创建线程池
CThreadPool Pool(5);
//for(int i=0;i<5;i++)
//{
// Task* ta=new Task;
//Pool.AddTask(ta);
//}
while(1)
{
socklen_t len=sizeof(cli);
int connectfd=accept(sockfd,(struct sockaddr*)&cli,&len);
if(connectfd<0)
{
printf("cli connect failed.");
// throw std::exception();
}
//收到客户端请求,即添加到任务队列去
else
{
CTask* ta=new CMyTask;
ta->SetConnFd(connectfd);
Pool.AddTask(ta);
}
}
close(sockfd);
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
// assert(sockfd!=-1);
struct sockaddr_in ser,cli;
memset(&ser,0x00,sizeof(ser));
ser.sin_family=AF_INET;
inet_aton("127.0.0.1",&ser.sin_addr);
ser.sin_port=htons(6500);
int res=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
//assert(res!=-1);
while(1)
{
printf("please input:");
fflush(stdout);
char buff[128]={0};
fgets(buff,128,stdin);
//std::cin>>buff;
if(strncmp(buff,"end",3)==0)
{
close(sockfd);
break;
}
send(sockfd,buff,strlen(buff)-1,0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("%s\n",buff);
}
close(sockfd);
return 0;
}
编写makefile 文件:
CC:= g++
TARGET:= test
INCLUDE:= -I./
LIBS:= -lpthread -lstdc++
# C++语言编译参数
CXXFLAGS:= -std=c++0x -g -Wall -D_REENTRANT
# C预处理参数
# CPPFLAGS:=
OBJECTS :=Service.o Thread.o
$(TARGET): $(OBJECTS)
$(CC) -o $(TARGET) $(OBJECTS) $(LIBS)
# $@表示所有目标集
%.o:%.cpp
$(CC) -c $(CXXFLAGS) $(INCLUDE) $< -o $@
.PHONY : clean
clean:
-rm -f $(OBJECTS) $(TARGET)
注意makefile 文件 编写中的tab 键, 以及编译链接的库文件。
在linux测试,效果良好,可以同时开多个客户端。与服务端保持通讯。