【网络编程】实现UDP/TCP客户端、服务器


目录

一、UDP

1、Linux客户端、服务器

1.1udpServer.hpp

1.2udpServer.cc

1.3udpClient.hpp

1.4udpClient.cc

1.5onlineUser.hpp

2、Windows客户端

二、TCP

1、单进程版的TCP客户端、服务器

1.1tcpServer.hpp

1.2tcpServer.cc

1.3tcpClient.hpp

1.4tcpClient.cc

1.5log.hpp

2、多进程版的TCP客户端、服务器

3、多线程版的TCP客户端、服务器

4、线程池版的TCP客户端、服务器

4.1tcpServer.hpp

4.2ThreadPool.hpp 

4.3Task.hpp

5、守护进程+多线程版的TCP客户端、服务器

5.1daemon.hpp

5.2tcpServer.cc


UDP/TCP客户端、服务器代码可参考本人gitee

UDP/TCP套接字的创建流程可参考此处

一、UDP

1、Linux客户端、服务器

1.1udpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
namespace Server
{
    const static string defaultIp="0.0.0.0";//缺省的IP
    const static int gnum=1024;
    typedef function<void(int,string,uint16_t,string)> func_t;
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        OPEN_ERR,
    };
    class udpServer
    {
    public:
        udpServer(const func_t& callback,const uint16_t& port,const string& ip=defaultIp)
        :_callback(callback)//udpServer.cc传入的对客户端数据处理的函数
        ,_port(port)
        ,_ip(ip)
        ,_sockfd(-1)
        {}
        void initServer()
        {
             //1、创建socket
            _sockfd=socket(AF_INET,SOCK_DGRAM,0);//网络通信+数据报
            if(-1==_sockfd)
            {
                cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"socket success"<<":"<<_sockfd<<endl;
            //2、绑定IP和端口号
            struct sockaddr_in local;
            bzero(&local,sizeof(local));//将一段内存初始化为全0
            local.sin_family=AF_INET;//协议族设置为网络通信
            local.sin_port=htons(_port);//设置端口号,需要转为大端,主机转网络
            local.sin_addr.s_addr=inet_addr(_ip.c_str());//将IP字符串转uint32_t的同时转为网络字节序
            //local.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY就是0,表明任何IP都可以访问这个服务器的_port端口
            int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(-1==n)
            {
                cout<<"bind error"<<errno<<":"<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
        }
        void start()
        {
            char buffer[gnum];
            while(1)
            {
                //循环读取数据
                struct sockaddr_in local;//输出型参数
                socklen_t len=sizeof(local);//必填
                size_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&local,&len);//阻塞式读取
                //这里需要关心1、数据是什么2、数据是谁发的
                if(s>0)
                {
                    buffer[s]=0;//加上'\0'
                    //1、这是从网络读出来的IP,需要由网络字节序转主机字节序2、整数转点分十进制IP,用inet_ntoa进行转换
                    string clientIp=inet_ntoa(local.sin_addr);//将32位IPv4地址(in_addr结构体)转换成点分十进制字符串形式的IP地址
                    uint16_t clientPort=ntohs(local.sin_port);//一样需要转换字节序
                    string message=buffer;
                    cout<<clientIp<<"["<<clientPort<<"]#"<<message<<endl;
                    //对数据进行处理
                    _callback(_sockfd,clientIp,clientPort,message);
                }
            }
        }
        ~udpServer()
        {

        }
    private:
        uint16_t _port;//端口号
        string _ip;//IP地址(服务器不建议固定的绑定一个IP地址,因为服务器需要接收所有的IP)
        int _sockfd;//套接字文件描述符
        func_t _callback;//回调函数
    };
}

1.2udpServer.cc

#include <memory>
#include <unordered_map>
#include <fstream>
#include <signal.h>
using namespace std;
#include "udpServer.hpp"
#include "onlineUser.hpp"
using namespace Server;
// const std::string dictTxt="./dict.txt";
// unordered_map<string,string> dict;//字典
// std::string key,value;

static void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<"local_ip local_port\n\n";
}

// static bool cutString(const string& target,string* key,string* value,const string& sep)//字符串截取
// {
//     //string sep=":";
//     auto pos=target.find(sep,0);
//     if(pos==string::npos)
//     {
//         return false;
//     }
//     *key=target.substr(0,pos);
//     *value=target.substr(pos+sep.size());
//     return true;
// }
// static void initDict()//文件操作
// {
//     ifstream in(dictTxt,std::ios_base::binary);
//     if(!in.is_open())//如果文件打开失败
//     {
//         cerr<<"open file"<<dictTxt<<"error"<<endl;
//         exit(OPEN_ERR);
//     }
//     string line;
//     while(getline(in,line))
//     {
//         if(cutString(line,&key,&value,":"))//如果截取成功
//         {
//             dict.insert(make_pair(key,value));//dict.insert(key,value);
//         }
//         else //截取失败
//         {
//             //...
//         }
//     }
//     in.close();
//     cout<<"load dict success"<<endl;
// }
// static void debugPrint()//测试打印函数
// {
//     for(auto& dt:dict)
//     {
//         cout<<dt.first<<"/"<<dt.second<<endl;
//     }
// }
// //客户端单词翻译代码
// void handMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
// {
//     //对客户端的信息进行特定的业务处理,实现了server通信和业务的解耦
//     string response_message;//将查找的字符串保存至此处
//     unordered_map<string,string>::iterator iter=dict.find(message);
//     if(iter==dict.end())
//     {
//         response_message="unknow";
//     }
//     else
//         response_message=iter->second;
//     //服务端向客户端回发数据
//     struct sockaddr_in client;
//     bzero(&client,sizeof(client));
//     client.sin_family=AF_INET;
//     client.sin_addr.s_addr=inet_addr(clientIp.c_str());
//     client.sin_port=htons(clientPort);
//     sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
// }
// //解析客户端上传的命令
// void execCommand(int sockfd,string clientIp,uint16_t clientPort,string cmd)
// {
//     //对客户端的信息进行特定的业务处理,实现了server通信和业务的解耦
//     auto end=cmd.find("rm");
//     if(end!=string::npos)
//     {
//         cerr<<clientIp<<":"<<clientPort<<"非法操作"<<cmd<<endl;
//         return;
//     }
//     string response_message;//将客户端上传的指令保存至此处
//     FILE* fp=popen(cmd.c_str(),"r");
//     if(fp==nullptr)
//     {
//         response_message=cmd+" exec failed";
//     }
//     char line[1024];
//     while(fgets(line,sizeof(line),fp))
//     {
//         response_message+=line;//读出客户端传入的指令
//     }
//     pclose(fp);
//     //服务端向客户端回发数据
//     struct sockaddr_in client;
//     bzero(&client,sizeof(client));
//     client.sin_family=AF_INET;
//     client.sin_addr.s_addr=inet_addr(clientIp.c_str());
//     client.sin_port=htons(clientPort);
//     sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
// }
//聊天室
OnlineUser olUser;
void routeMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
{
    //上线就新增,下线就减掉
    if(message=="online")
    {
        olUser.addUser(clientIp,clientPort);
    }
    if(message=="offline")
    {
        olUser.delUser(clientIp,clientPort);
    }
    if(olUser.isOnline(clientIp,clientPort))
    {
        //广播消息
        olUser.broadcastMessage(sockfd,clientIp,clientPort,message);
    }
    else
    {
        //服务端向客户端回发数据
        string response_message="请先运行online";
        struct sockaddr_in client;
        bzero(&client,sizeof(client));
        client.sin_family=AF_INET;
        client.sin_addr.s_addr=inet_addr(clientIp.c_str());
        client.sin_port=htons(clientPort);
        sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
    }
}
// void reload(int signo)//热加载回调函数
// {
//     (void)signo;
//     initDict();
// }
int main(int argc,char* argv[])//./udpServer port
{
    if(argc!=2)//判断外部传入的参数是否为3
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=atoi(argv[1]);//需要转uint16_t整型

    // signal(2,reload);//发送信号,实现文本的热加载
    // initDict();
    //std::unique_ptr<udpServer> usvr(new udpServer(handMessage,port));//在线翻译
    //std::unique_ptr<udpServer> usvr(new udpServer(execCommand,port));//指令解析
    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage,port));//聊天室
    usvr->initServer();
    usvr->start();
    return 0;
}

