欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言实现 select模式编程

程序员文章站 2023-10-06 23:54:58
server: // tcpserver.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #incl...

server:

// tcpserver.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include    
#include 

#pragma comment(lib,"ws2_32.lib")   

#define   port   9990   
#define   data_bufsize   8192   

// 定义套接字信息
typedef   struct   _socket_information   {   
    char   buffer[data_bufsize];        // 发送和接收数据的缓冲区
    wsabuf   databuf;                       // 定义发送和接收数据缓冲区的结构体,包括缓冲区的长度和内容
    socket   socket;                            // 与客户端进行通信的套接字
    dword   bytessend;                  // 保存套接字发送的字节数
    dword   bytesrecv;                  // 保存套接字接收的字节数
} socket_information,   *   lpsocket_information;   

dword   totalsockets = 0;               // 记录正在使用的套接字总数量
lpsocket_information   socketarray[fd_setsize];         // 保存socket信息对象的数组,fd_setsize表示select模型中允许的最大套接字数量

// 创建socket信息
bool   createsocketinformation(socket   s)   
{   
    lpsocket_information   si;                                      // 用于保存套接字的信息       
 //   printf("accepted   socket   number   %d\n",   s);         // 打开已接受的套接字编号
    // 为si分配内存空间
    if ((si = (lpsocket_information) globalalloc(gptr,   
              sizeof(socket_information)))   ==   null)   
    {   
        printf("globalalloc()   failed   with   error   %d\n",   getlasterror());   
        return   false;   
    }   
    // 初始化si的值    
    si->socket = s;   
    si->bytessend = 0;   
    si->bytesrecv = 0;   
    // 在socketarray数组中增加一个新元素,用于保存si对象 
    socketarray[totalsockets]   =   si;       
    totalsockets++;                     // 增加套接字数量

    return(true);   
}     

// 从数组socketarray中删除指定的lpsocket_information对象
void   freesocketinformation(dword   index)   
{   
    lpsocket_information si = socketarray[index];   // 获取指定索引对应的lpsocket_information对象
    dword   i;   
    // 关闭套接字
    closesocket(si->socket);   
    //printf("closing   socket   number   %d\n",   si->socket);   
    // 释放指定lpsocket_information对象资源
    globalfree(si);   
    // 将数组中index索引后面的元素前移
    for (i = index; i < totalsockets; i++)   
    {   
        socketarray[i] = socketarray[i+1];   
    }   
     totalsockets--;        // 套接字总数减1
}   

