日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux网络编程——tcp并发服务器(多线程)

發布時間:2023/11/30 linux 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux网络编程——tcp并发服务器(多线程) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

tcp多線程并發服務器

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

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



我們在使用多線程并發服務器時,直接使用以上框架,我們僅僅修改client_fun()里面的內容。代碼示例:
  • #include <stdio.h>
  • #include <stdlib.h>
  • #include <string.h>
  • #include <unistd.h>
  • #include <sys/socket.h>
  • #include <netinet/in.h>
  • #include <arpa/inet.h>
  • #include <pthread.h>
  • /************************************************************************
  • 函數名稱: void *client_fun(void *arg)
  • 函數功能: 線程函數,處理客戶信息
  • 函數參數: 已連接套接字
  • 函數返回: 無
  • ************************************************************************/
  • void *client_fun(void *arg)
  • {
  • int recv_len = 0;
  • char recv_buf[1024] = ""; // 接收緩沖區
  • int connfd = (int)arg; // 傳過來的已連接套接字
  • // 接收數據
  • while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
  • {
  • printf("recv_buf: %s\n", recv_buf); // 打印數據
  • send(connfd, recv_buf, recv_len, 0); // 給客戶端回數據
  • }
  • printf("client closed!\n");
  • close(connfd); //關閉已連接套接字
  • return NULL;
  • }
  • //===============================================================
  • // 語法格式: void main(void)
  • // 實現功能: 主函數,建立一個TCP并發服務器
  • // 入口參數: 無
  • // 出口參數: 無
  • //===============================================================
  • int main(int argc, char *argv[])
  • {
  • int sockfd = 0; // 套接字
  • int connfd = 0;
  • int err_log = 0;
  • struct sockaddr_in my_addr; // 服務器地址結構體
  • unsigned short port = 8080; // 監聽端口
  • pthread_t thread_id;
  • printf("TCP Server Started at port %d!\n", port);
  • sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP套接字
  • if(sockfd < 0)
  • {
  • perror("socket error");
  • exit(-1);
  • }
  • bzero(&my_addr, sizeof(my_addr)); // 初始化服務器地址
  • my_addr.sin_family = AF_INET;
  • my_addr.sin_port = htons(port);
  • my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  • printf("Binding server to port %d\n", port);
  • // 綁定
  • err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  • if(err_log != 0)
  • {
  • perror("bind");
  • close(sockfd);
  • exit(-1);
  • }
  • // 監聽,套接字變被動
  • err_log = listen(sockfd, 10);
  • if( err_log != 0)
  • {
  • perror("listen");
  • close(sockfd);
  • exit(-1);
  • }
  • printf("Waiting client...\n");
  • while(1)
  • {
  • char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客戶端IP地址
  • struct sockaddr_in client_addr; // 用于保存客戶端地址
  • socklen_t cliaddr_len = sizeof(client_addr); // 必須初始化!!!
  • //獲得一個已經建立的連接
  • connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  • if(connfd < 0)
  • {
  • perror("accept this time");
  • continue;
  • }
  • // 打印客戶端的 ip 和端口
  • inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  • printf("----------------------------------------------\n");
  • printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  • if(connfd > 0)
  • {
  • //由于同一個進程內的所有線程共享內存和變量,因此在傳遞參數時需作特殊處理,值傳遞。
  • pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd); //創建線程
  • pthread_detach(thread_id); // 線程分離,結束時自動回收資源
  • }
  • }
  • close(sockfd);
  • return 0;
  • }

  • 運行結果:

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

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


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

  • while(1)
  • {
  • int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  • pthread_create(&thread_id, NULL, (void *)client_fun, (void *)&connfd);
  • pthread_detach(thread_id);
  • }

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


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

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

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

  • 示例代碼:

  • #include <stdio.h>
  • #include <stdlib.h>
  • #include <string.h>
  • #include <unistd.h>
  • #include <sys/socket.h>
  • #include <netinet/in.h>
  • #include <arpa/inet.h>
  • #include <pthread.h>
  • pthread_mutex_t mutex; // 定義互斥鎖,全局變量
  • /************************************************************************
  • 函數名稱: void *client_process(void *arg)
  • 函數功能: 線程函數,處理客戶信息
  • 函數參數: 已連接套接字
  • 函數返回: 無
  • ************************************************************************/
  • void *client_process(void *arg)
  • {
  • int recv_len = 0;
  • char recv_buf[1024] = ""; // 接收緩沖區
  • int connfd = *(int *)arg; // 傳過來的已連接套接字
  • // 解鎖,pthread_mutex_lock()喚醒,不阻塞
  • pthread_mutex_unlock(&mutex);
  • // 接收數據
  • while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
  • {
  • printf("recv_buf: %s\n", recv_buf); // 打印數據
  • send(connfd, recv_buf, recv_len, 0); // 給客戶端回數據
  • }
  • printf("client closed!\n");
  • close(connfd); //關閉已連接套接字
  • return NULL;
  • }
  • //===============================================================
  • // 語法格式: void main(void)
  • // 實現功能: 主函數,建立一個TCP并發服務器
  • // 入口參數: 無
  • // 出口參數: 無
  • //===============================================================
  • int main(int argc, char *argv[])
  • {
  • int sockfd = 0; // 套接字
  • int connfd = 0;
  • int err_log = 0;
  • struct sockaddr_in my_addr; // 服務器地址結構體
  • unsigned short port = 8080; // 監聽端口
  • pthread_t thread_id;
  • pthread_mutex_init(&mutex, NULL); // 初始化互斥鎖,互斥鎖默認是打開的
  • printf("TCP Server Started at port %d!\n", port);
  • sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP套接字
  • if(sockfd < 0)
  • {
  • perror("socket error");
  • exit(-1);
  • }
  • bzero(&my_addr, sizeof(my_addr)); // 初始化服務器地址
  • my_addr.sin_family = AF_INET;
  • my_addr.sin_port = htons(port);
  • my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  • printf("Binding server to port %d\n", port);
  • // 綁定
  • err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  • if(err_log != 0)
  • {
  • perror("bind");
  • close(sockfd);
  • exit(-1);
  • }
  • // 監聽,套接字變被動
  • err_log = listen(sockfd, 10);
  • if( err_log != 0)
  • {
  • perror("listen");
  • close(sockfd);
  • exit(-1);
  • }
  • printf("Waiting client...\n");
  • while(1)
  • {
  • char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客戶端IP地址
  • struct sockaddr_in client_addr; // 用于保存客戶端地址
  • socklen_t cliaddr_len = sizeof(client_addr); // 必須初始化!!!
  • // 上鎖,在沒有解鎖之前,pthread_mutex_lock()會阻塞
  • pthread_mutex_lock(&mutex);
  • //獲得一個已經建立的連接
  • connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  • if(connfd < 0)
  • {
  • perror("accept this time");
  • continue;
  • }
  • // 打印客戶端的 ip 和端口
  • inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  • printf("----------------------------------------------\n");
  • printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  • if(connfd > 0)
  • {
  • //給回調函數傳的參數,&connfd,地址傳遞
  • pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); //創建線程
  • pthread_detach(thread_id); // 線程分離,結束時自動回收資源
  • }
  • }
  • close(sockfd);
  • return 0;
  • }


  • 運行結果:


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

    總結

    以上是生活随笔為你收集整理的Linux网络编程——tcp并发服务器(多线程)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。