1.3udpClient.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
namespace Client
{
    using namespace std;
    class udpClient
    {
    public:
        udpClient(const string& serverIp,const uint16_t& serverPort)
        :_sockfd(-1)
        ,_serverPort(serverPort)
        ,_serverIp(serverIp)
        {}
        void initClient()
        {
            //创建socket
            _sockfd=socket(AF_INET,SOCK_DGRAM,0);
            if(-1==_sockfd)
            {
                cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;
                exit(2);
            }
            cout<<"socket syuccess"<<":"<<_sockfd<<endl;
        }
        static void* readMessage(void* args)//类内创建线程,有个this指针干扰
        {
            int sockfd=*static_cast<int*>(args);
            pthread_detach(pthread_self());
            while(1)
            {
                //接收服务端发送的数据
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t len=sizeof(temp);
                size_t s=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
                if(s>0)
                {
                    buffer[s]=0;//字符串以'\0'结尾
                }
                cout<<buffer<<endl;
            }
            return nullptr;
        }
        void run()
        {
            pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);
            struct sockaddr_in server;
            memset(&server,sizeof(server),0);//初始化为全0
            server.sin_family=AF_INET;
            server.sin_addr.s_addr=inet_addr(_serverIp.c_str());
            server.sin_port=htons(_serverPort);//主机转网络
            string message;
            char cmdline[1024];
            while(1)
            {
                //cerr<<"Please Enter#";
                // cin>>message;
                fprintf(stderr,"Enter#");
                fflush(stderr);
                fgets(cmdline,sizeof(cmdline),stdin);
                cmdline[strlen(cmdline)-1]=0;
                message=cmdline;
                //发送数据,sendto的时候,操作系统会帮我们自动绑定客户端端口+IP地址
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
                
            }
        }
        ~udpClient()
        {}
    private:
        int _sockfd;
        uint16_t _serverPort;
        string _serverIp;
        pthread_t _reader;//读线程
    };
}

1.4udpClient.cc

#include <memory>
#include "udpClient.hpp"
using namespace Client;
static void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<"server_ip server_port\n\n";
}
int main(int argc,char* argv[])//./udpClient server_ip server_port
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverIp=argv[1];
    uint16_t serverPort=atoi(argv[2]);
    unique_ptr<udpClient> ucli(new udpClient(serverIp,serverPort));
    ucli->initClient();
    ucli->run();
    return 0;
}

1.5onlineUser.hpp

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
using namespace std;
class User
{
public:
    User(const string& ip,const uint16_t& port)
        :_ip(ip)
        ,_port(port)
    {

    }
    ~User()
    {}
    string ip()
    {
        return _ip;
    }
    uint16_t port()
    {
        return _port;
    }
private:
    string _ip;//用户IP
    uint16_t _port;//用户端口号

};
class OnlineUser
{
public:
    OnlineUser()
    {}
    ~OnlineUser()
    {}
    void addUser(const string& ip,const uint16_t& port)//新增用户
    {
        string id=ip+"-"+to_string(port);
        users.insert(make_pair(id,User(ip,port)));
    }
    void delUser(const string& ip,const uint16_t& port)//删除用户
    {
        string id=ip+"-"+to_string(port);
        users.erase(id);
    }
    bool isOnline(const string& ip,const uint16_t& port)//是否在线
    {
        string id=ip+"-"+to_string(port);
        return users.find(id)==users.end()?false:true;
    }
    void broadcastMessage(int sockfd,const string& ip,const uint16_t& port,const string& message)//给所有的user广播消息
    {
        for(auto& user:users)
        {
            //服务端向客户端回发数据
            struct sockaddr_in client;
            bzero(&client,sizeof(client));
            client.sin_family=AF_INET;
            client.sin_addr.s_addr=inet_addr(user.second.ip().c_str());
            client.sin_port=htons(user.second.port());
            string s=ip+"_"+to_string(port)+"# ";//id+"#"
            s+=message;
            sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));
        }
    }
private:
    unordered_map<string,User> users;//string:id=ip+"-"+to_string(port);User:User类
};

