实现的效果:服务器启动,监测客户端连接的个数,监测每个客户端的IP地址以及端口号,当每个客户端发送消息时,
服务器上会有线程专门将每个客户端发送的信息记录在界面上,就类似平时使用QQ群聊一样。
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)创建一条线程用于将客户端直接收发的信息分发到客户端处进行显示。
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
//设置客户端最大的个数为40个
#define MAXCONNECTION 40
#define msleep(x) (usleep(x*1000))
struct Data
{
int live ; //0 无人用 1有人用
int sockfd ;
struct in_addr in ;
unsigned short port ;
};
struct Data array[MAXCONNECTION] = {0} ;
void *do_thread_showconnect(void *arg);
void *do_thread_clientopt(void *arg);
int main(void)
{
int sockfd ;
//1.获取套接字
sockfd = socket(AF_INET , SOCK_STREAM , 0);
if(sockfd < 0)
{
perror("get socket fail");
return -1 ;
}
//2.设置端口复用
int on = 4 ;
setsockopt(sockfd , SOL_SOCKET , SO_REUSEADDR , &on , sizeof(int));
//3.绑定IP与端口
struct sockaddr_in addr ;
addr.sin_family = AF_INET ;
addr.sin_port = htons(10000);
//设置为INADDR_ANY,表示监听所有的。
addr.sin_addr.s_addr = INADDR_ANY ;
int ret ;
ret = bind(sockfd , (struct sockaddr *)&addr , sizeof(struct sockaddr_in)) ;
if(ret < 0)
{
perror("bind error");
return -2 ;
}
//4.监听
listen(sockfd , 30);
int peersockfd ;
struct sockaddr_in peeraddr ;
socklen_t len = sizeof(struct sockaddr_in) ;
struct Data tmp ;
char *message = "too more connction , connect fail" ;
int i ;
pthread_t tid ;
//创建一条线程用于显示已连接的客户端的个数
pthread_create(&tid , NULL , do_thread_showconnect , NULL);
pthread_detach(tid);
while(1)
{
peersockfd = accept(sockfd , (struct sockaddr *)&peeraddr , &len);
if(peersockfd < 0)
{
perror("accept fail");
return -3 ;
}
tmp.sockfd = peersockfd ;
tmp.in = peeraddr.sin_addr ;
tmp.port = ntohs(peeraddr.sin_port);
tmp.live = 1 ;
for(i = 0 ; i < MAXCONNECTION ; i++)
{
if(array[i].live == 0)
{
array[i] = tmp ;
break;
}
}
//判断是否连接个数已满
if(i == MAXCONNECTION)
{
write(peersockfd , message , strlen(message));
close(peersockfd);
continue ;
}
//创建一条线程用于显示客户端之间互相发送的即时信息
pthread_create(&tid , NULL , do_thread_clientopt , (void *)i);
pthread_detach(tid);
}
return 0;
}
//线程执行函数,用于显示已连接的客户端的个数。
void *do_thread_showconnect(void *arg)
{
int i , count = 0;
while(1)
{
system("clear");
printf("客户端连接信息: 连接人数:%d\n" , count);
count = 0 ;
for(i = 0 ; i < MAXCONNECTION ; i++)
{
if(array[i].live == 1)
{
count++ ;
printf("IP:%s port:%d \n" , inet_ntoa(array[i].in) , array[i].port);
}
}
msleep(100);
}
}
//线程执行函数,用于显示客户端之间互相发送的即时信息
void *do_thread_clientopt(void *arg)
{
//转发信息
int num = (int)arg ;
char buffer[10240] = {0};
char tmp[10240] = {0};
int ret ;
struct timeval tv ;
struct timezone tz ;
struct tm *tt ;
int i ;
while(1)
{
ret = read(array[num].sockfd , tmp , 1024);
if(ret <= 0)
break;
tmp[ret] = '\0' ;
gettimeofday(&tv , &tz);
tt = localtime(&tv.tv_sec);
sprintf(buffer , "%s @ %d:%d:%d :\n%s" ,inet_ntoa(array[num].in) , tt->tm_hour , tt->tm_min , tt->tm_sec , tmp);
for(i = 0 ; i < MAXCONNECTION ; i++)
{
if(array[i].live == 1)
{
write(array[i].sockfd , buffer , strlen(buffer));
}
}
}
close(array[num].sockfd);
array[num].live = 0 ;
}
服务端的工作已经设置完毕,显示就开始设置客户端吧,客户端就可以把它想象成我们的QQ群聊,只要每个人一发信息,那么整个群都可以看得到。
1.2客户端将要完成的工作
(1)连接对应的服务器,必须指定服务器的ip地址
(2)创建一条线程,用于读取从服务器转发过来的消息
(3)客户端可以自由的输入,通过服务器,发送给其它的客户端,让它们也可以看得到。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
void *do_thread(void * arg);
int main(void)
{
int sd ;
sd = socket(AF_INET , SOCK_STREAM , 0);
if(sd < 0)
{
perror("get socket fail");
return -1 ;
}
//1.连接对应的服务器
//connect
struct sockaddr_in addr ;
addr.sin_family = AF_INET ;
addr.sin_port = htons(10000);
addr.sin_addr.s_addr = inet_addr("101.132.71.152");
//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret ;
ret = connect(sd , (struct sockaddr *)&addr , sizeof(struct sockaddr_in));
if(ret != 0)
{
perror("connect fail");
return -3 ;
}
printf("connect success ... \n");
pthread_t tid ;
//创建一条线程用于接收从服务器端收到的数据
pthread_create(&tid , NULL , do_thread , (void *)sd);
pthread_detach(tid);
char buffer[1024] = {0};
while(1)
{
//阻塞从标准输出读取信息到buffer
ret = read(0 , buffer , 1024);
if(ret > 1024)
continue ;
//按下回车后将buffer中的内容写到文件描述符
//通过服务器转发给其它正在连接的客户端
write(sd, buffer , ret);
}
return 0 ;
}
void *do_thread(void * arg)
{
int sd = (int)arg;
int ret ;
char buffer[1024] = {0};
while(1)
{
//从服务器读取数据并显示在客户端上
ret = read(sd , buffer , 1024);
if(ret <= 0)
break;
buffer[ret] = '\0' ;
printf("recv:%s" , buffer);
}
}
源码编写完毕,接下来测试一下这个简单聊天室的功能:编译过程省略,注意,该程序在32位操作系统上运行,且要加上线程库才可以编译成功。分别编译server.c和client.c
gcc server.c -o server -m32 -lpthread
gcc client.c -o client -m32 -lpthread
VT100控制码表
1 具体格式有两种,
2 一种数字形式,
3 \033[<数字>m .
4 如 \033[40m ,表示让后面字符输出用背景黑色输出 \033[0m 表示取消前面的设置。
5 另一种是控制字符形式。
6 \033[K 清除从光标到行尾的内容
7 \033[nC 光标右移 n 行
8 输出时, 也可以用 ^[来代替.
9 VT100 控制码
10 VT100 控制码归类如下。
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 显示光标
30 VT100 关于颜色的说明:
31 VT100 的颜色输出分为,注意要同时输出前景的字符颜色和背景颜色。
32 字背景颜色范围:40----49
33 40:黑
34 41:深红
35 42:绿
36 43:黄色
37 44:蓝色
38 45:紫色
39 46:深绿
40 47:白色
41 字颜色:30-----------39
42 30:黑
43 31:红
44 32:绿
45 33:黄
46 34:蓝色
47 35:紫色
48 36:深绿
49 37:白色
50 这样输出一个字符串比较完整如下
51 echo "\033[字背景颜色;字体颜色 m 字符串\033[0m"
52 例:
53 echo "\033[41;36m something here \033[0m"
1 例如:
2 C语言编程里可以这么用
3 设置光标位置 x=1 y=2
4 printf("\033[%d;%dH\033[43m \033[0m" ,1, 2);
实验效果: