利用多執行緒和TCP技術,實現客戶端與服務端之間的通訊
阿新 • • 發佈:2019-01-25
server.c
#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<pthread.h> struct ps{ int st; pthread_t *thid; }; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//定義一個鎖的變數 int status = 0;//定義一個全域性變數,用來標記客戶端登陸 //伺服器端 //同樣有兩個執行緒,一個向client端傳送資料,一個向client端讀取資料 //此執行緒有一個重要解釋: //當客戶端程式想要連線伺服器端時,但它之前有客戶端正連線,伺服器程式往客戶端程式傳送了一個關閉客戶端訊息,客戶端也會發送一個訊息給伺服器端 //伺服器端收到此訊息後,會結束服務端的 recvsocket執行緒,並將全域性標誌位置0 void * recvsocket(void * arg)//從client端往server端讀取資料 { struct ps *p = (struct ps *) arg; int st = p ->st; char buf[1024]; while(1) { memset(buf,0,sizeof(buf)); int rc = recv(st,buf,sizeof(buf),0); if(rc <= 0 ) break; printf("%s\n",buf); } pthread_mutex_lock(&mutex);//為全域性變數加一個互斥鎖,防止與執行緒函式同時讀寫變數的衝突 status = 0; pthread_mutex_unlock(&mutex); pthread_cancel(*(p->thid)); pthread_exit(0); } //但客戶端退出後,服務端生成的 sendsocket執行緒,不會退出,會造成資源浪費。需要藉助recvsocket執行緒的pthread_cancel函式將此執行緒殺死 void * sendsocket(void * arg)//向client端寫資料 { int st = *(int *) arg; char s[1024]; while(1) { memset(s, 0, sizeof(s)); read(STDIN_FILENO, s, sizeof(s));//從鍵盤讀取使用者輸入資訊 send(st, s, strlen(s), 0); memset(s, 0, sizeof(s)); } pthread_exit(0); } int main() { int st = socket(AF_INET,SOCK_STREAM,0);//初始化socket struct sockaddr_in addr;//定義一個IP地址的結構 addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1; if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { printf("setsockopt failed %s\n", strerror(errno)); return EXIT_FAILURE; } //伺服器端程式,需要使用bind將IP與server程式繫結 if(bind(st,(struct sockaddr *) &addr,sizeof(addr)) == -1) { printf("bind failed %s\n", strerror(errno)); return 0; } if(listen(st,20) == -1) { printf("listen failed %s\n", strerror(errno)); return 0; } int client_st = 0; struct sockaddr_in client_addr; pthread_t thid1,thid2; while(1) { memset(&client_addr,0,sizeof(client_addr)); socklen_t len = sizeof(client_addr); //accept會阻塞,直到有客戶端連線過來,accept返回client的socket描述符 client_st = accept(st,(struct sockaddr *) &client_addr,&len);//阻塞呼叫 pthread_mutex_lock(&mutex);//為全域性變數加一個互斥鎖,防止與執行緒函式同時讀寫變數的衝突 status++; pthread_mutex_unlock(&mutex); if(status > 1)//若status大於1代表這是第二個socket連線,有兩個客戶端登陸連線了 { close(client_st);//將第二個客戶端套介面關閉,服務端程式向客戶端程式傳送一個訊號,在客戶端程式中recv值為0的訊息會,客戶端的接收訊號的執行緒結束生命 continue;//跳出此迴圈 } if (client_st == -1) { printf("accept failed %s\n", strerror(errno)); return EXIT_FAILURE; } printf("accept by %s\n",inet_ntoa(client_addr.sin_addr)); struct ps ps1; ps1.st = client_st; ps1.thid = &thid2; pthread_create(&thid1,NULL,recvsocket,&ps1);//向recvsocket執行緒只能傳遞一個引數,但可以寫一個結構,實現了一次可以傳多個引數的作用 //結構的第一個引數仍為客戶端的套介面,第二個引數為sendsocket執行緒的id號,目的是讓recvsocket執行緒結束自己的生命 pthread_create(&thid2,NULL,sendsocket,&client_st); pthread_detach(thid1); pthread_detach(thid2); } close(st); return 0; }
client.c
#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<pthread.h> //本程式實現客戶端和伺服器端之間相互通訊,但伺服器一次只能和一個客戶端之間連線,若有其他客戶端連線會直接掛死 //使用多執行緒技術 //客戶端程式 //出一個解釋:當一個客戶端想要連線伺服器端時,此時在它之前正有一個客戶端連線, //此時它就會被服務端程式殺死,並通過服務端send函式傳送一個訊息,在客戶端recvsocket函式中recv一個值為0,並結束此執行緒 void * recvsocket(void * arg)//接收client端socket資料的執行緒 { int st = *(int *) arg; char buf[1024]; memset(buf,0,sizeof(buf)); while(1) { int rc = recv(st,buf,sizeof(buf),0); if(rc <= 0)//如果recv返回小於等於0,代表socket已經關閉或者出錯了 break; printf("%s\n",buf); memset(buf, 0, sizeof(buf)); } pthread_exit(0); } void * sendsocket(void * arg)//向client端socket傳送資料的執行緒 { int st = *(int *) arg; char buf[1024]; memset(buf,0,sizeof(buf)); while(1) { read(STDIN_FILENO,buf,sizeof(buf));//從鍵盤中讀取使用者輸入 if(send(st,buf,strlen(buf),0) == -1)//傳送buf的資料 { printf("send failed %s\n",strerror(errno)); return 0; } } pthread_exit(0); } int main(void) { //初始化socket int st = socket(AF_INET,SOCK_STREAM,0); //定義一個IP地址的結構 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = inet_addr("192.168.1.103"); //客戶端程式需要使用connect函式連線到結構addr指定的埠和IP地址上 if(connect(st,(struct sockaddr * ) &addr,sizeof(addr)) == -1) { printf("connect failed %s\n",strerror(errno)); return 0; } //連線成功後就可以傳送和接收資料了,但需要建立兩個執行緒 pthread_t thid1,thid2; pthread_create(&thid1,NULL,recvsocket,&st); pthread_create(&thid2,NULL,sendsocket,&st); pthread_detach(thid2); pthread_join(thid1, NULL); close(st); //關閉socket return EXIT_SUCCESS; }