Linux網絡編程——tcp并發服務器(多線程)

https://blog.csdn.net/lianghe_work/article/details/46504243

tcp多線程并發服務器

多線程服務器是對多進程服務器的改進,由于多進程服務器在創建進程時要消耗較大的系統資源,所以用線程來取代進程,這樣服務處理程序可以較快的創建。據統計,創建線程與創建進程要快 10100 倍,所以又把線程稱為“輕量級”進程。線程與進程不同的是:一個進程內的所有線程共享相同的全局內存、全局變量等信息,這種機制又帶來了同步問題。

tcp多線程并發服務器框架:



我們在使用多線程并發服務器時,直接使用以上框架,我們僅僅修改client_fun()里面的內容。
代碼示例:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <pthread.h>
  9. /************************************************************************
  10. 函數名稱: void *client_fun(void *arg)
  11. 函數功能: 線程函數,處理客戶信息
  12. 函數參數: 已連接套接字
  13. 函數返回: 無
  14. ************************************************************************/
  15. void *client_fun(void *arg)
  16. {
  17. int recv_len = 0;
  18. char recv_buf[1024] = ""; // 接收緩沖區
  19. int connfd = (int)arg; // 傳過來的已連接套接字
  20. // 接收數據
  21. while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
  22. {
  23. printf("recv_buf: %s\n", recv_buf); // 打印數據
  24. send(connfd, recv_buf, recv_len, 0); // 給客戶端回數據
  25. }
  26. printf("client closed!\n");
  27. close(connfd); //關閉已連接套接字
  28. return NULL;
  29. }
  30. //===============================================================
  31. // 語法格式: void main(void)
  32. // 實現功能: 主函數,建立一個TCP并發服務器
  33. // 入口參數: 無
  34. // 出口參數: 無
  35. //===============================================================
  36. int main(int argc, char *argv[])
  37. {
  38. int sockfd = 0; // 套接字
  39. int connfd = 0;
  40. int err_log = 0;
  41. struct sockaddr_in my_addr; // 服務器地址結構體
  42. unsigned short port = 8080; // 監聽端口
  43. pthread_t thread_id;
  44. printf("TCP Server Started at port %d!\n", port);
  45. sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP套接字
  46. if(sockfd < 0)
  47. {
  48. perror("socket error");
  49. exit(-1);
  50. }
  51. bzero(&my_addr, sizeof(my_addr)); // 初始化服務器地址
  52. my_addr.sin_family = AF_INET;
  53. my_addr.sin_port = htons(port);
  54. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  55. printf("Binding server to port %d\n", port);
  56. // 綁定
  57. err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  58. if(err_log != 0)
  59. {
  60. perror("bind");
  61. close(sockfd);
  62. exit(-1);
  63. }
  64. // 監聽,套接字變被動
  65. err_log = listen(sockfd, 10);
  66. if( err_log != 0)
  67. {
  68. perror("listen");
  69. close(sockfd);
  70. exit(-1);
  71. }
  72. printf("Waiting client...\n");
  73. while(1)
  74. {
  75. char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客戶端IP地址
  76. struct sockaddr_in client_addr; // 用于保存客戶端地址
  77. socklen_t cliaddr_len = sizeof(client_addr); // 必須初始化!!!
  78. //獲得一個已經建立的連接
  79. connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  80. if(connfd < 0)
  81. {
  82. perror("accept this time");
  83. continue;
  84. }
  85. // 打印客戶端的 ip 和端口
  86. inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  87. printf("----------------------------------------------\n");
  88. printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  89. if(connfd > 0)
  90. {
  91. //由于同一個進程內的所有線程共享內存和變量,因此在傳遞參數時需作特殊處理,值傳遞。
  92. pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd); //創建線程
  93. pthread_detach(thread_id); // 線程分離,結束時自動回收資源
  94. }
  95. }
  96. close(sockfd);
  97. return 0;
  98. }

運行結果:


注意
1.上面pthread_create()函數的最后一個參數是void *類型,為啥可以傳值connfd
  1. while(1)
  2. {
  3. int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  4. pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd);
  5. pthread_detach(thread_id);
  6. }

因為void *是4個字節,而connfd為int類型也是4個字節,故可以傳值。如果connfd為char、short,上面傳值就會出錯


2.上面pthread_create()函數的最后一個參數是可以傳地址嗎?可以,但會對服務器造成不可預知的問題

  1. while(1)
  2. {
  3. int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  4. pthread_create(&thread_id, NULL, (void *)client_fun, (void *)&connfd);
  5. pthread_detach(thread_id);
  6. }

原因:假如有多個客戶端要連接這個服務器,正常的情況下,一個客戶端連接對應一個 connfd,相互之間獨立不受影響,但是,假如多個客戶端同時連接這個服務器,A 客戶端的連接套接字為 connfd,服務器正在用這個 connfd 處理數據,還沒有處理完,突然來了一個 B 客戶端,accept()之后又生成一個 connfd, 因為是地址傳遞, A 客戶端的連接套接字也變成 B 這個了,這樣的話,服務器肯定不能再為 A 客戶端服務器了


2.如果我們想將多個參數傳給線程函數,我們首先考慮到就是結構體參數,而這時傳值是行不通的,只能傳遞地址

這時候,我們就需要考慮多任務的互斥或同步問題了,這里通過互斥鎖來解決這個問題,確保這個結構體參數值被一個臨時變量保存過后,才允許修改。

  1. #include <pthread.h>
  2. pthread_mutex_t mutex; // 定義互斥鎖,全局變量
  3. pthread_mutex_init(&mutex, NULL); // 初始化互斥鎖,互斥鎖默認是打開的
  4. // 上鎖,在沒有解鎖之前,pthread_mutex_lock()會阻塞
  5. pthread_mutex_lock(&mutex);
  6. int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  7. //給回調函數傳的參數,&connfd,地址傳遞
  8. pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); //創建線程
  9. // 線程回調函數
  10. void *client_process(void *arg)
  11. {
  12. int connfd = *(int *)arg; // 傳過來的已連接套接字
  13. // 解鎖,pthread_mutex_lock()喚醒,不阻塞
  14. pthread_mutex_unlock(&mutex);
  15. return NULL;
  16. }

示例代碼:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <pthread.h>
  9. pthread_mutex_t mutex; // 定義互斥鎖,全局變量
  10. /************************************************************************
  11. 函數名稱: void *client_process(void *arg)
  12. 函數功能: 線程函數,處理客戶信息
  13. 函數參數: 已連接套接字
  14. 函數返回: 無
  15. ************************************************************************/
  16. void *client_process(void *arg)
  17. {
  18. int recv_len = 0;
  19. char recv_buf[1024] = ""; // 接收緩沖區
  20. int connfd = *(int *)arg; // 傳過來的已連接套接字
  21. // 解鎖,pthread_mutex_lock()喚醒,不阻塞
  22. pthread_mutex_unlock(&mutex);
  23. // 接收數據
  24. while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
  25. {
  26. printf("recv_buf: %s\n", recv_buf); // 打印數據
  27. send(connfd, recv_buf, recv_len, 0); // 給客戶端回數據
  28. }
  29. printf("client closed!\n");
  30. close(connfd); //關閉已連接套接字
  31. return NULL;
  32. }
  33. //===============================================================
  34. // 語法格式: void main(void)
  35. // 實現功能: 主函數,建立一個TCP并發服務器
  36. // 入口參數: 無
  37. // 出口參數: 無
  38. //===============================================================
  39. int main(int argc, char *argv[])
  40. {
  41. int sockfd = 0; // 套接字
  42. int connfd = 0;
  43. int err_log = 0;
  44. struct sockaddr_in my_addr; // 服務器地址結構體
  45. unsigned short port = 8080; // 監聽端口
  46. pthread_t thread_id;
  47. pthread_mutex_init(&mutex, NULL); // 初始化互斥鎖,互斥鎖默認是打開的
  48. printf("TCP Server Started at port %d!\n", port);
  49. sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP套接字
  50. if(sockfd < 0)
  51. {
  52. perror("socket error");
  53. exit(-1);
  54. }
  55. bzero(&my_addr, sizeof(my_addr)); // 初始化服務器地址
  56. my_addr.sin_family = AF_INET;
  57. my_addr.sin_port = htons(port);
  58. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  59. printf("Binding server to port %d\n", port);
  60. // 綁定
  61. err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  62. if(err_log != 0)
  63. {
  64. perror("bind");
  65. close(sockfd);
  66. exit(-1);
  67. }
  68. // 監聽,套接字變被動
  69. err_log = listen(sockfd, 10);
  70. if( err_log != 0)
  71. {
  72. perror("listen");
  73. close(sockfd);
  74. exit(-1);
  75. }
  76. printf("Waiting client...\n");
  77. while(1)
  78. {
  79. char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客戶端IP地址
  80. struct sockaddr_in client_addr; // 用于保存客戶端地址
  81. socklen_t cliaddr_len = sizeof(client_addr); // 必須初始化!!!
  82. // 上鎖,在沒有解鎖之前,pthread_mutex_lock()會阻塞
  83. pthread_mutex_lock(&mutex);
  84. //獲得一個已經建立的連接
  85. connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  86. if(connfd < 0)
  87. {
  88. perror("accept this time");
  89. continue;
  90. }
  91. // 打印客戶端的 ip 和端口
  92. inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  93. printf("----------------------------------------------\n");
  94. printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  95. if(connfd > 0)
  96. {
  97. //給回調函數傳的參數,&connfd,地址傳遞
  98. pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); //創建線程
  99. pthread_detach(thread_id); // 線程分離,結束時自動回收資源
  100. }
  101. }
  102. close(sockfd);
  103. return 0;
  104. }


