socket通信模式代码 C++

教程和ppt图片来源:c/c++_socket编程_哔哩哔哩_bilibili

 代码:

Iomode.h

//服务端

#pragma once

#include <iostream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#define RECV_POSTED 10

#pragma comment(lib, "ws2_32.lib")
using namespace std;

//句柄关联信息
struct PER_HANDLE_DATA
{
	SOCKET socket;
	sockaddr_in clientaddr;
};

//重叠结构IO关联信息
struct PER_IO_DATA
{
	OVERLAPPED overlapped;
	SOCKET client;
	WSABUF wsbuf;
	char buffer[1024];
	int OperationType;
};

class Iomode
{
private:
	sockaddr_in serveraddr;

public:
	Iomode(int port,string ipaddr);
	~Iomode();

	static SOCKET clientInstance;
	static WSABUF wsbuffer;
	static CRITICAL_SECTION cs;

	//选择模式
	void selectmode();
	//阻塞模式
	void blockmode();
	//事件选择模型
	int WSeventSelectmode();
	//重叠模型 事件通知模型
	int overlappedmode();
	//重叠模型 事件通知例程模型
	int overlappedInstanceMode();	
	//重叠模型 IOCP模型
	void completionportmode();
};

Iomode.cpp

#include "Iomode.h"
#include <vector>
#include <map>
Iomode::Iomode(int port, string ipaddr)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2,2),&wsaData);
	serveraddr.sin_family = AF_INET;
	/*网络字节顺序与本地字节顺序之间的转换函数:
		htonl()--"Host to Network Long"
		ntohl()--"Network to Host Long"
		htons()--"Host to Network Short"
		ntohs()--"Network to Host Short"*/
	serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = inet_addr(ipaddr.c_str());
}

Iomode::~Iomode()
{
	WSACleanup();
}

//阻塞模式
void Iomode::blockmode()
{  
	SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //阻塞模式,0:阻塞,1:非阻塞
    unsigned long ul = 0;
    //控制socket阻塞模式
    //FIONBIO属性 此属性的作用是“允许或者禁止套接字的非阻塞模式
    int nret = ioctlsocket(server, FIONBIO, (unsigned long*)&ul);

    int err;
    err = bind(server,(sockaddr*)&serveraddr,sizeof(serveraddr));

    char buffer[1024];
    sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    recvfrom(server,buffer,1023,0,(sockaddr*)&clientaddr,&len);

    //监听客户机传来的消息
    err = listen(server, 5);
    std::cout << "listen...." << std::endl;
    //处理连接请求
    SOCKET client = accept(server, (sockaddr*)&clientaddr, &len);

    
}

//选择模式
void Iomode::selectmode()
{
    std::vector<SOCKET> clientset;
    //套接字,地址
    std::map<SOCKET, sockaddr_in> s2addr;
    
    int ret = 0;

    SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //阻塞模式,0:阻塞,1:非阻塞
    unsigned long ul = 1;
    //控制socket阻塞模式
    //FIONBIO属性 此属性的作用是“允许或者禁止套接字的非阻塞模式
    int nret = ioctlsocket(server, FIONBIO, (unsigned long*)&ul);

    bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
    listen(server, 5);

    //用于检查可读取数据的套接字集合
    fd_set readfd;

    //不断接收检查从客户机传来的消息集合
    while (1)
    {
        sockaddr_in addr;
        int len = sizeof(addr);
        SOCKET client = accept(server, (sockaddr*)&addr, &len);
        //如果client有效,加入
        if (client != INVALID_SOCKET)
        {
            clientset.push_back(client);
            s2addr[client] = addr;
            cout << inet_ntoa(addr.sin_addr) << "已经加入连接..." << "当前连接数:" << clientset.size() << endl;
        }
        //将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空
        //,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
        FD_ZERO(&readfd);
        for (size_t i = 0; i < clientset.size(); i++)
        {
            FD_SET((int)(clientset[i]), &readfd);
        }
        //查看各个客户机是否发送数据过来
        ret = 0;
        if (!clientset.empty())
        {
            timeval tv = { 0,0 };
            ret = select(clientset[clientset.size() - 1] + 1, &readfd, NULL, NULL, &tv);
        }
        //处理接收的数据
        if (ret>0)
        {
            std::vector<SOCKET> deleteclient;
            for (size_t i = 0; i < clientset.size(); i++)
            {
                if (FD_ISSET((int)(clientset[i]),&readfd))
                {
                    //接受客户机的消息
                    char recvdata[1024] = { 0 };

                    //为了防止溢出,设置成1023
                    recv(clientset[i], recvdata, 1023, 0);
                    string data = recvdata;
                    if (data == "quit")
                        deleteclient.emplace_back(clientset[i]);
                    else
                        cout <<"来自" << inet_ntoa(s2addr[clientset[i]].sin_addr) << ":" << recvdata<< endl;
                }

            }
            //关闭要退出的套接字,在套接字集合中删除它
            if(!deleteclient.empty())
            {
                for (size_t i = 0; i < deleteclient.size(); i++)
                {
                    cout << "客户机" << inet_ntoa(s2addr[deleteclient[i]].sin_addr) << "已退出连接,剩余连接数:" << clientset.size()-1 << endl;
                    auto it = find(clientset.begin(), clientset.end(), deleteclient[i]);
                    clientset.erase(it);
                }
            }
        }
 

    }

}

