前言
本筆記記錄的是 單個服務端併發式處理多個客戶端。
下次有空在發個 單執行緒多個服務端併發式處理多種客戶端。其實就是本筆記的一個改良版,用到select() / poll() / epoll()。
原文:https://www.cnblogs.com/lizhuming/p/14943969.html
實現原理
實現原理很簡單,寫出一個簡單的TCP伺服器後,其客戶端處理方式採用執行緒化處理即可。
其中要注意的是多執行緒併發問題。
多執行緒處理客戶端是把 connect_sockfd 傳到執行緒,然後讓執行緒處理。
TCP 服務端
簡要步驟
- 建立 socket 。
- 配置地址資料。
- socket 繫結地址。
- 監聽。(
listen()
) - 進入迴圈處理。
accept()
獲取一個連線進來客戶端的 connect_socket。- 建立一條執行緒專門處理該連結,並把該 connect_socket 傳給該執行緒。
注意
注意:
- 傳遞給客戶端處理執行緒的是 connect_socket 的值。注意傳遞的技巧及執行裝置的系統 bit 位數。
- 本程式採用一個全域性原子整數來維護客戶端的連線數。
- 其中
#include "atomic_lzm.h"
是個人實現的 linux 應用層的原子庫,就只是簡單的封裝一下而已,利用的是 posix 互斥鎖 API。有需要的直接去我 gitee 上拿。
TCP 客戶端
簡要步驟
- 建立 socket 。
- 配置地址資料。
- 建立 TCP 連線。
- 連線成功後進入資料互動。
注意
注意:
- 注意服務端的 IP 和 埠號。
實驗結果
需要測試客戶端限制的可以多開幾個客戶端即可。有興趣的同學可以自己驗證哈哈。
參考原始碼
服務端參考原始碼
/** @file server.c
* @brief 簡要說明
* @details 詳細說明
* @author lzm
* @date 2021-06-10 16:49:46
* @version v1.0
* @copyright Copyright By lizhuming, All Rights Reserved
* @blog https://www.cnblogs.com/lizhuming/
**********************************************************
* @LOG 修改日誌:
**********************************************************
*/
#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>
#include <semaphore.h>
#include "atomic_lzm.h"
// 注意: 接收緩衝區對應對方的傳送緩衝區小於等於就不會出現對方接收多次。
#define CLIENT_BUFF_MAX 1024 // 客戶端接收緩衝區 size
#define MY_SERVER_PORT 8080 // 服務端監聽埠
#define CLIENT_PENDING_NUM 10 // 核心未完成佇列的客戶端緩衝數
#define CLIENT_MAX_NUM 2 // 客戶端連線數限制
// 定義一個無名訊號量
//sem_t sem_client;
atomic_lzm_t g_client_num;
/**
* @name client_fun
* @brief 執行緒函式,處理 client
* @param arg:資源
* @retval
* @author lzm
*/
void *client_fun(void *arg)
{
int recv_len = 0; // 接收長度
char recv_buff[CLIENT_BUFF_MAX] = "";
long connfd = (long)arg; // 已連線socket。 值傳遞(地址傳遞時注意多執行緒併發問題)
// [1] 接收資料
while((recv_len = recv(connfd, recv_buff, sizeof(recv_buff), 0)) > 0)
{
printf("[%ld]recv_buff:%s\r\n", connfd, recv_buff); // 列印資料
send(connfd, "OK", 2, 0); // 返回 client
}
// [2] 關閉 socket
printf("client closed!\r\n");
close(connfd); // 關閉 socket
//sem_post(&sem_client);
atomic_lzm_sub(&g_client_num);
printf("can link num is [%d]\n", CLIENT_MAX_NUM-atomic_lzm_get(&g_client_num));
return NULL;
}
/**
* @name main
* @brief main函式,服務端處理
* @param
* @retval
* @author lzm
*/
int main(int argc, char *argv[])
{
int sockfd = 0; // 監聽 socket
long connfd = 0; // 已連線 socket 。long 避免使用 64bit 機器而出錯
int ret = 0; // 返回緩衝
struct sockaddr_in local_server_addr; // 本地伺服器地址
unsigned short local_server_port = MY_SERVER_PORT; // 本地伺服器監聽埠
pthread_t thread_client_id; // client 執行緒 id
atomic_lzm_init(&g_client_num, 0);
printf("TCP server started at port [%d]!\n", local_server_port);
// [1] 建立套接字
printf("create server socket!\n");
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
printf("%s-%s-%d:sockfd create faild!", __FILE__, __FUNCTION__, __LINE__);
perror("socket create error");
exit(-1);
}
// [2] 初始化地址資料
printf("init server address!\n");
bzero(&local_server_addr, sizeof(local_server_addr)); // 初始化伺服器地址
local_server_addr.sin_family = AF_INET;
local_server_addr.sin_port = htons(local_server_port);
local_server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// [3] 繫結
printf("bing server socket and addr!\n");
ret = bind(sockfd, (struct sockaddr*)&local_server_addr, sizeof(local_server_addr));
if(ret != 0)
{
printf("%s-%s-%d:sockfd bind faild!", __FILE__, __FUNCTION__, __LINE__);
perror("socket bind error");
close(sockfd);
exit(-1);
}
// [4] 監聽
printf("listen server socket!\n");
ret = listen(sockfd, CLIENT_PENDING_NUM);
if(ret != 0)
{
printf("%s-%s-%d:sockfd listen faild!", __FILE__, __FUNCTION__, __LINE__);
perror("socket listen error");
close(sockfd);
exit(-1);
}
//sem_init(&sem_client, 0, CLIENT_MAX_NUM);
printf("accept!\n");
// [5] 處理 client
while(1)
{
char client_ip[INET_ADDRSTRLEN] = ""; // use for save client ip
struct sockaddr_in client_addr; // use for save client address
socklen_t client_len = sizeof(client_addr); // 必須初始化
//sem_wait(&sem_client);
// [5][1] 獲取一個已建立的連線
connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if(connfd < 0)
{
printf("%s-%s-%d:sockfd accept faild!", __FILE__, __FUNCTION__, __LINE__);
perror("accept error");
continue;
}
if(atomic_lzm_get(&g_client_num) >= CLIENT_MAX_NUM)
{
close(connfd);
}
else
{
// [5][2] 處理客戶端資料
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("clien ip = [%s], port = [%d]\n", client_ip, ntohs(client_addr.sin_port));
if(connfd > 0)
{
// 執行緒處理客戶端資料
pthread_create(&thread_client_id, NULL, (void *)client_fun, (void *)connfd); // creat thread。注意 64bit 機器地址是8byte的
pthread_detach(thread_client_id); // thread 分離。即時,執行緒回撥函式結束時自動回收該執行緒資源
atomic_lzm_add(&g_client_num);
}
}
printf("can link num is [%d]\n", CLIENT_MAX_NUM-atomic_lzm_get(&g_client_num));
}
// [6] 關閉 server socket
close(sockfd);
return 0;
}
TCP 客戶端原始碼
/** @file client.c
* @brief 簡要說明
* @details 詳細說明
* @author lzm
* @date 2021-06-10 09:49:46
* @version v1.0
* @copyright Copyright By lizhuming, All Rights Reserved
* @blog https://www.cnblogs.com/lizhuming/
**********************************************************
* @LOG 修改日誌:
**********************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#define sHOST "192.168.112.128" // 伺服器端IP
#define sPORT 8080 // 伺服器程式埠號
#define BUFFER_SIZE (1*1024) // 資料域大小
// 注意: 接收緩衝區對應對方的傳送緩衝區小於等於就不會出現對方接收多次。
/**
* @brief 讀 執行緒
* @param
* @retval
* @author
*/
void *client_read_fun(void *arg)
{
int recv_len = 0; // 接收長度
char recv_buff[BUFFER_SIZE] = "";
int sockfd = *(int *)arg; // 已連線的 socket
while((recv_len = recv(sockfd, recv_buff, sizeof(recv_buff), 0)) > 0)
{
printf("[%d]recv_buff:%s\r\n", sockfd, recv_buff);
}
printf("exit read thread!");
return NULL;
}
/**
* @brief 主函式
* @param
* @retval
* @author
*/
int main(void)
{
int sockfd; // 套接字描述符
int ret; // 返回結果
struct sockaddr_in server; // 套接字地址
char data_buffer[BUFFER_SIZE]; // app 資料域
pthread_t thread_read_id; // 讀 執行緒 id
/* [1] 建立套接字 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
printf("creat socket failed!\n");
exit(1);
}
/* [2] 初始化地址 */
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(sPORT);
server.sin_addr.s_addr = inet_addr(sHOST);
/* [3] 建立TCP連線 */
ret = connect(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr));
if(ret == -1)
{
printf("connect server failed!\n");
close(sockfd);
exit(1);
}
printf("connect server success!");
/* [4][1] 建立 讀 執行緒 */
//pthread_create(&thread_read_id, NULL, (void *)client_read_fun, (void *)&(sockfd));
//pthread_detach(thread_read_id); // 執行緒分離
/* [4][2] 進入迴圈 寫 */
while(1)
{
printf("please enter some text:");
fgets(data_buffer, BUFFER_SIZE, stdin);
// 輸入end,退出迴圈
if(strncmp(data_buffer, "end", 3) == 0)
break;
send(sockfd, data_buffer, sizeof(data_buffer), 0);
bzero(data_buffer, sizeof(data_buffer));
recv(sockfd, data_buffer, sizeof(data_buffer), 0);
printf("data_buff:%s\r\n", data_buffer);
}
/* [5] 退出 */
close(sockfd);
exit(0);
}