• Linux网络套接字之TCP网络程序


     (。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

    点个关注不迷路⌯'▾'⌯

    目录

    一、接口介绍

    1.socket

    2.listen

    3.accept

    3.connect

    4.send

    二.基础代码

    1.tcp_server

    2.tcp_server.hpp

     3.tcp_client

    4.结果

    三、多进程版本

    四、多线程版本

    五、线程池版本

    六、变成小写转换大写的方法


    一、接口介绍

    1.socket

    不做过多介绍,前文有

    SOCK_STREM,TCP用这个

    2.listen

    1. #include /* See NOTES */
    2. #include
    3. int listen(int sockfd, int backlog);

    将TCP套接字设置成监听状态

    • 参数一:创建好的套接字
    • 参数二:目前无法理解,

    3.accept

    1. #include /* See NOTES */
    2. #include
    3. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    获取链接

    • 参数一:套接字
    • 参数二:输出型参数
    • 参数三:输出输入型参数
    • 返回值:成功返回成功的套接字,失败则返回-1

    3.connect

    1. #include /* See NOTES */
    2. #include
    3. int connect(int sockfd, const struct sockaddr *addr,
    4. socklen_t addrlen);

    根据创建好的套接字,让客户端具有连接服务器的能力,会自动绑客户端的ip和端口

    • 参数一:套接字
    • 参数二:输出型参数
    • 参数三:输入型参数

    4.send

    1. #include
    2. #include
    3. ssize_t send(int sockfd, const void *buf, size_t len, int flags);

    基于TCP向目标返回消息

    参数四:一般为0

    5.recv

    1. #include
    2. #include
    3. ssize_t recv(int sockfd, void *buf, size_t len, int flags);

    基于TCP来读消息

    参数四:一般为0

    二.基础代码

    1.tcp_server

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

    2.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 &client_ip, const uint16_t &client_port)
    15. {
    16. // echo server
    17. char buffer[1024];
    18. while (1)
    19. {
    20. // 先读取
    21. // read和write可以直接被使用,套接字也就是文件描述符
    22. ssize_t s = read(sock, &buffer, sizeof buffer);
    23. if (s > 0)
    24. {
    25. buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
    26. std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
    27. }
    28. else if (s = 0) // 对端关闭连接
    29. {
    30. logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
    31. break;
    32. }
    33. else // 读取失败
    34. {
    35. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    36. break;
    37. }
    38. write(sock, &buffer, sizeof buffer);
    39. }
    40. }
    41. class TcpServer
    42. {
    43. const static int g_backlog = 20; // 不能太大也不能太小
    44. public:
    45. TcpServer(uint16_t port, std::string ip = "")
    46. : port_(port), ip_(ip), listensock_(-1)
    47. {
    48. }
    49. void initServer()
    50. {
    51. // 1.创建套接字
    52. listensock_ = socket(AF_INET, SOCK_STREAM, 0);
    53. if (listensock_ < 0)
    54. {
    55. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    56. exit(2);
    57. }
    58. logMessage(NORMAL, "create socket success, listensock_: %d", listensock_);
    59. // 2.bind,将服务器和端口信息进行绑定
    60. struct sockaddr_in local;
    61. memset(&local, 0, sizeof(local));
    62. local.sin_family = AF_INET;
    63. local.sin_port = htons(port_);
    64. local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
    65. if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
    66. {
    67. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    68. exit(3);
    69. }
    70. // 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
    71. // 3.开始监听
    72. if (listen(listensock_, g_backlog) < 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. signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    82. while (1)
    83. {
    84. // 4.获取连接,服务器要获取客户端的连接,这样来方便返回
    85. struct sockaddr_in src;
    86. socklen_t len = sizeof(src);
    87. // listensock_和listensock_有什么区别呢?
    88. // 其中listensock_通过这个套接字只是为了把底层的连接获取上来
    89. // 而listensock_才是真正的进行连接的
    90. int listensock = accept(listensock_, (struct sockaddr *)&src, &len);
    91. if (listensock < 0)
    92. {
    93. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    94. continue;
    95. }
    96. // 获取成功了
    97. uint16_t client_port = ntohs(src.sin_port);
    98. std::string client_ip = inet_ntoa(src.sin_addr);
    99. logMessage(NORMAL, "link success, listensock_: %d | %s : %d |\n",
    100. listensock, client_ip.c_str(), client_port);
    101. // 开始进行通信
    102. // 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
    103. //这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
    104. //service(listensock_, client_ip, client_port);
    105. //多进程版本
    106. //创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
    107. //是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
    108. pid_t id= fork();
    109. assert(id!=-1);
    110. if(id==0)//子进程
    111. {
    112. //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
    113. //子进程是来提供服务的,需不需要监听socket呢?
    114. service(listensock_, client_ip, client_port);
    115. close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
    116. exit(0);//子进程退出会造成僵尸进程
    117. }
    118. close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
    119. //父进程
    120. //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
    121. }
    122. }
    123. ~TcpServer()
    124. {
    125. }
    126. private:
    127. uint16_t port_;
    128. std::string ip_;
    129. int listensock_;
    130. };

     3.tcp_client

    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"
    13. << std::endl;
    14. }
    15. int main(int argc, char *argv[])
    16. {
    17. if (argc != 3)
    18. {
    19. usage(argv[0]);
    20. exit(1);
    21. }
    22. // 获取ip和port
    23. std::string ip = argv[1];
    24. uint16_t port = atoi(argv[2]);
    25. // 创建套接字
    26. int sock = socket(AF_INET, SOCK_STREAM, 0);
    27. if (sock < 0)
    28. {
    29. std::cerr << "socket error" << std::endl;
    30. exit(2);
    31. }
    32. // client是不需要显示bind的,因为如果bind了就是一个非常具体的端口号了,多个客户端可能会出现冲突,可能会导致启动失败,所以不需要显示的bind
    33. // 让OS自动选择
    34. // 但是客户端必须拥有连接的能力
    35. struct sockaddr_in server;
    36. memset(&server, 0, sizeof(server));
    37. server.sin_family = AF_INET;
    38. server.sin_port = ntohs(port);
    39. server.sin_addr.s_addr = inet_addr(ip.c_str());
    40. if (connect(sock, (struct sockaddr *)&server, sizeof(server)))
    41. {
    42. std::cerr << "connect error" << std::endl;
    43. exit(3); // TODO
    44. }
    45. std::cout << "connect success" << std::endl;
    46. //连接成功直接通信
    47. while(1)
    48. {
    49. std::string line;
    50. std::cout<< "请输入# ";
    51. std::getline(std::cin,line);
    52. //发送数据
    53. send(sock,line.c_str(),line.size(),0);
    54. char buffer[1024];
    55. ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
    56. if(s>0)
    57. {
    58. buffer[s]=0;
    59. std::cout << "server 回显# " << buffer << std::endl;
    60. }
    61. else if(s==0)
    62. {
    63. break;
    64. }
    65. else
    66. {
    67. break;
    68. }
    69. }
    70. return 0;
    71. }

    4.结果

    可以看到我们的server是由两个的,一个是子进程一个是父进程

    三、多进程版本

    上面的其实已经是多进程版本的了,但是我们使用的signal信号来实现子进程退出的,下面我们用另一种方式来实现

    为什么我们创建了子进程之后还要再创建一下呢?这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程,会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!

    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 "log.hpp"
    15. static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
    16. {
    17. // echo server
    18. char buffer[1024];
    19. while (1)
    20. {
    21. // 先读取
    22. // read和write可以直接被使用,套接字也就是文件描述符
    23. ssize_t s = read(sock, &buffer, sizeof buffer);
    24. if (s > 0)
    25. {
    26. buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
    27. std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
    28. }
    29. else if (s = 0) // 对端关闭连接
    30. {
    31. logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
    32. break;
    33. }
    34. else // 读取失败
    35. {
    36. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    37. break;
    38. }
    39. write(sock, &buffer, sizeof buffer);
    40. }
    41. }
    42. class TcpServer
    43. {
    44. const static int g_backlog = 20; // 不能太大也不能太小
    45. public:
    46. TcpServer(uint16_t port, std::string ip = "")
    47. : port_(port), ip_(ip), listensock_(-1)
    48. {
    49. }
    50. void initServer()
    51. {
    52. // 1.创建套接字
    53. listensock_ = socket(AF_INET, SOCK_STREAM, 0);
    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_);
    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)))
    67. {
    68. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    69. exit(3);
    70. }
    71. // 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
    72. // 3.开始监听
    73. if (listen(listensock_, g_backlog) < 0)
    74. {
    75. logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
    76. exit(4);
    77. }
    78. logMessage(NORMAL, "init server success");
    79. }
    80. void start()
    81. {
    82. // signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    83. while (1)
    84. {
    85. // 4.获取连接,服务器要获取客户端的连接,这样来方便返回
    86. struct sockaddr_in src;
    87. socklen_t len = sizeof(src);
    88. // listensock_和servicesock有什么区别呢?
    89. // 其中listensock_通过这个套接字只是为了把底层的连接获取上来
    90. // 而servicesock才是真正的进行连接的
    91. int servicesock = accept(listensock_, (struct sockaddr *)&src, &len);
    92. if (servicesock < 0)
    93. {
    94. logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
    95. continue;
    96. }
    97. // 获取成功了
    98. uint16_t client_port = ntohs(src.sin_port);
    99. std::string client_ip = inet_ntoa(src.sin_addr);
    100. logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",
    101. servicesock, client_ip.c_str(), client_port);
    102. // 开始进行通信
    103. // 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
    104. // 这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
    105. // service(listensock_, client_ip, client_port);
    106. // 多进程版本
    107. // 创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
    108. // 是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
    109. // pid_t id= fork();
    110. // assert(id!=-1);
    111. // if(id==0)//子进程
    112. // {
    113. // //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
    114. // //子进程是来提供服务的,需不需要监听socket呢?
    115. // service(listensock_, client_ip, client_port);
    116. // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
    117. // exit(0);//子进程退出会造成僵尸进程
    118. // }
    119. // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
    120. // //父进程
    121. // //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
    122. // 2.2多进程版本,不用signal
    123. pid_t id = fork();
    124. if (id == 0)
    125. {
    126. if (fork() > 0)//这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程
    127. //会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
    128. {
    129. close(listensock_);
    130. exit(0);
    131. service(servicesock, client_ip, client_port);
    132. exit(0);
    133. }
    134. }
    135. // 父进程
    136. waitpid(id, nullptr, 0);
    137. close(servicesock);
    138. }
    139. }
    140. ~TcpServer()
    141. {
    142. }
    143. private:
    144. uint16_t port_;
    145. std::string ip_;
    146. int listensock_;
    147. };

    四、多线程版本

    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 &client_ip, const uint16_t &client_port)
    17. {
    18. // echo server
    19. char buffer[1024];
    20. while (1)
    21. {
    22. // 先读取
    23. // read和write可以直接被使用,套接字也就是文件描述符
    24. ssize_t s = read(sock, &buffer, sizeof buffer);
    25. if (s > 0)
    26. {
    27. buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
    28. std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
    29. }
    30. else if (s = 0) // 对端关闭连接
    31. {
    32. logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
    33. break;
    34. }
    35. else // 读取失败
    36. {
    37. logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    38. break;
    39. }
    40. write(sock, &buffer, sizeof buffer);
    41. }
    42. }
    43. class ThreadData
    44. {
    45. public:
    46. int sock_;
    47. std::string ip_;
    48. uint16_t port_;
    49. };
    50. class TcpServer
    51. {
    52. const static int g_backlog = 20; // 不能太大也不能太小
    53. static void* threadRoutine(void* args)//这里我们要让新线程去干事情,肯定需要套接字,但是上面已经写了所以我们直接调用这个函数就可以了
    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. : port_(port), ip_(ip), listensock_(-1)
    64. {
    65. }
    66. void initServer()
    67. {
    68. // 1.创建套接字
    69. listensock_ = socket(AF_INET, SOCK_STREAM, 0);
    70. if (listensock_ < 0)
    71. {
    72. logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
    73. exit(2);
    74. }
    75. logMessage(NORMAL, "create socket success, listensock_: %d", listensock_);
    76. // 2.bind,将服务器和端口信息进行绑定
    77. struct sockaddr_in local;
    78. memset(&local, 0, sizeof(local));
    79. local.sin_family = AF_INET;
    80. local.sin_port = htons(port_);
    81. local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
    82. if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
    83. {
    84. logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
    85. exit(3);
    86. }
    87. // 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
    88. // 3.开始监听
    89. if (listen(listensock_, g_backlog) < 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. // signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
    99. while (1)
    100. {
    101. // 4.获取连接,服务器要获取客户端的连接,这样来方便返回
    102. struct sockaddr_in src;
    103. socklen_t len = sizeof(src);
    104. // listensock_和servicesock有什么区别呢?
    105. // 其中listensock_通过这个套接字只是为了把底层的连接获取上来
    106. // 而servicesock才是真正的进行连接的
    107. int servicesock = accept(listensock_, (struct sockaddr *)&src, &len);
    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. //3.0多线程版本
    119. ThreadData *td = new ThreadData();
    120. td->sock_ = servicesock;
    121. td->ip_= client_ip;
    122. td->port_ = client_port;
    123. pthread_t tid;
    124. //在多线程这里是不用关闭特定的文件描述符的,因为主线程与副线程是共享文件描述符的
    125. pthread_create(&tid,nullptr,threadRoutine,td);
    126. //close(servicesock)//走到最后主线程结束还是要关闭的
    127. // 1.0开始进行通信
    128. // 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
    129. // 这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
    130. // service(listensock_, client_ip, client_port);
    131. // 2.0多进程版本
    132. // 创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
    133. // 是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
    134. // pid_t id= fork();
    135. // assert(id!=-1);
    136. // if(id==0)//子进程
    137. // {
    138. // //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
    139. // //子进程是来提供服务的,需不需要监听socket呢?
    140. // service(listensock_, client_ip, client_port);
    141. // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
    142. // exit(0);//子进程退出会造成僵尸进程
    143. // }
    144. // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
    145. // //父进程
    146. // //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
    147. // 2.1多进程版本,不用signal
    148. // pid_t id = fork();
    149. // if (id == 0)
    150. // {
    151. // if (fork() > 0)//这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程
    152. // //会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
    153. // {
    154. // close(listensock_);
    155. // exit(0);
    156. // service(servicesock, client_ip, client_port);
    157. // exit(0);
    158. // }
    159. // }
    160. // // 父进程
    161. // waitpid(id, nullptr, 0);
    162. // close(servicesock);
    163. }
    164. }
    165. ~TcpServer()
    166. {
    167. }
    168. private:
    169. uint16_t port_;
    170. std::string ip_;
    171. int listensock_;
    172. };

    五、线程池版本

    代码自取

    六、变成小写转换大写的方法

  • 相关阅读:
    JAVA微信小程序核酸预约小程序系统毕业设计 开题报告
    【机器学习基础】机器学习的基本术语
    JAVA多线程进阶篇-探索线程池ThreadPoolExecutor源码
    vue3 使用 mitt 插件实现非父子组件传值
    npm ERR! While resolving: ruoyi@3.8.5npm ERR! Found: webpack@5.89.0
    分布式系统第五讲:分布式事务及实现方案
    以太坊历史发展进程
    flv.js源码知识点(中)
    Microsoft SQL Server中的错误配置
    Javascript知识【jQuery:数组遍历和事件】
  • 原文地址:https://blog.csdn.net/ky233/article/details/135131665