tcp 同步阻塞+多线程 模型实现多客户端与服务器通信(封装好)

该代码实现了一个基于Windows Socket的TCP网络服务器,能够接收客户端连接并进行文件传输。服务器端通过监听指定端口等待客户端连接,接收到文件传输请求后,接收文件并按块发送确认。客户端则负责连接服务器,发送文件信息并等待服务器确认,然后发送文件内容。整个过程涉及多线程处理、阻塞IO和文件读写操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器端

.h头文件

#ifndef CTCPNET_H
#define CTCPNET_H
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#include <list>
#include <map>
#include <iostream>
#include <QDebug>
using namespace  std;

class CTCPNet
{
public:
    CTCPNet();
public:
    //初始化网络
    bool InitNetWork();
    void UnInitNetWork();
    //收发数据
    bool SendData(SOCKET sock,char *szbuf,int nlen);
    void RecvData();
public:
    static unsigned _stdcall ThreadProc(void*);//全局变成局部
    static unsigned _stdcall ThreadRecv(void*);
private:
    SOCKET m_sock;
    HANDLE m_thread;
    bool m_flagquit;
    list<HANDLE> m_lstThread;//用来存放创建线程返回的句柄
    map<unsigned int,SOCKET> m_mapIdToSock;
};

#endif // CTCPNET_H

类.cpp

#include "ctcpnet.h"
#include <iostream>
#include <QDebug>
using namespace  std;

CTCPNet::CTCPNet()
{
    m_sock = NULL;
    m_flagquit = true;
}

bool CTCPNet::InitNetWork()
{
    //1.选择种类 火锅 韩餐  烤肉--
      WORD wVersionRequested;
      WSADATA wsaData;
      int err;

      wVersionRequested = MAKEWORD(2, 2);

      err = WSAStartup(wVersionRequested, &wsaData);
      if (err != 0) {

          printf("WSAStartup failed with error: %d\n", err);
          return false;
      }

      if (LOBYTE(wsaData.wVersion) != 2 ||
          HIBYTE(wsaData.wVersion) != 2)
      {
          UnInitNetWork();
          return false;
      }
      else
          printf("The Winsock 2.2 dll was found okay\n");

      //2.雇个店长--
      m_sock =  socket(AF_INET,SOCK_STREAM,0);
      if(m_sock == INVALID_SOCKET)
      {
          UnInitNetWork();
          return false;
      }

      //3.找个地方
      sockaddr_in addrserver;
      addrserver.sin_family = AF_INET;
      addrserver.sin_port = htons(8899);
      addrserver.sin_addr.S_un.S_addr = 0;
     if(SOCKET_ERROR == bind(m_sock,( const sockaddr *)&addrserver,sizeof(addrserver)))
     {
         UnInitNetWork();
         return false;
     }
      //4.店长宣传
     if(SOCKET_ERROR == listen(m_sock,10))
     {
         UnInitNetWork();
         return false;
     }
     m_thread = (HANDLE)_beginthreadex(0,0,&ThreadProc,this,0,0);
     if(m_thread)
     {
         m_lstThread.push_back(m_thread);
     }
     return true;
}

unsigned _stdcall CTCPNet::ThreadProc(void *lpvoid)
{
    CTCPNet *pthis =(CTCPNet*)lpvoid;
    unsigned int threadid;
    //接收连接
    while(pthis->m_flagquit){
        //阻塞
        SOCKET sockWaiter = accept(pthis->m_sock,0,0);
        if (sockWaiter == INVALID_SOCKET){
            continue;
        }
        //多线程
       HANDLE hThread = (HANDLE)_beginthreadex(0,0,ThreadRecv,pthis,0,&threadid);
       if (hThread){
           pthis->m_lstThread.push_back(hThread);
           pthis->m_mapIdToSock[threadid] = sockWaiter; //线程id与sockwaiter的映射
       }
    }
    return 0;
}

unsigned _stdcall CTCPNet::ThreadRecv(void *lpvoid)
{
    //一个线程对应一个waiter 用map
    CTCPNet *pthis =(CTCPNet*)lpvoid;

    pthis->RecvData();

    return 0;
}

void CTCPNet::RecvData()
{
    //获取线程id映射sockWaiter
    SOCKET sockWaiter = m_mapIdToSock[GetCurrentThreadId()];
    int nPackSize;
    int nRecvNum;
    char* szbuf=NULL;
    int offset;
    while(m_flagquit)
    {
        nRecvNum = recv(sockWaiter,(char*)&nPackSize,sizeof (int),0);
        if (nRecvNum<0)
        {
            //客户端下线
            if (WSAGetLastError() == WSAECONNRESET)
            {
            break;
            }
            continue;
        }
        if (nPackSize<=0) continue;

        //解决粘包问题
        szbuf = new char[nPackSize];
        offset = 0;
        while(nPackSize)
        {
            nRecvNum = recv(sockWaiter,szbuf+offset,nPackSize,0);
            offset += nRecvNum;
            nPackSize -= nRecvNum;
        }
        //处理数据
        cout<<szbuf<<endl;
        delete []szbuf;
        szbuf = NULL;
    }
}

 bool CTCPNet::SendData(SOCKET sock,char *szbuf,int nlen)
{
     if(sock == INVALID_SOCKET || !szbuf || nlen<0)
         return false;
     //发送大小
     if(send(sock,(char*)&nlen,sizeof(int),0) <= 0)
         return false;
    //发送内容
     if(send(sock,szbuf,nlen,0)<=0)
         return false;
     return true;
}

