• socket


    Socket应用
    C/C++ TCP-Client
    1. 定义绑定IP的地址结构
    struct sockaddr_in sinclient;
    SOCKET c_Socket; //创建socket返回的描述字
    2. 创建socket
    c_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    AF_INET:协议域,代表IPV4
    SOCK_STREAM:协议类型,一般与IPPROTO_TCP配对
    IPPROTO_TCP:TCP协议
    3. 指定IP、端口连接
    int port;
    char host[64];
    sinclient.sin_addr.s_addr = inet_addr(host); //指定所连接的IP
    sinclient.sin_family = AF_INET;
    sinclient.sin_port = htons(port); //指定所连接的IP端口

    //设置套接字为非阻塞模式
    #ifdef OS_WINDOWS //windows
    u_long argp = 1;
    if (ioctlsocket(c_Socket, FIONBIO, &argp) == SOCKET_ERROR)
    #else //Linux
    int flags;
    flags = fcntl(c_Socket, F_GETFL, 0);
    if (fcntl(c_Socket, F_SETFL, flags | O_NONBLOCK) < 0) //O_NDELAY

    #endif
    1) ioctlsocket参数说明
    ioctlsocket函数头文件为#include <winsock.h>
    int PASCAL FAR ioctlsocket (
    _In_ SOCKET s,
    _In_ long cmd,
    _Inout_ u_long FAR *argp);
     s: 创建socket描述符
     cmd: FIONBIO 允许或禁止套接口s为非阻塞模式;
    FIONREAD 确定套接口s自动读入的数据量;
    SIOCATMARK 确认是否所有带外数据都已被读入。
     argp:1表示是非阻塞 0表示阻塞
    2) fcntl参数说明
    Linux下分两步完成非阻塞的设置
     F_GETFL:获取fd文件状态标志
     F_SETFL:设置flags描述符状态标志,O_NONBLOCK属于非阻塞IO

    connect(c_Socket, (struct sockaddr *)& sinclient, sizeof(sinclient))<0表示连接成功
    if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, (socklen_t *)&len) == 0)
    {
    调用此函数获取任意类型、任意状态套接口的当前值存入error;
    调用AGetLastError()函数打印error值;
    }
    4. send和recv调用
    1) 先检测套接描述符是否可读、可写
    fd_set fdwSet; //存储描述字集合
    fd_set fdrSet;
    struct timeval tv;//时间结构体
    /*判断soket是否可写*/
    FD_ZERO(&fdrSet); //清除集合
    FD_SET(m_Socket, &fdrSet); //加入要监听的描述符
    FD_ZERO(&fdwSet);
    FD_SET(m_Socket, &fdwSet);
    tv.tv_sec = 0;
    tv.tv_usec = 10 * 1000;

    len = select(m_Socket + 1, &fdrSet, &fdwSet, 0, &tv);//监测读、写的描述符
    if (len > 0)
    {
    if (FD_ISSET(m_Socket, &fdwSet)) //可写
    {
    rtn = ::send(m_Socket , (char *)buff , len , 0);
    }
    if (FD_ISSET(m_Socket, &fdrSet)) //可读
    {
    len = ::recv(m_Socket , (char *)buff , 8000 , 0);
    }
    }
    C/C++ TCP-Server
    1. 定义绑定IP的地址结构
    struct sockaddr_in sinserver;
    SOCKET s_Socket; //创建socket返回的描述字
    SOCKET a_Socket; //accept返回的连接描述字
    2. 创建socket
    s_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    AF_INET:协议域,代表IPV4
    SOCK_STREAM:协议类型,一般与IPPROTO_TCP配对
    IPPROTO_TCP:TCP协议
    3. 设置IP重复绑定
    //设置IP可以重复绑定
    if(setsockopt(s_Socket,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on))!=0)
    {
    设置失败!
    continue;
    }
    4. 设置网络端口绑定
    memset(&sin, 0, sizeof(sin));
    sinserver.sin_family = AF_INET;
    sinserver.sin_addr.s_addr = htonl((uint64)INADDR_ANY); //绑定本地IP
    sinserver.sin_port = htons((unsigned short)m_ListenPort);//可以监听多个端口

    rtn = ::bind(s_Socket, (struct sockaddr *)&sin, sizeof(sin)); //同一个端口只绑定一次
    5. 监听客户端
    rtn = listen(s_Socket, m_ListenNum); //可以监听多个客户端,但是一个收发线程管理一个连接

    6. 接收客户端连接
    fd_set rfds;
    timeval timevalue;
    timevalue.tv_sec = 0;
    timevalue.tv_usec = 40000;
    FD_ZERO(&rfds);
    for(i=0;i<m_ListenPortSize;i++) //可以把多个描述符塞进集合进行监听
    FD_SET(s_Socket[i], &rfds);
    int retval = select(FD_SETSIZE, &rfds, NULL, NULL, &timevalue);

    if(retval > 0)
    {
    if (FD_ISSET(s_Socket,&rfds))
    {
    a_Socket = accept(s_Socket, (struct sockaddr *)&fsin, &alen);
    memset(clientIp,0,sizeof(clientIp));
    strcpy(clientIp,inet_ntoa(fsin.sin_addr));
    }
    }

    7. 指定客户端连接
    void CTcpServerListenThread::AccepClientIpConnect(char *recIp,int sockfd,int listenport)
    {
    int i,j;
    int ReNo=0;
    CTcpServerRxTxThreadPtr ptrTcp;
    if (m_TcpServerRxTxQueue.size()<=0)
    return;
    vector<CTcpServerRxTxThreadPtr>::iterator it;
    for (it = m_TcpServerRxTxQueue.begin();it!=m_TcpServerRxTxQueue.end();it++)
    {
    ptrTcp = *it;
    if(m_ProtocolId==ptrTcp->m_ptrCFesChan->m_Param.ProtocolId)
    {
    for (i = 0; i < CN_FesMaxNetRouteNum; i++)
    {
    if((strcmp(recIp,ptrTcp->m_ptrCFesChan->m_Param.NetRoute[i].NetDesc)==0)&&
    (listenport==ptrTcp->m_ptrCFesChan->m_Param.NetRoute[i].PortNo))
    {
    ReNo = CheckChanNo(ptrTcp->m_ptrCFesChan->m_Param.ChanNo);
    if(ReNo!=SOCKET_ERROR)
    {
    if (m_AcceptSocket[ReNo]!=SOCKET_ERROR) //该客户已经有老的连接
    {
    LOGINFO("The %s already connect! 第 %d 个ip已经连接过 m_AcceptSocket[%d] = %d ",recIp,i,ReNo,m_AcceptSocket[ReNo]);
    TcpClose(m_AcceptSocket[ReNo]);
    // SetDiscNetEvent(ptrTcp->m_ptrCFesChan);
    m_AcceptSocket[ReNo] = INVALID_SOCKET;
    m_AcceptSocket[ReNo] = sockfd;
    RecordNetEvent(m_AcceptSocket[ReNo],ptrTcp->m_ptrCFesChan);
    return;
    }
    else//该客户还没有建立连接
    {
    m_AcceptSocket[ReNo] = sockfd; RecordNetEvent(m_AcceptSocket[ReNo],ptrTcp->m_ptrCFesChan);
    return;
    }
    }
    else
    {
    LOGINFO("所连接的客户端个数已经满了!");
    return;
    }
    }
    }
    }
    }
    LOGINFO("不是所配置的客户端IP!");
    TcpClose(sockfd);
    }

    8. send和recv调用

    2) 先检测套接描述符是否可读、可写
    fd_set fdwSet; //存储描述字集合
    fd_set fdrSet;
    struct timeval tv;//时间结构体
    /*判断soket是否可写*/
    FD_ZERO(&fdrSet); //清除集合
    FD_SET(m_Socket, &fdrSet); //加入要监听的描述符
    FD_ZERO(&fdwSet);
    FD_SET(m_Socket, &fdwSet);
    tv.tv_sec = 0;
    tv.tv_usec = 10 * 1000;

    len = select(m_Socket + 1, &fdrSet, &fdwSet, 0, &tv);//监测读、写的描述符
    if (len > 0)
    {
    if (FD_ISSET(m_Socket, &fdwSet)) //可写
    {
    rtn = ::send(m_Socket , (char *)buff , len , 0);
    }
    if (FD_ISSET(m_Socket, &fdrSet)) //可读
    {
    len = ::recv(m_Socket , (char *)buff , 8000 , 0);
    }
    }
    epoll与select比较
    select的三大缺点:
    1.每次调用select,都需要把fd集合从用户态拷贝到内核态,开销在fd很多时候很大
    2.同时每次调用select都需要在内核遍历传进来的所有fd,开销在fd很多时候很大
    3.select支持的文件描述数量太小了,默认1024

    epoll优点:
    epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

      1.对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

      2.对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

      3.对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

    总结:
    (1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

    (2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
    Socket头文件
    1.Linux平台:
    #include <sys/socket.h>
    #include <sys/ioctl.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <fcntl.h>
    2.Windows平台:
    #include <Winsock.h>
    #include <Windows.h>
    /**
    * @brief CTcpServerListenThread::InitWinsock
    *
    * Windows系统下网络库初始化,调用此接口才能在windows下正常使用
    * @return
    */
    bool CTcpServerListenThread::InitWinsock()
    {
    #ifdef WIN32
    WSADATA wsdata;
    WORD wVersion = MAKEWORD(2, 2);
    if ((WSAStartup(wVersion, &wsdata)) != 0)
    {
    LOGERROR("WSAStartup() ERROR, errno:%d ", WSAGetLastError());
    return false;
    }
    return true;
    #else
    return true;
    #endif
    }
    Win32 UDP-Server
    1.lib库及头文件
    #include <WINSOCK2.H> 或 #include <Winsock.h>
    #pragma comment(lib,"WS2_32.lib")
    #define BUF_SIZE 1024
    2.初始化套接字动态库
    WSADATA wsd;
    WORD wVersion = MAKEWORD(2, 2);
    // 初始化套接字动态库
    if (WSAStartup(wVersion, &wsd) != 0)
    {
    cout<<"WSAStartup failed !"<<endl;
    return 1;
    }
    3.创建socket和bind定端口、IP
    SOCKET socketSrv = socket(AF_INET, SOCK_DGRAM, 0);
    SOCKADDR_IN addrSrv;
    char buf[BUF_SIZE];
    int len = sizeof(SOCKADDR_IN);

    AF_INET:协议域,代表IPV4
    SOCK_DGRAM:专门用于局域网,基于UDP,基于广播
    IPPROTO_UDP:UDP协议,可选可不选

    // 设置服务器地址
    ZeroMemory(buf, BUF_SIZE); //就是memset()函数的封装
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //允许接收任意IP客户端的数据
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(5000);


    // 绑定套接字
    nRet = bind(socketSrv,(SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
    if (SOCKET_ERROR == nRet)
    {
    printf("bind failed ! ");
    closesocket(socketSrv);
    WSACleanup();
    return -1;
    }
    4.设置套接字为非阻塞模式
    u_long argp = 1;
    if (ioctlsocket(socketSrv, FIONBIO, &argp) == SOCKET_ERROR)
    {
    printf("设置套接字为非阻塞模式失败! ");
    return -1;
    }

    5.recvfrom和sendto的收发
    // 从客户端接收数据
    ZeroMemory(buf, BUF_SIZE);
    nRet = recvfrom(socketSrv, buf, BUF_SIZE, 0, (SOCKADDR *)&addrSrv, &len);
    // 向客户端发送数据
    sendto(socketSrv, "UDP Hello World !", sizeof("UDP Hello World !"), 0, (SOCKADDR *)&addrSrv, len);

    函数原型:
    int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
    int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
    sendto(),是把UDP数据报发给指定地址;recvfrom()是从指定地址接收UDP数据报。
    参数说明
    ?s: socket描述符。
    ?uf: UDP数据报缓存地址。
    ?len: UDP数据报长度。
    ?flags: 该参数一般为0。
    ? o: sendto()函数参数,struct sockaddr_in类型,指明UDP数据发往哪里报。
    ? olen: 对方地址长度,一般为:sizeof(struct sockaddr_in)。
    ?fromlen:recvfrom()函数参数,struct sockaddr_in类型,指明从哪里接收UDP数据报。
    Win32 UDP-Client
    1.lib库及头文件
    #include <WINSOCK2.H> 或 #include <Winsock.h>
    #pragma comment(lib,"WS2_32.lib")
    #define BUF_SIZE 1024
    2.初始化套接字动态库
    WSADATA wsd;
    WORD wVersion = MAKEWORD(2, 2);
    // 初始化套接字动态库
    if (WSAStartup(wVersion, &wsd) != 0)
    {
    cout<<"WSAStartup failed !"<<endl;
    return 1;
    }
    3.创建socket和设置服务器地址
    char buf[BUF_SIZE]; // 接受数据
    SOCKADDR_IN servAddr; // 服务器套接字地址
    OCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
    int nRet;

    // 设置服务器地址
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    servAddr.sin_port = htons(5000);

    4. recvfrom和sendto的收发
    // 向服务器发送数据
    int nServAddLen = sizeof(servAddr);
    sendto(sockClient, buf, BUF_SIZE, 0, (sockaddr *)&servAddr, nServAddLen);
    nRet = recvfrom(sockClient, buf, BUF_SIZE, 0, (sockaddr *)&servAddr, &nServAddLen);

    Linux UDP-Server
    案例:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #define UDP_TEST_PORT 50001

    int main(int argC, char* arg[])
    {

    struct sockaddr_in addr;
    int sockfd, len = 0;
    int addr_len = sizeof(struct sockaddr_in);
    char buffer[256];
    /* 建立socket,注意必须是SOCK_DGRAM */
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
    perror("socket");
    exit(1);
    }
    /* 填写sockaddr_in 结构 */
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(UDP_TEST_PORT);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);// 接收任意IP发来的数据
    /* 绑定socket */
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
    perror("connect");
    exit(1);
    }
    while (1)
    {
    bzero(buffer, sizeof(buffer));
    len = recvfrom(sockfd, buffer, sizeof(buffer), 0,(struct sockaddr *)&addr, &addr_len);
    /* 显示client端的网络地址和收到的字符串消息 */
    printf("Received a string from client %s, string is: %s ",
    inet_ntoa(addr.sin_addr), buffer);
    /* 将收到的字符串消息返回给client端 */
    sendto(sockfd, buffer, len, 0, (struct sockaddr *)&addr, addr_len);

    }
    return 0;
    }
    Linux UDP-Client
    案例:
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>

    #define UDP_TEST_PORT 50001
    #define UDP_SERVER_IP "127.0.0.1"

    int main(int argC, char* arg[])
    {
    struct sockaddr_in addr;
    int sockfd, len = 0;
    int addr_len = sizeof(struct sockaddr_in);
    char buffer[256];
    /* 建立socket,注意必须是SOCK_DGRAM */
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
    perror("socket");
    exit(1);
    }
    /* 填写sockaddr_in*/
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(UDP_TEST_PORT);
    addr.sin_addr.s_addr = inet_addr(UDP_SERVER_IP);
    while (1)
    {
    bzero(buffer, sizeof(buffer));
    printf("Please enter a string to send to server: ");
    /* 从标准输入设备取得字符串*/
    len = read(STDIN_FILENO, buffer, sizeof(buffer));
    /* 将字符串传送给server端*/
    sendto(sockfd, buffer, len, 0, (struct sockaddr *)&addr, addr_len);
    /* 接收server端返回的字符串*/
    len = recvfrom(sockfd, buffer, sizeof(buffer), 0,(struct sockaddr *)&addr, &addr_len);
    printf("Receive from server: %s ", buffer);
    }
    return 0;
    }

    不为其他,只为快乐!
  • 相关阅读:
    SciTE 快捷键
    MySQL数据库性能优化
    常用的正则表达式全面总结
    PHP中的Memcache的应用
    经典数学题:态度决定一切
    PHP Socket基础
    由浅入深探究mysql索引结构原理、性能分析与优化
    深入理解HTTP协议
    PHP会话控制之Session介绍原理
    PHP会话控制之Cookie使用例子
  • 原文地址:https://www.cnblogs.com/1521299249study/p/11582292.html
Copyright © 2020-2023  润新知