赞
踩
需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。
目录
- #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;//回调函数
- };
- }
- #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;
- }
- #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;//读线程
- };
- }
- #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;
- }
- #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类
- };
可先让上方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;
- }
单线程会一直在ServerIO读取写入数据,为一个客户端服务,如果此时连接的客户端不止一个,其他客户端发送的信息将不会被显示。需要使用多线程或多进程解决。
- #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地址
- };
- }
- #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;
- }
- #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
- };
- #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;
- }
- #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;
- }
更换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)
更换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在使用完毕后需要进行关闭。
其他文件和单进程版一样。
- #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地址
- };
- }
- #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;
- #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;//回调函数
- };
其他文件和单进程版一样。
- #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);
- }
- }
- #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;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。