void CTCPNet::UnInitNetWork()
{
    //销毁线程
    m_flagquit = false;
    list<HANDLE>::iterator ite = m_lstThread.begin();
    while(ite != m_lstThread.end())
    if (*ite)
    {
        if (WAIT_TIMEOUT==WaitForSingleObject(*ite,100))
        {
               TerminateThread(*ite,-1);
        }
        CloseHandle(*ite);
        *ite = NULL;
    }
    ite++;
    m_lstThread.clear();
    //关闭套接字
    if(m_sock){
        closesocket(m_sock);
        m_sock = NULL;
    }
    WSACleanup();
}

main.cpp

#include <QCoreApplication>
#include <ctcpnet.h>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CTCPNet tcpnet;
    if (!tcpnet.InitNetWork())
    {
        cout<<" ctcpnet InitNetWork failed"<<endl;
    }
    return a.exec();
}

客户端

#include <QCoreApplication>
#include <winsock.h>
#include <iostream>
#include <QDebug>
using namespace std;
#pragma comment(lib, "ws2_32.lib")

struct FileHeader{
    char m_szFileName[MAX_PATH];
    long m_FileSize;
};
#define MAX_RESULT 5
#define MAX_PAGE 4096
char g_szResult[MAX_RESULT];
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //加载库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {

        printf("WSAStartup failed with error: %d\n", err);
        return 1;
    }

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        printf("Could not find a usable version of Winsock.dll\n");
        WSACleanup();
        return 1;
    }
    else
        printf("The ClientWinsock 2.2 dll was found okay\n");
    //创建socket
    SOCKET sock = socket(AF_INET,SOCK_STREAM,0);
    if (sock == INVALID_SOCKET)
    {
        WSACleanup();
        return 1;
    }
    //链接
    sockaddr_in addrserver;
    addrserver.sin_family = AF_INET;
    addrserver.sin_port = htons(8899);
    addrserver.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
    if (SOCKET_ERROR == connect(sock,(const sockaddr*)&addrserver,sizeof (addrserver)))
    {
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    //文件传输
    //1.获取文件名、文件大小
    char SZ_FileName[MAX_PATH] = {0};//SZ_FileName:文件路径 MAX_PATH:260字节
    //cout<<"请输入要传输的文件名"<<endl;
    qDebug()<<"请输入要传输的文件名";
    cin>>SZ_FileName;

    char *ptemp = SZ_FileName;  //获取文件名:先让指针走到尾部,在往回走
    while(*ptemp != '\0') ptemp++;
    while(*ptemp != '\\') ptemp--;//“//”代表“/”   “/”代表转义字符
    ptemp++; //文件名
    //文件大小
    FILE *pfile = fopen(SZ_FileName,"rb");//rb:以二进制流的方式在读
    if(!pfile) return 1;
    //fseek():移动文件指针到文件尾:C中没有获取文件大小的方法,但是可以先移动到尾部,在获取当前指针的位置来得到文件的大小
    fseek(pfile,0,SEEK_END);
    long lFileSize = ftell(pfile);//ftell():获取文件指针指针当前所在位置。
    //移动文件指针到文件头
    fseek(pfile,0,SEEK_SET);

    //2.发送
    FileHeader fh;
    //结构体成员赋初值
    fh.m_FileSize = lFileSize;
    strcpy(fh.m_szFileName,ptemp);

    send(sock,(const char *)&fh,sizeof(fh),0);
    //3.等对方回复 no yes
    recv(sock,g_szResult,sizeof(g_szResult),0);
    char szContent[MAX_PAGE] = {0};
    //4.如果接收的话
    if (0 == strcmp(g_szResult,"yes"))
    {

          while (1)
          {
              //5.读文件内容
              size_t nReadNum = fread(szContent,sizeof(char),MAX_PAGE,pfile);
              if (nReadNum > 0)
              {
                  //6.发送
                  send(sock,szContent,nReadNum,0);
              }
              else{
                  break;
              }

          }
     }
    //7.关闭文件
    fclose(pfile);



    char szbuf[1024] = {0};
    cin>>szbuf;
    //发送
    //1.发送包长度
    int nPackSize = sizeof(szbuf);
    send(sock,(const char*)&nPackSize,sizeof(int),0);//发送一块缓冲区,所以第二个参数就是(const char*)&nPackSize 第三个参数:代表第二个参数的大小 第二个参数是int类型,所以是sizeof(int)
    //2.发送包内容
    send(sock,szbuf,sizeof(szbuf),0);
    //接收
    int RecvNum = recv(sock,szbuf,sizeof (szbuf),0);
    if (RecvNum > 0)
    {
       cout<<szbuf<<endl;
    }
    //关闭socket
    closesocket(sock);
    //卸载库
    WSACleanup();

    return a.exec();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值