運行結果:


注意:這種用互斥鎖對服務器的運行效率有致命的影響

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383690.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383690.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383690.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

計算機網絡【三】物理層數據通信

物理層傳輸媒介 導向傳輸媒體&#xff0c;比如光纖和銅線 雙絞線&#xff08;屏蔽雙絞線STP 五屏蔽雙絞線UTP&#xff09;電線扭曲在一起可以降低互相之間的電磁干擾 同軸電纜 (50歐姆的基帶同軸電纜&#xff0c;75歐姆的寬帶同軸電纜) 10M和100M網絡只使用了四根線&#xf…

02_算法分析

02_算法分析 0.1 算法的時間復雜度分析0.1.1 函數漸近增長概念&#xff1a;輸入規模n>2時&#xff0c;算法A1的漸近增長小于算法B1 的漸近增長隨著輸入規模的增大&#xff0c;算法的常數操作可以忽略不計測試二&#xff1a;隨著輸入規模的增大&#xff0c;與最高次項相乘的常…

Linux網絡編程——I/O復用之select詳解

https://blog.csdn.net/lianghe_work/article/details/46506143一、I/O復用概述I/O復用概念&#xff1a;解決進程或線程阻塞到某個 I/O 系統調用而出現的技術&#xff0c;使進程不阻塞于某個特定的 I/O 系統調I/O復用使用的場合&#xff1a;1.當客戶處理多個描述符&#xff08;…

Linux多進程拷貝文件

學習了mmap以后&#xff0c;實現一個簡單的小程序&#xff0c;進行多個進程對一個文件進行拷貝。 Linux mmap共享內存學習可以參考我的另一篇博客&#xff1a;傳送門 實現思想 我們可以將原來的文件利用mmap分成多個段分別進行傳輸。 實現代碼 #include<stdio.h> #…

斐波那契查找(Fibonacci Search)和折半查找

兩個查找算法都是針對有序數組進行查找&#xff0c;不同點在于分界點的取值不同。 算法介紹 折半查找很簡單&#xff0c;每次與當前區間的中點進行比較&#xff0c;然后決定查找前一部分還是后一部分。 Fibonacci查找利用了Fibonacci序列每一項等于前兩項和的特點進行劃分&a…

Linux網絡編程——tcp并發服務器(I/O復用之select)

https://blog.csdn.net/lianghe_work/article/details/46519633與多線程、多進程相比&#xff0c;I/O復用最大的優勢是系統開銷小&#xff0c;系統不需要建立新的進程或者線程&#xff0c;也不必維護這些線程和進程。代碼示例&#xff1a;#include <stdio.h> #include &l…

操作系統【二】死鎖問題以及處理方法

死鎖的概念 死鎖&#xff1a;在并發環境下&#xff0c;個進程因為競爭資源而造成的一種互相等待對方手里的資源&#xff0c;導致各進程都阻塞&#xff0c;無法向前推進的現象。 區別&#xff1a; 饑餓&#xff1a;由于長期得不到想要的資源進程無法向前推進的現象。死循環&a…

Linux網絡編程——I/O復用之poll函數

https://blog.csdn.net/lianghe_work/article/details/46534029一、回顧前面的selectselect優點&#xff1a;目前幾乎在所有的平臺上支持&#xff0c;其良好跨平臺支持也是它的一個優點select缺點&#xff1a;1.每次調用 select()&#xff0c;都需要把 fd 集合從用戶態拷貝到內…