2、Windows客户端

        可先让上方Linux服务器先运行起来,再让Windows客户端连接上该服务端,实现网络通信。

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <cstring>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
uint16_t serverPort = 8080;
string serverIp = "43.XXX.105.XX";//你的云服务器IP
#define NUM 1024
int main()
{
	WSAData wsd;
	//启动Winsock
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		cout << "WSAStartUp Error = " << WSAGetLastError() << endl;
		return -1;
	}
	else
	{
		cout << "WSAStartup Success" << endl;
	}
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字
	if (sock == SOCKET_ERROR)
	{
		cout<<"socket Error = "<< WSAGetLastError() << endl;
		return -2;
	}
	else
	{
		cout << "socket Success" << endl;
	}
	struct sockaddr_in server;
	memset(&server, 0, sizeof(server));
	server.sin_addr.s_addr = inet_addr(serverIp.c_str());
	server.sin_family = AF_INET;
	server.sin_port = htons(serverPort);
	string line;
	char buffer[NUM];
	while (1)
	{
		//发送数据
		cout << "Please Enter#";
		getline(cin, line);
		int n = sendto(sock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));
		if (n < 0)
		{
			cerr << "sendto Error" << endl;
			break;
		}
		cout << "发送成功" << endl;
		//接收数据
		buffer[0] = 0;//C式清空数组
		struct sockaddr_in peer;
		int len = (int)sizeof(peer);
		n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
		if (n > 0)
		{
			buffer[n] = 0;
			cout << "server 返回的消息是" << buffer << endl;
		}
		else break;
	}
	closesocket(sock);//关闭套接字
	WSACleanup();
	return 0;
}

二、TCP

1、单进程版的TCP客户端、服务器

        单线程会一直在ServerIO读取写入数据,为一个客户端服务,如果此时连接的客户端不止一个,其他客户端发送的信息将不会被显示。需要使用多线程或多进程解决。

1.1tcpServer.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include "log.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                //5、使用accept的返回值sock进行通信,均为文件操作
                ServerIO(sock);
                close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏
            }
        }
        void ServerIO(int sock)
        {
            char buffer[1024];
            while(1)
            {
                //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message:"<<buffer<<std::endl;
                    std::string outBuffer=buffer;
                    outBuffer+="[server echo]";
                    //服务器将数据处理后发送回客户端
                    write(sock,outBuffer.c_str(),outBuffer.size());
                }
                else if(0==n)//服务器read返回值为0,说明客户端关闭了
                {
                    LogMessage(NORMAL,"client quit,server quit");
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

1.2tcpServer.cc

#include "tcpServer.hpp"
#include "memory"
using namespace Server;
static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"serverPort\n\n";
}
//./tcpServer local_port
int main(int argc,char* argv[])
{
    if(argc!=2)//判断外部传入的参数是否为2
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->InitServer();
    tsvr->Start();
    return 0;
}

1.3tcpClient.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#define NUM 1024
class TcpClient
{
public:
    TcpClient(const std::string& serverIp,const uint16_t& serverPort)
        :_serverIp(serverIp)
        ,_serverPort(serverPort)
        ,_sock(-1)
    {

    }
    void InitClient()
    {
        //1、创建套接字
        _sock=socket(AF_INET,SOCK_STREAM,0);
        if(_sock<0)
        {   
            std::cerr<<"cerete socket err"<<std::endl;
            exit(2);
        }
        //2、客户端需要bind,但是客户端的绑定不需要我们自己写,操作系统会去绑定;(无需程序员bind)
    }
    void Start()
    {
        //3、客户端发起连接
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_addr.s_addr=inet_addr(_serverIp.c_str());
        server.sin_family=AF_INET;
        server.sin_port=htons(_serverPort);
        if(connect(_sock,(struct sockaddr*)&server,sizeof(server))<0)//连接失败
        {
            std::cerr<<"sock connect error"<<std::endl;
        }
        else//连接成功
        {
            //4、客户端发送/接收消息,文件操作
            std::string msg;
            while(1)
            {
                std::cout<<"Enter:";
                std::getline(std::cin,msg);
                write(_sock,msg.c_str(),msg.size());
                char buffer[NUM];
                int n=read(_sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"Server 回显消息:"<<buffer<<std::endl;
                }
                else
                    break;
            }
        }
    }
    ~TcpClient()
    {
        if(_sock>=0)
        {
            close(_sock);
        }
    }
private:
    int _sock;//客户端套接字
    uint16_t _serverPort;//服务器端口号
    std::string _serverIp;//服务器ip
};

1.4tcpClient.cc

#include "tcpClient.hpp"
#include <memory>
static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"local_ip local_port\n\n";
}
//./tcpClient serverIp serverPort
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverIp=argv[1];
    uint16_t serverPort=std::stoi(argv[2]);
    std::unique_ptr<TcpClient> tcli(new TcpClient(serverIp,serverPort));
    tcli->InitClient();
    tcli->Start();
    return 0;
}

