一、問題引入
Linux網路編程:socket & fork()多行程 實作clients/server通信 隨筆介紹了通過fork()多行程實作了服務器與多客戶端通信,但除了多行程能實作之外,多執行緒也是一種實作方式,
重要的是,多行程和多執行緒是涉及作業系統層次,隨筆不僅要利用pthread_create()實作多執行緒編程,也要理解執行緒和行程的區別,
二、解決程序
client 代碼無需修改,請參考 Linux網路編程:socket & fork()多行程 實作clients/server通信
2-1 server 代碼
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/syscall.h>
#define IP "10.8.198.227"
#define PORT 8887
#define gettid() syscall(__NR_gettid)
#define PTHREAD_MAX_SIZE 3 // 允許最大客戶端連接數
typedef struct PTHREAD_DATA_ST
{
pthread_t pthread_id;
int connfd;
char socket[128];
struct sockaddr_in cliaddr;
}PTHREAD_DATA_ST;
//static int g_pthread_num = 3; // 允許最大客戶端連接數
static struct PTHREAD_DATA_ST g_pthread_data[PTHREAD_MAX_SIZE];
static void pthread_data_index_init(void)
{
for (int i = 0; i < PTHREAD_MAX_SIZE; i++)
{
memset(&g_pthread_data[i], 0 , sizeof(struct PTHREAD_DATA_ST));
g_pthread_data[i].connfd = -1;
}
}
static int pthread_data_index_find(void)
{
int i;
for (i = 0; i < PTHREAD_MAX_SIZE; i++)
{
if (g_pthread_data[i].connfd == -1)
break;
}
return i;
}
static int string_toupper(const char *src, int str_len, char *dst)
{
int count = 0;
for (int i = 0; i < str_len; i++)
{
dst[i] = toupper(src[i]);
count++;
}
return count;
}
void *pthread_handle(void *arg)
{
struct PTHREAD_DATA_ST *pthread = (struct PTHREAD_DATA_ST *)arg;
int connfd = pthread->connfd;
int recv_len, send_len;
pid_t tid = gettid();
char read_buf[1024], write_buf[1024];
while (1)
{
memset(read_buf, 0, sizeof(read_buf));
memset(write_buf, 0, sizeof(write_buf));
recv_len = read(connfd, read_buf, sizeof(read_buf));
if (recv_len <= 0)
{
printf("%s close, child %d terminated\n", pthread->socket, tid);
close(connfd);
pthread->connfd = -1;
pthread_exit(NULL);
}
printf("%s:%s(%d Byte)\n", pthread->socket, read_buf, recv_len);
send_len = string_toupper(read_buf, strlen(read_buf), write_buf);
write(connfd, write_buf, send_len);
if (strcmp("exit", read_buf) == 0)
{
printf("%s exit, child %d terminated\n", pthread->socket, tid);
close(connfd);
pthread->connfd = -1;
pthread_exit(NULL);
}
}
}
int main(void)
{
int listenfd, connfd;
struct sockaddr_in server_sockaddr;
struct sockaddr_in client_addr;
char buf[1024];
char client_socket[128];
socklen_t length;
int idx;
int opt = 1;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = inet_addr(IP);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0)
{
perror("socket error");
exit(1);
}
// 設定埠復用
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) < 0)
{
perror("bind error");
exit(1);
}
if (listen(listenfd, 5) < 0)
{
perror("listen error");
exit(1);
}
pthread_data_index_init();
while (1)
{
// 接受來自客戶端的資訊
printf("accept start \n");
memset(&client_addr, 0, sizeof(client_addr));
length = sizeof(client_addr);
if ((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &length)) < 0)
{
if (errno == EINTR)
continue;
else
{
perror("accept error");
exit(1);
}
}
idx = pthread_data_index_find();
if (idx == PTHREAD_MAX_SIZE)
{
printf("client connected upper limit, refused connect\n");
close(connfd);
continue;
}
memset(&client_socket, 0, sizeof(client_socket));
printf("client addr:%s por:%d\n",
inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)),
ntohs(client_addr.sin_port));
snprintf(client_socket, sizeof(client_socket), "client socket (%s:%d)",
inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)),
ntohs(client_addr.sin_port));
g_pthread_data[idx].connfd = connfd;
g_pthread_data[idx].cliaddr = client_addr;
strcpy(g_pthread_data[idx].socket, client_socket);
pthread_create(&(g_pthread_data[idx].pthread_id), NULL, pthread_handle, &(g_pthread_data[idx]));
pthread_detach(g_pthread_data[idx].pthread_id);
}
close(listenfd);
return EXIT_SUCCESS;
}
2-2 編譯運行
?? 編譯server.c時,出現 對‘pthread_create’未定義的參考
原因:pthread.h庫不是linux系統的默認庫,添加編譯引數:-lpthread
即可 (或 pthread
也行)
- 編譯源檔案
gcc server.c -g -std=gnu99 -lpthread -o server
- client 1 和client 2連接server
- client 1 斷開連接
- client 2 斷開連接
?? 在server未設定埠復用,server主動斷開,重啟報錯: bind error: Adress already in use
解決方法:
int opt = 1;
// 設定埠復用
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2-3 多執行緒處理
- 執行緒id
?? 同一行程下的行程號pid相同,各個執行緒的執行緒號tid不相同
在linux中無法直接使用gettid()獲取執行緒id,正確解決辦法在源檔案中添加如下宏:
#include <sys/syscall.h>
#define gettid() syscall(__NR_gettid)
- 多執行緒
通過宏 PTHREAD_MAX_SIZE
定義執行緒最大數量,畢竟計算機資源是有限的,
accept()成功回傳,此時某一個client已經完成TCP三次握手,接下來可以準備創建執行緒通信,但不能無腦創建執行緒,應檢測當前已創建的執行緒數量,只有未超越執行緒最大數量才能創建執行緒,
1、父執行緒負責監聽新的客戶端連接,并創建新的執行緒
2、子執行緒負責和客戶端進行通信
?? 注意:使用多執行緒要將子執行緒設定為分離屬性, 讓執行緒在退出之后自己回收資源
// pthread_create(), pthread_detach(), pthread_exit()原型宣告在如下頭檔案中
#include <pthread.h>
/*
* @param *thread 新執行緒創建成功后的執行緒指標
* @param *attr 創建執行緒時,設定屬性,默認可設定為NULL
* @param *(*start_routine) (void *) 執行緒運行函式指標
* @param *arg 執行緒運行函式指標的形參
* @return 若成功,回傳值為0,否則失敗
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
/* pthread_detact()在執行緒結束后,回收資源
* @param thread 新執行緒創建成功后的執行緒指標
* @return 若成功,回傳值為0,否則失敗
*/
int pthread_detach(pthread_t thread);
//Compile and link with -pthread.
執行緒呼叫 | 描述 |
---|---|
pthread_create | 創建一個新執行緒 |
pthread_exit | 結束呼叫的執行緒 |
pthread_join | 等待一個特定的執行緒退出 |
pthread_yield | 釋放CPU來運行另外一個執行緒 |
pthread_attr_init | 創建并初始化一個執行緒的屬性結構 |
pthread_attr_destroy | 洗掉(銷毀)一個執行緒的屬性結構 |
三、反思總結
3-1 行程
在行程模型中,計算機上所有可運行的軟體,通常也包括草錯系統,被組織成若干順序行程,簡稱行程(process),
行程是資源(CPU、記憶體等)分配的基本單位,它是程式執行時的一個實體,
3-2 執行緒
執行緒是程式執行時的最小單位,它是行程的一個執行流,是CPU調度和分派的基本單位,一個行程可以由一個或多個執行緒組成,執行緒間共享行程的所有資源,每個執行緒有自己的堆疊和區域變數,
四、參考參考
UNIX網路編程 卷1:套接字聯網API 第3版
現代作業系統(第四版)
Unix網路編程學習筆記
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552900.html
標籤:C
上一篇:驅動開發:通過應用堆實作多次通信
下一篇:返回列表