"); //-->
新闻??|?? 论坛??|?? 博客??|?? 在线研讨会
C语言实现简易Linux终端版本聊天室
3249821294 | 2019-09-26 16:37:38 ?? 阅读:6656?? 发布文章

今天我们来实现一个简单的小项目,在这个项目中,我们将实现一个终端版的简易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

? 2#include

? 3#include

? 4#include

? 5#include

? 6#include

? 7#include

? 8#include

? 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[i].live == 0)

?78? ? ? ? ? ? {

?79? ? ? ? ? ? ? ? array[i] = 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[i].live == 1)

111? ? ? ? ? ? {

112? ? ? ? ? ? ? ? count++ ;?

113? ? ? ? ? ? ? ? printf("IP:%s? ?port:%d \n" , inet_ntoa(array[i].in) , array[i].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[i].live == 1)

145? ? ? ? ? ? {

146? ? ? ? ? ? ? ? write(array[i].sockfd , buffer , strlen(buffer));

147? ? ? ? ? ? }

148? ? ? ? }

149? ? }

150? ? close(array[num].sockfd);

151? ? array[num].live = 0 ;?

152

153}

? ??


服务端的工作已经设置完毕,显示就开始设置客户端吧,客户端就可以把它想象成我们的QQ群聊,只要每个人一发信息,那么整个群都可以看得到。

1

1.2客户端将要完成的工作


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


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


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


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



```c

?1#include

?2#include

?3#include

?4#include

?5#include

?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}



源码编写完毕,接下来测试一下这个简单聊天室的功能:编译过程省略,注意,该程序在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/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9TWWljZUpLNzhDSTlQTjlyaWM1cU5SaWNpY2NROUNHaWJmQUdtb0M5c1JzNnppYmhwakgzUGZ5aWF6YmUzcTdGSFV6bGJ6RDMxNGU3WTBuVGFTNURFaWNTRmppY01MUS82NDA?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?? 一种数字形式,

?3\033[<数字>m .

?4如 \033[40m ,表示让后面字符输出用背景黑色输出 \033[0m 表示取消前面的设置。

?5?? 另一种是控制字符形式。

?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例如:

2C语言编程里可以这么用

3设置光标位置 x=1 y=2

4printf("\033[%d;%dH\033[43m \033[0m" ,1, 2);

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客