在单进程/单线程的版本中,一个用户连接上来了之后,与服务端进行通信,其他的客户端是无法与服务端通信的,StartServer() 里调用 accept() 获取新连接后,立刻进入 Service() 循环。Service() 是死循环,会一直阻塞在 read() ,等待当前客户端发数据。只要这个客户端不断开,函数就不会返回, accept() 永远无法再次执行,新连接只能排队,无法处理。
所以第一个想法使用多进程可不可以解决呢?
主线程原来接听到来的客户端,到有客户端到来时,创建一个子进程来执行对连接上来的客户端的操作。
子进程做什么?
-
fork 返回值 == 0,进入子进程
-
子进程不关心监听套接字的事情,close(listensock_),
-
和客户端通信(Service)
-
通信结束 close(sockfd)
-
exit() 退出子进程
父进程做什么?
-
fork 返回值 != 0,父进程
-
sockfd已经交给子进程去管理了,父进程不关系了,close(sockfd)
-
waitpid() 等待子进程结束
-
继续循环,accept 新连接
cpp
void StartServer()
{
lg(Info, "tcpserver is running...");
for (;;)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
if (sockfd < 0)
{
lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno));
continue;
}
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, client ip: %s, client port: %d",
sockfd, clientip, clientport);
//version 1 单进程/单线程版
// Service(sockfd, clientip, clientport);
// close(sockfd);
//version 2 多进程版
pid_t id = fork();
if(id == 0)
{
//child
close(listensock_);
Service(sockfd, clientip, clientport);
close(sockfd);
exit(0);
}
close(sockfd);
pid_t rid = waitpid(id, nullptr, 0);
if(rid == id)
std::cout << "father wait success" << std::endl;
}
}
这个想法看起来可以,但是是不行的,在父进程监听到一个连接后,交给了子进程,但是子进程对于分发的客户端的服务是长时间,也就是说子进程不会退出,我们的父进程会阻塞的在哪里等待着子进程,也就无法监听到新的连接了。
使用主孙进程来实现进程的并行
原本的多进程:父进程 fork创建子进程,子进程对连接的客户端进行通信,父进程 waitpid阻塞子进程。
问题:waitpid 会阻塞父进程,直到子进程退出,父进程才能继续 accept。
不能让父进程等子进程!要让父进程立刻继续监听,虽然父进程会阻塞等待子进程的退出,但是不会等待孙进程的退出。父进程(服务器主进程)accept 到新连接,父进程 fork 出子进程,子进程立刻再 fork 出孙子进程,由孙子进程来负责和客户端通信(执行 Service),子进程:直接 exit 退出,父进程:很快 wait 到子进程退出,不阻塞,继续 accept。
子进程退出后,孙子进程没有父进程了,变成孤儿进程。操作系统会自动把孤儿进程领养给 init/systemd。
代码:
cpp
void StartServer()
{
lg(Info,"TcpServer is running");
for(;;)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//阻塞等待
int sockfd=accept(listensock_,(struct sockaddr*)(&client),&len);
if(sockfd<0)
{
lg(Warning,"accept error,errno:%d,errstring:%s",errno,strerror(errno));
continue;
}
uint16_t clientport=ntohs(client.sin_port);
string clientip=inet_ntoa(client.sin_addr);
lg(Info,"get a new link...,clientip:%s,clientport:%d",clientip,clientport);
pid_t id=fork();
if(id==0)
{
//子进程
close(listensock_);
//子进程创建孙进程后直接退出
if(fork()>0) exit(0);
//孙进程
Service(sockfd,clientip,clientport);
close(sockfd);
}
close(sockfd);
pid_t rid=waitpid(id,nullptr,0);
}
}
代码测试:
运行结果:
即使在有多个客户端连接上来之后,服务端还是可以监听到,创建孙进程来完成进程间的并行。
多线程版
将需要传递给线程函数的变量使用一个类进行封装,之后只需要传类指针即可。类里面的参数包括服务器类的 this 指针,客户端 sockfd ,客户端 IP,客户端 port
cpp
class TcpServer;
struct ThreadData
{
public:
ThreadData(int fd,const string&ip,const uint16_t&p,TcpServer*t)
:sockfd(fd)
,clientip(ip)
,clientport(p)
,tsvr(t)
{}
public:
int sockfd;
string clientip;
uint16_t clientport;
TcpServer*tsvr;
};
线程传递函数
线程函数 Routine 负责调用 Service 和客户端通信,使用传递进来的ThreadData类中的TcpServer指针来进行调用Service与客户端通信。
注意:线程函数必须是 static
类成员函数默认带 this ,参数格式和 pthread_create 要求不匹配,加 static 后,函数没有 this ,类型匹配,可直接传给线程库。
cpp
static void*Routine(void*args)
{
pthread_detach(pthread_self());
ThreadData*td=static_cast<ThreadData*>(args);
td->tsvr->Service(td->sockfd,td->clientip,td->clientport);
close(td->sockfd);
delete td;
}
创建线程执行线程函数
为监听到的连接创建线程,执行线程函数。
cpp
void StartServer()
{
lg(Info,"TcpServer is running");
for(;;)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//阻塞等待
int sockfd=accept(listensock_,(struct sockaddr*)(&client),&len);
if(sockfd<0)
{
lg(Warning,"accept error,errno:%d,errstring:%s",errno,strerror(errno));
continue;
}
uint16_t clientport=ntohs(client.sin_port);
string clientip=inet_ntoa(client.sin_addr);
lg(Info,"get a new link...,clientip:%s,clientport:%d",clientip,clientport);
// pid_t id=fork();
// if(id==0)
// {
// //子进程
// close(listensock_);
// //子进程创建孙进程后直接退出
// if(fork()>0) exit(0);
// //孙进程
// Service(sockfd,clientip,clientport);
// close(sockfd);
// }
// close(sockfd);
// pid_t rid=waitpid(id,nullptr,0);
ThreadData*td=new ThreadData(sockfd,clientip,clientport,this);
pthread_t id;
pthread_create(&id,nullptr,Routine,td);
}
}
代码测试:

