• 网络和Linux网络_3(套接字编程)TCP网络通信代码(多个版本)


    目录

    1. TCP网络编程

    1.1 前期代码

    log.hpp

    tcp_server.cc

    1.2 accept和单进程版代码

    1.3 多进程版strat代码

    1.4 client.cc客户端

    1.5 多进程版strat代码改进+多线程

    1.6 线程池版本

    Task.hpp

    lockGuard.hpp

    thread.hpp

    threadPool.hpp

    多个回调任务

    tcp_client.cc

    tcp_server.hpp

    2. 笔试选择题

    答案及解析

    本篇完。


    1. TCP网络编程

    框架和前面udp通信一样,接口函数上一篇也讲了,这里直接放一部分代码:

    1.1 前期代码

    log.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. // 日志是有日志级别的
    8. #define DEBUG 0
    9. #define NORMAL 1
    10. #define WARNING 2
    11. #define ERROR 3
    12. #define FATAL 4
    13. const char *gLevelMap[] = {
    14. "DEBUG",
    15. "NORMAL",
    16. "WARNING",
    17. "ERROR",
    18. "FATAL"
    19. };
    20. #define LOGFILE "./threadpool.log"
    21. // 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
    22. void logMessage(int level, const char *format, ...) // 可变参数
    23. {
    24. #ifndef DEBUG_SHOW
    25. if(level== DEBUG)
    26. {
    27. return;
    28. }
    29. #endif
    30. char stdBuffer[1024]; // 标准日志部分
    31. time_t timestamp = time(nullptr); // 获取时间戳
    32. // struct tm *localtime = localtime(×tamp); // 转化麻烦就不写了
    33. snprintf(stdBuffer, sizeof(stdBuffer), "[%s] [%ld] ", gLevelMap[level], timestamp);
    34. char logBuffer[1024]; // 自定义日志部分
    35. va_list args; // 提取可变参数的 -> #include 了解一下就行
    36. va_start(args, format);
    37. // vprintf(format, args);
    38. vsnprintf(logBuffer, sizeof(logBuffer), format, args);
    39. va_end(args); // 相当于ap=nullptr
    40. printf("%s%s\n", stdBuffer, logBuffer);
    41. // FILE *fp = fopen(LOGFILE, "a"); // 追加到文件,这里写好了就不演示了
    42. // fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    43. // fclose(fp);
    44. }

    client.cc

    1. #include
    2. int main()
    3. {
    4. return 0;
    5. }

    tcp_server.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include // 网络四件套
    10. #include
    11. #include
    12. #include
    13. #include "log.hpp"
    14. class TcpServer
    15. {
    16. protected:
    17. const static int gbacklog = 20; // listen的第二个参数,现在先不管
    18. public:
    19. TcpServer(uint16_t port, std::string ip="")
    20. :_listensock(-1)
    21. , _port(port)
    22. , _ip(ip)
    23. {}
    24. void initServer()
    25. {
    26. // 1. 创建socket -- 进程和文件
    27. _listensock = socket(AF_INET, SOCK_STREAM, 0); // 域 + 类型 + 0 // UDP第二个参数是SOCK_DGRAM
    28. if(_listensock < 0)
    29. {
    30. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    31. exit(2);
    32. }
    33. logMessage(NORMAL, "create socket success, _listensock: %d", _listensock); // 3
    34. // 2. bind -- 文件 + 网络
    35. struct sockaddr_in local;
    36. memset(&local, 0, sizeof local);
    37. local.sin_family = AF_INET;
    38. local.sin_port = htons(_port);
    39. local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
    40. if(bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
    41. {
    42. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    43. exit(3);
    44. }
    45. // 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接,UDP没这一步
    46. if(listen(_listensock, gbacklog) < 0) // 第一个参数是套接字,第二个参数后面再说
    47. {
    48. logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
    49. exit(4);
    50. }
    51. logMessage(NORMAL, "init server success");
    52. }
    53. void start()
    54. {
    55. while(true)
    56. {
    57. sleep(7);
    58. }
    59. }
    60. ~TcpServer()
    61. {}
    62. protected:
    63. uint16_t _port;
    64. std::string _ip;
    65. int _listensock;
    66. };

    tcp_server.cc

    1. #include "tcp_server.hpp"
    2. #include
    3. static void usage(std::string proc)
    4. {
    5. std::cout << "\nUsage: " << proc << " port\n" << std::endl;
    6. }
    7. // ./tcp_server port
    8. int main(int argc, char *argv[])
    9. {
    10. if(argc != 2)
    11. {
    12. usage(argv[0]);
    13. exit(1);
    14. }
    15. uint16_t port = atoi(argv[1]);
    16. std::unique_ptr svr(new TcpServer(port));
    17. svr->initServer();
    18. svr->start();
    19. return 0;
    20. }

    编译运行:

    此时程序就跑起来了。


    1.2 accept和单进程版代码

    再写start函数:

    1. void start()
    2. {
    3. while(true)
    4. {
    5. // 4. 获取连接
    6. struct sockaddr_in src;
    7. socklen_t len = sizeof(src);
    8. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    9. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    10. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    11. if(servicesock < 0)
    12. {
    13. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    14. continue;
    15. }
    16. // 获取连接成功了
    17. uint16_t client_port = ntohs(src.sin_port);
    18. std::string client_ip = inet_ntoa(src.sin_addr);
    19. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    20. /*换行符*/servicesock, client_ip.c_str(), client_port);
    21. // 开始进行通信服务
    22. // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    23. // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    24. service(servicesock, client_ip, client_port);
    25. }
    26. }

    第4步获取链接,写在start函数中,如上图所示,使用accept来接收客户端的连接请求,有点像udp中的recvfrom一样,只是accept是用来接收套接字的连接请求,而recvfrom是接收套接字中的数据的。man accept:

    accept系统调用的参数和recvfrom中的一样,如上图所示,accept的作用就是接收来自套接字中的连接请求,也就是来自客户端的连接请求。

    设置为listen状态的套接字不用了通信,只是用来接收客户端的网络请求,具体体现在accept的返回值上。

    第一步中创建的套接字就像是一个门童,使用accept来接收客户端的连接请求,如果有连接请求并且接收成功,那么会返回一个文件描述符fd。 这里的文件描述符sock和前面的_listensock不是一个东西,_listensock是我们创建的,是专门用来接收连接请求的,而accept返回的sock是操作系统在接收成功连接请求后新创建的套接字的文件描述符。 sock指向的文件描述符是服务端专门用来和客户端通信的,所以每有一个客户端向服务器发起连接请求,客户端接收成功够都会创建一个套接字用来一对一的提供服务。

    如果accept接收连接请求失败,则返回-1,并且设置错误码。这里的失败并不是致命的,就像门童拉客一样,拉客失败也没有什么,继续进行下一次拉客就行。 所以accept失败也没有什么,继续接收下一个连接请求即可,所以在代码中,如果接收失败,使用了continue继续接收连接请求。

    accept是阻塞执行的,在没有网络连接请求的时候,会阻塞等待,直到客户端的网络连接请求到来。


    至此,进行tcp网络通信的所有准备工作已经做完,接下来就是进行具体的服务了,也就是读取客户端发送来的数据并做相应的处理了。看一下start在最后调用的service函数:

    1. static void service(int sock, const std::string &clientip, const uint16_t &clientport)
    2. {
    3. //echo server
    4. char buffer[1024];
    5. while(true)
    6. {
    7. // read && write 可以直接被使用
    8. ssize_t s = read(sock, buffer, sizeof(buffer)-1);
    9. if(s > 0)
    10. {
    11. buffer[s] = 0; // 将发过来的数据当做字符串
    12. std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
    13. }
    14. else if(s == 0) // 对端关闭连接
    15. {
    16. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    17. break;
    18. }
    19. else
    20. {
    21. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    22. break;
    23. }
    24. write(sock, buffer, strlen(buffer));
    25. }
    26. }

    如代码所示,就是服务器指向的具体服务函数。 客户端读取客户端发送来的数据时,是从accept返回的文件描述符sock指向的套接字中读取数据的,因为这个套接字是专门用来服务客户端的。

    读取数据时,使用的是read系统调用,和读取普通文件一模一样。

    数据读取成功后,做一些处理,先将读取的数据打印一下,加一个回显,再给客户端发送过去。

    发送数据时,使用的是write系统调用,写入的也是sock指向的套接字,同样与向普通文件中写入数据一模一样。

    在读取普通文件的时候,如果文件被读完了,read会返回0,表示文件的内容被读取完毕。 但是在使用read读取tcp套接字的时候,如果读取到0,表示客户端关闭了它的套接字,代表着客户端不再进行网络通信了,此时服务端就可以结束这次通信了,也就是将sock指向的套接字关闭。

    这里再放下tcp_server.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include // 网络四件套
    10. #include
    11. #include
    12. #include
    13. #include "log.hpp"
    14. static void service(int sock, const std::string &clientip, const uint16_t &clientport)
    15. {
    16. //echo server
    17. char buffer[1024];
    18. while(true)
    19. {
    20. // read && write 可以直接被使用
    21. ssize_t s = read(sock, buffer, sizeof(buffer)-1);
    22. if(s > 0)
    23. {
    24. buffer[s] = 0; // 将发过来的数据当做字符串
    25. std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
    26. }
    27. else if(s == 0) // 对端关闭连接
    28. {
    29. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    30. break;
    31. }
    32. else
    33. {
    34. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    35. break;
    36. }
    37. write(sock, buffer, strlen(buffer));
    38. }
    39. }
    40. class TcpServer
    41. {
    42. protected:
    43. const static int gbacklog = 20; // listen的第二个参数,现在先不管
    44. public:
    45. TcpServer(uint16_t port, std::string ip="")
    46. :_listensock(-1)
    47. , _port(port)
    48. , _ip(ip)
    49. {}
    50. void initServer()
    51. {
    52. // 1. 创建socket -- 进程和文件
    53. _listensock = socket(AF_INET, SOCK_STREAM, 0); // 域 + 类型 + 0 // UDP第二个参数是SOCK_DGRAM
    54. if(_listensock < 0)
    55. {
    56. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    57. exit(2);
    58. }
    59. logMessage(NORMAL, "create socket success, _listensock: %d", _listensock); // 3
    60. // 2. bind -- 文件 + 网络
    61. struct sockaddr_in local;
    62. memset(&local, 0, sizeof local);
    63. local.sin_family = AF_INET;
    64. local.sin_port = htons(_port);
    65. local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
    66. if(bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
    67. {
    68. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    69. exit(3);
    70. }
    71. // 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接,UDP没这一步
    72. if(listen(_listensock, gbacklog) < 0) // 第一个参数是套接字,第二个参数后面再说
    73. {
    74. logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
    75. exit(4);
    76. }
    77. logMessage(NORMAL, "init server success");
    78. }
    79. void start()
    80. {
    81. while(true)
    82. {
    83. // 4. 获取连接
    84. struct sockaddr_in src;
    85. socklen_t len = sizeof(src);
    86. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    87. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    88. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    89. if(_servicesock < 0)
    90. {
    91. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    92. continue;
    93. }
    94. // 获取连接成功了
    95. uint16_t client_port = ntohs(src.sin_port);
    96. std::string client_ip = inet_ntoa(src.sin_addr);
    97. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    98. /*换行符*/servicesock, client_ip.c_str(), client_port);
    99. // 开始进行通信服务
    100. // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    101. // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    102. service(servicesock, client_ip, client_port);
    103. }
    104. }
    105. ~TcpServer()
    106. {}
    107. protected:
    108. uint16_t _port;
    109. std::string _ip;
    110. int listensock;
    111. };

    编译运行:

    telnet 是一个远程链接命令,这里切换到了root输入了这个下载指令:yum -y install telnet telnet-server xinetd,以后输入telnet 127.0.0.1 7070链接到启动了的程序,然后Ctrl+],再回车就能发消息了,最后Ctrl+]输入quit就退出了。


    1.3 多进程版strat代码

    只用改strat函数:

    1. void start()
    2. {
    3. // 下面父进程的阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    4. signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    5. while(true)
    6. {
    7. // 4. 获取连接
    8. struct sockaddr_in src;
    9. socklen_t len = sizeof(src);
    10. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    11. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    12. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    13. if(servicesock < 0)
    14. {
    15. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    16. continue;
    17. }
    18. // 获取连接成功了
    19. uint16_t client_port = ntohs(src.sin_port);
    20. std::string client_ip = inet_ntoa(src.sin_addr);
    21. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    22. /*换行符*/servicesock, client_ip.c_str(), client_port);
    23. // 开始进行通信服务
    24. // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    25. // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    26. // service(servicesock, client_ip, client_port);
    27. // 2 version 2.0 -- 多进程版 --- 创建子进程
    28. // 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢? -> 能
    29. pid_t id = fork();
    30. assert(id != -1);
    31. if(id == 0) // 子进程
    32. {
    33. // 子进程会不会继承父进程打开的文件与文件fd呢? -> 会
    34. // 子进程是来进行提供服务的,需不需要知道监听socket呢? -> 不需要
    35. close(_listensock); // 关闭自己不需要的套接字
    36. service(servicesock, client_ip, client_port); // 子进程对新链接提供服务
    37. exit(0); // 僵尸状态
    38. }
    39. // 父进程
    40. close(servicesock); // 父进程关闭自己不需要的套接字,不关的话文件描述符会越少
    41. // 如果父进程关闭servicesock,会不会影响子进程?
    42. // 前面有了signal(SIGCHLD, SIG_IGN); -> 父进程不用等了
    43. // waitpid(); // 阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    44. }
    45. }

    编译运行:

    此时就完整了多进程的第一个版本。


    1.4 client.cc客户端

    看看接口,man accept

    如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来,

    accept()返回时传出客户端的地址和端口号。

    addr是一个传输出型参数,如果给addr 参数传NULL,表示不关心客户端的地址。

    addrlen参数是一个输入输出型参数(value-result argument),

    输入的是调用者提供的,缓冲区addr的长度以避免缓冲区溢出问题,

    man connect

    客户端需要调用connect()连接服务器;
    connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址;
    connect()成功返回0,出错返回-1。

    client.cc

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void usage(std::string proc)
    11. {
    12. std::cout << "\nUsage: " << proc << " serverIp serverPort\n" << std::endl;
    13. }
    14. // ./tcp_client targetIp targetPort
    15. int main(int argc, char *argv[])
    16. {
    17. if (argc != 3)
    18. {
    19. usage(argv[0]);
    20. exit(1);
    21. }
    22. std::string serverip = argv[1];
    23. uint16_t serverport = atoi(argv[2]);
    24. int sock = 0;
    25. sock = socket(AF_INET, SOCK_STREAM, 0);
    26. if (sock < 0)
    27. {
    28. std::cerr << "socket error" << std::endl;
    29. exit(2);
    30. }
    31. // client 要不要bind呢?不需要显示的bind,但是一定是需要port
    32. // 需要让os自动进行port选择,客户端需要连接别人的能力 -> connect
    33. struct sockaddr_in server;
    34. memset(&server, 0, sizeof(server)); // 清零
    35. server.sin_family = AF_INET;
    36. server.sin_port = htons(serverport);
    37. server.sin_addr.s_addr = inet_addr(serverip.c_str());
    38. if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) // 比UDP多了这一步
    39. {
    40. std::cerr << "connect error" << std::endl;
    41. exit(3);
    42. }
    43. std::cout << "connect success" << std::endl; // 到这链接成功了
    44. while (true)
    45. {
    46. std::string line;
    47. std::cout << "请输入# ";
    48. std::getline(std::cin, line);
    49. send(sock, line.c_str(), line.size(), 0); // 比write就多了后面的0,效果一样
    50. char buffer[1024];
    51. ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0); // 比read就多了后面的0,效果一样
    52. if (s > 0)
    53. {
    54. buffer[s] = 0;
    55. std::cout << "server 回显# " << buffer << std::endl;
    56. }
    57. else // 关闭链接或者读取失败
    58. {
    59. break;
    60. }
    61. }
    62. close(sock);
    63. return 0;
    64. }

    编译运行:


    1.5 多进程版strat代码改进+多线程

    直接放代码:

    1. void start()
    2. {
    3. // 下面父进程的阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    4. signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    5. while(true)
    6. {
    7. // 4. 获取连接
    8. struct sockaddr_in src;
    9. socklen_t len = sizeof(src);
    10. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    11. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    12. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    13. if(servicesock < 0)
    14. {
    15. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    16. continue;
    17. }
    18. // 获取连接成功了
    19. uint16_t client_port = ntohs(src.sin_port);
    20. std::string client_ip = inet_ntoa(src.sin_addr);
    21. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    22. /*换行符*/servicesock, client_ip.c_str(), client_port);
    23. // 开始进行通信服务
    24. // // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    25. // // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    26. // service(servicesock, client_ip, client_port);
    27. // // 2 version 2.0 -- 多进程版 --- 创建子进程
    28. // // 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢? -> 能
    29. // pid_t id = fork();
    30. // assert(id != -1);
    31. // if(id == 0) // 子进程
    32. // {
    33. // // 子进程会不会继承父进程打开的文件与文件fd呢? -> 会
    34. // // 子进程是来进行提供服务的,需不需要知道监听socket呢? -> 不需要
    35. // close(_listensock); // 关闭自己不需要的套接字
    36. // service(servicesock, client_ip, client_port); // 子进程对新链接提供服务
    37. // exit(0); // 僵尸状态
    38. // }
    39. // // 父进程
    40. // close(servicesock); // 父进程关闭自己不需要的套接字,不关的话文件描述符会越少
    41. // // 如果父进程关闭servicesock,会不会影响子进程?
    42. // // 前面有了signal(SIGCHLD, SIG_IGN); -> 父进程不用等了
    43. // // waitpid(); // 阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    44. // version2.1 -- 多进程版 -- version 2.0 改版
    45. pid_t id = fork();
    46. if(id == 0)
    47. {
    48. // 子进程
    49. close(_listensock);
    50. if(fork() > 0) // 再创建子进程,子进程本身
    51. exit(0); //子进程本身立即退出
    52. // 到这是(孙子进程),是孤儿进程,被OS领养,OS在孤儿进程退出的时候,由OS自动回收孤儿进程
    53. service(servicesock, client_ip, client_port);
    54. exit(0);
    55. }
    56. // 父进程
    57. waitpid(id, nullptr, 0); // 不会阻塞
    58. close(servicesock);
    59. }
    60. }

    还是和1.4 一样的效果,创建进程的成本是很高的,所以再改成多线程版,直接放代码:

    (给Makefile加上-lpthread)

    这是改的部分:

    tcp_server.hpp:

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include // 网络四件套
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include "log.hpp"
    16. static void service(int sock, const std::string &clientip, const uint16_t &clientport)
    17. {
    18. //echo server
    19. char buffer[1024];
    20. while(true)
    21. {
    22. // read && write 可以直接被使用
    23. ssize_t s = read(sock, buffer, sizeof(buffer)-1);
    24. if(s > 0)
    25. {
    26. buffer[s] = 0; // 将发过来的数据当做字符串
    27. std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
    28. }
    29. else if(s == 0) // 对端关闭连接
    30. {
    31. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    32. break;
    33. }
    34. else
    35. {
    36. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    37. break;
    38. }
    39. write(sock, buffer, strlen(buffer));
    40. }
    41. }
    42. class ThreadData
    43. {
    44. public:
    45. int _sock;
    46. std::string _ip;
    47. uint16_t _port;
    48. };
    49. class TcpServer
    50. {
    51. protected:
    52. const static int gbacklog = 20; // listen的第二个参数,现在先不管
    53. static void *threadRoutine(void *args) // 加上static就没this指针了
    54. {
    55. pthread_detach(pthread_self()); // 线程分离
    56. ThreadData *td = static_cast(args);
    57. service(td->_sock, td->_ip, td->_port);
    58. delete td;
    59. return nullptr;
    60. }
    61. public:
    62. TcpServer(uint16_t port, std::string ip="")
    63. :_listensock(-1)
    64. , _port(port)
    65. , _ip(ip)
    66. {}
    67. void initServer()
    68. {
    69. // 1. 创建socket -- 进程和文件
    70. _listensock = socket(AF_INET, SOCK_STREAM, 0); // 域 + 类型 + 0 // UDP第二个参数是SOCK_DGRAM
    71. if(_listensock < 0)
    72. {
    73. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    74. exit(2);
    75. }
    76. logMessage(NORMAL, "create socket success, _listensock: %d", _listensock); // 3
    77. // 2. bind -- 文件 + 网络
    78. struct sockaddr_in local;
    79. memset(&local, 0, sizeof local);
    80. local.sin_family = AF_INET;
    81. local.sin_port = htons(_port);
    82. local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
    83. if(bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
    84. {
    85. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    86. exit(3);
    87. }
    88. // 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接,UDP没这一步
    89. if(listen(_listensock, gbacklog) < 0) // 第一个参数是套接字,第二个参数后面再说
    90. {
    91. logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
    92. exit(4);
    93. }
    94. logMessage(NORMAL, "init server success");
    95. }
    96. void start()
    97. {
    98. // 下面父进程的阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    99. signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    100. while(true)
    101. {
    102. // 4. 获取连接
    103. struct sockaddr_in src;
    104. socklen_t len = sizeof(src);
    105. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    106. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    107. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    108. if(servicesock < 0)
    109. {
    110. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    111. continue;
    112. }
    113. // 获取连接成功了
    114. uint16_t client_port = ntohs(src.sin_port);
    115. std::string client_ip = inet_ntoa(src.sin_addr);
    116. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    117. /*换行符*/servicesock, client_ip.c_str(), client_port);
    118. // 开始进行通信服务
    119. // // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    120. // // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    121. // service(servicesock, client_ip, client_port);
    122. // // 2 version 2.0 -- 多进程版 --- 创建子进程
    123. // // 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢? -> 能
    124. // pid_t id = fork();
    125. // assert(id != -1);
    126. // if(id == 0) // 子进程
    127. // {
    128. // // 子进程会不会继承父进程打开的文件与文件fd呢? -> 会
    129. // // 子进程是来进行提供服务的,需不需要知道监听socket呢? -> 不需要
    130. // close(_listensock); // 关闭自己不需要的套接字
    131. // service(servicesock, client_ip, client_port); // 子进程对新链接提供服务
    132. // exit(0); // 僵尸状态
    133. // }
    134. // // 父进程
    135. // close(servicesock); // 父进程关闭自己不需要的套接字,不关的话文件描述符会越少
    136. // // 如果父进程关闭servicesock,会不会影响子进程?
    137. // // 前面有了signal(SIGCHLD, SIG_IGN); -> 父进程不用等了
    138. // // waitpid(); // 阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    139. // // version2.1 -- 多进程版 -- version 2.0 改版
    140. // pid_t id = fork();
    141. // if(id == 0)
    142. // {
    143. // // 子进程
    144. // close(_listensock);
    145. // if(fork() > 0) // 再创建子进程,子进程本身
    146. // exit(0); //子进程本身立即退出
    147. // // 到这是(孙子进程),是孤儿进程,被OS领养,OS在孤儿进程退出的时候,由OS自动回收孤儿进程
    148. // service(servicesock, client_ip, client_port);
    149. // exit(0);
    150. // }
    151. // // 父进程
    152. // waitpid(id, nullptr, 0); // 不会阻塞
    153. // close(servicesock);
    154. // version 3 --- 多线程版本
    155. // 创建进程的成本是很高的,所以再改成多线程版(不封装了)
    156. ThreadData *td = new ThreadData();
    157. td->_sock = servicesock;
    158. td->_ip = client_ip;
    159. td->_port = client_port;
    160. pthread_t tid;
    161. // 在多线程这里不用(不能)进程关闭特定的文件描述符,因为是共享的
    162. pthread_create(&tid, nullptr, threadRoutine, td); // 到这里写threadRoutine再写ThreadData类
    163. }
    164. }
    165. ~TcpServer()
    166. {}
    167. protected:
    168. uint16_t _port;
    169. std::string _ip;
    170. int _listensock;
    171. };

    编译运行:还是和上面一样的效果


    1.6 线程池版本

    把以前写的线程池和有关的代码拷过来:(只有Task.hpp要改一改)

    Task.hpp

    1. #pragma once
    2. #include
    3. // typedef std::function func_t;
    4. using func_t = std::function<void (int , const std::string &, const uint16_t &, const std::string &)>; // 和上一行一样的效果
    5. class Task
    6. {
    7. public:
    8. Task()
    9. {}
    10. Task(int sock, const std::string ip, uint16_t port, func_t func)
    11. : _sock(sock)
    12. , _ip(ip)
    13. , _port(port)
    14. , _func(func)
    15. {}
    16. void operator ()(const std::string &name)
    17. {
    18. _func(_sock, _ip, _port, name);
    19. }
    20. public:
    21. int _sock;
    22. std::string _ip;
    23. uint16_t _port;
    24. // int type;
    25. func_t _func;
    26. };

    lockGuard.hpp

    1. #pragma once
    2. #include
    3. #include
    4. class Mutex
    5. {
    6. public:
    7. Mutex(pthread_mutex_t* mtx)
    8. :_pmtx(mtx)
    9. {}
    10. void lock()
    11. {
    12. pthread_mutex_lock(_pmtx);
    13. // std::cout << "进行加锁成功" << std::endl;
    14. }
    15. void unlock()
    16. {
    17. pthread_mutex_unlock(_pmtx);
    18. // std::cout << "进行解锁成功" << std::endl;
    19. }
    20. ~Mutex()
    21. {}
    22. protected:
    23. pthread_mutex_t* _pmtx;
    24. };
    25. class lockGuard // RAII风格的加锁方式
    26. {
    27. public:
    28. lockGuard(pthread_mutex_t* mtx) // 因为不是全局的锁,所以传进来,初始化
    29. :_mtx(mtx)
    30. {
    31. _mtx.lock();
    32. }
    33. ~lockGuard()
    34. {
    35. _mtx.unlock();
    36. }
    37. protected:
    38. Mutex _mtx;
    39. };

    thread.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. // typedef std::function fun_t;
    6. typedef void *(*fun_t)(void *); // 定义函数指针->返回值是void*,函数名是fun_t,参数是void*->直接用fun_t
    7. class ThreadData // 线程数据
    8. {
    9. public:
    10. void *_args; // 真实参数
    11. std::string _name; // 名字
    12. };
    13. class Thread // 封装的线程
    14. {
    15. public:
    16. Thread(int num, fun_t callback, void *args)
    17. : _func(callback) // 回调函数
    18. {
    19. char nameBuffer[64];
    20. snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num); // 格式化到nameBuffer
    21. _name = nameBuffer;
    22. _tdata._args = args; // 线程构造时把参数和名字带给线程数据
    23. _tdata._name = _name;
    24. }
    25. void start() // 启动线程
    26. {
    27. pthread_create(&_tid, nullptr, _func, (void*)&_tdata); // 传入线程数据
    28. }
    29. void join() // join自己
    30. {
    31. pthread_join(_tid, nullptr);
    32. }
    33. std::string name() // 返回线程名
    34. {
    35. return _name;
    36. }
    37. ~Thread() // 析构什么也不做
    38. {}
    39. protected:
    40. std::string _name; // 线程名字
    41. pthread_t _tid; // 线程tid
    42. fun_t _func; // 线程要执行的函数
    43. ThreadData _tdata; // 线程数据
    44. };

    threadPool.hpp

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "thread.hpp"
    7. #include "lockGuard.hpp"
    8. const int g_thread_num = 7;
    9. // 线程池->有一批线程,一批任务,有任务push有任务pop,本质是: 生产消费模型
    10. template<class T>
    11. class ThreadPool
    12. {
    13. private:
    14. ThreadPool(int thread_num = g_thread_num)
    15. :_num(thread_num)
    16. {
    17. pthread_mutex_init(&lock, nullptr);
    18. pthread_cond_init(&cond, nullptr);
    19. for(int i = 1; i <= _num; ++i)
    20. {
    21. _threads.push_back(new Thread(i, routine, this));
    22. }
    23. }
    24. ThreadPool(const ThreadPool &other) = delete;
    25. const ThreadPool &operator=(const ThreadPool &other) = delete;
    26. public:
    27. static ThreadPool *getThreadPool(int num = g_thread_num) // 多线程使用单例的过程
    28. {
    29. // 可以有效减少未来必定要进行加锁检测的问题
    30. // 拦截大量的在已经创建好单例的时候,剩余线程请求单例的而直接访问锁的行为
    31. // 如果这里不加if,未来任何一个线程想获取单例,都必须调用getThreadPool接口
    32. // 一定会存在大量的申请和释放锁的行为,这个是无用且浪费资源的
    33. if (nullptr == thread_ptr)
    34. {
    35. lockGuard lockguard(&mutex);
    36. // pthread_mutex_lock(&mutex);
    37. if (nullptr == thread_ptr)
    38. {
    39. thread_ptr = new ThreadPool(num);
    40. }
    41. // pthread_mutex_unlock(&mutex);
    42. }
    43. return thread_ptr;
    44. }
    45. void run() // 1. 线程池的整体启动
    46. {
    47. for (auto &iter : _threads)
    48. {
    49. iter->start();
    50. std::cout << iter->name() << " 启动成功" << std::endl;
    51. }
    52. }
    53. pthread_mutex_t *getMutex()
    54. {
    55. return &lock;
    56. }
    57. bool isEmpty()
    58. {
    59. return _task_queue.empty();
    60. }
    61. void waitCond() // 特定的条件变量下等待
    62. {
    63. pthread_cond_wait(&cond, &lock);
    64. }
    65. T getTask()
    66. {
    67. T t = _task_queue.front();
    68. _task_queue.pop();
    69. return t;
    70. }
    71. static void *routine(void *args) // 每个线程启动后做的工作
    72. { // 类的成员函数有this指针 -> 两个参数 -> 类型不匹配 -> 所以加static
    73. // 消费过程 -> 访问_task_queue -> 静态访问不了 -> 构造函数传this指针
    74. ThreadData *td = (ThreadData *)args;
    75. ThreadPool *tp = (ThreadPool *)td->_args;
    76. while (true)
    77. {
    78. T task;
    79. {
    80. lockGuard lockguard(tp->getMutex()); // 出花括号自动调用析构,花括号里的接口全是加锁的
    81. while (tp->isEmpty()) // 空就等待
    82. {
    83. tp->waitCond();
    84. }
    85. // 任务队列不为空,读取任务
    86. task = tp->getTask(); // 是共享的-> 将任务从共享,拿到自己的私有空间
    87. }
    88. task(td->_name); // 告诉哪一个线程去处理这个任务就行了
    89. }
    90. }
    91. void pushTask(const T &task) // 2. 任务到来时 -> push进线程池 -> 处理任务
    92. {
    93. lockGuard lockguard(&lock); // 加锁,执行完这个函数自动解锁
    94. _task_queue.push(task); // 生产一个任务
    95. pthread_cond_signal(&cond); // 唤醒一个线程
    96. }
    97. // void joins()
    98. // {
    99. // for (auto &iter : _threads)
    100. // {
    101. // iter->join();
    102. // }
    103. // }
    104. ~ThreadPool()
    105. {
    106. for (auto &iter : _threads)
    107. {
    108. // iter->join();
    109. delete iter;
    110. }
    111. pthread_mutex_destroy(&lock);
    112. pthread_cond_destroy(&cond);
    113. }
    114. protected:
    115. std::vector _threads; // 保存一堆线程的容器
    116. int _num; // 线程的数量
    117. std::queue _task_queue; // 任务队列
    118. pthread_mutex_t lock;
    119. pthread_cond_t cond;
    120. static ThreadPool *thread_ptr; // 懒汉模式的单例对象指针
    121. static pthread_mutex_t mutex; // 单例对象的锁
    122. };
    123. template <typename T>
    124. ThreadPool *ThreadPool::thread_ptr = nullptr; // 定义初始化为空
    125. template <typename T>
    126. pthread_mutex_t ThreadPool::mutex = PTHREAD_MUTEX_INITIALIZER; // 定义锁

    多个回调任务

    改下tcp_server.hpp:

    tcp_server.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include // 网络四件套
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include "log.hpp"
    17. #include "threadPool.hpp"
    18. #include "Task.hpp"
    19. // static void service(int sock, const std::string &clientip, const uint16_t &clientport)
    20. // {
    21. // //echo server
    22. // char buffer[1024];
    23. // while(true)
    24. // {
    25. // // read && write 可以直接被使用
    26. // ssize_t s = read(sock, buffer, sizeof(buffer)-1);
    27. // if(s > 0)
    28. // {
    29. // buffer[s] = 0; // 将发过来的数据当做字符串
    30. // std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
    31. // }
    32. // else if(s == 0) // 对端关闭连接
    33. // {
    34. // logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    35. // break;
    36. // }
    37. // else
    38. // {
    39. // logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    40. // break;
    41. // }
    42. // write(sock, buffer, strlen(buffer));
    43. // }
    44. // }
    45. static void service(int sock, const std::string &clientip,
    46. const uint16_t &clientport, const std::string &thread_name) // 带上线程名的service
    47. {
    48. // echo server
    49. char buffer[1024];
    50. while (true)
    51. {
    52. // read && write 可以直接被使用
    53. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    54. if (s > 0)
    55. {
    56. buffer[s] = 0; // 将发过来的数据当做字符串
    57. std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
    58. }
    59. else if (s == 0) // 对端关闭连接
    60. {
    61. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    62. break;
    63. }
    64. else
    65. {
    66. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    67. break;
    68. }
    69. write(sock, buffer, strlen(buffer));
    70. }
    71. close(sock);
    72. }
    73. // class ThreadData
    74. // {
    75. // public:
    76. // int _sock;
    77. // std::string _ip;
    78. // uint16_t _port;
    79. // };
    80. class TcpServer
    81. {
    82. protected:
    83. const static int gbacklog = 20; // listen的第二个参数,现在先不管
    84. // static void *threadRoutine(void *args) // 加上static就没this指针了
    85. // {
    86. // pthread_detach(pthread_self()); // 线程分离
    87. // ThreadData *td = static_cast(args);
    88. // service(td->_sock, td->_ip, td->_port);
    89. // delete td;
    90. // return nullptr;
    91. // }
    92. public:
    93. TcpServer(uint16_t port, std::string ip="")
    94. :_listensock(-1)
    95. , _port(port)
    96. , _ip(ip)
    97. , _threadpool_ptr(ThreadPool::getThreadPool())
    98. {}
    99. void initServer()
    100. {
    101. // 1. 创建socket -- 进程和文件
    102. _listensock = socket(AF_INET, SOCK_STREAM, 0); // 域 + 类型 + 0 // UDP第二个参数是SOCK_DGRAM
    103. if(_listensock < 0)
    104. {
    105. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    106. exit(2);
    107. }
    108. logMessage(NORMAL, "create socket success, _listensock: %d", _listensock); // 3
    109. // 2. bind -- 文件 + 网络
    110. struct sockaddr_in local;
    111. memset(&local, 0, sizeof local);
    112. local.sin_family = AF_INET;
    113. local.sin_port = htons(_port);
    114. local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
    115. if(bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
    116. {
    117. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    118. exit(3);
    119. }
    120. // 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接,UDP没这一步
    121. if(listen(_listensock, gbacklog) < 0) // 第一个参数是套接字,第二个参数后面再说
    122. {
    123. logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
    124. exit(4);
    125. }
    126. logMessage(NORMAL, "init server success");
    127. }
    128. void start()
    129. {
    130. // 下面父进程的阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    131. // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    132. _threadpool_ptr->run();
    133. while(true)
    134. {
    135. // 4. 获取连接
    136. struct sockaddr_in src;
    137. socklen_t len = sizeof(src);
    138. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    139. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    140. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    141. if(servicesock < 0)
    142. {
    143. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    144. continue;
    145. }
    146. // 获取连接成功了
    147. uint16_t client_port = ntohs(src.sin_port);
    148. std::string client_ip = inet_ntoa(src.sin_addr);
    149. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    150. /*换行符*/servicesock, client_ip.c_str(), client_port);
    151. // 开始进行通信服务
    152. // // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    153. // // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    154. // service(servicesock, client_ip, client_port);
    155. // // 2 version 2.0 -- 多进程版 --- 创建子进程
    156. // // 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢? -> 能
    157. // pid_t id = fork();
    158. // assert(id != -1);
    159. // if(id == 0) // 子进程
    160. // {
    161. // // 子进程会不会继承父进程打开的文件与文件fd呢? -> 会
    162. // // 子进程是来进行提供服务的,需不需要知道监听socket呢? -> 不需要
    163. // close(_listensock); // 关闭自己不需要的套接字
    164. // service(servicesock, client_ip, client_port); // 子进程对新链接提供服务
    165. // exit(0); // 僵尸状态
    166. // }
    167. // // 父进程
    168. // close(servicesock); // 父进程关闭自己不需要的套接字,不关的话文件描述符会越少
    169. // // 如果父进程关闭servicesock,会不会影响子进程?
    170. // // 前面有了signal(SIGCHLD, SIG_IGN); -> 父进程不用等了
    171. // // waitpid(); // 阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    172. // // version2.1 -- 多进程版 -- version 2.0 改版
    173. // pid_t id = fork();
    174. // if(id == 0)
    175. // {
    176. // // 子进程
    177. // close(_listensock);
    178. // if(fork() > 0) // 再创建子进程,子进程本身
    179. // exit(0); //子进程本身立即退出
    180. // // 到这是(孙子进程),是孤儿进程,被OS领养,OS在孤儿进程退出的时候,由OS自动回收孤儿进程
    181. // service(servicesock, client_ip, client_port);
    182. // exit(0);
    183. // }
    184. // // 父进程
    185. // waitpid(id, nullptr, 0); // 不会阻塞
    186. // close(servicesock);
    187. // // version 3 --- 多线程版本
    188. // // 创建进程的成本是很高的,所以再改成多线程版(不封装了)
    189. // ThreadData *td = new ThreadData();
    190. // td->_sock = servicesock;
    191. // td->_ip = client_ip;
    192. // td->_port = client_port;
    193. // pthread_t tid;
    194. // // 在多线程这里不用(不能)进程关闭特定的文件描述符,因为是共享的
    195. // pthread_create(&tid, nullptr, threadRoutine, td); // 到这里写threadRoutine再写ThreadData类
    196. // verison4 --- 线程池版本
    197. Task t(servicesock, client_ip, client_port, service);
    198. _threadpool_ptr->pushTask(t);
    199. }
    200. }
    201. ~TcpServer()
    202. {}
    203. protected:
    204. uint16_t _port;
    205. std::string _ip;
    206. int _listensock;
    207. std::unique_ptr> _threadpool_ptr;
    208. };

    编译运行

    完成了多个线程实现回显任务的情景。下面写一个回调的函数,实现小写字母转大写字母:

    1. static void change(int sock, const std::string &clientip,
    2. const uint16_t &clientport, const std::string &thread_name)
    3. {
    4. // 一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    5. char buffer[1024];
    6. // read && write 可以直接被使用
    7. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    8. if (s > 0)
    9. {
    10. buffer[s] = 0; // 将发过来的数据当做字符串
    11. std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
    12. std::string message;
    13. char *start = buffer;
    14. while(*start)
    15. {
    16. char c;
    17. if(islower(*start))
    18. {
    19. c = toupper(*start);
    20. }
    21. else
    22. {
    23. c = *start;
    24. }
    25. message.push_back(c);
    26. ++start;
    27. }
    28. write(sock, message.c_str(), message.size());
    29. }
    30. else if (s == 0) // 对端关闭连接
    31. {
    32. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    33. }
    34. else
    35. {
    36. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    37. }
    38. close(sock);
    39. }

    只需要改一下回调方法:

    把client.cc改成循环的:

    tcp_client.cc

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void usage(std::string proc)
    11. {
    12. std::cout << "\nUsage: " << proc << " serverIp serverPort\n" << std::endl;
    13. }
    14. // ./tcp_client targetIp targetPort
    15. int main(int argc, char *argv[])
    16. {
    17. if (argc != 3)
    18. {
    19. usage(argv[0]);
    20. exit(1);
    21. }
    22. std::string serverip = argv[1];
    23. uint16_t serverport = atoi(argv[2]);
    24. bool alive = false;
    25. int sock = 0;
    26. std::string line;
    27. while (true) // 客户端不断的链接
    28. {
    29. if (!alive)
    30. {
    31. sock = socket(AF_INET, SOCK_STREAM, 0);
    32. if (sock < 0)
    33. {
    34. std::cerr << "socket error" << std::endl;
    35. exit(2);
    36. }
    37. // client 要不要bind呢?不需要显示的bind,但是一定是需要port
    38. // 需要让os自动进行port选择,客户端需要连接别人的能力 -> connect
    39. struct sockaddr_in server;
    40. memset(&server, 0, sizeof(server)); // 清零
    41. server.sin_family = AF_INET;
    42. server.sin_port = htons(serverport);
    43. server.sin_addr.s_addr = inet_addr(serverip.c_str());
    44. if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) // 比UDP多了这一步
    45. {
    46. std::cerr << "connect error" << std::endl;
    47. exit(3);
    48. }
    49. std::cout << "connect success" << std::endl; // 到这链接成功了
    50. alive = true;
    51. }
    52. std::cout << "请输入# ";
    53. std::getline(std::cin, line);
    54. if (line == "quit")
    55. break;
    56. ssize_t s = send(sock, line.c_str(), line.size(), 0); // 比write就多了后面的0,效果一样
    57. if (s > 0) // ssize_t有符号的整数
    58. {
    59. char buffer[1024];
    60. ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0); // 比read就多了后面的0,效果一样
    61. if (s > 0)
    62. {
    63. buffer[s] = 0;
    64. std::cout << "server 回显# " << buffer << std::endl;
    65. }
    66. else if (s == 0)
    67. {
    68. alive = false;
    69. close(sock);
    70. }
    71. }
    72. else // 关闭链接或者读取失败
    73. {
    74. alive = false;
    75. close(sock);
    76. }
    77. }
    78. return 0;
    79. }
    80. /*
    81. int main(int argc, char *argv[])
    82. {
    83. if (argc != 3)
    84. {
    85. usage(argv[0]);
    86. exit(1);
    87. }
    88. std::string serverip = argv[1];
    89. uint16_t serverport = atoi(argv[2]);
    90. int sock = 0;
    91. sock = socket(AF_INET, SOCK_STREAM, 0);
    92. if (sock < 0)
    93. {
    94. std::cerr << "socket error" << std::endl;
    95. exit(2);
    96. }
    97. // client 要不要bind呢?不需要显示的bind,但是一定是需要port
    98. // 需要让os自动进行port选择,客户端需要连接别人的能力 -> connect
    99. struct sockaddr_in server;
    100. memset(&server, 0, sizeof(server)); // 清零
    101. server.sin_family = AF_INET;
    102. server.sin_port = htons(serverport);
    103. server.sin_addr.s_addr = inet_addr(serverip.c_str());
    104. if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) // 比UDP多了这一步
    105. {
    106. std::cerr << "connect error" << std::endl;
    107. exit(3);
    108. }
    109. std::cout << "connect success" << std::endl; // 到这链接成功了
    110. while (true)
    111. {
    112. std::string line;
    113. std::cout << "请输入# ";
    114. std::getline(std::cin, line);
    115. send(sock, line.c_str(), line.size(), 0); // 比write就多了后面的0,效果一样
    116. char buffer[1024];
    117. ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0); // 比read就多了后面的0,效果一样
    118. if (s > 0)
    119. {
    120. buffer[s] = 0;
    121. std::cout << "server 回显# " << buffer << std::endl;
    122. }
    123. else // 关闭链接或者读取失败
    124. {
    125. break;
    126. }
    127. }
    128. close(sock);
    129. return 0;
    130. }
    131. */

    编译运行:

    此时链接的时候没有把回显信息打印出来,不过先不改了。下面再弄一个类似英汉互译的:

    1. static void dictOnline(int sock, const std::string &clientip,
    2. const uint16_t &clientport, const std::string &thread_name)
    3. {
    4. // 一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    5. char buffer[1024];
    6. static std::unordered_map dict = {
    7. {"apple", "苹果"},
    8. {"watermelon", "西瓜"},
    9. {"banana", "香蕉"},
    10. {"hard", "好难"}
    11. };
    12. // read && write 可以直接被使用
    13. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    14. if (s > 0)
    15. {
    16. buffer[s] = 0; // 将发过来的数据当做字符串
    17. std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
    18. std::string message;
    19. auto iter = dict.find(buffer);
    20. if(iter == dict.end())
    21. message = "此字典找不到...";
    22. else
    23. message = iter->second;
    24. write(sock, message.c_str(), message.size());
    25. }
    26. else if (s == 0) // 对端关闭连接
    27. {
    28. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    29. }
    30. else
    31. {
    32. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    33. }
    34. close(sock);
    35. }

    编译运行:

    完成运行,为了方便这里再放下tcp_server.hpp。

    tcp_server.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include // 网络四件套
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include "log.hpp"
    17. #include "threadPool.hpp"
    18. #include "Task.hpp"
    19. #include
    20. // static void service(int sock, const std::string &clientip, const uint16_t &clientport)
    21. // {
    22. // //echo server
    23. // char buffer[1024];
    24. // while(true)
    25. // {
    26. // // read && write 可以直接被使用
    27. // ssize_t s = read(sock, buffer, sizeof(buffer)-1);
    28. // if(s > 0)
    29. // {
    30. // buffer[s] = 0; // 将发过来的数据当做字符串
    31. // std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
    32. // }
    33. // else if(s == 0) // 对端关闭连接
    34. // {
    35. // logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    36. // break;
    37. // }
    38. // else
    39. // {
    40. // logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    41. // break;
    42. // }
    43. // write(sock, buffer, strlen(buffer));
    44. // }
    45. // }
    46. static void service(int sock, const std::string &clientip,
    47. const uint16_t &clientport, const std::string &thread_name) // 带上线程名的service
    48. {
    49. // echo server
    50. char buffer[1024];
    51. while (true)
    52. {
    53. // read && write 可以直接被使用
    54. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    55. if (s > 0)
    56. {
    57. buffer[s] = 0; // 将发过来的数据当做字符串
    58. std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
    59. }
    60. else if (s == 0) // 对端关闭连接
    61. {
    62. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    63. break;
    64. }
    65. else
    66. {
    67. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    68. break;
    69. }
    70. write(sock, buffer, strlen(buffer));
    71. }
    72. close(sock);
    73. }
    74. static void change(int sock, const std::string &clientip,
    75. const uint16_t &clientport, const std::string &thread_name)
    76. {
    77. // 一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    78. char buffer[1024];
    79. // read && write 可以直接被使用
    80. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    81. if (s > 0)
    82. {
    83. buffer[s] = 0; // 将发过来的数据当做字符串
    84. std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
    85. std::string message;
    86. char *start = buffer;
    87. while(*start)
    88. {
    89. char c;
    90. if(islower(*start))
    91. {
    92. c = toupper(*start);
    93. }
    94. else
    95. {
    96. c = *start;
    97. }
    98. message.push_back(c);
    99. ++start;
    100. }
    101. write(sock, message.c_str(), message.size());
    102. }
    103. else if (s == 0) // 对端关闭连接
    104. {
    105. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    106. }
    107. else
    108. {
    109. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    110. }
    111. close(sock);
    112. }
    113. static void dictOnline(int sock, const std::string &clientip,
    114. const uint16_t &clientport, const std::string &thread_name)
    115. {
    116. // 一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    117. char buffer[1024];
    118. static std::unordered_map dict = {
    119. {"apple", "苹果"},
    120. {"watermelon", "西瓜"},
    121. {"banana", "香蕉"},
    122. {"hard", "好难"}
    123. };
    124. // read && write 可以直接被使用
    125. ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    126. if (s > 0)
    127. {
    128. buffer[s] = 0; // 将发过来的数据当做字符串
    129. std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
    130. std::string message;
    131. auto iter = dict.find(buffer);
    132. if(iter == dict.end())
    133. message = "此字典找不到...";
    134. else
    135. message = iter->second;
    136. write(sock, message.c_str(), message.size());
    137. }
    138. else if (s == 0) // 对端关闭连接
    139. {
    140. logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
    141. }
    142. else
    143. {
    144. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    145. }
    146. close(sock);
    147. }
    148. // class ThreadData
    149. // {
    150. // public:
    151. // int _sock;
    152. // std::string _ip;
    153. // uint16_t _port;
    154. // };
    155. class TcpServer
    156. {
    157. protected:
    158. const static int gbacklog = 20; // listen的第二个参数,现在先不管
    159. // static void *threadRoutine(void *args) // 加上static就没this指针了
    160. // {
    161. // pthread_detach(pthread_self()); // 线程分离
    162. // ThreadData *td = static_cast(args);
    163. // service(td->_sock, td->_ip, td->_port);
    164. // delete td;
    165. // return nullptr;
    166. // }
    167. public:
    168. TcpServer(uint16_t port, std::string ip="")
    169. :_listensock(-1)
    170. , _port(port)
    171. , _ip(ip)
    172. , _threadpool_ptr(ThreadPool::getThreadPool())
    173. {}
    174. void initServer()
    175. {
    176. // 1. 创建socket -- 进程和文件
    177. _listensock = socket(AF_INET, SOCK_STREAM, 0); // 域 + 类型 + 0 // UDP第二个参数是SOCK_DGRAM
    178. if(_listensock < 0)
    179. {
    180. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    181. exit(2);
    182. }
    183. logMessage(NORMAL, "create socket success, _listensock: %d", _listensock); // 3
    184. // 2. bind -- 文件 + 网络
    185. struct sockaddr_in local;
    186. memset(&local, 0, sizeof local);
    187. local.sin_family = AF_INET;
    188. local.sin_port = htons(_port);
    189. local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
    190. if(bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
    191. {
    192. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    193. exit(3);
    194. }
    195. // 3. 因为TCP是面向连接的,当正式通信的时候,需要先建立连接,UDP没这一步
    196. if(listen(_listensock, gbacklog) < 0) // 第一个参数是套接字,第二个参数后面再说
    197. {
    198. logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
    199. exit(4);
    200. }
    201. logMessage(NORMAL, "init server success");
    202. }
    203. void start()
    204. {
    205. // 下面父进程的阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    206. // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    207. _threadpool_ptr->run();
    208. while(true)
    209. {
    210. // 4. 获取连接
    211. struct sockaddr_in src;
    212. socklen_t len = sizeof(src);
    213. int servicesock = accept(_listensock, (struct sockaddr*)&src, &len); // accept获取新链接
    214. // servicesock(服务套接字,相当于此小区域专门给你负责的)
    215. // 对比 类内的_sock(类似于小区域外面拉客的,拉来小区域就交给servicesock管了)
    216. if(servicesock < 0)
    217. {
    218. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    219. continue;
    220. }
    221. // 获取连接成功了
    222. uint16_t client_port = ntohs(src.sin_port);
    223. std::string client_ip = inet_ntoa(src.sin_addr);
    224. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
    225. /*换行符*/servicesock, client_ip.c_str(), client_port);
    226. // 开始进行通信服务
    227. // // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
    228. // // 很显然,是不能够直接被使用的 -- 为什么? -> 要从单进程改成多线程
    229. // service(servicesock, client_ip, client_port);
    230. // // 2 version 2.0 -- 多进程版 --- 创建子进程
    231. // // 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢? -> 能
    232. // pid_t id = fork();
    233. // assert(id != -1);
    234. // if(id == 0) // 子进程
    235. // {
    236. // // 子进程会不会继承父进程打开的文件与文件fd呢? -> 会
    237. // // 子进程是来进行提供服务的,需不需要知道监听socket呢? -> 不需要
    238. // close(_listensock); // 关闭自己不需要的套接字
    239. // service(servicesock, client_ip, client_port); // 子进程对新链接提供服务
    240. // exit(0); // 僵尸状态
    241. // }
    242. // // 父进程
    243. // close(servicesock); // 父进程关闭自己不需要的套接字,不关的话文件描述符会越少
    244. // // 如果父进程关闭servicesock,会不会影响子进程?
    245. // // 前面有了signal(SIGCHLD, SIG_IGN); -> 父进程不用等了
    246. // // waitpid(); // 阻塞式等待 -> 用信号代替 -> 子进程退出会像父进程发送SIGCHLD信号
    247. // // version2.1 -- 多进程版 -- version 2.0 改版
    248. // pid_t id = fork();
    249. // if(id == 0)
    250. // {
    251. // // 子进程
    252. // close(_listensock);
    253. // if(fork() > 0) // 再创建子进程,子进程本身
    254. // exit(0); //子进程本身立即退出
    255. // // 到这是(孙子进程),是孤儿进程,被OS领养,OS在孤儿进程退出的时候,由OS自动回收孤儿进程
    256. // service(servicesock, client_ip, client_port);
    257. // exit(0);
    258. // }
    259. // // 父进程
    260. // waitpid(id, nullptr, 0); // 不会阻塞
    261. // close(servicesock);
    262. // // version 3 --- 多线程版本
    263. // // 创建进程的成本是很高的,所以再改成多线程版(不封装了)
    264. // ThreadData *td = new ThreadData();
    265. // td->_sock = servicesock;
    266. // td->_ip = client_ip;
    267. // td->_port = client_port;
    268. // pthread_t tid;
    269. // // 在多线程这里不用(不能)进程关闭特定的文件描述符,因为是共享的
    270. // pthread_create(&tid, nullptr, threadRoutine, td); // 到这里写threadRoutine再写ThreadData类
    271. // verison4 --- 线程池版本
    272. // Task t(servicesock, client_ip, client_port, service);
    273. // Task t(servicesock, client_ip, client_port, change);
    274. Task t(servicesock, client_ip, client_port, dictOnline);
    275. _threadpool_ptr->pushTask(t);
    276. }
    277. }
    278. ~TcpServer()
    279. {}
    280. protected:
    281. uint16_t _port;
    282. std::string _ip;
    283. int _listensock;
    284. std::unique_ptr> _threadpool_ptr;
    285. };

    2. 笔试选择题

    1. 在网络字节序中,所谓”小端”(little endian)说法正确的是( )

    A.高字节数据存放在低地址处,低字节数据存放在高地址处

    B.低字节位数据存放在内存低地址处, 高字节位数据存放在内存高地址处

    C.和编译器相关

    D.上述答案都不正确

    2. 当一个UDP报文道达目的主机时,操作系统使用(  )选择正确的socket.

    A.源IP地址

    B.源端口号

    C.目的端口号

    D.目的IP地址

    3. 以下有关于端口号的说法错误的是()

    A.tcp的最大端口号是65535

    B.端口号是一个2字节16位的整数

    C.IP地址 + 端口号能够标识网络上的某一台主机的某一个进程

    D.一个进程至多只能绑定一个端口

    4. 【多选题】socket编程中经常需要进行字节序列的转换,下列哪几个函数是将网络字节序列转换为主机字节序列?()

    A.htons

    B.ntohs

    C.htonl

    D.ntohl

    5. 【多选题】Socket,即套接字,是一个对 TCP / IP协议进行封装 的编程调用接口。socket的使用类型主要有()

    A.基于TCP协议,采用流方式,提供可靠的字节流服务

    B.基于IP协议,采用流数据提供数据网络发送服务

    C.基于HTTP协议,采用数据包方式提供可靠的数据包装服务

    D.基于UDP协议,采用数据报方式提供数据打包发送服务

    6. 下列有关Socket的说法,错误的是()

    A.Socket用于描述IP地址和端口,是一个通信链的句柄

    B.Socket通信必须建立连接

    C.Socket客户端的端口是不固定的

    D.Socket服务端的端口是固定的

    答案及解析

    1. B

    小端:低字节位数据存放在内存低地址处, 高字节位数据存放在内存高地址处

    大端:高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址

    网络字节序为大端字节序

    2. C

    IP地址用于标识主机,端口号用于标识主机上的对应网络通信socket

    因此正确选项为C选项,通过目的端口号来区分是哪个socket

    3. D

    端口号是一个无符号16位的整数,用于表示主机上的网络通信socket,因为是16位,因此最大端口号是65535(从0开始)

    IP地址可以在网络当中标识一台主机,端口号可以在主机当中标识一个进程(socket会关联到对应进程)

    一个进程当中对于端口的绑定是和socket强相关,理论上该进程如果创建多个socket,可以给每一个socket都进行绑定一个端口

    基于以上理解,错误选项为:D (一个进程可能会创建多个socket,因此有可能会绑定多个端口)

    4. BD

    函数名称解析:

    • n 对应是 network
    • h 对应是 host
    • s 对应是 short
    • l 对应是 long

    因此网络字节序到主机字节序的转换是 ntoh

    5. AD

    TCP协议,采用流方式,SOCK_STREAM, 可靠

    UDP协议,采用数据报方式,SOCK_DGRAM, 不可靠

    HTTP协议是应用层协议,在传输层基于TCP协议实现, (可靠是TCP提供的,TCP提供字节流传输)

    IP协议是网络层协议,TCP和UDP协议在网络层都是基于IP协议的。(实现数据报传输)

    6. B

    A正确:概念性理解,socket就是一条通信的句柄

    B错误:socket 可以基于TCP 面向连接 也可以基于UDP无连接

    C正确:客户端的端口我们推荐是不主动绑定策略,这样可以尽可能的避免端口冲突,让系统选择合适端口绑定,因此不固定

    D正确:服务端的端口必须是固定的,因为总是客户端先请求服务端,因此必须提前获知服务端地址端口信息,但是一旦服务器端端口改变,会造成之前的客户端的信息失效找不到服务端了


    本篇完。

    下一篇:网络和Linux网络_4(应用层)序列化和反序列化(网络计算器)。

  • 相关阅读:
    计算机网络-应用层(1)
    第15章 基于规格说明的测试技术 15.1 - 基于规格说明的测试的概述 15.2 - 测试用例设计方法
    Selenium实战案例之爬取js加密数据
    【Transformers】第 2 章:主题的实践介绍
    内卷下的智能投影行业,未来何去何从?
    【开发教程3】开源蓝牙心率防水运动手环-开发环境搭建
    使用扩散模型从文本生成图像
    Rails进阶——框架理论认知与构建方案建设(一)
    uniapp android 原生插件开发-测试流程
    环境温湿度在线监测如何实现?有何应用场景?
  • 原文地址:https://blog.csdn.net/GRrtx/article/details/133365173