微信公眾號:嵌入式開發(fā)圈
關注可了解更多的教程。問題或建議,請公眾號留言;
如果你覺得本文對你有幫助,歡迎贊賞
▲長按圖片保存可分享至朋友圈
簡易Linux終端聊天室
今天我們來實現(xiàn)一個簡單的小項目,在這個項目中,我們將實現(xiàn)一個終端版的簡易Linux聊天室。
實現(xiàn)的效果:服務器啟動,監(jiān)測客戶端連接的個數(shù),監(jiān)測每個客戶端的IP地址以及端口號,當每個客戶端發(fā)送消息時,服務器上會有線程專門將每個客戶端發(fā)送的信息記錄在界面上,就類似平時使用QQ群聊一樣。我們來看看這個簡易的Linux聊天室如何來實現(xiàn)吧。如圖4-5-12所示。
1、實現(xiàn)一個基本的服務器和客戶端的步驟
一、創(chuàng)建服務器的流程
(1)調用socket函數(shù)創(chuàng)建一個套接口,并返回描述符。
(2)調用bind函數(shù)使服務器進程與一個端口號綁定。
(3)調用listen設置客戶端如隊列的大小。
(4)調用accept接收一個連接,如果接入隊列不為空的話。并且相應返回一個已連接的套接口描述符。
(5)調用send和recv用來在已連接的套接口間進行發(fā)送和接收數(shù)據(jù)。
二、創(chuàng)建客戶端流程
(1)調用socket函數(shù)創(chuàng)建一個套接口,并返回描述符。
(2)調用connect向服務器發(fā)送連接請求,返回一個已連接的套接口。
(3)調用send和recv在已連接的套接口間發(fā)送和接收數(shù)據(jù)。
1.1服務器將要完成的工作
(1)獲取套接字
(2)設置端口復用
(3)綁定連接的IP還有端口號
(4)監(jiān)聽
(5)創(chuàng)建一條線程用于顯示客戶端連接信息,具體連接的人數(shù),順便將客戶連接的IP以及端口號打印出來。
(6)開始接收
(7)創(chuàng)建一條線程用于將客戶端直接收發(fā)的信息分發(fā)到客戶端處進行顯示。
下面具體看看服務器代碼的實現(xiàn) server.c
1#include
2#include
3#include
4#include
5#include
6#include
7#include
8#include
9//設置客戶端最大的個數(shù)為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,表示監(jiān)聽所有的。
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.監(jiān)聽
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 //創(chuàng)建一條線程用于顯示已連接的客戶端的個數(shù)
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 //判斷是否連接個數(shù)已滿
84 if(i == MAXCONNECTION)
85 {
86 write(peersockfd , message , strlen(message));
87 close(peersockfd);
88 continue ;
89 }
90 //創(chuàng)建一條線程用于顯示客戶端之間互相發(fā)送的即時信息
91 pthread_create(&tid , NULL , do_thread_clientopt , (void *)i);
92 pthread_detach(tid);
93 }
94
95
96 return 0 ;
97}
98
99//線程執(zhí)行函數(shù),用于顯示已連接的客戶端的個數(shù)。
100void *do_thread_showconnect(void *arg)
101{
102 int i , count = 0;
103 while(1)
104 {
105 system("clear");
106 printf("客戶端連接信息: 連接人數(shù):%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//線程執(zhí)行函數(shù),用于顯示客戶端之間互相發(fā)送的即時信息
120void *do_thread_clientopt(void *arg)
121{
122 //轉發(fā)信息
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}
服務端的工作已經(jīng)設置完畢,顯示就開始設置客戶端吧,客戶端就可以把它想象成我們的QQ群聊,只要每個人一發(fā)信息,那么整個群都可以看得到。
1.2客戶端將要完成的工作
(1)連接對應的服務器,必須指定服務器的ip地址
(2)創(chuàng)建一條線程,用于讀取從服務器轉發(fā)過來的消息
(3)客戶端可以自由的輸入,通過服務器,發(fā)送給其它的客戶端,讓它們也可以看得到。
下面具體看看客戶端代碼的實現(xiàn) client.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 //創(chuàng)建一條線程用于接收從服務器端收到的數(shù)據(jù)
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中的內(nèi)容寫到文件描述符
46 //通過服務器轉發(fā)給其它正在連接的客戶端
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 //從服務器讀取數(shù)據(jù)并顯示在客戶端上
60 ret = read(sd , buffer , 1024);
61 if(ret <= 0)
62 break;
63 buffer[ret] = '\0' ;
64 printf("recv:%s" , buffer);
65 }
66}
源碼編寫完畢,接下來測試一下這個簡單聊天室的功能:編譯過程省略,注意,該程序在32位操作系統(tǒng)上運行,且要加上線程庫才可以編譯成功。分別編譯server.c和client.c
1gcc server.c -o server -m32 -lpthread
2gcc client.c -o client -m32 -lpthread
下面先運行服務器,執(zhí)行./server如圖4-5-13所示。
下面啟動不同IP的客戶端,找多一臺電腦即可測試。在我方47服務器上執(zhí)行客戶端./client,如圖4-5-14所示。客戶端連接成功了!
接下來看看服務器上有什么變化,如圖4-5-15所示。
在我方56服務器上執(zhí)行客戶端./client,如圖4-5-16所示。
接下來看看服務器上有什么變化,如圖4-5-17所示。
在47服務器上的客戶端發(fā)送一條消息給56服務器上的客戶端,同樣的在56服務器上的客戶端也發(fā)送一條信息給47的服務器上的客戶端,觀察變化,如圖4-5-18所示。
在這里我們看到,56服務器上的客戶端發(fā)送hello world的消息給47服務器上的客戶端,47服務器上的客戶端也收到了helloworld消息,同樣的,47服務器上的客戶端給56服務器上的客戶端發(fā)送I am 47 server的消息,56服務器上的客戶端也收到了I am 47 server的消息。這個簡易版本的Linux聊天室就算完成了,接下來,請讀者發(fā)揮自己的想象力,結合VT100控制碼,寫出一個更漂亮的終端版聊天工具吧。
VT100控制碼表
1具體格式有兩種,
2? 一種數(shù)字形式,
3\033[<數(shù)字>m .
4如 \033[40m ,表示讓后面字符輸出用背景黑色輸出 \033[0m 表示取消前面的設置。
5? 另一種是控制字符形式。
6\033[K 清除從光標到行尾的內(nèi)容
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 清除從光標到行尾的內(nèi)容
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);
下一節(jié),我將帶領大家使用VT100控制碼來實現(xiàn)一個好玩的方塊。
另外推薦相關課程:
韋東山老師優(yōu)質嵌入式學習干貨推薦:包括ARM裸機開發(fā)、Linux設備驅動程序、Linux應用程序開發(fā)、Android系統(tǒng)學習、Linux設備樹等。
在我這里購買韋東山老師的課程還可得到本人的技術支持,手把手帶你學習嵌入式!
王爭老師優(yōu)秀數(shù)據(jù)結構算法學習課程推薦
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!