1.5log.hpp

#pragma once
#include <iostream>
#include <string>
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
//日志功能
void LogMessage(int level,const std::string& message)
{
    //[日志等级][时间戳/时间][pid][message]
    std::cout<<message<<std::endl;
}

2、多进程版的TCP客户端、服务器

        更换tcpServer.hpp即可,其他文件和单进程版一样。

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include "log.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                // //5、使用accept的返回值sock进行通信,均为文件操作
                pid_t id=fork();
                if(id==0)//子进程
                {
                    close(_listenSocket);//子进程的
                    if(fork()>0) exit(0);//让子进程退出,孙子进程成为孤儿进程,交给1号进程托管回收其退出资源
                    ServerIO(sock);
                    close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)
                    exit(0);
                }
                close(sock);//这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用技术-1,直至孙子进程退出,该fd才会减为0,关闭
                //父进程
                //waitpid()
                pid_t ret=waitpid(id,nullptr,0);//这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(没有新的客户端来连接的话)
                if(ret>0)
                {
                    std::cout<<"waitsucceess"<<ret<<std::endl;
                }
            }
        }       
        void ServerIO(int sock)
        {
            char buffer[1024];
            while(1)
            {
                //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message:"<<buffer<<std::endl;
                    std::string outBuffer=buffer;
                    outBuffer+="[server echo]";
                    //服务器将数据处理后发送回客户端
                    write(sock,outBuffer.c_str(),outBuffer.size());
                }
                else if(0==n)//服务器read返回值为0,说明客户端关闭了
                {
                    LogMessage(NORMAL,"client quit,server quit");
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

区别在于这张图里的代码:

1、close(_listenSocket):关闭子进程的监听fd(虽然手动关不关都行,因为下一句代码就让子进程退出了,最好还是手动关一下)

2、if(fork()>0) exit(0):让子进程创建孙子进程,子进程退出。提前干掉子进程,这样父进程在外部就可以不用阻塞式等待子进程退出了。同时孙子进程成为孤儿进程,会被1号进程领养,程序员无需关心孤儿进程的退出善后工作。

3、孙子进程close(sock):必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)

4、父进程close(sock):这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用计数-1,直至孙子进程退出,该fd才会减为0,关闭,所以父进程提前关闭该fd不会影响孙子进程。但是这里父进程如果不关,客户端连一个fd+1,存在文件描述符泄露。

5、pid_t ret=waitpid(id,nullptr,0):这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(如果没有新的客户端来连接的话,将一直卡在accept)

3、多线程版的TCP客户端、服务器

更换tcpServer.hpp即可,其他文件和单进程版一样。

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <pthread.h>
#include "log.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer;
    struct ThreadData//用于线程函数传参
    {
        ThreadData(TcpServer* self,const int& sock)
            :_self(self)
            ,_sock(sock)
        {}
        TcpServer* _self;//this
        int _sock;//通信fd
    };
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                //5、使用accept的返回值sock进行通信,均为文件操作
                //多线程版
                pthread_t tid;
                ThreadData* td=new ThreadData(this,sock);
                pthread_create(&tid,nullptr,threadRoutine,(void*)td);
            }
        }   
        static void* threadRoutine(void* args)
        {
            pthread_detach(pthread_self());//线程分离
            ThreadData* td=static_cast<ThreadData*>(args);
            td->_self->ServerIO(td->_sock);//线程调用服务函数
            close(td->_sock);
            delete td;
            return nullptr;
        }
        void ServerIO(int sock)
        {
            char buffer[1024];
            while(1)
            {
                //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message:"<<buffer<<std::endl;
                    std::string outBuffer=buffer;
                    outBuffer+="[server echo]";
                    //服务器将数据处理后发送回客户端
                    write(sock,outBuffer.c_str(),outBuffer.size());
                }
                else if(0==n)//服务器read返回值为0,说明客户端关闭了
                {
                    LogMessage(NORMAL,"client quit,server quit");
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

        在一个进程中的所有线程都可以访问到文件描述符表,属于共享资源,一个线程所对应的fd在使用完毕后需要进行关闭。

4、线程池版的TCP客户端、服务器

        其他文件和单进程版一样。

4.1tcpServer.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <pthread.h>
#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer;
    struct ThreadData//用于线程函数传参
    {
        ThreadData(TcpServer* self,const int& sock)
            :_self(self)
            ,_sock(sock)
        {}
        TcpServer* _self;//this
        int _sock;//通信fd
    };
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            //4、线程池初始化
            ThreadPool<Task>::getInstance()->run();//线程启动
            while(1)
            {
                //5、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                ThreadPool<Task>::getInstance()->push(Task(sock,ServerIO));
            }
        }   
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

4.2ThreadPool.hpp 

#pragma once
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace ThreadNs;
const int gnum =5;

template <class T>//声明
class ThreadPool;

template <class T>
struct ThreadData
{
    ThreadData(ThreadPool<T>* tp,const std::string& s)
    :_threadPool(tp)
    ,_name(s)
    {}
    ThreadPool<T>* _threadPool;
    std::string _name;
};
template <class T>
class ThreadPool
{
private:
    //因为普通成员函数第一个参数是this指针,和回调方法不匹配,故改成static类型
    static void* handlerTask(void* args)//args是ThreadData对象指针
    {
        ThreadData<T>* td=static_cast<ThreadData<T>*>(args);
        while(1)
        {
            T t;
            {   //RAII,出了作用域LockGuard会销毁,将析构锁
                LockGuard lockGuard(td->_threadPool->mutex());//加锁
                while(td->_threadPool->IsQueueEmpty())//如果队列为空,则等待
                {
                    td->_threadPool->ThreadWait();
                }
                //线程能走到这里,说明队列一定有任务给线程
                t=td->_threadPool->Pop();//从队列中取出任务
            }
            t();//Task的operator()
        }
        delete td;//析构ThreadData对象
        return nullptr;
    }
    ThreadPool(const int& num=gnum)
    :_num(num)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
        //创建线程
        for(int i=0;i<_num;++i)
        {
            _threads.push_back(new Thread());
        }
    }
    ThreadPool(const ThreadPool<T>&)=delete;//禁用拷贝构造
    ThreadPool<T>& operator=(const ThreadPool<T>&)=delete;//禁用赋值运算符重载

public://解决静态handlerTask是静态函数的问题,这几个都是偷家函数
    void LockQueue()   {pthread_mutex_lock(&_mutex);}
    void UnLockQueue() {pthread_mutex_unlock(&_mutex);}
    bool IsQueueEmpty(){return _taskQueue.empty();}
    void ThreadWait()  {pthread_cond_wait(&_cond,&_mutex);}
    T Pop()         
    {
        T t=_taskQueue.front();
        _taskQueue.pop();
        return t;
    } 
    pthread_mutex_t* mutex()
    {
        return &_mutex;
    }
public: 
    void run()//线程启动
    {
        for(const auto& t:_threads)
        {
            ThreadData<T>* td=new ThreadData<T>(this,t->threadName());
            t->start(handlerTask,(void*)td);
            std::cout<<t->threadName()<<"start..."<<std::endl;
        }
    }
    void push(const T& in)
    {
        //RAII,出了作用域,锁将会被释放
        LockGuard lockGuard(&_mutex);
        _taskQueue.push(in);
        pthread_cond_signal(&_cond);
        std::cout<<"任务发送成功"<<std::endl;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for(const auto& t:_threads)
        {
            delete t;
        }
    }
    static ThreadPool<T>* getInstance()//这里的static的作用是让这个函数只有一份,获取单例对象。tp是临界资源,需要加锁
    {
        if(nullptr==tp)//因为锁只创建一次,防止线程进来被锁阻塞
        {
            //只进来一次就够了
            _singletonLock.lock();
            if(nullptr==tp)//说明对象还没有被创建
            {
                tp=new ThreadPool<T>(); 
            }
            _singletonLock.unlock();
        }
        return tp;
    }
private:
    int _num;//线程个数
    std::vector<Thread*> _threads;//使用vector存放线程
    std::queue<T> _taskQueue;//任务队列,往里面放任务,它是共享资源,需要加锁保护
    pthread_mutex_t _mutex;//互斥锁
    pthread_cond_t _cond;//条件变量

    static ThreadPool<T>* tp;//单例模式静态的对象指针
    static std::mutex _singletonLock;//获取单例对象使用的锁

};
template <class T>
ThreadPool<T>* ThreadPool<T>::tp=nullptr;

