嵌入式爱好者

查看: 14580|回复: 0

[Linux] C语言实现简易Linux终端版本聊天室

[复制链接]

50

主题

51

帖子

121

积分

扫一扫,手机访问本帖
发表于 2019-9-26 16:35:49 | 显示全部楼层 |阅读模式
简易Linux终端聊天室
必备Linux命令和C语言基础
http://www.makeru.com.cn/video/1876.html?s=69821

今天我们来实现一个简单的小项目,在这个项目中,我们将实现一个终端版的简易Linux聊天室。  

实现的效果:服务器启动,监测客户端连接的个数,监测每个客户端的IP地址以及端口号,当每个客户端发送消息时,服务器上会有线程专门将每个客户端发送的信息记录在界面上,就类似平时使用QQ群聊一样。我们来看看这个简易的Linux聊天室如何来实现吧。如图4-5-12所示。
1
2
3
1、实现一个基本的服务器和客户端的步骤

一、创建服务器的流程

(1)调用socket函数创建一个套接口,并返回描述符。

(2)调用bind函数使服务器进程与一个端口号绑定。

(3)调用listen设置客户端如队列的大小。

(4)调用accept接收一个连接,如果接入队列不为空的话。并且相应返回一个已连接的套接口描述符。

(5)调用send和recv用来在已连接的套接口间进行发送和接收数据。

二、创建客户端流程

(1)调用socket函数创建一个套接口,并返回描述符。

(2)调用connect向服务器发送连接请求,返回一个已连接的套接口。

(3)调用send和recv在已连接的套接口间发送和接收数据。

1.1服务器将要完成的工作

(1)获取套接字

(2)设置端口复用

(3)绑定连接的IP还有端口号

(4)监听

(5)创建一条线程用于显示客户端连接信息,具体连接的人数,顺便将客户连接的IP以及端口号打印出来。

(6)开始接收

(7)创建一条线程用于将客户端直接收发的信息分发到客户端处进行显示。

下面具体看看服务器代码的实现 server.c

1#include <stdio.h>
  2#include <unistd.h>
  3#include <sys/socket.h>
  4#include <netinet/in.h>
  5#include <netinet/ip.h>
  6#include <string.h>
  7#include <pthread.h>
  8#include <sys/time.h>
  9//设置客户端最大的个数为40个
10#define   MAXCONNECTION        40
11#define   msleep(x)     (usleep(x*1000))
12struct  Data
13{
14    int  live ;   //0  无人用   1有人用
15    int  sockfd ;
16    struct in_addr in ;
17    unsigned short  port ;
18};
19
20struct Data array[MAXCONNECTION] = {0} ;
21void *do_thread_showconnect(void *arg);
22void *do_thread_clientopt(void *arg);
23int main(void)
24{
25    int sockfd ;
26    //1.获取套接字
27    sockfd =  socket(AF_INET ,  SOCK_STREAM , 0);
28    if(sockfd < 0)
29    {
30        perror("get socket fail");
31        return -1 ;
32    }
33    //2.设置端口复用
34    int  on  = 4 ;  
35    setsockopt(sockfd , SOL_SOCKET , SO_REUSEADDR , &on , sizeof(int));
36    //3.绑定IP与端口
37    struct  sockaddr_in  addr ;
38    addr.sin_family = AF_INET ;
39    addr.sin_port = htons(10086);
40    //设置为INADDR_ANY,表示监听所有的。
41    addr.sin_addr.s_addr = INADDR_ANY ;
42    int ret ;
43    ret = bind(sockfd , (struct sockaddr *)&addr , sizeof(struct sockaddr_in)) ;
44    if(ret < 0)
45    {
46        perror("bind error");
47        return  -2 ;
48    }
49    //4.监听
50    listen(sockfd , 30);
51    int  peersockfd ;
52    struct  sockaddr_in  peeraddr ;
53    socklen_t   len =   sizeof(struct sockaddr_in) ;   
54    struct Data tmp ;
55    char *message = "too more connction , connect fail" ;
56    int i ;
57    pthread_t tid ;
58    //创建一条线程用于显示已连接的客户端的个数
59    pthread_create(&tid , NULL , do_thread_showconnect , NULL);
60    pthread_detach(tid);
61
62    while(1)
63    {
64        peersockfd = accept(sockfd , (struct sockaddr *)&peeraddr , &len);
65        if(peersockfd < 0)
66        {
67            perror("accept fail");
68            return  -3 ;
69        }
70        tmp.sockfd = peersockfd ;   
71        tmp.in = peeraddr.sin_addr ;
72        tmp.port = ntohs(peeraddr.sin_port);   
73        tmp.live = 1 ;
74
75        for(i = 0 ; i < MAXCONNECTION ; i++)
76        {
77            if(array.live == 0)
78            {
79                array = tmp ;
80                break;
81            }
82        }
83        //判断是否连接个数已满
84        if(i == MAXCONNECTION)
85        {
86            write(peersockfd , message , strlen(message));
87            close(peersockfd);
88            continue ;
89        }
90        //创建一条线程用于显示客户端之间互相发送的即时信息
91        pthread_create(&tid , NULL , do_thread_clientopt , (void *)i);
92        pthread_detach(tid);
93    }
94
95
96    return 0 ;
97}
98
99//线程执行函数,用于显示已连接的客户端的个数。
100void *do_thread_showconnect(void *arg)
101{
102    int  i , count = 0;
103    while(1)   
104    {
105        system("clear");
106        printf("客户端连接信息:  连接人数:%d\n" , count);
107        count = 0 ;
108        for(i = 0 ; i < MAXCONNECTION ; i++)
109        {
110            if(array.live == 1)
111            {
112                count++ ;
113                printf("IP:%s   port:%d \n" , inet_ntoa(array.in) , array.port);
114            }
115        }
116        msleep(100);
117    }
118}
119//线程执行函数,用于显示客户端之间互相发送的即时信息
120void *do_thread_clientopt(void *arg)
121{
122    //转发信息
123    int num = (int)arg ;
124    char buffer[12240] = {0};
125    char tmp[10240] = {0};
126    int ret ;
127    struct  timeval  tv ;
128    struct  timezone tz ;
129    struct  tm *tt ;
130    int i ;
131
132    while(1)
133    {
134        ret = read(array[num].sockfd , tmp , 1024);
135        if(ret <= 0)
136            break;
137        tmp[ret] = '\0'  ;
138        gettimeofday(&tv , &tz);
139        tt = localtime(&tv.tv_sec);
140        sprintf(buffer , "%s @ %d:%d:%d :\n%s" ,inet_ntoa(array[num].in) , tt->tm_hour , tt->tm_min , tt->tm_sec , tmp);
141
142        for(i = 0 ; i < MAXCONNECTION ; i++)
143        {
144            if(array.live == 1)
145            {
146                write(array.sockfd , buffer , strlen(buffer));
147            }
148        }
149    }
150    close(array[num].sockfd);
151    array[num].live = 0 ;
152
153}
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
服务端的工作已经设置完毕,显示就开始设置客户端吧,客户端就可以把它想象成我们的QQ群聊,只要每个人一发信息,那么整个群都可以看得到。
1
1.2客户端将要完成的工作

