个人聊天程序之服务器端

项目简介

最近年底,总体来说比较空闲,开始倒腾倒腾自己做点项目吧,时间荒废了也不太好,就抽空把之前学习的东西做个总结。现在大家时间都宝贵,我也把程序介绍的尽量简单,方便大家阅读,也方便未来回顾。做完这个项目可以入门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
感谢阅读~

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值