源代码:
client.cpp
cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(const string& str)
{
cout << "\n\tUsage: " << str << " serverip serverport" << endl;
}
// 客户端启动格式:./tcpclient serverip serverport
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 0;
}
string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
cerr << "socket create err" << endl;
return 1;
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(serverport);
server.sin_addr.s_addr=inet_addr(serverip.c_str());
socklen_t len=sizeof(server);
//客户端发起connect请求时,操作系统会自动进行端口号的随机bind绑定
int n=connect(sockfd,(struct sockaddr*)(&server),len);
if(n<0)
{
perror("connect error");
cerr<<"connect error..."<<endl;
return 2;
}
string message;
char inbuffer[4096];
while(true)
{
cout << "Please Enter# ";
getline(cin, message);
int n = write(sockfd, message.c_str(), message.size());
if(n < 0)
{
cerr << "write err" << endl;
break;
}
n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
if(n > 0)
{
inbuffer[n] = 0;
cout << inbuffer << endl;
}
else
{
break;
}
}
close(sockfd);
return 0;
}
main.cpp
cpp
#include <iostream>
#include <memory>
#include "server.hpp"
void Usage(const std::string str)
{
std::cout << "\n\tUsage: " << str << " port[1024+]\n" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(UsageError);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> server(new TcpServer(port));
server->init();
server->StartServer();
return 0;
}
makefile
cpp
.PHONY:all
all:server client
server:main.cpp
g++ -o $@ $^ -std=c++11 -lpthread
client:client.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f server client
server.hpp
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "log.hpp"
extern Log lg;
using namespace std;
const int backlog=10;
const string ip="0.0.0.0";
const uint16_t port=8080;
enum
{
UsageError=1,
SocketError,
BindError,
ListenError
};
class TcpServer;
struct ThreadData
{
public:
ThreadData(int fd,const string&ip,const uint16_t&p,TcpServer*t)
:sockfd(fd)
,clientip(ip)
,clientport(p)
,tsvr(t)
{}
public:
int sockfd;
string clientip;
uint16_t clientport;
TcpServer*tsvr;
};
class TcpServer
{
public:
TcpServer(uint16_t defaultport=port)
:ip_(ip)
,port_(port)
{}
void init()
{
listensock_=socket(AF_INET,SOCK_STREAM,0);
if(listensock_<0)
{
lg(Fatal,"create socket error,errno:%d,errstring:%s",errno,strerror(errno));
exit(SocketError);
}
lg(Info,"socked create success,listensock_:%d",listensock_);
//防止偶发性服务器无法重启
int opt=1;
setsockopt(listensock_,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(port_);
server.sin_addr.s_addr=inet_addr(ip_.c_str());
socklen_t len=sizeof(server);
if(bind(listensock_,(struct sockaddr*)(&server),len)<0)
{
lg(Fatal,"bind socket error,errno:%d,errstring:%s",errno,strerror(errno));
exit(BindError);
}
lg(Info,"socked bind success");
if(listen(listensock_,backlog)<0)
{
lg(Fatal,"listen error,errno:%d,errstring:%s",errno,strerror(errno));
exit(ListenError);
}
lg(Info,"listen success");
}
void Service(int sockfd,string clientip,uint16_t clientport)
{
while(true)
{
char buffer[4096];
ssize_t n=read(sockfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
cout<<"client say#"<<buffer<<endl;
string server_echo="server say#";
server_echo+=buffer;
write(sockfd,server_echo.c_str(),server_echo.size());
}
else if(n==0)
{
//客户端断开连接
lg(Info,"%s:%d quit ,server close sockfd:%d",clientip,clientport,sockfd);
break;
}
else
{
//异常情况
lg(Info,"read error,%s:%d quit,server close sockfd:%d",clientip,clientport,sockfd);
break;
}
}
}
static void*Routine(void*args)
{
pthread_detach(pthread_self());
ThreadData*td=static_cast<ThreadData*>(args);
td->tsvr->Service(td->sockfd,td->clientip,td->clientport);
close(td->sockfd);
delete td;
}
void StartServer()
{
lg(Info,"TcpServer is running");
for(;;)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//阻塞等待
int sockfd=accept(listensock_,(struct sockaddr*)(&client),&len);
if(sockfd<0)
{
lg(Warning,"accept error,errno:%d,errstring:%s",errno,strerror(errno));
continue;
}
uint16_t clientport=ntohs(client.sin_port);
string clientip=inet_ntoa(client.sin_addr);
lg(Info,"get a new link...,clientip:%s,clientport:%d",clientip,clientport);
// pid_t id=fork();
// if(id==0)
// {
// //子进程
// close(listensock_);
// //子进程创建孙进程后直接退出
// if(fork()>0) exit(0);
// //孙进程
// Service(sockfd,clientip,clientport);
// close(sockfd);
// }
// close(sockfd);
// pid_t rid=waitpid(id,nullptr,0);
ThreadData*td=new ThreadData(sockfd,clientip,clientport,this);
pthread_t id;
pthread_create(&id,nullptr,Routine,td);
}
}
private:
int listensock_;
string ip_;
uint16_t port_;
};