(1)连接对应的服务器,必须指定服务器的ip地址

(2)创建一条线程,用于读取从服务器转发过来的消息

(3)客户端可以自由的输入,通过服务器,发送给其它的客户端,让它们也可以看得到。

下面具体看看客户端代码的实现 client.c


```c
1#include <stdio.h>
2#include <sys/socket.h>
3#include <netinet/in.h>
4#include <netinet/ip.h>
5#include <pthread.h>
6void *do_thread(void * arg);
7int main(void)
8{
9    int sd ;   
10
11    sd = socket(AF_INET ,  SOCK_STREAM ,  0);
12    if(sd < 0)
13    {
14        perror("get socket  fail");
15        return -1 ;
16    }
17    //1.连接对应的服务器
18    //connect
19    struct sockaddr_in   addr ;
20    addr.sin_family = AF_INET ;
21    addr.sin_port = htons(10086);
22    addr.sin_addr.s_addr  =  inet_addr("10.126.72.56");
23
24    int ret ;
25    ret = connect(sd , (struct sockaddr *)&addr , sizeof(struct sockaddr_in));
26    if(ret != 0)
27    {
28        perror("connect fail");
29        return -3 ;
30    }
31    printf("connect success ... \n");
32    pthread_t tid ;
33    //创建一条线程用于接收从服务器端收到的数据
34    pthread_create(&tid , NULL , do_thread , (void *)sd);
35    pthread_detach(tid);
36
37    char buffer[1024] = {0};
38
39    while(1)
40    {
41        //阻塞从标准输出读取信息到buffer
42        ret = read(0 , buffer , 1024);  
43        if(ret > 1024)
44            continue ;
45        //按下回车后将buffer中的内容写到文件描述符
46        //通过服务器转发给其它正在连接的客户端
47        write(sd, buffer , ret);
48    }
49
50    return 0 ;
51}
52void *do_thread(void * arg)
53{
54    int sd = (int)arg;
55    int ret ;
56    char buffer[1024] = {0};
57    while(1)
58    {
59        //从服务器读取数据并显示在客户端上
60        ret = read(sd , buffer , 1024);
61        if(ret <= 0)
62            break;
63        buffer[ret] = '\0' ;
64        printf("recv:%s" , buffer);
65    }
66}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

源码编写完毕,接下来测试一下这个简单聊天室的功能:编译过程省略,注意,该程序在32位操作系统上运行,且要加上线程库才可以编译成功。分别编译server.c和client.c

1gcc server.c  -o  server  -m32  -lpthread
2gcc client.c  -o  client  -m32  -lpthread


下面先运行服务器,执行./server如图4-5-13所示。

![在这里插入图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtUUpoS2lhV1luaWJOY3IzT0hJSzh3czFkdlpaVzU2bHY1WGxzenppYnNQTFlpYzczYXJoWDBVSUlMUS82NDA?x-oss-process=image/format,png)


下面启动不同IP的客户端,找多一台电脑即可测试。在我方47服务器上执行客户端./client,如图4-5-14所示。客户端连接成功了!  

![在这里插入图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtYzRzR0NWb1JmN1hDYVVJaWN4a1JvNzJZdVJpYjNvcXQzdUZtOEt0WTltRjIxUk9TQnpLNW15WmcvNjQw?x-oss-process=image/format,png)


接下来看看服务器上有什么变化,如图4-5-15所示。

![在这里插入图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtY1FQQllaSGMxRDVqWThKZzNuRWdPRGliNDlzOFAzUjhjT05YV1dJWlBEVWg1NGZ0UWdRZkxwUS82NDA?x-oss-process=image/format,png)


在我方56服务器上执行客户端./client,如图4-5-16所示。


![在这里插入图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtb0M5c1JzNnppYmhwakgzUGZ5aWF6YmUzcTdGSFV6bGJ6RDMxNGU3WTBuVGFTNU**WNTRmppY01MUS82NDA?x-oss-process=image/format,png)

接下来看看服务器上有什么变化,如图4-5-17所示。



![在这里插入图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtYnVpYlp6Mm9mSk9LMjJMdHJna0xpY2ZPb3RpYXdobVZRSVVyZnNKRlZPTVdpY2F3am5lWW9zZmVjdy82NDA?x-oss-process=image/format,png)

    在47服务器上的客户端发送一条消息给56服务器上的客户端,同样的在56服务器上的客户端也发送一条信息给47的服务器上的客户端,观察变化,如图4-5-18所示。

![在这里插入图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtMlhPRmZZdkF0MXNiRW1kUGhKMkcwRGliSWdBVzBRQkRTcWliNWsxWDdHbzVpYm1kaWNMRWliRmVUQlEvNjQw?x-oss-process=image/format,png)



    在这里我们看到,56服务器上的客户端发送hello world的消息给47服务器上的客户端,47服务器上的客户端也收到了helloworld消息,同样的,47服务器上的客户端给56服务器上的客户端发送I am 47 server的消息,56服务器上的客户端也收到了I am 47 server的消息。这个简易版本的Linux聊天室就算完成了,接下来,请读者发挥自己的想象力,结合VT100控制码,写出一个更漂亮的终端版聊天工具吧。


VT100控制码表

```c
1具体格式有两种,
2&#8226;  一种数字形式,
3\033[<数字>m .
4如 \033[40m ,表示让后面字符输出用背景黑色输出 \033[0m 表示取消前面的设置。
5&#8226;  另一种是控制字符形式。
6\033[K 清除从光标到行尾的内容
7\033[nC 光标右移 n 行
8输出时, 也可以用 ^[来代替.
9VT100  控制码
10VT100 控制码归类如下。
11\033[0m 关闭所有属性
12\033[1m 设置高亮度
13\033[4m 下划线
14\033[5m 闪烁
15\033[7m 反显
16\033[8m 消隐
17\033[30m -- \033[37m 设置前景色
18\033[40m -- \033[47m 设置背景色
19\033[nA 光标上移 n 行
20\033[nB 光标下移 n 行
21\033[nC 光标右移 n 行
22\033[nD 光标左移 n 行
23\033[y;xH 设置光标位置
24\033[2J 清屏
25\033[K 清除从光标到行尾的内容
26\033[s 保存光标位置
27\033[u 恢复光标位置
28\033[?25l 隐藏光标
29\033[?25h 显示光标
30VT100   关于颜色的说明:
31VT100 的颜色输出分为,注意要同时输出前景的字符颜色和背景颜色。
32字背景颜色范围:40----49
3340:黑
3441:深红
3542:绿
3643:黄色
3744:蓝色
3845:紫色
3946:深绿
4047:白色
41字颜色:30-----------39
4230:黑
4331:红
4432:绿
4533:黄
4634:蓝色
4735:紫色
4836:深绿
4937:白色
50这样输出一个字符串比较完整如下
51echo "\033[字背景颜色;字体颜色 m 字符串\033[0m"
52例:
53echo "\033[41;36m something here \033[0m"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
1例如:
2C语言编程里可以这么用
3设置光标位置 x=1 y=2
4printf("\033[%d;%dH\033[43m \033[0m" ,1, 2);
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋| 飞凌嵌入式 ( 冀ICP备12004394号-1 )

GMT+8, 2024-11-24 18:11

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表