template <class T>
std::mutex ThreadPool<T>::_singletonLock;

4.3Task.hpp

#pragma once
#include <iostream>
#include <functional>
#include <string>
void ServerIO(int sock)
{
    char buffer[1024];
    while(1)//适应快速响应的任务,这个任务while其实不太合适
    {
        //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
        ssize_t n=read(sock,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<"recv message:"<<buffer<<std::endl;
            std::string outBuffer=buffer;
            outBuffer+="[server echo]";
            //服务器将数据处理后发送回客户端
            write(sock,outBuffer.c_str(),outBuffer.size());
        }
        else if(0==n)//服务器read返回值为0,说明客户端关闭了
        {
            close(sock);
            LogMessage(NORMAL,"client quit,server quit");
            break;
        }
    }
}
class Task
{
    //using func_t=std::function<int(int,int,char)>;
    typedef std::function<void(int)> func_t;//函数对象
public:
    Task()
    {}
    Task(int sock,func_t func)
    :_sock(sock)
    ,_callBack(func)
    {}
    void operator()()//消费者调用
    {
        _callBack(_sock);
    }
private:
    int _sock;
    func_t _callBack;//回调函数
};

5、守护进程+多线程版的TCP客户端、服务器

        其他文件和单进程版一样。

5.1daemon.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV "/dev/null"//数据黑洞,向它写入的数据会被吃掉,读取数据什么都读不到(不会使进程退出)
void DaemonSele(const char* currrPath=nullptr)
{
    //1、让调用进程屏蔽异常的信号
    //SIGPIPE信号会在进程向一个已经关闭的socket连接写数据时产生,如果不处理这个信号,进程会被强制退出。通过忽略SIGPIPE信号,可以避免进程因为这个信号而退出。
    signal(SIGPIPE,SIG_IGN);
    //2、让自己不是组长,调用setsid
    if(fork()>0) exit(0);//守护进程也称精灵进程,本质就是一个孤儿进程
    pid_t n=setsid();
    assert(n!=-1);//失败返回-1
    //3、守护进程脱离终端,所以要关闭或重定向进程默认打开的文件及文件描述符
    int fd=open(DEV,O_RDWR);//以读写的方式打开文件黑洞
    if(fd>=0)//创建成功:重定向
    {
        dup2(fd,0);//将fd覆盖标准输入
        dup2(fd,1);
        dup2(fd,2);
        close(fd);
    }
    else//创建失败:手动关闭文件描述符
    {
        close(0);
        close(1);
        close(2);
    }
    //4、进程执行路径更改(可改可不改)
    if(currrPath)
    {
        chdir(currrPath);
    }
}

5.2tcpServer.cc

#include "tcpServer.hpp"
#include "memory"
#include "daemon.hpp"
using namespace Server;
static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"serverPort\n\n";
}
//./tcpServer local_port
int main(int argc,char* argv[])
{
    if(argc!=2)//判断外部传入的参数是否为2
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->InitServer();
    DaemonSele();//守护进程化,让这个独立的孤儿进程去启动服务器
    tsvr->Start();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/gfdxx/article/details/130776548