【LINUX/UNIX网络编程】之简单多线程服务器(多人群聊系统)
RT,Linux下使用c实现的多线程服务器。这个真是简单的不能再简单的了,有写的不好的地方,还希望大神轻拍。(>﹏<)
本学期Linux、unix网络编程的第四个作业。
先上实验要求:
【实验目的】
1、熟练掌握线程的创建与终止方法;
2、熟练掌握线程间通信同步方法;
3、应用套接字函数完成多线程服务器,实现服务器与客户端的信息交互。
【实验内容】
通过一个服务器实现最多5个客户之间的信息群发。
服务器显示客户的登录与退出;
客户连接后首先发送客户名称,之后发送群聊信息;
客户输入bye代表退出,在线客户能显示其他客户的登录于退出。
实现提示:
1、服务器端:
主线程:
定义一个全局客户信息表ent,每个元素对应一个客户,存储:socket描述符、客户名、客户IP、客户端口、状态(初值为0)。
主线程循环接收客户连接请求,在ent中查询状态为0的元素,
如果不存在状态为0的元素(即连接数超过最大连接数),向客户发送EXIT标志;
否则,修改客户信息表中该元素的socket描述符、客户IP、客户端口号,状态为1(表示socket可用);
同时创建一个通信线程并将客户索引号index传递给通信线程。
通信线程:
首先向客户端发送OK标志
循环接收客户发来信息,若信息长度为0,表示客户端已关闭,向所有在线客户发送该用户退出;
若信息为用户名,修改全局客户信息表ent中index客户的用户名name,并显示该用户登录;
若信息为退出,修改全局客户信息表ent中index客户状态为0,并显示该用户退出,终止线程;
同时查询全局客户信息表ent,向状态为1的客户发送接收的信息。
2、客户端:
根据用户从终端输入的服务器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以后即可退出聊天并下线。当有客户下线的时候,在线的客户端会收到下线提醒,客户端会有日志打印输出。