TCP-select网络模型

#include "main.h"



/*1.打开网络库
* 2.校验网络库版本
* 3.创建SOCKET
* 4.绑定IP地址和端口
* 5.开始监听
* 6.创建客户端socket/接受链接
* 7.与客户端收发消息
* 8.(6.7)两步的函数accept,send,recv 有堵塞,可以用select解决,这种函数可以处理小型网络
*/
int create(const char* IpAdress)
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    /* 使用Windef.h中声明的MAKEWORD(低字节、高字节)宏 */
    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;
    }

    /*确认WinSock DLL支持2.2*/

    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;
    }


    //创建套接字。 创建网络类型 tcp或者upd
    SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (INVALID_SOCKET == socketServer)
    {
       string ret = to_string( WSAGetLastError());
        MessageBoxA(0, ret.c_str(),"error_socket",0);
        //清理网络库
        WSACleanup();
        return -1;
    }
    //设置sockaddr结构
    sockaddr_in saServer;
    saServer.sin_family = AF_INET;
    saServer.sin_addr.s_addr = INADDR_ANY;
    saServer.sin_port = htons(9999);

  

    // 绑定本机(服务器)IP和端口
    //sockaddr结构中的信息
    if (SOCKET_ERROR == bind(socketServer, (SOCKADDR*)&saServer, sizeof(saServer)))
    {
        string ret = to_string(WSAGetLastError());
        MessageBoxA(0, ret.c_str(), "error_bind", 0);
        //释放stocket
        closesocket(socketServer);
        //清理网络库
        WSACleanup();
        return -1;
    }
    
    /*监听本机(服务器)的套接字*/

    if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
    {
        string ret = to_string(WSAGetLastError());
        MessageBoxA(0, ret.c_str(), "error_listen", 0);
        //释放stocket
        closesocket(socketServer);
        //清理网络库
        WSACleanup();
        return -1;
    }



    fd_set allSockets;
    //清空
    FD_ZERO(&allSockets);

    //将服务器加入链表
    FD_SET(socketServer,&allSockets);


    while (true)
    {
        //中间变量
        fd_set read_allSockets = allSockets;//可读
        fd_set write_allSockets = allSockets;//可写
        fd_set except_allSockets = allSockets;//错误

        timeval st;
        st.tv_sec = 3;
        st.tv_usec = 0;
        /* select解决阻塞问题accept recv  send函数,*/
        int  select_ret = select(0, &read_allSockets, &write_allSockets,&except_allSockets, &st);

        if (0 == select_ret)
        {
            //select 无响应
            continue;
        }
        else if (select_ret > 0)
        {
            有响应




            /*处理错误*/
            for (u_int loop = 0; loop < except_allSockets.fd_count; loop++)
            {
                char error[100] = {0};
                int len = sizeof(error) -1;
               
                if (SOCKET_ERROR == getsockopt(except_allSockets.fd_array[loop], SOL_SOCKET, SO_ERROR, error, &len))
                {
                    printf("无法得到错误信息");
                }

                printf("%s\n", error);
            }
          



            //for (u_int loop = 0; loop < write_allSockets.fd_count; loop++)
            //{
            //    //printf("write:%d \n", write_allSockets.fd_array[loop]);
            //    char send_buff[] = "服务器:write链接成功!";
            //    send(write_allSockets.fd_array[loop], send_buff, strlen(send_buff), 0);
        
        
            //}



            for (u_int loop = 0; loop < read_allSockets.fd_count; loop++)
            {

                /*本机(服务器)收到accept请求链接消息*/
                if (read_allSockets.fd_array[loop] == socketServer)
                {

                    sockaddr_in clientMsg;
                    int len = sizeof(clientMsg);
                    /*保存监听到的客户端数据,这个函数是阻塞的,没客户端链接就会死等,故放进select调用解决*/
                    SOCKET socketClient = accept(socketServer, (SOCKADDR*)&clientMsg, &len);

                    if (INVALID_SOCKET == socketClient)
                    {
                        continue;
                    }

                    /*将取到的客户端socket放进allSockets链表*/
                    FD_SET(socketClient,&allSockets);

                    printf("%d.%d.%d.%d:%d  \n", (
                        SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b1,
                        (SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b2,
                        (SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b3,
                        (SOCKADDR*)clientMsg.sin_addr.S_un.S_un_b.s_b4,
                        (SOCKADDR*)clientMsg.sin_port);

                    /*参数二还要打包ip 链路层数据等,所以要预留100字节,故参数2最大1400*/
                    char send_buff[] = "服务器:链接成功!";
                    if (SOCKET_ERROR == send(socketClient, send_buff, strlen(send_buff), 0))
                    {
                        string ret = to_string(WSAGetLastError());
                        MessageBoxA(0, ret.c_str(), "error_send", 0);
                    }
                }
                else /*客户端和服务器通信消息*/
                {

                    /*将客户端数据复制到自己的缓存区
                    * 缓冲区大小最大1500,为了保留/0,故大小-1
                    * 如果未发生错误, recv 将返回收到的字节数, buf 参数指向的缓冲区将包含接收的此数据。 如果连接已正常关闭,则返回值为零。
                        否则,将返回SOCKET_ERROR值,并且可以通过调用 WSAGetLastError 来检索特定的错误代码
                    */
                    char buff[1024] = { 0 };
                    int recv_ret = recv(read_allSockets.fd_array[loop], buff, 1024, 0);
                    if (0 == recv_ret) /*客户端正常下线*/
                    {
                        //在链表中释放掉这个socket
                        FD_CLR(read_allSockets.fd_array[loop], &allSockets);
                        //释放stocket
                        closesocket(read_allSockets.fd_array[loop]);
                     
                        MessageBoxA(0, "网络中断,客户端已下线", "error_recv", 0);
                    }
                    else if (SOCKET_ERROR == recv_ret)
                    {
                        int error_ret = WSAGetLastError();
                        if (error_ret == 10054) //客户端强制下线了
                        {
                            //在链表中释放掉这个socket
                            FD_CLR(read_allSockets.fd_array[loop], &allSockets);
                            //释放stocket
                            closesocket(read_allSockets.fd_array[loop]);

                            MessageBoxA(0, "网络中断,客户端已强制下线", "error_recv", 0);
                            continue;
                        }
  
                        string ret = to_string(error_ret);
                        MessageBoxA(0, ret.c_str(), "error_recv", 0);
                    }
                    else
                    {
                        /*接收到了消息*/
                        printf("%s\n", buff);
                    }
                   

                }
            }

        }
        else if (SOCKET_ERROR == select_ret )
        {
            //有错误
            string ret = to_string(WSAGetLastError());
            MessageBoxA(0, ret.c_str(), "error_select_ret", 0);
        }

    }




    system("pause");

    //释放stocket
    for (int loop = 0; loop < allSockets.fd_count; loop++)
    {
        closesocket(allSockets.fd_array[loop]);
    }
    //清理网络库
    WSACleanup();
}






int main()
{

 
   create("127.0.0.1");


	return 0;
}

select是将accept和recv的阻塞变成半阻塞,本质是把客户端socket放进数组里,然后遍历数组查询哪个socket有消息

你可能感兴趣的:(网络编程,网络,tcp/ip,php)