RT,Linux下使用c實現的多線程服務器。這個真是簡單的不能再簡單的了,有寫的很差的地方,還但願大神輕拍。(>﹏<)編程
本學期Linux、unix網絡編程的第四個做業。ubuntu
先上實驗要求:centos
【實驗目的】服務器
一、熟練掌握線程的建立與終止方法;網絡
二、熟練掌握線程間通訊同步方法;多線程
三、應用套接字函數完成多線程服務器,實現服務器與客戶端的信息交互。socket
【實驗內容】函數
經過一個服務器實現最多5個客戶之間的信息羣發。測試
服務器顯示客戶的登陸與退出;spa
客戶鏈接後首先發送客戶名稱,以後發送羣聊信息;
客戶輸入bye表明退出,在線客戶能顯示其餘客戶的登陸於退出。
實現提示:
一、服務器端:
主線程:
定義一個全局客戶信息表ent,每一個元素對應一個客戶,存儲:socket描述符、客戶名、客戶IP、客戶端口、狀態(初值爲0)。
主線程循環接收客戶鏈接請求,在ent中查詢狀態爲0的元素,
若是不存在狀態爲0的元素(即鏈接數超過最大鏈接數),向客戶發送EXIT標誌;
不然,修改客戶信息表中該元素的socket描述符、客戶IP、客戶端口號,狀態爲1(表示socket可用);
同時建立一個通訊線程並將客戶索引號index傳遞給通訊線程。
通訊線程:
首先向客戶端發送OK標誌
循環接收客戶發來信息,若信息長度爲0,表示客戶端已關閉,向全部在線客戶發送該用戶退出;
若信息爲用戶名,修改全局客戶信息表ent中index客戶的用戶名name,並顯示該用戶登陸;
若信息爲退出,修改全局客戶信息表ent中index客戶狀態爲0,並顯示該用戶退出,終止線程;
同時查詢全局客戶信息表ent,向狀態爲1的客戶發送接收的信息。
二、客戶端:
根據用戶從終端輸入的服務器IP地址及端口號鏈接到相應的服務器;
鏈接成功後,接收服務端發來的信息,若爲EXIT,則達到最大用戶量,退出;
若爲OK,能夠通信,首先先發送客戶名稱;
主進程循環從終端輸入信息,並將信息發送給服務器;
當發送給服務器爲bye後,程序退出。
同時建立一個線程負責接收服務器發來的信息,並顯示,當接收的長度小於等於0時終止線程;
有了上一次多進程服務器的編寫經驗之後,寫起多線程就簡單多了。
照例仍是繪製一下流程圖,以方便咱們理清思路。
好啦,如今能夠開始擼代碼了。
先實現一下用於通訊的結構體clientmsg.h:(和多進程服務器是同樣的)
1 //CLIENTMSG between server and client 2 #ifndef _clientmsg 3 #define _clientmsg 4 5 //USER MSG EXIT for OP of CLIENTMSG 6 #define EXIT -1 7 #define USER 1 8 #define MSG 2 9 #define OK 3 10 11 #ifndef CMSGLEN 12 #define CMSGLEN 100 13 #endif 14 15 struct CLIENTMSG{ 16 int OP; 17 char username[20]; 18 char buf[CMSGLEN]; 19 }; 20 21 #endif
客戶端程序看起來比較簡單,我們把它實現了,client.c:
1 #include <stdio.h> 2 #include <string.h> 3 #include <sys/socket.h> 4 #include <netinet/in.h> 5 #include <stdlib.h> 6 #include <sys/types.h> 7 #include <sys/wait.h> 8 #include <signal.h> 9 #include <unistd.h> 10 #include <pthread.h> 11 #include "clientmsg.h" 12 13 struct ARG{ 14 int sockfd; 15 struct CLIENTMSG clientMsg; 16 }; 17 18 void *func(void *arg); 19 void process_cli(int sockfd,struct CLIENTMSG clientMsg); 20 int main(){ 21 int sockfd; 22 char ip[20]; 23 int port; 24 pthread_t tid; 25 struct sockaddr_in server; 26 struct CLIENTMSG clientMsgSend; 27 struct ARG *arg; 28 /*---------------------socket---------------------*/ 29 if((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1){ 30 perror("socket error\n"); 31 exit(1); 32 } 33 34 /*---------------------connect--------------------*/ 35 printf("Please input the ip:\n"); 36 scanf("%s",ip); 37 printf("Please input the port:\n"); 38 scanf("%d",&port); 39 bzero(&server,sizeof(server)); 40 server.sin_family = AF_INET; 41 server.sin_port = htons(port); 42 inet_aton(ip,&server.sin_addr); 43 if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))== -1){ 44 perror("connect() error\n"); 45 exit(1); 46 } 47 recv(sockfd,&clientMsgSend,sizeof(clientMsgSend),0); 48 if(clientMsgSend.OP == OK){ 49 //建立一個線程 50 arg = (struct ARG *)malloc(sizeof(struct ARG)); 51 arg->sockfd = sockfd; 52 pthread_create(&tid,NULL,func,(void *)arg); 53 //主線程 54 printf("Please input the username:\n"); 55 scanf("%s",clientMsgSend.username); 56 clientMsgSend.OP = USER; 57 send(sockfd,&clientMsgSend,sizeof(clientMsgSend),0); 58 while(1){ 59 clientMsgSend.OP = MSG; 60 scanf("%s",clientMsgSend.buf); 61 if(strcmp("bye",clientMsgSend.buf) == 0){ 62 clientMsgSend.OP = EXIT; 63 send(sockfd,&clientMsgSend,sizeof(clientMsgSend),0); 64 break; 65 } 66 send(sockfd,&clientMsgSend,sizeof(clientMsgSend),0); 67 } 68 pthread_cancel(tid); 69 } 70 else{ 71 printf("以達到最大鏈接數!\n"); 72 } 73 /*------------------------close--------------------------*/ 74 close(sockfd); 75 76 return 0; 77 } 78 79 80 void *func(void *arg){ 81 struct ARG *info; 82 info = (struct ARG *)arg; 83 process_cli(info->sockfd,info->clientMsg); 84 free(arg); 85 pthread_exit(NULL); 86 } 87 void process_cli(int sockfd,struct CLIENTMSG clientMsg){ 88 int len; 89 while(1){ 90 bzero(&clientMsg,sizeof(clientMsg)); 91 len =recv(sockfd,&clientMsg,sizeof(clientMsg),0); 92 if(len > 0){ 93 if(clientMsg.OP ==USER){ 94 printf("the user %s is login.\n",clientMsg.username ); 95 } 96 else if(clientMsg.OP == EXIT){ 97 printf("the user %s is logout.\n",clientMsg.username); 98 } 99 else if(clientMsg.OP == MSG){ 100 printf("%s: %s\n",clientMsg.username,clientMsg.buf ); 101 } 102 } 103 } 104 }
而後是服務器端的實現,server.c:
1 #include <stdio.h> 2 #include <string.h> 3 #include <sys/socket.h> 4 #include <netinet/in.h> 5 #include <stdlib.h> 6 #include <sys/types.h> 7 #include <sys/wait.h> 8 #include <sys/stat.h> 9 #include <unistd.h> 10 #include <fcntl.h> 11 #include <sys/ipc.h> 12 #include <pthread.h> 13 #include "clientmsg.h" 14 15 struct Entity{ 16 int sockfd; 17 char username[20]; 18 char buf[CMSGLEN]; 19 struct sockaddr_in client; 20 int stat; 21 }; 22 23 void *func(void *arg); 24 void communicate_process(int index); 25 struct Entity ent[5]; 26 27 int main(){ 28 29 struct sockaddr_in server; 30 struct sockaddr_in client; 31 int listenfd,connetfd; 32 char ip[20]; 33 int port; 34 int addrlen; 35 struct CLIENTMSG clientMsg; 36 pthread_t tid; 37 int *arg; 38 /*---------------------socket-------------------*/ 39 if((listenfd = socket(AF_INET,SOCK_STREAM,0))== -1){ 40 perror("socket() error\n"); 41 exit(1); 42 } 43 44 /*----------------------IO-----------------------*/ 45 printf("Please input the ip:\n"); 46 scanf("%s",ip); 47 printf("Please input the port:\n"); 48 scanf("%d",&port); 49 50 /*---------------------bind----------------------*/ 51 bzero(&server,sizeof(server)); 52 server.sin_family = AF_INET; 53 server.sin_port = htons(port); 54 server.sin_addr.s_addr = inet_addr(ip); 55 if(bind(listenfd,(struct sockaddr *)&server,sizeof(server))== -1){ 56 perror("bind() error\n"); 57 exit(1); 58 } 59 60 /*----------------------listen-------------------*/ 61 if (listen(listenfd,5)== -1){ 62 perror("listen() error\n"); 63 exit(1); 64 } 65 int i; 66 for(i=0;i<5;i++){ 67 ent[i].stat = 0; 68 } 69 while(1){ 70 addrlen = sizeof(client); 71 if((connetfd = accept(listenfd,(struct sockaddr *)&client,&addrlen))== -1){ 72 perror("accept() error\n"); 73 exit(1); 74 } 75 int index = 5; 76 for(i=0;i<5;i++){ 77 if(ent[i].stat == 0){ 78 index = i; 79 break; 80 } 81 } 82 if(index <= 4){ 83 printf("connetfd:%d\n",connetfd ); 84 ent[index].client = client; 85 ent[index].sockfd = connetfd; 86 ent[index].stat = 1; 87 arg = malloc(sizeof(int)); 88 *arg = index; 89 pthread_create(&tid,NULL,func,(void *)arg); 90 91 } 92 else{ 93 bzero(&clientMsg,sizeof(clientMsg)); 94 clientMsg.OP = EXIT; 95 send(connetfd,&clientMsg,sizeof(clientMsg),0); 96 close(connetfd); 97 } 98 99 } 100 101 /*----------------------close-------------------*/ 102 103 close(listenfd); 104 105 return 0; 106 } 107 108 109 /*----------------------------函數實現區----------------------------*/ 110 void *func(void *arg){ 111 int *info ; 112 info = (int *)arg; 113 communicate_process( *info); 114 pthread_exit(NULL); 115 } 116 void communicate_process(int index){ 117 struct CLIENTMSG sendMsg; 118 struct CLIENTMSG recvMsg; 119 printf("sockfd:%d\n",ent[index].sockfd ); 120 sendMsg.OP = OK; 121 send(ent[index].sockfd,&sendMsg,sizeof(sendMsg),0); 122 123 while(1){ 124 bzero(&sendMsg,sizeof(sendMsg)); 125 bzero(&recvMsg,sizeof(recvMsg)); 126 int len =recv(ent[index].sockfd,&recvMsg,sizeof(recvMsg),0); 127 if(len > 0){ 128 if(recvMsg.OP == USER){ 129 printf("user %s login from ip:%s,port:%d\n",recvMsg.username,inet_ntoa(ent[index].client.sin_addr),ntohs(ent[index].client.sin_port) ); 130 bcopy(recvMsg.username,ent[index].username,strlen(recvMsg.username)); 131 sendMsg.OP = USER; 132 } 133 else if(recvMsg.OP == EXIT){ 134 printf("user %s is logout\n",recvMsg.username ); 135 sendMsg.OP = EXIT; 136 ent[index].stat = 0; 137 int i; 138 for(i=0;i<5;i++){ 139 if(ent[i].stat == 1){ 140 141 send(ent[i].sockfd,&sendMsg,sizeof(sendMsg),0); 142 } 143 } 144 break; 145 } 146 else if(recvMsg.OP == MSG){ 147 sendMsg.OP = MSG; 148 } 149 bcopy(recvMsg.username,sendMsg.username,strlen(recvMsg.username)); 150 bcopy(recvMsg.buf,sendMsg.buf,strlen(recvMsg.buf)); 151 int i; 152 for(i=0;i<5;i++){ 153 if(ent[i].stat == 1){ 154 printf("stat 1...\n"); 155 send(ent[i].sockfd,&sendMsg,sizeof(sendMsg),0); 156 } 157 } 158 } 159 else{ 160 continue; 161 } 162 } 163 }
最後是makefile文件:
main:server.o client.o gcc server.o -oserver -lpthread gcc client.o -oclient -lpthread server.o:server.c clientmsg.h gcc -c server.c client.o:client.c clientmsg.h gcc -c client.c
若是程序中引入了#include <pthread.h>,要記得在編譯的時候 加上 -lpthread。
下面上一下演示過程:(測試環境,Red Hat Enterprise Linux 6 + centos系Linux,ubuntu下可能會有些問題。)
首先先把服務端啓動開來,爲了方便測試,這裏直接使用的是127.0.0.1的localhost。
而後啓動兩個客戶端用來測試,在用戶登陸的時候客戶端會有消息提醒。服務端會有日誌打印輸出客戶端的名字和登陸ip、端口。
客戶能夠發送消息了,如圖發送與接收均正常。這裏爲了簡單演示只是啓動了2個。
輸入bye之後便可退出聊天並下線。當有客戶下線的時候,在線的客戶端會收到下線提醒,客戶端會有日誌打印輸出。