服务端(个人聊天小程序)
项目简介
最近年底,总体来说比较空闲,开始倒腾倒腾自己做点项目吧,时间荒废了也不太好,就抽空把之前学习的东西做个总结。现在大家时间都宝贵,我也把程序介绍的尽量简单,方便大家阅读,也方便未来回顾。做完这个项目可以入门linux应用。。。(吹牛的)
这个项目大概是希望做一个类似于QQ一样的东西,服务器是挂在腾讯云上的,然后客户端是用QT做的,客户端可以定义自己的用户名向服务端发起请求,服务端接收后会把客户加入到群聊中,并分出线程单独处理该客户的各种消息和请求。
做了一个相对丑陋的界面,如下图,也是现在的V0.1版本。
中间用到的知识相对来说也比较简单。
服务端:C++,TCP,多线程,Makefile。。。
客户端:QT,TCP。。。
都是一些基础的知识,但是对于新手程序猿练手而言感觉还是不错的项目,比起之前预想的来说遇到的困难还是挺多的,文章的最后我会把程序弄出来,大家可以自行去下载,然后跑一下,我这里也主要是做个记录,避免以后学完就忘记了。这篇文章还是主要介绍下服务端的一些东西。
TCP
TCP作为服务端,需要做的是以下几个步骤
1.Socket ->Bind->Listen
创建套接字、绑定IP和端口、进入监听模式
"这段代码包含了创建套接字、绑定IP和端口、进入监听模式这三个过程"
//创建一个新的套接字
if( (sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP )) == -1 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
cout << "socket..." << endl;
opt = SO_REUSEADDR;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//设置本机的地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
//bind()
if( bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
cout << "bind....." << endl;
//listen()
if( listen(sockfd, 10) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}
cout << "listen..." << endl;
2.Accept
此时会进入阻塞模式,直到有TCP请求。接收到请求后建立连接,产生一个新的Socket(原来的套接字可以继续使用Accept来接收新的请求。)
"这段代码主要介绍的功能是连接到客户端,并创建用户,传入到新的线程当中"
//等待客户机的连接秦请求
printf("======waiting for client's request======\n");
while(1){
//查询可用连接端
for(int i=0;i<=9;i++){
if(user[i].online<0){
toconnect=i;
break;
}
}
//等待连接后接受
if( (user[toconnect].connfd = accept(sockfd, (struct sockaddr*)&addr, (socklen_t*)&sin_size)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
//在可用连接端创建用户
printf("Create user at No.%d\n",toconnect);
//创建一个用户与之对应
memcpy((void*)&user[toconnect].addr,&addr,sizeof(addr));
user[toconnect].online=1;
//获取用户名
n = read(user[toconnect].connfd, buff, MAXLINE);
buff[n] = '\0';
strcpy(user[toconnect].name,buff);
//开辟一个线程传入用户参数
int ret = pthread_create(&tids[toconnect], NULL, chatwithme, (void*)&user[toconnect]);
if (ret != 0)
{
cout << "pthread_create error: error_code=" << ret << endl;
}
Connected++;
//pthread_join(tids[toconnect],NULL);
//用户数量到达上限
if(Connected==MAXCLIENT){
cout << "用户数量到达上限" << endl;
while(1){
if(Connected<MAXCLIENT){
break;
}
}
}
}
3.Recv/Send(Read/Write)
此时可以对新的套接字进行读写操作,来实现发送和接收消息的方式,这里需查询是否收到消息,所以需要单独创建一个线程来处理。
"注释写的比较详细了"
//开辟单独的聊天线程
void* chatwithme(void *argv)
{
//变量申明
char str[4096];
int n;
char buff[4096];
time_t timep;
struct tm *p;
client *user=(client *)argv; //客户端信息录入线程
printf("IP:%s %s\n",inet_ntoa(user->addr.sin_addr),user->name); //打印客户端的用户名
//循环接受消息
while(1){
//n = recv(*user->connfd, buff, MAXLINE, 0); //读取传输信息
n = read(user->connfd, buff, MAXLINE); //读取传输信息
//判断信息长度
if(n!=0){
time (&timep); //获取当前计算机时间
p=gmtime(&timep);
//加上字符串结尾
buff[n] = '\0';
//若信息首字符为‘0’则断开连接
if(buff[0]=='0'){
printf("%s has cut off\n",user->name);
break;
}
//打印控制台信息
sprintf(str,"User:%s Time:%d:%d:%02d: Message:%s",user->name,p->tm_hour,p->tm_min,p->tm_sec, buff);
printf("%s",str);
user->m.lock();
user->messagehandle(buff);
user->m.unlock();
}
}
//断开后
user->online=-1;
Connected--;
close(user->connfd);
user->connfd=0;
return 0;
}
4.Close
close(sockfd);
接收到结束的消息后来关闭socket
多线程
多线程在这里的应用其实很简单,只要在Accept阻塞结束,将新创建的套接字传入到新的线程中,就可以实现对该套接字的读写了。
步骤
1.pthread_t 创建线程变量
pthread_t Ctr;
2.创建线程函数
void* Ctrl(void *argv){
Console();
}
3.pthread_create 开辟线程
第四个参数是一个空指针,也可以自己创建对应的指针,但是传入的方式必须是void*类型的,可以在传入线程之后再转换回来。
//开辟一个线程作为控制台程序
int ret = pthread_create(&Ctr, NULL, Ctrl, (void*)NULL);
if (ret != 0)
{
cout << "pthread_create error: error_code=" << ret << endl;
}
Makefile
Makefile就比较基础了
不介绍,自己看下也能看懂。
文件目录
makefile
OBJ = main.o pthread.o tcp.o server.o
TAR = App/app
SOURCE = Source/
$(TAR): $(OBJ)
g++ $(OBJ) -lpthread -o $(TAR)
main.o:$(SOURCE)main.cpp
g++ $(SOURCE)main.cpp -c -I Header
pthread.o:$(SOURCE)pthread.cpp
g++ $(SOURCE)pthread.cpp -c -I Header
tcp.o:$(SOURCE)tcp.cpp
g++ $(SOURCE)tcp.cpp -c -I Header
server.o:$(SOURCE)server.cpp
g++ $(SOURCE)server.cpp -c -I Header
clean:
rm -rf $(OBJ) $(TAR)
shell脚本
一键编译清理+编译+运行
REBULID.sh
#! /bin/bash
date
echo ""
echo "clean"
make clean
echo ""
echo "rebulid"
make
echo ""
echo "run"
./App/app
运行效果图
最后贴个git地址:
https://github.com/haishiyoumingtian/TCP_Server.git
感谢阅读~