//事件选择模型
int Iomode::WSeventSelectmode()
{
   /* 在WSAEventSelect模型中,基本流程如下:
        1. 创建一个事件对象数组,用于存放所有的事件对象;
        2. 创建一个事件对象(WSACreateEvent);
        3. 将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组;
        4. 等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents);
        5. 对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents);
        6. 针对不同的事件类型进行不同的处理;
        7. 循环进行 .4*/

    //事件数组
    WSAEVENT eventarray[WSA_MAXIMUM_WAIT_EVENTS], newevent;
    //客户机套接字
    SOCKET socketarray[WSA_MAXIMUM_WAIT_EVENTS];
    //套接字,地址
    map<SOCKET, sockaddr_in> s2addr;
    //事件总数,这个事件总数也就是套接字与事件数组的长度,index返回通知事件的索引
    DWORD eventtotal = 0, index;
    //接收数据字符数组
    char buffer[1024] = { 0 };

    SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //创建一个事件
    newevent = WSACreateEvent();

    //绑定
    bind(server, (sockaddr*)&serveraddr,sizeof(serveraddr));
    //接听
    listen(server,5);

    //为服务器套接字添加接收连接的网络事件,将服务器套接字与参数二事件关联
    //一旦服务器套接字上发生网络事件,参数二的事件会通知我们
    WSAEventSelect(server,newevent,FD_ACCEPT);

    //在相应的套接字数组与事件数组中添加服务器套接字
    socketarray[eventtotal] = server;
    eventarray[eventtotal] = newevent;
    eventtotal++;

    while (1)
    {
        //需要等待事件的通知,检查是否有网络事件发生,返回的index就是发生时间的套接字的下标
        //WSA_INFINITE永远等待
        index = WSAWaitForMultipleEvents(eventtotal,eventarray,false,WSA_INFINITE,FALSE);
        //规范套接字的下标,获取函数返回事件对象的索引
        index = index - WSA_WAIT_EVENT_0;

        //创建网络事件的结构体,用于检查发生了哪些网络事件
        _WSANETWORKEVENTS networkevents;
        //检查参数1套接字对应参数2事件对应的网络事件,在参数三中保存
        WSAEnumNetworkEvents(socketarray[index], eventarray[index],&networkevents);
        //接收
        int a = networkevents.lNetworkEvents;
        int b = FD_ACCEPT;
        int aa = networkevents.lNetworkEvents & FD_ACCEPT;
        if (networkevents.lNetworkEvents &FD_ACCEPT)
        {
            //使用错误码判断是否发生错误
            if (networkevents.iErrorCode[FD_ACCEPT_BIT] !=0)
            {
                std::cout << "FD_ACCEPT failed!" <<endl;
                break;
            }
            //接收客户机连接
            sockaddr_in addr;
            int len = sizeof(addr);
            SOCKET client = accept(socketarray[index], (sockaddr*)&addr, &len);
            //判断是否超出最大事件数
            if (eventtotal>= WSA_MAXIMUM_WAIT_EVENTS)
            {
                std::cout << "超过最大数目" << endl;
                break;
            }
            //记录套字节地址
            s2addr[client] = addr;

            //在这里继续使用ws事件将桃子姐和新事件关联,检查网络事件的读取和关闭操作
             //创建一个事件
            newevent = WSACreateEvent();
            //为服务器套接字添加接收连接的网络事件,将服务器套接字与参数二事件关联
            //一旦服务器套接字上发生网络事件,参数二的事件会通知我们
            WSAEventSelect(client, newevent, FD_READ | FD_CLOSE);
            //相应套字节中加入
             //在相应的套接字数组与事件数组中添加服务器套接字
            socketarray[eventtotal] = client;
            eventarray[eventtotal] = newevent;
            eventtotal++;
            //打印连接信息
            std::cout << inet_ntoa(addr.sin_addr) << client << "connected....." << endl;
            WSAResetEvent(eventarray[index]);
        }
        //接收
        if (networkevents.lNetworkEvents & FD_READ)
        {
            //使用错误码判断是否发生错误
            if (networkevents.iErrorCode[FD_READ_BIT] != 0)
            {
                std::cout << "FD_READ failed!" << endl;
                break;
            }
            //读数据
            //为了防止溢出,设置成1023
            recv(socketarray[index - WSA_WAIT_EVENT_0], buffer, 1023, 0);
            //判断客户机是否关闭连接
            string recvdata = buffer;
            if (recvdata == "quit")
            {
                std::cout << inet_ntoa(s2addr[socketarray[index - WSA_WAIT_EVENT_0]].sin_addr) 
                    << socketarray[index - WSA_WAIT_EVENT_0] << "disconnected....." << endl;
                //z在这里关闭套字节
                closesocket(socketarray[index - WSA_WAIT_EVENT_0]);
            }
            else
                std::cout << inet_ntoa(s2addr[socketarray[index - WSA_WAIT_EVENT_0]].sin_addr)
                << socketarray[index - WSA_WAIT_EVENT_0] << ":" <<recvdata<< endl;
            WSAResetEvent(eventarray[index]);
        }
        //接收
        if (networkevents.lNetworkEvents & FD_CLOSE)
        {
            //使用错误码判断是否发生错误
            int num = networkevents.iErrorCode[FD_CLOSE_BIT];
            if (networkevents.iErrorCode[FD_CLOSE_BIT] != 0)
            {
                std::cout << "FD_CLOSE failed!" << endl;
                break;
            }
            //从socketarray中删除
            for (int j =index;j<eventtotal;j++)
            {
                socketarray[j] = socketarray[j+1];
                eventarray[j] = eventarray[j+1];
            }
            eventtotal--;
            WSAResetEvent(eventarray[index]);
        }

    }
    return 0;
}


//重叠模型 事件通知模型
int Iomode::overlappedmode()
{
    //1.listen
    SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //绑定
    bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
    //接听
    listen(server, 5);

    //2.accpet
    //接收客户机连接
    sockaddr_in addr;
    int len = sizeof(addr);
    SOCKET client = accept(server, (sockaddr*)&addr, &len);

    //3.创建重叠结构体,ws事件结构体
    WSAOVERLAPPED overlapped;
    //初始化
    ZeroMemory(&overlapped, sizeof(overlapped));
    WSAEVENT eventarray[WSA_MAXIMUM_WAIT_EVENTS];

    DWORD eventtotal = 0,recvbytes=0,flags=0;
    eventarray[eventtotal] = WSACreateEvent();
    overlapped.hEvent = eventarray[eventtotal];
    eventtotal++;
    
    //4.使用重叠io,wsarecv
    while (1)
    {
        WSABUF wsbuf = { 0 };
        char buffer[1024] = { 0 };
        wsbuf.len = 1024;
        wsbuf.buf = buffer;
        WSARecv(client, &wsbuf, 1, &recvbytes, &flags, &overlapped, NULL);

        //5.等待重叠io操作完成,WSAWaitForMultipleEvents
        //需要等待事件的通知,检查是否有网络事件发生,返回的index就是发生时间的套接字的下标
        //WSA_INFINITE永远等待
        int index = WSAWaitForMultipleEvents(eventtotal, eventarray, false, WSA_INFINITE, FALSE);
        //6.重设事件对象
        WSAResetEvent(eventarray[index - WSA_WAIT_EVENT_0]);
        //7.wsagetoverlappedresult,重叠io是否成功完成,重复4-7
        WSAGetOverlappedResult(client, &overlapped, &recvbytes, TRUE, &flags);
        if (recvbytes == 0)
        {
            closesocket(client);
            WSACloseEvent(eventarray[index - WSA_WAIT_EVENT_0]);
        }
        cout << wsbuf.buf << endl;
        ZeroMemory(&overlapped, sizeof(overlapped));
        overlapped.hEvent = eventarray[index - WSA_WAIT_EVENT_0];
        //恢复信息
        flags = 0;
    }
    return 0;
}

//定义成静态变量
SOCKET Iomode::clientInstance;
WSABUF Iomode::wsbuffer = { 0 };
void CALLBACK WorkerRoutine(DWORD error, DWORD bytes, LPWSAOVERLAPPED overlapped, DWORD flags)
{
    if (error != 0 || bytes == 0)
    {
        closesocket(Iomode::clientInstance);
        exit(0);
    }
    cout << Iomode::wsbuffer.buf << endl;
    ZeroMemory(&overlapped, sizeof(overlapped));
};

//重叠模型 事件通知例程模型
int Iomode::overlappedInstanceMode()
{
    //1.listen
    SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //绑定
    bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
    //接听
    listen(server, 5);

    //2.accpet
    //接收客户机连接
    sockaddr_in addr;
    int len = sizeof(addr);

    clientInstance = accept(server, (sockaddr*)&addr, &len);

    //3.创建重叠结构体,ws事件结构体
    WSAOVERLAPPED overlapped;
    //初始化
    ZeroMemory(&overlapped, sizeof(overlapped));
    WSAEVENT eventarray[WSA_MAXIMUM_WAIT_EVENTS];

    DWORD eventtotal = 0, recvbytes = 0, flags = 0;
    eventarray[eventtotal] = WSACreateEvent();
    eventtotal++;

    char buffer[1024] = { 0 };
    wsbuffer.len = 1024;
    wsbuffer.buf = buffer;
    WSARecv(clientInstance, &wsbuffer, 1, &recvbytes, &flags, &overlapped, WorkerRoutine);

    //4.使用重叠io,wsarecv
    while (1)
    {
        //5.等待重叠io操作完成,WSAWaitForMultipleEvents
        //需要等待事件的通知,检查是否有网络事件发生,返回的index就是发生时间的套接字的下标
        //WSA_INFINITE永远等待
        //最后一个标志位要true,例程的时候
        int index = WSAWaitForMultipleEvents(eventtotal, eventarray, FALSE, WSA_INFINITE, TRUE);
        //6.重设事件对象
        WSAResetEvent(eventarray[0]);

        wsbuffer.len = 1024;
        wsbuffer.buf = buffer;
        WSARecv(clientInstance, &wsbuffer, 1, &recvbytes, &flags, &overlapped, WorkerRoutine);
    }
    WSACleanup();
    return 0;
}


CRITICAL_SECTION Iomode::cs;
DWORD WINAPI WorkerThreadData(LPVOID completionPortID)
{
    int result = 0;
    DWORD recvbytes = 0, flags = 0;
    PER_HANDLE_DATA* perhandleData = NULL;
    PER_IO_DATA* perIoData = NULL;
    while (1)
    {
        //等待与完成端口关联的任意套接字上的I/O完成
        result = GetQueuedCompletionStatus(completionPortID,&recvbytes,(PULONG_PTR)&perhandleData,(OVERLAPPED**)&perIoData,INFINITE);
        //检查看是否有错误或者断开连接
        if (recvbytes == 0 && perIoData->OperationType == RECV_POSTED)
        {
            EnterCriticalSection(&Iomode::cs);
            cout << perIoData->client << "disconnectd...." << endl;
            LeaveCriticalSection(&Iomode::cs);
            closesocket(perIoData->client);
            GlobalFree(perIoData);
            perIoData = NULL;
            continue;
        }
        //打印一下
        if (perIoData->OperationType == RECV_POSTED)
        {
            EnterCriticalSection(&Iomode::cs);
            cout << perIoData->client << ":" << perIoData->wsbuf.buf <<endl;
            LeaveCriticalSection(&Iomode::cs);
        }
        WSARecv(perIoData->client,&(perIoData->wsbuf),1,&recvbytes, &flags, &(perIoData->overlapped), NULL);
    }
    return 0;
}

