该代码利用socket套接字建立Tcp连接,包含服务器和客户端。当服务器和客户端启动时需要把端口号或ip地址以命令行参数的形式传入。服务器启动如果接受到客户端发来的请求连接,accept函数会返回一个打开的socket文件描述符,区别于监听连接的listensock,它用来为客户端提供服务的。因为有线程池的存在,可以立即使用已经创建好的线程来为客户端提供服务。线程池中存在一个数据结构专门用来存放客户端IP与端口信息,如果没有新的客户端连接服务器,那么该数据结构内容为空,那么多余的线程就会因为没有用户连接而阻塞,直到新用户的到来。以上就是对代码的大概介绍了。
下面是关于代码的六点细节解释:
1.查看网络连接
netstat -nltp
2.可以用read函数读取TCP套接字的数据,而UDP不行。因为UDP是面向数据报,而TCP是面向数据流。所以代码使用了read与write函数进行收发消息,这也印证了网络并没有多么高大上,socket也是一个文件描述符。
3.客户端不需要手动bind,listen,accept,但是客户端需要自己connect服务器,connect会做两件事,bind和connect。
4.客户端的端口号要操作系统随机分配,防止客户端出现启动冲突。想想如果多个应用程序都想占用一个端口号进行网络通信的场景。
5.inet_aton函数
#include
#include
#include
int inet_aton(const char *cp, struct in_addr *inp);//cp必须是点分十进制字符串
把“192.168.1.1”这样的点分十进制字符串转换成struct sockaddr_in结构体里面的in_addr结构体(网络序列)。已知in_addr结构体里面只有一个成员,一个32位无符号整数。
成功返回0,失败返回非0;
使用方法:
6.使用signal(SIGCHLD, SIG_IGN)处理僵尸进程
通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN)。表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
代码:
tcp_server.cc
- #include "tcp_server.hpp"
- #include
- #include
- #include
- using namespace ns_server;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " port\n"<< endl;
- }
-
- static string echo(string message)
- {
- return message;
- }
-
-
- // .tcp_server serverport
- int main(int argc,char* argv[])
- {
- if(argc!=2)
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
- uint16_t serverport=atoi(argv[1]);
-
- unique_ptr
tsvr(new TcpServer(serverport,echo)) ; -
- tsvr->InitServer();
- tsvr->Start();
-
- return 0;
- }
tcp_server.hpp
- #pragma once
- #include
- using namespace std;
- #include
- #include "err.hpp"
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include"ThreadPool.hpp"
- #include "Task.hpp"
- #include "Thread.hpp"
- #include "lockGuard.hpp"
- #include "log.hpp"
-
- namespace ns_server
- {
-
- class TcpServer;
- class ThreadData
- {
- public:
- ThreadData(TcpServer* current,int sock,string client_ip,uint16_t client_port)
- :_current(current),_sock(sock),_client_ip(client_ip),_client_port(client_port)
- {
-
- }
-
- ~ThreadData(){}
-
- TcpServer* _current;
- int _sock;
- string _client_ip;
- uint16_t _client_port;
-
- };
-
-
- static const uint32_t backlog = 32;
- static const uint16_t defaultport = 8888;
- using func_t = function<string(const string&)>;
-
- class TcpServer
- {
-
- public:
- TcpServer(uint16_t port ,func_t func)
- : _port(port), _func(func), _quit(true)
- {}
-
- void InitServer()
- {
- // 1.创建监听套接字
- _listensock = socket(AF_INET, SOCK_STREAM, 0);
- if (_listensock < 0)
- {
- //cerr << "Socket create error!" << endl;
- logMessage(Fatal, "create socket error, code: %d, error string: %s",errno,strerror(errno));
- exit(SOCKET_ERR);
- }
- logMessage(Info, "create socket success, code: %d, error string: %s", errno, strerror(errno));
-
-
- // 2.绑定本地端口与IP
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_addr.s_addr = htons(INADDR_ANY); // 32位全零,不用转成网络序列,因为主机序列与网络序列一样;
- local.sin_port = htons(_port);
- int n1 = bind(_listensock, (const struct sockaddr *)&local, sizeof(local));
- if (n1 < 0)
- {
- //cerr << "Bind socket error!" << endl;
- logMessage(Fatal, "bind socket error, code: %d, error string: %s",errno,strerror(errno));
- exit(BIND_ERR);
- }
- logMessage(Info, "bind socket success, code: %d, error string: %s",errno,strerror(errno));
-
- // 3.监听
- int n2 = listen(_listensock, backlog);
- if (n2 < 0)
- {
- //cerr << "Listen socket error!" << endl;
- logMessage(Fatal, "listen socket error, code: %d, error string: %s", errno, strerror(errno));
- exit(LISTEN_ERR);
- }
- logMessage(Info, "listen socket success, code: %d, error string: %s", errno, strerror(errno));
- }
-
- void Start()
- {
- // signal(SIGCHLD,SIG_IGN);//不关心子进程的退出,由内核回收;
-
- _quit = false;
-
-
-
-
- while (!_quit)
- {
-
- struct sockaddr_in client;
- socklen_t len = sizeof(client);
-
- // 4.不断获取新的客户端的连接,没有就阻塞;
- int sock = accept(_listensock, (struct sockaddr *)&client, &len);
-
- if (sock < 0)
- {
- //cerr << "ACCEPT error!" << endl;
- logMessage(Warning, "accept error, code: %d, error string: %s", errno, strerror(errno));
- continue;
- }
-
-
- // 提取client信息---debug;
- string client_ip = inet_ntoa(client.sin_addr);//从网络序列转成主机序列,并将点分十进制字符串
- uint16_t client_port = ntohs(client.sin_port);// 网络序列转为主机序列
- //cout << client_ip << "-" << client_port<<"连接成功"<< endl;
- logMessage(Info, "accept success,%d from %d,name:%s-%d",sock,_listensock,client_ip.c_str(),client_port);
-
-
- //线程池(主线程只负责接受客户端信息,并为其创建套接字进行沟通)
- //1.创建线程池
- //2.利用次线程处理沟通
-
- //线程池一定是有限个线程个数,一定是处理短任务
- Task t(sock,client_ip,client_port,bind(&TcpServer::service,this,placeholders::_1,placeholders::_2,placeholders::_3));
- ThreadPool
::getinstance()->pushtask(t); -
-
-
- // //多线程
- // pthread_t tid;
- // ThreadData* td=new ThreadData(this,sock,client_ip,client_port);
- // //因为threadRoutine函数只能有一个参数,想让线程执行service函数就必须把this指针,还有其它参数传过去,这时候可以利用一个结构体;
- // pthread_create(&tid,nullptr,threadRoutine,td);
-
-
-
-
-
- // //多进程(父进程负责连接,子进程负责业务)
- // pid_t id=fork();
- // if(id<0)
- // {
- // //创建子进程失败;
- // close(sock);
- // cerr<
- // continue;
- // }
- // else if(id==0)
- // {
- // //子进程
-
- // close(_listensock);
- // // if(fork>0) exit(0);//创建一个孙子进程,儿子进程直接退出。利用孤儿进程处理业务,系统自动回收资源;
-
-
- // service(sock,client_ip,client_port);
- // exit(0);//子进程执行完服务直接退出;
- // }
-
- // //父进程
-
- // close(sock);//子进程已经继承到sock文件描述符,关闭父进程的sock;
-
- // // //回收子进程
- // // int ret=waitpid(id,nullptr,0);//不获取退出码
- // // if(ret==id) cout<<"回收子进程"<
-
- }
- }
- // static void *threadRoutine(void* args)
- // {
- // //直接分离不用回收
- // pthread_detach(pthread_self());
-
- // ThreadData* td=static_cast
(args); -
- // td->_current->service(td->_sock,td->_client_ip,td->_client_port);
- // delete td;
- // return nullptr;
- // }
-
-
- void service(int sock,string client_ip,uint16_t client_port)
- {
-
- string name;
- name+=client_ip;
- name+="-";
- name+=to_string(client_port);
- char buffer[1024];
-
- while (true)//为某个客户端不间断服务
- {
- int n = read(sock, buffer, sizeof(buffer) - 1);
- if (n > 0)
- {
- buffer[n]=0;
- string res=_func(buffer);
-
- logMessage(Debug, "%s# %s", name.c_str(), res.c_str());
- write(sock,res.c_str(),res.size());
- }
-
- else if (n == 0)
- {
- //说明对方断开连接了
-
- close(sock);//关闭文件描述符
- logMessage(Info, "%s quit,me too",name.c_str());
- break;
- }
- else
- {
- //cout << "read error:" <
- logMessage(Error, "read error, %d:%s", errno, strerror(errno));
- break;
- }
- }
- }
-
- ~TcpServer()
- {
- }
-
- private:
- uint16_t _port;
- int _listensock;
- bool _quit;
- func_t _func;
- };
-
- }
tcp_client.cc
-
- #include
- using namespace std;
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- //sockaddr_in结构体的头文件,当然也包含一些主机转网络序列的函数比如htons
- #include
- #include
-
- #include "err.hpp"
-
-
-
-
- // static void* rfo(void *args)
- // {
- // int sock=*(static_cast
(args)); - // while(true)
- // {
- // //收
- // char buffer[4096];
- // struct sockaddr_in tmp;//输入型参数;
- // socklen_t len=sizeof(tmp);//要初始化,不然没法修改;
-
- // //阻塞式接收
- // int n=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&len);
-
- // if(n>0)//接收服务器数据成功
- // {
- // buffer[n]=0;
- // cout<
- // }
- // }
-
-
- // }
-
- //当传入程序参数个数不对时,调用这个Usage函数告诉他什么是他妈的惊喜!
- static void Usage(string proc)
- {
- cout<<"Usage:\n\t"<
" serverip "<<" serverport\n"< - }
-
-
-
-
-
-
-
- // ./tcp_client serverip serverport
- int main(int argc,char* argv[])
- {
- if(argc!=3)
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
-
- //保留输入的服务器的IP地址与端口号
- string serverip=argv[1];
- uint16_t serverport=atoi(argv[2]);
-
- //1.客户端创建套接字
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock < 0)
- {
- cerr << " create socket error " << strerror(errno) << endl;
- exit(SOCKET_ERR);
- }
-
-
-
- //明确server是谁
- struct sockaddr_in server;
- memset((void*)&server,0,sizeof(server));
- server.sin_family=AF_INET;
- server.sin_port=htons(serverport);//主机序列转网络序列
-
- //把字符串转成sockaddr_in结构体里的结构体
- inet_aton(serverip.c_str(),&(server.sin_addr));
-
- socklen_t len =sizeof(server);
-
-
- int cnt=5;
- cout<<"cnt=5"<
- //2.连接服务器
- while(connect(sock,(struct sockaddr*)&server,len)!=0)
- {
- cout<<"正在重新连接中,还有"<
"次重新连接机会"< -
- if(cnt--<=0) break;
- }
-
- if(cnt<=0)
- {
- cout<<"连接失败"<
- exit(CONNECT_ERR);
- }
-
- else
- {
- cout<<"连接成功"<
- }
-
- char buffer[1024];
- while(true)
- {
- string message;
- cout<<"Enter>>>";
- getline(cin,message);
-
- write(sock,message.c_str(),message.size());//给服务器发数据
-
-
- ssize_t n=read(sock,buffer,sizeof(buffer)-1);//如果服务器没有发送数据,这里会阻塞;
- if(n>0)
- {
- buffer[n]=0;
- cout<<"server echo>>>"<
//打印服务器传输来的数据; - }
- else if(n==0)
- {
- cout<<"与服务器断开了"<
- break;
- }
- else
- {
- cout<<"read error"<<strerror(errno)<
- break;
- }
-
- }
-
- //关闭文件描述符(虽然进程退出自动关闭)
- close(sock);
-
- return 0;
- }
log.hpp
- #pragma once
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- enum
- {
- Debug = 0,
- Info,
- Warning,
- Error,
- Fatal,
- Uknown
- };
-
- static std::string toLevelString(int level)
- {
- switch (level)
- {
- case Debug:
- return "Debug";
- case Info:
- return "Info";
- case Warning:
- return "Warning";
- case Error:
- return "Error";
- case Fatal:
- return "Fatal";
- default:
- return "Uknown";
- }
- }
-
- static std::string gettime()
- {
- time_t curr = time(nullptr);
- struct tm *tmp = localtime(&curr);
- char buffer[128];
- snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
- tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
- return buffer;
- }
-
- // 日志格式: 日志等级 时间 pid 消息体
-
- void logMessage(int level, const char *format, ...) // format是%d、%s这样的形式,也就是printf("%d %s",a,b);
- {
- char logLeft[1024];
- std::string level_string = toLevelString(level);
- std::string curr_time = gettime();
- snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d]", level_string.c_str(), curr_time.c_str(), getpid());
-
- char logRight[1024];
- // 可变参数
- va_list p; // 指向可变参数的开始处,
- //va_arg(p, int); // 根据类型提取参数,凭借%d这样的格式判定类型与大小。
- va_start(p, format); // p=const char*& format
-
- vsnprintf(logRight, sizeof(logRight), format, p); // 向logRight缓冲区里面打印所有参数。
-
- va_end(p); // p=nullptr
- printf("%s%s\n", logLeft, logRight);
-
-
- }
ThreadPool.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- #include "Task.hpp"
- #include "Thread.hpp"
- #include "lockGuard.hpp"
- #include "log.hpp"
- const int N = 6;
-
- template <class T>
- class ThreadPool
- {
-
- public:
- // 单例模式
- static ThreadPool
*getinstance() - {
- if (_instance == nullptr) // 当一个对象已经被创建以后,就不进入申请锁并且判断的环节了;
- {
- lockGuard lock(&_mutex_instance);
- if (_instance == nullptr)
- {
-
- _instance = new ThreadPool
(); - logMessage(Debug, "线程池单例形成");
- _instance->init();
- _instance->start();
- }
- }
- return _instance;
- }
-
- bool isEmpty()
- {
- return _tasks.empty();
- }
-
- void init()
- {
- // 创建线程
- for (int i = 1; i <= _num; ++i)
- // pthread_create(&_threads[i],nullptr,ThreadRoutine,this);
- {
- _threads.push_back(Thread(i, ThreadRoutine, this));
- logMessage(Debug, "%d thread running", i);
- }
- }
-
- void start()
- {
- // 线程启动
- for (auto &e : _threads)
- {
- e.run();
- }
- }
-
- void check()
- {
- for (auto &e : _threads)
- {
- std::cout << "线程ID" << e.threadid() << " , " << e.threadname() << "is running··· " << std::endl;
- }
- }
-
- // 放入任务
- void pushtask(const T &task)
- {
- lockGuard lock(&_mutex);
- _tasks.push(task);
-
- threadwakeup(); // 有新任务进来,唤醒线程去处理
- }
-
- // 拿出任务
- T poptask()
- {
- T t = _tasks.front();
- _tasks.pop();
- return t;
- }
-
- private:
- // 重点!!!
- static void *ThreadRoutine(void *args)
- {
- pthread_detach(pthread_self());
- // 指针强转成线程池对象类型;
- ThreadPool
*tp = static_cast *>(args); - while (true)
- {
-
- T t;
- {
-
- lockGuard lock(tp->getlock());
-
- // 1.判断是否有任务
- // 有->处理
- // 无->等待
- // 如果任务队列为空,则等待
- while (tp->isEmpty())
- {
- tp->threadwait();
- }
- t = tp->poptask(); // 从共有区域拿到线程独立栈上;
- }
-
- t(); // 调用task类里面的仿函数处理任务
- }
- }
-
- private:
- ThreadPool(int num = N)
- : _num(num)
-
- {
- pthread_mutex_init(&_mutex, nullptr);
- pthread_cond_init(&_cond, nullptr);
- }
-
-
- ThreadPool(const ThreadPool
&tp) = delete; // 删除默认拷贝构造 - void operator=(const ThreadPool
&tp) = delete; // 删除赋值函数 -
- pthread_mutex_t *getlock()
- {
- return &_mutex;
- }
-
- void threadwait()
- {
- // 挂起一个线程
- pthread_cond_wait(&_cond, &_mutex);
- }
-
- void threadwakeup()
- {
- // 唤醒一个线程
- pthread_cond_signal(&_cond);
- }
-
- ~ThreadPool()
- {
- for (auto &e : _threads)
- {
- e.join();
- }
- pthread_mutex_destroy(&_mutex);
- pthread_cond_destroy(&_cond);
- }
-
- private:
- std::vector
_threads; - int _num; // 线程池里有几个线程;
-
- std::queue
_tasks; // 使用STL的自动扩容的特性 -
- pthread_mutex_t _mutex;
- pthread_cond_t _cond;
-
- static ThreadPool
*_instance; // 懒汉方式实现单例模式 - static pthread_mutex_t _mutex_instance; // 单例对象有自己的锁
- };
-
- // 静态变量初始化
- template <class T>
- ThreadPool
*ThreadPool::_instance = nullptr; -
- template <class T>
- pthread_mutex_t ThreadPool
::_mutex_instance = PTHREAD_MUTEX_INITIALIZER;
Thread.hpp
- #pragma once
-
- #include
- #include
- #include
-
- class Thread
- {
- public:
- typedef void* ( *func_t) (void*);
- typedef enum
- {
- NEW=0,
- RUNNING,
- EXITED
- }ThreadStatus;
-
- public:
- //状态:new,running,exited
- int status()
- {
- return _status;
- }
-
- //线程名
- std::string threadname()
- {
- return _name;
- }
-
- //线程ID(共享库中的进程地址空间的虚拟地址)
- pthread_t threadid()
- {
- if(_status==RUNNING)//线程已经被创建,线程id已经输入到成员变量_tid中;
- return _tid;
- else
- {
- return 0;
- }
- }
-
- public:
-
- //构造函数;
- Thread(int num,func_t func,void* args)
- :_tid(0),
- _status(NEW),
- _func(func),
- _args(args)
-
- {
- char name[128];
- snprintf(name,sizeof(name),"thread-%d",num);
- _name=name;
- }
-
- //析构函数
- ~Thread(){}
-
-
-
- //静态成员函数不能访问类内所有成员,因为没有this指针;
- static void* runHelper(void *args)
- {
- Thread* td=(Thread*)args;
- (*td)();//调用仿函数执行线程的回调函数;
- return nullptr;
- }
-
- void operator()()//仿函数
- {
- //如果函数指针不为空,则执行该函数指针指向的回调函数;
- if(_func!=nullptr) _func(_args);
- }
-
- //创建线程
- void run()
- {
- //因为runHelper函数必须只能有一个void*参数,所以runHelper函数在类内必须定义为static,这样才没有this指针;
- int n=pthread_create(&_tid,nullptr,runHelper,this);
- if(n!=0) return exit(1);//线程创建失败,那么直接退出进程;
- _status=RUNNING;
- }
-
- //等待线程结束
- void join()
- {
- int n=pthread_join(_tid,nullptr);
- if(n!=0)
- {
- std::cerr<<"main thread join thread "<<_name<<" error "<
- return;
- }
- _status=EXITED;//线程退出;
- }
-
- private:
- pthread_t _tid;//线程ID(原生线程库中为该线程所创建的TCB起始虚拟地址)
- std::string _name;//线程名
- func_t _func;//线程要执行的回调
- void* _args;//线程回调函数参数
- ThreadStatus _status;//枚举类型:状态
- };
-
lockGuard.hpp
- #pragma once
-
- #include
- #include
-
- class Mutex//成员:加锁函数和解锁函数
- {
- public:
- Mutex(pthread_mutex_t* pmutex):_pmutex(pmutex) {}
-
- void lock()
- {
- pthread_mutex_lock(_pmutex);
- }
-
- void unlock()
- {
- pthread_mutex_unlock(_pmutex);
- }
-
-
- ~Mutex(){}
-
- private:
- pthread_mutex_t* _pmutex;//需要传入一个互斥量(锁)的指针;
- };
-
-
- //对Mutex进行二次封装;
- //创建该对象时自动加锁,析构时自动解锁;
- class lockGuard
- {
- public:
- lockGuard(pthread_mutex_t* pmutex):_mutex(pmutex)//利用锁的指针构建Mutex对象
- {
- _mutex.lock();
- }
-
- ~lockGuard()
- {
- _mutex.unlock();
- }
-
- private:
- Mutex _mutex;//类内创建对象
- };
Task.hpp
- #pragma once
- #include
- #include
- #include
- using namespace std;
- using cb_t=function<void(int,string,uint16_t)>;
-
-
- class Task
- {
- public:
- Task(){}
-
- Task(int sock,string clientip,uint16_t clientport,cb_t func)
- :_sock(sock),
- _clientip(clientip),
- _clientport(clientport),
- _func(func)
- {}
-
- int operator()()
- {
- //开始为客户端---处理任务
- cout<<"开始为客户端"<<_clientip<<"-"<<_clientport<<"服务"<
- _func(_sock,_clientip,_clientport);//实际上是一个已经绑了一个参数的TcpServer::service函数;
- }
-
-
- ~Task() {}
-
- private:
- int _sock;
- string _clientip;
- uint16_t _clientport;
- cb_t _func;
- };
err.hpp
- #pragma once
- enum
- {
- USAGE_ERR=1,
- SOCKET_ERR,
- BIND_ERR,
- LISTEN_ERR,
- CONNECT_ERR,
- SETSID_ERR
- };
-
相关阅读:
C/C++ 单元自动化测试解决方案实践
Linux-编译器
java-php-python-ssm网上零食进销存计算机毕业设计
二维码怎么做调查问卷?二维码统计数据的技巧
历次工业革命的本质,都是能源转换的革命。(电学史的伟大瞬间)【电的本质】
arudino 知识整理
【gcc】RtpTransportControllerSend学习笔记 1
阿里云物联网IOT平台使用案例教程(模拟智能设备)
Qt之xml文件解析
Vue3学习——标签的ref属性
-
原文地址:https://blog.csdn.net/zzxz8/article/details/132855673