// 主函数,启动服务器
int _tmain(int argc, _tchar* argv[])
{
    socket   listensocket;                  // 监听套接字
    socket   acceptsocket;                  // 与客户端进行通信的套接字
    sockaddr_in   internetaddr;         // 服务器的地址
    wsadata   wsadata;                      // 用于初始化套接字环境
    int   ret;                                          // winsock api的返回值
    fd_set   writeset;                          // 获取可写性的套接字集合
    fd_set   readset;                           // 获取可读性的套接字集合
    dword   total = 0;                              // 处于就绪状态的套接字数量
    dword   sendbytes;                      // 发送的字节数
    dword   recvbytes;                      // 接收的字节数


    // 初始化winsock环境
    if ((ret = wsastartup(0x0202,&wsadata)) != 0)   
    {   
        printf("wsastartup()   failed   with   error   %d\n",   ret);   
        wsacleanup();   
        return -1;   
    }   
    // 创建用于监听的套接字 
    if ((listensocket = wsasocket(af_inet, sock_stream, 0, null, 0,   
              wsa_flag_overlapped))   ==   invalid_socket)     
    {   
        printf("wsasocket()   failed   with   error   %d\n",   wsagetlasterror());   
        return -1;   
    }   
    // 设置监听地址和端口号
    internetaddr.sin_family = af_inet;   
    internetaddr.sin_addr.s_addr = htonl(inaddr_any);   
    internetaddr.sin_port = htons(port);   
    // 绑定监听套接字到本地地址和端口
    if(bind(listensocket, (psockaddr)&internetaddr, sizeof(internetaddr)) == socket_error)   
    {   
        printf("bind()   failed   with   error   %d\n",   wsagetlasterror());   
        return -1;   
    }   
    // 开始监听
    if   (listen(listensocket,   5))   
    {   
        printf("listen()   failed   with   error   %d\n",   wsagetlasterror());   
        return -1;   
    }   


/*  // 设置为非阻塞模式
    ulong nonblock = 1;   
    if(ioctlsocket(listensocket, fionbio, &nonblock) == socket_error)   
    {   
        printf("ioctlsocket() failed with error %d\n", wsagetlasterror());   
        return -1;   
    }   
    // 为listensocket套接字创建对应的socket_information
    // 这样就可以把listensocket添加到socketarray数组中

    */

    createsocketinformation(listensocket);    
    while(true)   
    {   
        // 准备用于网络i/o通知的读/写套接字集合
        fd_zero(&readset);   
        fd_zero(&writeset);   
        // 向readset集合中添加监听套接字listensocket
        fd_set(listensocket,   &readset);   
        // 将socketarray数组中的所有套接字添加到writeset和readset集合中
        // socketarray数组中保存着监听套接字和所有与客户端进行通信的套接字
        // 这样就可以使用select()判断哪个套接字有接入数据或者读取/写入数据
        for   (dword i=0; isocket,   &writeset);   
            fd_set(socketinfo->socket,   &readset);   
        }
        // 判断读/写套接字集合中就绪的套接字    
        if((total = select(0, &readset, &writeset, null, null)) == socket_error)   
        {   
            printf("select()   returned   with   error   %d\n",   wsagetlasterror());   
            return -1;   
        }      
        // 依次处理所有套接字。本服务器是一个回应服务器,即将从客户端收到的字符串再发回到客户端。
        for   (dword i=0; isocket,   &readset))   
            {   
                if(socketinfo->socket == listensocket)      // 对于监听套接字来说,可读表示有新的连接请求
                {
                    total--;    // 就绪的套接字减1
                    // 接受连接请求,得到与客户端进行通信的套接字acceptsocket
                    if((acceptsocket = accept(listensocket, null, null))   !=   invalid_socket)   
                    {   
                        printf("连接成功!\n");
                        /*
                        // 设置套接字acceptsocket为非阻塞模式
                        // 这样服务器在调用wsasend()函数发送数据时就不会被阻塞
                        nonblock   =   1;   
                        if(ioctlsocket(acceptsocket, fionbio, &nonblock)   ==   socket_error)   
                        {   
                            printf("ioctlsocket()   failed   with   error   %d\n",   wsagetlasterror());   
                            return -1;                             
                        }   
                        */

                        // 创建套接字信息,初始化lpsocket_information结构体数据,将acceptsocket添加到socketarray数组中
                        if(createsocketinformation(acceptsocket) == false)   
                            return -1;   
                    }   
                    else   
                    {   
                        if(wsagetlasterror() != wsaewouldblock)   
                        {   
                            printf("accept()   failed   with   error   %d\n",   wsagetlasterror());   
                            return -1;   
                        }   
                    }                   
                }
                else   // 接收数据
                {
                    // 如果当前套接字在readset集合中,则表明该套接字上有可以读取的数据
                    if   (fd_isset(socketinfo->socket,   &readset))   
                    {   
                        total--;                // 减少一个处于就绪状态的套接字
                        memset(socketinfo->buffer, ' ', data_bufsize);  
                        zeromemory(socketinfo->databuf.buf,data_bufsize);// 初始化缓冲区
                        socketinfo->databuf.buf = socketinfo->buffer;           // 初始化缓冲区位置
                        socketinfo->databuf.len   =   data_bufsize;             // 初始化缓冲区长度
                        // 接收数据
                        dword  flags = 0;   
                        if(wsarecv(socketinfo->socket, &(socketinfo->databuf), 1, &recvbytes, &flags, 
                            null, null) == socket_error)   
                        {   
                            // 错误编码等于wsaewouldblock表示暂没有数据,否则表示出现异常
                            if(wsagetlasterror() != wsaewouldblock)     
                            {   
                                printf("wsarecv()   failed   with   error   %d\n",   wsagetlasterror());   
                                freesocketinformation(i);       // 释放套接字信息
                            }   
                            continue;   
                        }     
                        else   // 接收数据
                        {   
                            socketinfo->bytesrecv = recvbytes;      // 记录接收数据的字节数                           
                            if(recvbytes == 0)                                  // 如果接收到0个字节,则表示对方关闭连接
                            {   
                                freesocketinformation(i);   
                                continue;   
                            }   
                            else                                                            // 如果成功接收数据,则打印收到的数据
                            {
                                printf(socketinfo->databuf.buf);
                                printf("\n");

                                //
                            }
                        }
                    }   
                }
            }
            else
            {
                // 如果当前套接字在writeset集合中,则表明该套接字的内部数据缓冲区中有数据可以发送
                if(fd_isset(socketinfo->socket,  &writeset))   
                {   
                    total--;            // 减少一个处于就绪状态的套接字
                    socketinfo->databuf.buf = socketinfo->buffer + socketinfo->bytessend;           // 初始化缓冲区位置
                    socketinfo->databuf.len = socketinfo->bytesrecv - socketinfo->bytessend;    // 初始化缓冲区长度
                    if(socketinfo->databuf.len > 0)     // 如果有需要发送的数据,则发送数据
                    {
                        if(wsasend(socketinfo->socket, &(socketinfo->databuf), 1, &sendbytes, 0,   
                                        null, null) == socket_error)   
                        {   
                            // 错误编码等于wsaewouldblock表示暂没有数据,否则表示出现异常
                            if(wsagetlasterror() != wsaewouldblock)   
                            {   
                                printf("wsasend()   failed   with   error   %d\n", wsagetlasterror());   
                                freesocketinformation(i);       // 释放套接字信息
                            }   
                            continue;   
                        }   
                        else   
                        {   
                            socketinfo->bytessend += sendbytes;         // 记录发送数据的字节数
                            // 如果从客户端接收到的数据都已经发回到客户端,则将发送和接收的字节数量设置为0
                            if (socketinfo->bytessend   ==   socketinfo->bytesrecv)   
                            {   
                                socketinfo->bytessend   =   0;   
                                socketinfo->bytesrecv   =   0;   
                            }   
                        }   
                    }
                }               

            }   // 如果listensocket未就绪,并且返回的错误不是wsaewouldblock(该错误表示没有接收的连接请求),则出现异常

        }           

    }   
    // 暂停,按任意键退出
    system("pause");
    return 0;
}