//重叠模型 IOCP模型
void Iomode::completionportmode()
{
    int result = 0;

    HANDLE completionPort;
    SYSTEM_INFO systeminfo;

    WSABUF wsbuf;
    char buffer[1024] = { 0 };
    wsbuf.len = 1024;
    wsbuf.buf = buffer;

    DWORD recvbytes = 0, flags = 0;
    //接收客户机连接
    sockaddr_in addr;
    int len = sizeof(addr);

    SOCKET server, client;
    server = WSASocket(AF_INET, SOCK_STREAM, 0,NULL,0,WSA_FLAG_OVERLAPPED);

    result = bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));

    result = listen(server, SOMAXCONN);

    GetSystemInfo(&systeminfo);
    //完成端口的创建
    completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

    
    InitializeCriticalSection(&cs);

    //创建2*cpu核心数量用于处理网络IO
    for (size_t i = 0; i < 2*systeminfo.dwNumberOfProcessors; i++)
    {
        HANDLE workerThreadHandle;
        workerThreadHandle = CreateThread(NULL,0, &WorkerThreadData,completionPort,0,NULL);
        CloseHandle(workerThreadHandle);
    }

    //主线程用死循环一直接收客户机连接
    while (1)
    {
        //接收连接并且绑定到完成端口
        //创建PER_HANDLE_DATA用于关联
        PER_HANDLE_DATA* perhandleData = (PER_HANDLE_DATA*)GlobalAlloc(GPTR,sizeof(PER_HANDLE_DATA));
        PER_IO_DATA *perIoData = (PER_IO_DATA*)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));

        client = accept(server, (sockaddr*)&addr, &len);
        EnterCriticalSection(&cs);
        cout <<client << "connectd...." << endl;
        LeaveCriticalSection(&cs);
        perhandleData->clientaddr = addr;
        perhandleData->socket = client;
        //关联客户机套接字和完成端口
        CreateIoCompletionPort((HANDLE)client, completionPort, (DWORD)perhandleData, 0);

        perIoData->client = client;
        perIoData->wsbuf.len = 1024;
        perIoData->wsbuf.buf = perIoData->buffer;
        perIoData->OperationType = RECV_POSTED;

        //投递一个WSARecv
        int nRecv = WSARecv(perIoData->client, &perIoData->wsbuf,1,&recvbytes,&flags,&(perIoData->overlapped),NULL);

        if ((SOCKET_ERROR == nRecv)&&(WSA_IO_PENDING != WSAGetLastError()))
        {
            EnterCriticalSection(&cs);
            cout << "投递第一个wsarecv失败"<<endl;
            exit(0);
            LeaveCriticalSection(&cs);
        }
    }
}
//服务端不断发送消息
int main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		/* Tell the user that we could not find a usable */
		/* Winsock DLL.                                  */
		printf("WSAStartup failed with error: %d\n", err);
		return 1;
	}

	/TCP传输
	//客户机三个步骤:socket\connect\send\
	//tcp传输
	//ipv4和tcp协议
	//创建套接字
	SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


	//绑定信息,需要地址信息
	sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;

	
	//htons的作用是
	serveraddr.sin_port = htons(12345);
	serveraddr.sin_addr.s_addr = inet_addr("192.168.43.147");
	err = connect(client, (sockaddr*)&serveraddr, sizeof(serveraddr));

	while (1)
	{
		//接受客户机的消息
		char data[1024] = { 0 };
		std::cout << "输入数据或者quit退出" << std::endl;
		cin >> data;
		string sendData = data;
		if (sendData == "quit") break;

		send(client, sendData.c_str(), 1023, 0);

	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38409301/article/details/120422140
今日推荐