操作系統【一】進程同步和信號量

基本概念 進程異步性特征&#xff1a;各并發執行的進程以各自獨立的&#xff0c;不可預知的速度向前推進。 進程同步又稱作直接制約關系&#xff0c;他是指為完成某種任務而建立的兩個或者多個進程&#xff0c;這些進程因為需要在某些位置上協調他們的工作順序而產生的制約關…

計算機網絡【四】數據鏈路層基本概念+點到點通信(PPP協議)

數據鏈路層基本概念 路由器是網絡層設備 數據鏈路層&#xff1a;數據管道&#xff0c;傳輸的是數據包加上發送地址&#xff0c;接收地址&#xff0c;校驗的數據幀 數據鏈路層的信道類型&#xff1a; 點到點信道&#xff1a;使用一對一的點到點通信方式&#xff08;兩個設備…

Linux網絡編程——tcp并發服務器(poll實現)

https://blog.csdn.net/lianghe_work/article/details/46535859想詳細徹底地了解poll或看懂下面的代碼請參考《Linux網絡編程——I/O復用之poll函數》 代碼&#xff1a;#include <string.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#…

二分查找的最大比較次數

二分查找很簡單&#xff0c;可是對于一個區間長度為n的數組&#xff0c;最大的比較次數為多少呢&#xff1f; 對于標準的二分查找&#xff0c;我們每次從區間[l,r)中取一個值&#xff0c;和中間值mid(lr)>>1進行比較&#xff0c;然后將數組分為[l,mid) [mid1,r)&#xf…

Linux網絡編程——I/O復用函數之epoll

https://blog.csdn.net/lianghe_work/article/details/46544567一、epoll概述epoll 是在 2.6 內核中提出的&#xff0c;是之前的 select() 和 poll() 的增強版本。相對于 select() 和 poll() 來說&#xff0c;epoll 更加靈活&#xff0c;沒有描述符限制。epoll 使用一個文件描述…

操作系統【三】內存管理基礎+連續內存分配

內存的基礎知識 內存分為按字節編址&#xff08;8位&#xff09;和字編制&#xff08;不同計算機不一樣&#xff0c;64位計算機就是64位&#xff0c;即8個字節&#xff09; 相對地址邏輯地址 絕對地址物理地址 從邏輯地址到物理地址的轉換由裝入解決。 裝入的三種方式 絕對…

MSG_PEEK標志

https://blog.csdn.net/aspnet_lyc/article/details/28937229 MSG_PEEK標志可以用來讀取套接字接收隊列中可讀的數據&#xff0c;一些情況會用到它&#xff0c;比如為了避免不阻塞而先檢查套接字接收隊列中可讀的數據長度&#xff0c;再采取相應操作。當然&#xff0c;不阻塞也…

快速排序詳解+各種實現方式

快速排序的思想大體來說比較簡單&#xff0c;就是從數組中挑選一個數字當做樞紐&#xff0c;然后將比樞紐大的和比樞紐小的分別放在樞紐的兩邊&#xff0c;再遞歸地對兩邊進行操作&#xff0c;從而進行分治解決問題。平均情況下快速排序是復雜度為O(nlogn)O(nlogn)O(nlogn)&…

C++的單例模式與線程安全單例模式(懶漢/餓漢)

https://www.cnblogs.com/qiaoconglovelife/p/5851163.html1 教科書里的單例模式我們都很清楚一個簡單的單例模式該怎樣去實現&#xff1a;構造函數聲明為private或protect防止被外部函數實例化&#xff0c;內部保存一個private static的類指針保存唯一的實例&#xff0c;實例的…

計算矩陣的逆和行列式的值(高斯消元+LU分解)

計算矩陣的逆 選主元的高斯消元法 樸素的高斯消元法是將矩陣A和單位矩陣放在一起&#xff0c;通過行操作&#xff08;或者列操作&#xff09;將A變為單位矩陣&#xff0c;這個時候單位矩陣就是矩陣A的逆矩陣。從上到下將A變為上三角矩陣的復雜度為O(n3n^3n3)&#xff0c;再從下…

Linux網絡編程——tcp并發服務器(epoll實現)

https://blog.csdn.net/lianghe_work/article/details/46551871通過epoll實現tcp并發回執服務器&#xff08;客戶端給服務器發啥&#xff0c;服務器就給客戶端回啥&#xff09; 代碼如下&#xff1a;#include <string.h>#include <stdio.h>#include <stdlib.h&g…