十三 多執行緒伺服器端的實現
阿新 • • 發佈:2019-02-14
執行緒相比於程序具有如下優點:
1.執行緒的建立和上下文切換比程序的建立和上下文切換要快.上下文切換時不需要切換資料區和堆.
2.執行緒間交換資料時無需特殊技術.可以利用資料區和堆交換資料.
執行緒同步(兩方面)
1.同時訪問同一記憶體空間時發生的情況.
2.需要指定訪問同一記憶體空間的執行緒執行順序的情況.
訊號量示例(控制訪問順序的同步)
示例場景:
執行緒A從使用者輸入得到值後存入全域性變數num,此時執行緒B將取走該值並累加.該過程共執行5次,完成後輸出總和並退出程式.
#include <stdio.h> #include <pthread.h> #include <semaphore.h> void * read(void * arg); void * accu(void * arg); static sem_t sem_one; static sem_t sem_two; static int num; int main(int argc, char *argv[]) { pthread_t id_t1, id_t2; sem_init(&sem_one, 0, 0); sem_init(&sem_two, 0, 1); pthread_create(&id_t1, NULL, read, NULL); pthread_create(&id_t2, NULL, accu, NULL); pthread_join(id_t1, NULL); pthread_join(id_t2, NULL); sem_destroy(&sem_one); sem_destroy(&sem_two); return 0; } void * read(void * arg) { int i; for (i = 0; i < 5; i++) { fputs("Input num: ", stdout); sem_wait(&sem_two); scanf("%d", &num); sem_post(&sem_one); } return NULL; } void * accu(void * arg) { int sum = 0 , i; for (i = 0; i < 5; i++) { sem_wait(&sem_one); sum+= num; sem_post(&sem_two); } printf("Result: %d \n", sum); return NULL; }
銷燬執行緒的方法:
1.呼叫pthread_join函式.會引起呼叫該函式的執行緒阻塞.
2.呼叫pthread_detach函式.不會阻塞,在終止的同時銷燬相應的執行緒.
多執行緒併發伺服器端的實現
伺服器端:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> #define BUF_SIZE 100 #define MAX_CLNT 256 void * handle_clnt(void * arg); void send_msg(char *msg, int len); void error_handling(char * msg); int clnt_cnt = 0; int clnt_socks[MAX_CLNT]; pthread_mutex_t mutx; int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; socklen_t clnt_adr_sz; pthread_t t_id; if (argc != 2) { printf("Usage : %s <port> \n", argv[0]); exit(1); } //建立互斥量 pthread_mutex_init(&mutx, NULL); serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) error_handling("bind() error"); if(listen(serv_sock, 5) == -1) error_handling("listen() error"); while (1) { clnt_adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); //阻斷,監聽客服端連線請求 //臨界區 pthread_mutex_lock(&mutx); //加鎖 clnt_socks[clnt_cnt++] = clnt_sock; //新連線的客服端儲存到clnt_socks數組裡 pthread_mutex_unlock(&mutx); //釋放鎖 //建立執行緒 pthread_create(&t_id, NULL, handle_clnt, (void*) &clnt_sock); pthread_detach(t_id); //銷燬執行緒,執行緒return後自動呼叫銷燬,不阻斷 printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr)); } close(serv_sock); return 0; } //執行緒執行 void * handle_clnt(void * arg) { int clnt_sock = *((int *)arg); int str_len = 0, i; char msg[BUF_SIZE]; while ((str_len = read(clnt_sock, msg, sizeof(msg))) != 0) send_msg(msg, str_len); //從陣列中移除當前客服端 pthread_mutex_lock(&mutx); for (i = 0; i < clnt_cnt; i++) { if (clnt_sock == clnt_socks[i]) { while (i++ < clnt_cnt - 1) clnt_socks[i] = clnt_socks[i + 1]; break; } } clnt_cnt--; pthread_mutex_unlock(&mutx); close(clnt_sock); return NULL; } //向所有連線的客服端傳送訊息 void send_msg(char * msg, int len) { int i; pthread_mutex_lock(&mutx); for (i = 0; i < clnt_cnt; i++) write(clnt_socks[i], msg, len); pthread_mutex_unlock(&mutx); } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
客戶端:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> #define BUF_SIZE 100 #define NAME_SIZE 20 void * send_msg(void * arg); void * recv_msg(void * arg); void error_handling(char *message); char name[NAME_SIZE] = "[DEFAULT]"; char msg[BUF_SIZE]; int main(int argc, const char * argv[]) { int sock; struct sockaddr_in serv_addr; pthread_t snd_thread, rcv_thread; void * thread_return; if(argc != 4) { printf("Usage: %s <IP> <port> name \n", argv[0]); exit(1); } sprintf(name, "[%s]", argv[3]); //聊天人名字,配置到編譯器引數裡 sock = socket(PF_INET, SOCK_STREAM, 0); if(sock == -1) error_handling("socket() error"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) error_handling("connect() error"); //多執行緒分離輸入和輸出 pthread_create(&snd_thread, NULL, send_msg, (void *)&sock); pthread_create(&rcv_thread, NULL, recv_msg, (void *)&sock); //阻塞,等待返回 pthread_join(snd_thread, &thread_return); pthread_join(rcv_thread, &thread_return); close(sock); return 0; } //傳送訊息 void * send_msg(void * arg) { int sock = *((int *)arg); char name_msg[NAME_SIZE + BUF_SIZE]; while (1) { fgets(msg, BUF_SIZE, stdin); if (!strcmp(msg, "q\n") || !strcmp(msg, "Q \n")) { close(sock); exit(0); } sprintf(name_msg, "%s %s", name, msg); write(sock, name_msg, strlen(name_msg)); } return NULL; } //接收訊息 void * recv_msg(void * arg) { int sock = *((int *)arg); char name_msg[NAME_SIZE + BUF_SIZE]; int str_len; while (1) { str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1); if(str_len == -1) return (void *)-1; name_msg[str_len] = 0; fputs(name_msg, stdout); } return NULL; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }