• 【Linux网络编程】Socket-TCP实例


    该代码利用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

    1. #include "tcp_server.hpp"
    2. #include
    3. #include
    4. #include
    5. using namespace ns_server;
    6. static void Usage(string proc)
    7. {
    8. cout << "Usage:\n\t" << proc << " port\n"<< endl;
    9. }
    10. static string echo(string message)
    11. {
    12. return message;
    13. }
    14. // .tcp_server serverport
    15. int main(int argc,char* argv[])
    16. {
    17. if(argc!=2)
    18. {
    19. Usage(argv[0]);
    20. exit(USAGE_ERR);
    21. }
    22. uint16_t serverport=atoi(argv[1]);
    23. unique_ptr tsvr(new TcpServer(serverport,echo));
    24. tsvr->InitServer();
    25. tsvr->Start();
    26. return 0;
    27. }

    tcp_server.hpp

    1. #pragma once
    2. #include
    3. using namespace std;
    4. #include
    5. #include "err.hpp"
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include"ThreadPool.hpp"
    16. #include "Task.hpp"
    17. #include "Thread.hpp"
    18. #include "lockGuard.hpp"
    19. #include "log.hpp"
    20. namespace ns_server
    21. {
    22. class TcpServer;
    23. class ThreadData
    24. {
    25. public:
    26. ThreadData(TcpServer* current,int sock,string client_ip,uint16_t client_port)
    27. :_current(current),_sock(sock),_client_ip(client_ip),_client_port(client_port)
    28. {
    29. }
    30. ~ThreadData(){}
    31. TcpServer* _current;
    32. int _sock;
    33. string _client_ip;
    34. uint16_t _client_port;
    35. };
    36. static const uint32_t backlog = 32;
    37. static const uint16_t defaultport = 8888;
    38. using func_t = function<string(const string&)>;
    39. class TcpServer
    40. {
    41. public:
    42. TcpServer(uint16_t port ,func_t func)
    43. : _port(port), _func(func), _quit(true)
    44. {}
    45. void InitServer()
    46. {
    47. // 1.创建监听套接字
    48. _listensock = socket(AF_INET, SOCK_STREAM, 0);
    49. if (_listensock < 0)
    50. {
    51. //cerr << "Socket create error!" << endl;
    52. logMessage(Fatal, "create socket error, code: %d, error string: %s",errno,strerror(errno));
    53. exit(SOCKET_ERR);
    54. }
    55. logMessage(Info, "create socket success, code: %d, error string: %s", errno, strerror(errno));
    56. // 2.绑定本地端口与IP
    57. struct sockaddr_in local;
    58. memset(&local, 0, sizeof(local));
    59. local.sin_family = AF_INET;
    60. local.sin_addr.s_addr = htons(INADDR_ANY); // 32位全零,不用转成网络序列,因为主机序列与网络序列一样;
    61. local.sin_port = htons(_port);
    62. int n1 = bind(_listensock, (const struct sockaddr *)&local, sizeof(local));
    63. if (n1 < 0)
    64. {
    65. //cerr << "Bind socket error!" << endl;
    66. logMessage(Fatal, "bind socket error, code: %d, error string: %s",errno,strerror(errno));
    67. exit(BIND_ERR);
    68. }
    69. logMessage(Info, "bind socket success, code: %d, error string: %s",errno,strerror(errno));
    70. // 3.监听
    71. int n2 = listen(_listensock, backlog);
    72. if (n2 < 0)
    73. {
    74. //cerr << "Listen socket error!" << endl;
    75. logMessage(Fatal, "listen socket error, code: %d, error string: %s", errno, strerror(errno));
    76. exit(LISTEN_ERR);
    77. }
    78. logMessage(Info, "listen socket success, code: %d, error string: %s", errno, strerror(errno));
    79. }
    80. void Start()
    81. {
    82. // signal(SIGCHLD,SIG_IGN);//不关心子进程的退出,由内核回收;
    83. _quit = false;
    84. while (!_quit)
    85. {
    86. struct sockaddr_in client;
    87. socklen_t len = sizeof(client);
    88. // 4.不断获取新的客户端的连接,没有就阻塞;
    89. int sock = accept(_listensock, (struct sockaddr *)&client, &len);
    90. if (sock < 0)
    91. {
    92. //cerr << "ACCEPT error!" << endl;
    93. logMessage(Warning, "accept error, code: %d, error string: %s", errno, strerror(errno));
    94. continue;
    95. }
    96. // 提取client信息---debug;
    97. string client_ip = inet_ntoa(client.sin_addr);//从网络序列转成主机序列,并将点分十进制字符串
    98. uint16_t client_port = ntohs(client.sin_port);// 网络序列转为主机序列
    99. //cout << client_ip << "-" << client_port<<"连接成功"<< endl;
    100. logMessage(Info, "accept success,%d from %d,name:%s-%d",sock,_listensock,client_ip.c_str(),client_port);
    101. //线程池(主线程只负责接受客户端信息,并为其创建套接字进行沟通)
    102. //1.创建线程池
    103. //2.利用次线程处理沟通
    104. //线程池一定是有限个线程个数,一定是处理短任务
    105. Task t(sock,client_ip,client_port,bind(&TcpServer::service,this,placeholders::_1,placeholders::_2,placeholders::_3));
    106. ThreadPool::getinstance()->pushtask(t);
    107. // //多线程
    108. // pthread_t tid;
    109. // ThreadData* td=new ThreadData(this,sock,client_ip,client_port);
    110. // //因为threadRoutine函数只能有一个参数,想让线程执行service函数就必须把this指针,还有其它参数传过去,这时候可以利用一个结构体;
    111. // pthread_create(&tid,nullptr,threadRoutine,td);
    112. // //多进程(父进程负责连接,子进程负责业务)
    113. // pid_t id=fork();
    114. // if(id<0)
    115. // {
    116. // //创建子进程失败;
    117. // close(sock);
    118. // cerr<
    119. // continue;
    120. // }
    121. // else if(id==0)
    122. // {
    123. // //子进程
    124. // close(_listensock);
    125. // // if(fork>0) exit(0);//创建一个孙子进程,儿子进程直接退出。利用孤儿进程处理业务,系统自动回收资源;
    126. // service(sock,client_ip,client_port);
    127. // exit(0);//子进程执行完服务直接退出;
    128. // }
    129. // //父进程
    130. // close(sock);//子进程已经继承到sock文件描述符,关闭父进程的sock;
    131. // // //回收子进程
    132. // // int ret=waitpid(id,nullptr,0);//不获取退出码
    133. // // if(ret==id) cout<<"回收子进程"<
    134. }
    135. }
    136. // static void *threadRoutine(void* args)
    137. // {
    138. // //直接分离不用回收
    139. // pthread_detach(pthread_self());
    140. // ThreadData* td=static_cast(args);
    141. // td->_current->service(td->_sock,td->_client_ip,td->_client_port);
    142. // delete td;
    143. // return nullptr;
    144. // }
    145. void service(int sock,string client_ip,uint16_t client_port)
    146. {
    147. string name;
    148. name+=client_ip;
    149. name+="-";
    150. name+=to_string(client_port);
    151. char buffer[1024];
    152. while (true)//为某个客户端不间断服务
    153. {
    154. int n = read(sock, buffer, sizeof(buffer) - 1);
    155. if (n > 0)
    156. {
    157. buffer[n]=0;
    158. string res=_func(buffer);
    159. logMessage(Debug, "%s# %s", name.c_str(), res.c_str());
    160. write(sock,res.c_str(),res.size());
    161. }
    162. else if (n == 0)
    163. {
    164. //说明对方断开连接了
    165. close(sock);//关闭文件描述符
    166. logMessage(Info, "%s quit,me too",name.c_str());
    167. break;
    168. }
    169. else
    170. {
    171. //cout << "read error:" <
    172. logMessage(Error, "read error, %d:%s", errno, strerror(errno));
    173. break;
    174. }
    175. }
    176. }
    177. ~TcpServer()
    178. {
    179. }
    180. private:
    181. uint16_t _port;
    182. int _listensock;
    183. bool _quit;
    184. func_t _func;
    185. };
    186. }

    tcp_client.cc

    1. #include
    2. using namespace std;
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. //sockaddr_in结构体的头文件,当然也包含一些主机转网络序列的函数比如htons
    11. #include
    12. #include
    13. #include "err.hpp"
    14. // static void* rfo(void *args)
    15. // {
    16. // int sock=*(static_cast(args));
    17. // while(true)
    18. // {
    19. // //收
    20. // char buffer[4096];
    21. // struct sockaddr_in tmp;//输入型参数;
    22. // socklen_t len=sizeof(tmp);//要初始化,不然没法修改;
    23. // //阻塞式接收
    24. // int n=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&len);
    25. // if(n>0)//接收服务器数据成功
    26. // {
    27. // buffer[n]=0;
    28. // cout<
    29. // }
    30. // }
    31. // }
    32. //当传入程序参数个数不对时,调用这个Usage函数告诉他什么是他妈的惊喜!
    33. static void Usage(string proc)
    34. {
    35. cout<<"Usage:\n\t"<" serverip "<<" serverport\n"<
    36. }
    37. // ./tcp_client serverip serverport
    38. int main(int argc,char* argv[])
    39. {
    40. if(argc!=3)
    41. {
    42. Usage(argv[0]);
    43. exit(USAGE_ERR);
    44. }
    45. //保留输入的服务器的IP地址与端口号
    46. string serverip=argv[1];
    47. uint16_t serverport=atoi(argv[2]);
    48. //1.客户端创建套接字
    49. int sock = socket(AF_INET, SOCK_STREAM, 0);
    50. if (sock < 0)
    51. {
    52. cerr << " create socket error " << strerror(errno) << endl;
    53. exit(SOCKET_ERR);
    54. }
    55. //明确server是谁
    56. struct sockaddr_in server;
    57. memset((void*)&server,0,sizeof(server));
    58. server.sin_family=AF_INET;
    59. server.sin_port=htons(serverport);//主机序列转网络序列
    60. //把字符串转成sockaddr_in结构体里的结构体
    61. inet_aton(serverip.c_str(),&(server.sin_addr));
    62. socklen_t len =sizeof(server);
    63. int cnt=5;
    64. cout<<"cnt=5"<
    65. //2.连接服务器
    66. while(connect(sock,(struct sockaddr*)&server,len)!=0)
    67. {
    68. cout<<"正在重新连接中,还有"<"次重新连接机会"<
    69. if(cnt--<=0) break;
    70. }
    71. if(cnt<=0)
    72. {
    73. cout<<"连接失败"<
    74. exit(CONNECT_ERR);
    75. }
    76. else
    77. {
    78. cout<<"连接成功"<
    79. }
    80. char buffer[1024];
    81. while(true)
    82. {
    83. string message;
    84. cout<<"Enter>>>";
    85. getline(cin,message);
    86. write(sock,message.c_str(),message.size());//给服务器发数据
    87. ssize_t n=read(sock,buffer,sizeof(buffer)-1);//如果服务器没有发送数据,这里会阻塞;
    88. if(n>0)
    89. {
    90. buffer[n]=0;
    91. cout<<"server echo>>>"<//打印服务器传输来的数据;
    92. }
    93. else if(n==0)
    94. {
    95. cout<<"与服务器断开了"<
    96. break;
    97. }
    98. else
    99. {
    100. cout<<"read error"<<strerror(errno)<
    101. break;
    102. }
    103. }
    104. //关闭文件描述符(虽然进程退出自动关闭)
    105. close(sock);
    106. return 0;
    107. }

    log.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. enum
    10. {
    11. Debug = 0,
    12. Info,
    13. Warning,
    14. Error,
    15. Fatal,
    16. Uknown
    17. };
    18. static std::string toLevelString(int level)
    19. {
    20. switch (level)
    21. {
    22. case Debug:
    23. return "Debug";
    24. case Info:
    25. return "Info";
    26. case Warning:
    27. return "Warning";
    28. case Error:
    29. return "Error";
    30. case Fatal:
    31. return "Fatal";
    32. default:
    33. return "Uknown";
    34. }
    35. }
    36. static std::string gettime()
    37. {
    38. time_t curr = time(nullptr);
    39. struct tm *tmp = localtime(&curr);
    40. char buffer[128];
    41. snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
    42. tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    43. return buffer;
    44. }
    45. // 日志格式: 日志等级 时间 pid 消息体
    46. void logMessage(int level, const char *format, ...) // format是%d、%s这样的形式,也就是printf("%d %s",a,b);
    47. {
    48. char logLeft[1024];
    49. std::string level_string = toLevelString(level);
    50. std::string curr_time = gettime();
    51. snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d]", level_string.c_str(), curr_time.c_str(), getpid());
    52. char logRight[1024];
    53. // 可变参数
    54. va_list p; // 指向可变参数的开始处,
    55. //va_arg(p, int); // 根据类型提取参数,凭借%d这样的格式判定类型与大小。
    56. va_start(p, format); // p=const char*& format
    57. vsnprintf(logRight, sizeof(logRight), format, p); // 向logRight缓冲区里面打印所有参数。
    58. va_end(p); // p=nullptr
    59. printf("%s%s\n", logLeft, logRight);
    60. }

    ThreadPool.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. #include "Task.hpp"
    10. #include "Thread.hpp"
    11. #include "lockGuard.hpp"
    12. #include "log.hpp"
    13. const int N = 6;
    14. template <class T>
    15. class ThreadPool
    16. {
    17. public:
    18. // 单例模式
    19. static ThreadPool *getinstance()
    20. {
    21. if (_instance == nullptr) // 当一个对象已经被创建以后,就不进入申请锁并且判断的环节了;
    22. {
    23. lockGuard lock(&_mutex_instance);
    24. if (_instance == nullptr)
    25. {
    26. _instance = new ThreadPool();
    27. logMessage(Debug, "线程池单例形成");
    28. _instance->init();
    29. _instance->start();
    30. }
    31. }
    32. return _instance;
    33. }
    34. bool isEmpty()
    35. {
    36. return _tasks.empty();
    37. }
    38. void init()
    39. {
    40. // 创建线程
    41. for (int i = 1; i <= _num; ++i)
    42. // pthread_create(&_threads[i],nullptr,ThreadRoutine,this);
    43. {
    44. _threads.push_back(Thread(i, ThreadRoutine, this));
    45. logMessage(Debug, "%d thread running", i);
    46. }
    47. }
    48. void start()
    49. {
    50. // 线程启动
    51. for (auto &e : _threads)
    52. {
    53. e.run();
    54. }
    55. }
    56. void check()
    57. {
    58. for (auto &e : _threads)
    59. {
    60. std::cout << "线程ID" << e.threadid() << " , " << e.threadname() << "is running··· " << std::endl;
    61. }
    62. }
    63. // 放入任务
    64. void pushtask(const T &task)
    65. {
    66. lockGuard lock(&_mutex);
    67. _tasks.push(task);
    68. threadwakeup(); // 有新任务进来,唤醒线程去处理
    69. }
    70. // 拿出任务
    71. T poptask()
    72. {
    73. T t = _tasks.front();
    74. _tasks.pop();
    75. return t;
    76. }
    77. private:
    78. // 重点!!!
    79. static void *ThreadRoutine(void *args)
    80. {
    81. pthread_detach(pthread_self());
    82. // 指针强转成线程池对象类型;
    83. ThreadPool *tp = static_cast *>(args);
    84. while (true)
    85. {
    86. T t;
    87. {
    88. lockGuard lock(tp->getlock());
    89. // 1.判断是否有任务
    90. // 有->处理
    91. // 无->等待
    92. // 如果任务队列为空,则等待
    93. while (tp->isEmpty())
    94. {
    95. tp->threadwait();
    96. }
    97. t = tp->poptask(); // 从共有区域拿到线程独立栈上;
    98. }
    99. t(); // 调用task类里面的仿函数处理任务
    100. }
    101. }
    102. private:
    103. ThreadPool(int num = N)
    104. : _num(num)
    105. {
    106. pthread_mutex_init(&_mutex, nullptr);
    107. pthread_cond_init(&_cond, nullptr);
    108. }
    109. ThreadPool(const ThreadPool &tp) = delete; // 删除默认拷贝构造
    110. void operator=(const ThreadPool &tp) = delete; // 删除赋值函数
    111. pthread_mutex_t *getlock()
    112. {
    113. return &_mutex;
    114. }
    115. void threadwait()
    116. {
    117. // 挂起一个线程
    118. pthread_cond_wait(&_cond, &_mutex);
    119. }
    120. void threadwakeup()
    121. {
    122. // 唤醒一个线程
    123. pthread_cond_signal(&_cond);
    124. }
    125. ~ThreadPool()
    126. {
    127. for (auto &e : _threads)
    128. {
    129. e.join();
    130. }
    131. pthread_mutex_destroy(&_mutex);
    132. pthread_cond_destroy(&_cond);
    133. }
    134. private:
    135. std::vector _threads;
    136. int _num; // 线程池里有几个线程;
    137. std::queue _tasks; // 使用STL的自动扩容的特性
    138. pthread_mutex_t _mutex;
    139. pthread_cond_t _cond;
    140. static ThreadPool *_instance; // 懒汉方式实现单例模式
    141. static pthread_mutex_t _mutex_instance; // 单例对象有自己的锁
    142. };
    143. // 静态变量初始化
    144. template <class T>
    145. ThreadPool *ThreadPool::_instance = nullptr;
    146. template <class T>
    147. pthread_mutex_t ThreadPool::_mutex_instance = PTHREAD_MUTEX_INITIALIZER;

    Thread.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. class Thread
    6. {
    7. public:
    8. typedef void* ( *func_t) (void*);
    9. typedef enum
    10. {
    11. NEW=0,
    12. RUNNING,
    13. EXITED
    14. }ThreadStatus;
    15. public:
    16. //状态:new,running,exited
    17. int status()
    18. {
    19. return _status;
    20. }
    21. //线程名
    22. std::string threadname()
    23. {
    24. return _name;
    25. }
    26. //线程ID(共享库中的进程地址空间的虚拟地址)
    27. pthread_t threadid()
    28. {
    29. if(_status==RUNNING)//线程已经被创建,线程id已经输入到成员变量_tid中;
    30. return _tid;
    31. else
    32. {
    33. return 0;
    34. }
    35. }
    36. public:
    37. //构造函数;
    38. Thread(int num,func_t func,void* args)
    39. :_tid(0),
    40. _status(NEW),
    41. _func(func),
    42. _args(args)
    43. {
    44. char name[128];
    45. snprintf(name,sizeof(name),"thread-%d",num);
    46. _name=name;
    47. }
    48. //析构函数
    49. ~Thread(){}
    50. //静态成员函数不能访问类内所有成员,因为没有this指针;
    51. static void* runHelper(void *args)
    52. {
    53. Thread* td=(Thread*)args;
    54. (*td)();//调用仿函数执行线程的回调函数;
    55. return nullptr;
    56. }
    57. void operator()()//仿函数
    58. {
    59. //如果函数指针不为空,则执行该函数指针指向的回调函数;
    60. if(_func!=nullptr) _func(_args);
    61. }
    62. //创建线程
    63. void run()
    64. {
    65. //因为runHelper函数必须只能有一个void*参数,所以runHelper函数在类内必须定义为static,这样才没有this指针;
    66. int n=pthread_create(&_tid,nullptr,runHelper,this);
    67. if(n!=0) return exit(1);//线程创建失败,那么直接退出进程;
    68. _status=RUNNING;
    69. }
    70. //等待线程结束
    71. void join()
    72. {
    73. int n=pthread_join(_tid,nullptr);
    74. if(n!=0)
    75. {
    76. std::cerr<<"main thread join thread "<<_name<<" error "<
    77. return;
    78. }
    79. _status=EXITED;//线程退出;
    80. }
    81. private:
    82. pthread_t _tid;//线程ID(原生线程库中为该线程所创建的TCB起始虚拟地址)
    83. std::string _name;//线程名
    84. func_t _func;//线程要执行的回调
    85. void* _args;//线程回调函数参数
    86. ThreadStatus _status;//枚举类型:状态
    87. };

    lockGuard.hpp

    1. #pragma once
    2. #include
    3. #include
    4. class Mutex//成员:加锁函数和解锁函数
    5. {
    6. public:
    7. Mutex(pthread_mutex_t* pmutex):_pmutex(pmutex) {}
    8. void lock()
    9. {
    10. pthread_mutex_lock(_pmutex);
    11. }
    12. void unlock()
    13. {
    14. pthread_mutex_unlock(_pmutex);
    15. }
    16. ~Mutex(){}
    17. private:
    18. pthread_mutex_t* _pmutex;//需要传入一个互斥量(锁)的指针;
    19. };
    20. //对Mutex进行二次封装;
    21. //创建该对象时自动加锁,析构时自动解锁;
    22. class lockGuard
    23. {
    24. public:
    25. lockGuard(pthread_mutex_t* pmutex):_mutex(pmutex)//利用锁的指针构建Mutex对象
    26. {
    27. _mutex.lock();
    28. }
    29. ~lockGuard()
    30. {
    31. _mutex.unlock();
    32. }
    33. private:
    34. Mutex _mutex;//类内创建对象
    35. };

    Task.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. using cb_t=function<void(int,string,uint16_t)>;
    7. class Task
    8. {
    9. public:
    10. Task(){}
    11. Task(int sock,string clientip,uint16_t clientport,cb_t func)
    12. :_sock(sock),
    13. _clientip(clientip),
    14. _clientport(clientport),
    15. _func(func)
    16. {}
    17. int operator()()
    18. {
    19. //开始为客户端---处理任务
    20. cout<<"开始为客户端"<<_clientip<<"-"<<_clientport<<"服务"<
    21. _func(_sock,_clientip,_clientport);//实际上是一个已经绑了一个参数的TcpServer::service函数;
    22. }
    23. ~Task() {}
    24. private:
    25. int _sock;
    26. string _clientip;
    27. uint16_t _clientport;
    28. cb_t _func;
    29. };

    err.hpp

    1. #pragma once
    2. enum
    3. {
    4. USAGE_ERR=1,
    5. SOCKET_ERR,
    6. BIND_ERR,
    7. LISTEN_ERR,
    8. CONNECT_ERR,
    9. SETSID_ERR
    10. };

  • 相关阅读:
    C/C++ 单元自动化测试解决方案实践
    Linux-编译器
    java-php-python-ssm网上零食进销存计算机毕业设计
    二维码怎么做调查问卷?二维码统计数据的技巧
    历次工业革命的本质,都是能源转换的革命。(电学史的伟大瞬间)【电的本质】
    arudino 知识整理
    【gcc】RtpTransportControllerSend学习笔记 1
    阿里云物联网IOT平台使用案例教程(模拟智能设备)
    Qt之xml文件解析
    Vue3学习——标签的ref属性
  • 原文地址:https://blog.csdn.net/zzxz8/article/details/132855673