client:

// tcpclient.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include    
#include 
#include 

#pragma comment(lib,"ws2_32.lib")   
#define buf_size    64          // 缓冲区大小  

int _tmain(int argc, _tchar* argv[])
{
    wsadata     wsd;                    // 用于初始化windows socket   
    socket      shost;                  // 与服务器进行通信的套接字   
    sockaddr_in servaddr;           // 服务器地址   
    char        buf[buf_size];          // 用于接受数据缓冲区   
    int         retval;                         // 调用各种socket函数的返回值   
    // 初始化windows socket
    if(wsastartup(makeword(2,2),&wsd) != 0)   
    {   
        printf("wsastartup failed !\n");   
        return 1;   
    }     
    // 创建套接字   
    shost = socket(af_inet,sock_stream,ipproto_ip);   
    if(invalid_socket == shost)   
    {   
        printf("socket failed !\n");   
        wsacleanup();   
        return -1;   
    }    

    // 设置服务器地址   
    servaddr.sin_family = af_inet;   
    servaddr.sin_addr.s_un.s_addr = inet_addr("127.0.0.1");     // 用户需要根据实际情况修改
    servaddr.sin_port = htons(9990);                                                    // 在实际应用中,建议将服务器的ip地址和端口号保存在配置文件中
    int sserveraddlen = sizeof(servaddr);                                               // 计算地址的长度       
    // 循环等待
    while(true)
    {
        // 连接服务器   
        sleep( 200 );
        retval = connect(shost,(lpsockaddr)&servaddr,sizeof(servaddr));   
        sleep( 200 );
        if(socket_error == retval)   
        {   
            int err = wsagetlasterror();
            if(err == wsaewouldblock || err == wsaeinval)           // 无法立即完成非阻塞套接字上的操作
            {
                //sleep(500);
                continue;
            }
            else if(err == wsaeisconn)                                              // 已建立连接
            {
                break;
            }
            else
            {
                                continue;

                //printf("connect failed !\n");   
                //closesocket(shost);   
                //wsacleanup();   
                //return -1;   
            }
        }   
    }
    // 循环向服务器发送字符串,并显示反馈信息。
    // 发送quit将使服务器程序退出,同时客户端程序自身也将退出
    while(true)
    {
        // 向服务器发送数据   
        printf("please input a string to send: ");
        // 接收输入的数据
        std::string str;
        std::getline(std::cin, str);
        // 将用户输入的数据复制到buf中
        zeromemory(buf,buf_size);  
        zeromemory(buf,buf_size);   
        strcpy(buf,str.c_str());   
        // 循环等待
        while(true)
        {
            // 向服务器发送数据
            retval = send(shost,buf,strlen(buf),0);   
            if(socket_error == retval)   
            {   
                int err = wsagetlasterror();
                if(err == wsaewouldblock)           // 无法立即完成非阻塞套接字上的操作
                {

                    continue;
                }
                else
                {
                    printf("send failed !\n");   
                    closesocket(shost);   
                    wsacleanup();   
                    return -1;   
                }
            }
            break;
        }     

        while(true)
        {
            zeromemory(buf,buf_size);                       // 清空接收数据的缓冲区
            retval = recv(shost,buf,sizeof(buf)+1,0);   // 接收服务器回传的数据   
            if(socket_error == retval)   
            {   
                int err = wsagetlasterror();                // 获取错误编码
                if(err == wsaewouldblock)           // 接收数据缓冲区暂无数据
                {
                    sleep(100);
                    printf("waiting back msg !\n");
                    continue;
                }
                else if(err == wsaetimedout || err == wsaenetdown)
                {
                    printf("recv failed !\n");   
                    closesocket(shost);   
                    wsacleanup();   
                    return -1;
                }
                break;
            }   
            break;
        }   
        //zeromemory(buf,buf_size);                     // 清空接收数据的缓冲区
        //retval = recv(shost,buf,sizeof(buf)+1,0);   // 接收服务器回传的数据   

        printf("recv from server: %s\n",buf);   
        // 如果收到quit,则退出
        if(strcmp(buf, "quit") == 0)
        {
            printf("quit!\n");
            break;
        }
    }
    // 释放资源   
    closesocket(shost);   
    wsacleanup();   
    // 暂停,按任意键继续
    system("pause");
    return 0;  
}