Linux C小项目 —— 聊天室




多线程的聊天室

服务器端:

实现多用户群体聊天功能(人数上限可设置);

每个用户所发送的消息,其他已连接服务器的用户均可以收到;

用户输入”bye”退出,服务器输入quit”退出。

服务端总共有三个函数

主函数main:服务器端的初始化,接受连接;

消息处理函数rcv_snd:接收某一用户的消息,简单处理后发送给其他所有用户;

退出函数quit:当输入quit时,服务器程序终止。

这三个函数分别属于三个线程(准确说是大于等于三个)。Main函数作为主线程,并创建一个退出函数所在的线程,以及一个每次连接新用户时创建一个对此连接的消息进行处理的线程。

客户端:

建立与服务器的连接,发送和接收消息

客户端总共有两个函数

主函数main:初始化客户端套接字,与服务器建立连接,接收消息并在本地显示

发送函数snd:从控制台读入消息,发送到服务器

这两个函数分别在两个线程上运行,一个是主函数所在的线程,另一个是主函数创建的发送函数所在的线程。

附:

宏定义调试打印开关

#define DEBUG_PRINT 1

#ifdef DEBUG_PRINT

#define DEBUG(format, ...) printf(“FILE: “__FILE__”, LINE: %d: “format”\n”, __LINE__, ##__VA_ARGS__)

#else

#define DEBUG(format, ...)

#endif

创建线程 gcc编译时需-lpthread

Phtread_t thread;  Pthread_creat(&thread, NULL, (void *)quit, NULL);

Pthread_creat(malloc(sizeof(pthread_t)), NULL, (void *)rcv_snd, (void *)i);

系统时间 #include <time.h>

Time_t ticks = time(NULL); printf(“%s\n”, ctime(&ticks));

服务端

Struct sockaddr_in serv_addr;

Bzero(&serv_addr, sizeof(serv_addr));

Serv_addr.sin_family = AF_INET;

Serv_addr.sin_port = htons(PORT);

Serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

Listenfd = socket(AF_INET, SOCK_STREAM, 0);

Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

Listen(listenfd, 10);

Int len = sizeof(cli_addr);

Connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);

客户端

Struct sockaddr_in serv_addr;

Bzero(&serv_addr, sizeof(serv_addr));

Serv_addr.sin_family = AF_INET;

Serv_addr.sin_port = htons(PORT);

Inet_pton(IP, &serv_addr.sin_addr);

Sockfd = socket(AF_INET, SOCK_STREAM, 0);

Connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

 源代码之 server.c

  1 #include <stdio.h>
  2 #include <stdlib.h>     // exit
  3 #include <string.h>
  4 #include <unistd.h>     // bind listen
  5 #include <time.h>       // time(NULL)   ctime(&ticks)
  6 #include <netinet/in.h>
  7 #include <arpa/inet.h>  // 必须包含,用于inet_ntop
  8 
  9 #define PORT 8000
 10 #define MAXMEM 10 
 11 #define BUFFSIZE 128
 12 
 13 //#define DEBUG_PRINT 1         // 宏定义 调试开关
 14 #ifdef DEBUG_PRINT
 15 #define DEBUG(format, ...) printf("FILE: "__FILE__", LINE: %d: "format"\n", __LINE__, ##__VA_ARGS__)
 16 #else
 17 #define DEBUG(format, ...)
 18 #endif
 19 
 20 int listenfd, connfd[MAXMEM];
 21 
 22 void quit();
 23 void rcv_snd(int p);
 24 
 25 int main()
 26 {
 27         struct sockaddr_in serv_addr, cli_addr;
 28 //      int len = sizeof(cli_addr), i;
 29         int i;
 30         time_t ticks;
 31         pthread_t thread;
 32         char buff[BUFFSIZE];
 33 
 34         printf("running...\n(Prompt: enter command ""quit"" to exit server)\n");
 35         DEBUG("=== initialize...");     // 初始化填充服务端地址结构
 36         bzero(&serv_addr, sizeof(struct sockaddr_in));
 37         serv_addr.sin_family = AF_INET;
 38         serv_addr.sin_port = htons(PORT);
 39         serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 40 
 41         DEBUG("=== socket..."); // socket 创建服务器端的监听套接字
 42         listenfd = socket(AF_INET, SOCK_STREAM, 0);
 43         if(listenfd < 0)
 44         {
 45                 perror("fail to socket");
 46                 exit(-1);
 47         }
 48 
 49         DEBUG("=== bind...");   // bind 将套接字与填充好的地址结构进行绑定
 50         if(bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
 51         {
 52                 perror("fail to bind");
 53                 exit(-2);
 54         }
 55 
 56         DEBUG("=== listening..."); // listen 将主动连接套接字变为被动倾听套接字
 57         listen(listenfd, MAXMEM);
 58 
 59         /* === 创建一个线程,对服务器程序进行管理,调用quit函数 === */
 60         pthread_create(&thread, NULL, (void *)(quit), NULL);
 61 
 62         // 将套接字描述符数组初始化为-1,表示空闲
 63         for(i=0; i<MAXMEM; i++)
 64                 connfd[i] = -1;
 65 
 66         while(1)
 67         {
 68                 int len;// = sizeof(cli_addr);
 69                 for(i=0; i<MAXMEM; i++)
 70                 {
 71                         if(connfd[i] == -1)
 72                                 break;
 73                 }
 74                 // accept 从listen接受的连接队列中取得一个连接
 75                 connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
 76                 if(connfd[i] < 0)
 77                 {
 78                         perror("fail to accept");
 79                 //      continue;       // 此句可以不用,accept会阻塞等待
 80                 }
 81                 ticks = time(NULL);
 82                 //sprintf(buff, "%.24s\r\n", ctime(&ticks));
 83                 printf("%.24s\n\tconnect from: %s, port %d\n",
 84                                 ctime(&ticks), inet_ntop(AF_INET, &(cli_addr.sin_addr), buff, BUFFSIZE),
 85                                 ntohs(cli_addr.sin_port));      // 注意 inet_ntop的使用,#include <arpa/inet.h>
 86 
 87                 /* === 针对当前套接字创建一个线程,对当前套接字的消息进行处理 === */
 88                 pthread_create(malloc(sizeof(pthread_t)), NULL, (void *)(&rcv_snd), (void *)i);
 89         }
 90         return 0;
 91 }
 92 
 93 void quit()
 94 {
 95         char msg[10];
 96         while(1)
 97         {
 98                 scanf("%s", msg);       // scanf 不同于fgets, 它不会读入最后输入的换行符
 99                 if(strcmp(msg, "quit") == 0)
100                 {
101                         printf("Byebye... \n");
102                         close(listenfd);
103                         exit(0);
104                 }
105         }
106 }
107 
108 void rcv_snd(int n)
109 {
110         int len, i;
111         char name[32], mytime[32], buf[BUFFSIZE];
112         time_t ticks;
113         int ret;
114 
115         // 获取此线程对应的套接字用户的名字
116         write(connfd[n], "your name: ", strlen("your name: "));
117         len = read(connfd[n], name, 32);
118         if(len > 0)
119                 name[len-1] = '\0';     // 去除换行符
120         strcpy(buf, name);
121         strcat(buf, "\tjoin in\n\0");
122         // 把当前用户的加入 告知所有用户
123         for(i=0; i<MAXMEM; i++)
124         {
125                 if(connfd[i] != -1)
126                         write(connfd[i], buf, strlen(buf));
127         }
128 
129         while(1)
130         {
131                 char temp[BUFFSIZE];
132                 if((len=read(connfd[n], temp, BUFFSIZE)) > 0)
133                 {
134                         temp[len-1] = '\0';
135                         // 当用户输入bye时,当前用户退出
136                         if(strcmp(temp, "bye") == 0)
137                         {
138                                 close(connfd[n]);
139                                 connfd[n] = -1;
140                                 pthread_exit(&ret);
141                         }
142                         ticks = time(NULL);
143                         sprintf(mytime, "%.24s\r\n", ctime(&ticks));
144                         //write(connfd[n], mytime, strlen(mytime));
145                         strcpy(buf, name);
146                         strcat(buf, "\t");
147                         strcat(buf, mytime);
148                         strcat(buf, "\r\t");
149                         strcat(buf, temp);
150                         strcat(buf, "\n");
151 
152                         for(i=0; i<MAXMEM; i++)
153                         {
154                                 if(connfd[i] != -1)
155                                         write(connfd[i], buf, strlen(buf));
156                         }
157                 }
158         }
159 
160 }
源代码之 client.c

  1 /*
  2  * FILE: client.c
  3  * DATE: 20180206
  4  * ==============
  5  */
  6 
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <string.h>
 10 
 11 #include <netinet/in.h>
 12 
 13 #define BUFFSIZE 128
 14 #define HOST_IP "192.168.159.3"
 15 #define PORT 8000
 16 
 17 int sockfd;
 18 
 19 void snd();
 20 
 21 int main()
 22 {
 23         pthread_t thread;       // pthread_t 线程,gcc编译时需加上-lpthread
 24         struct sockaddr_in serv_addr;   // struct sockaddr_in
 25         char buf[BUFFSIZE];
 26         // 初始化服务端地址结构
 27         bzero(&serv_addr, sizeof(struct sockaddr_in));  // bzero 清零
 28         serv_addr.sin_family = AF_INET;         // sin_family   AF_INET
 29         serv_addr.sin_port = htons(PORT);       // sin_port     htons(PORT)
 30         inet_pton(HOST_IP, &serv_addr.sin_addr);        // inet_pton
 31         // 创建客户端套接字
 32         sockfd = socket(AF_INET, SOCK_STREAM, 0);       // socket 创建套接字
 33         if(sockfd < 0)
 34         {
 35                 perror("fail to socket");
 36                 exit(-1);
 37         }
 38         // 与服务器建立连接
 39         printf("connecting... \n");
 40         if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) // connect
 41         {
 42                 perror("fail to connect");
 43                 exit(-2);
 44         }
 45         /* === 从此处开始 程序分做两个线程 === */
 46         // 创建发送消息的线程,调用发送消息的函数snd
 47         pthread_create(&thread, NULL, (void *)(&snd), NULL);    // pthread_create
 48         // 接收消息的线程
 49         while(1)
 50         {
 51                 int len;
 52                 if((len=read(sockfd, buf, BUFFSIZE)) > 0)       // read 读取通信套接字
 53                 {
 54                         buf[len] = '\0';        // 添加结束符,避免显示缓冲区中残留的内容
 55                         printf("\n%s", buf);
 56                         fflush(stdout);         // fflush 冲洗标准输出,确保内容及时显示
 57                 }
 58         }
 59         return 0;
 60 }
 61 
 62 // 发送消息的函数
 63 void snd()
 64 {
 65         char name[32], buf[BUFFSIZE];
 66 
 67         fgets(name, 32, stdin); // fgets 会读取输入字符串后的换行符
 68         write(sockfd, name, strlen(name));      // write 写入通信套接字
 69         while(1)
 70         {
 71                 fgets(buf, BUFFSIZE, stdin);
 72                 write(sockfd, buf, strlen(buf));
 73                 if(strcmp(buf, "bye\n") == 0)   // 注意此处的\n
 74                         exit(0);
 75         }
 76 }



猜你喜欢

转载自blog.csdn.net/trb331617/article/details/79275091