TCP/IP收发缓存_MSS/MTU算法机制_状态图_连接建立和断开之上的socket函数需要注意的问题

会阻塞的函数:connect, acceptsend/recv/sendto/recvfrom等读写函数.

不会阻塞的函数:bind, listen,socket, closesocket.


一、客户端socket函数:

// 1. socket 请求前要先加载socket驱动库,指定协议族,socket类型,具体协议。
SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
 if(INVALID_SOCKET == sClient)
 {
  printf("Get Socket Error: INVALID_SOCKET.\n");
  return 0;
 }

// socket默认是阻塞模型的,可以设置为非阻塞模型的,socket函数将会马上返回有不同的表现,比较影响性能。
unsigned long ul=1;
 int ret=ioctlsocket(sClient, FIONBIO, (unsigned long *)&ul);//设置成非阻塞模式。  
 if(ret==SOCKET_ERROR)//设置失败。  
 {  
   printf("Set socket unblock socket fail!!!.\n");
 } 

// 设置可以在TIME_WAIT期间,也就是主动关闭时候发送确认给对方关闭被动->主动之间的数据流后,服务器可以重新使用地址
int opt =  1;
 if ( setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 )
 {
  printf("setsockopt Failed.\n");
  return false;
 }

//2.connect指定的服务器地址,connect函数会在TCP三次握手中第二次返回,并且发送确认给服务器。
SOCKADDR_IN server;
 memset(&server, 0, sizeof(SOCKADDR_IN));
 // 网络socket三元组,网络类型,地址,端口
 server.sin_family = AF_INET;  
 server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);  
 server.sin_port = htons(PORT); 
 // 协议地址端口,来标识一个server进程,都要转换为网络字节顺序
 int nLen = sizeof(SOCKADDR_IN);// 大小为16 Byte,刚好是内存对齐模式,sockaddr也是16 Byte
 // 阻塞模式connect会阻塞程序,客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
 // 非阻塞会马上返回
 // connect时候会随机分配一个端口和地址给当前客户端网络进程,服务器会收到
 int nConnect = connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));
 if(SOCKET_ERROR == nConnect)
 {
  printf("Socket connnect Error.\n");
  return 0;
 }

//3.send,发送数据会做一些检查,非阻塞和阻塞下该函数的表现不一样,拷贝出错或者网络有问题需要处理.   
  // 阻塞模式下:发送前会先检查发送缓存是否在发送数据是则等待,不是则检查内核发送缓存,比较大小,
  // 如果小于那么拷贝到发送缓存,否则等待。
  // 非阻塞模式下:先检查发送缓存是否在发送,是等待,不是马上拷贝发送,能拷贝多少就拷贝多少。
  // 拷贝到内核发送缓存出错,那么返回SOCKET_ERROR,等待或者拷贝的过程中网络断开也返回SOCKET_ERROR
// 非阻塞windows下面 
int nSendRes = send(sClient, szMessage, strlen(szMessage), 0);// strlen求得的字符串长度不包含'\0'
  if(nRet == SOCKET_ERROR)
    {
            if(GetLastError() == WSAEWOULDBLOCK)// TCP发送缓存数据已满
            {
                // 把剩余的内容复制到开始处
                return true;
            }
            else
            {
              printf("send faild %d!", GetLastError());
                closesocket(m_Socket);
                m_Socket = INVALID_SOCKET;
                return false;

  }

// 非阻塞linux下面

int nRet = send(pConn->m_Socket, pData, size, 0);

                    if(nRet <= 0)
                    {
                        if(errno==EINTR||errno== EWOULDBLOCK||errno == EAGAIN)
                        {
                            break;
                        }
                        else
                        {
                            printf("send date faild(%d)(%s)  Close()!!!!!!!!!", errno, strerror(errno));
                            fflush(stdout);
                            closesocket(pConn->m_Socket);
                            pConn->m_Socket = INVALID_SOCKET;
                            printf("EPOLL ERROR!!!\n");
                            fflush(stdout);
                            pClient->m_bThreadRun = false;
                            return 0;
                        }
                 }


   // 4. recv, 接收数据,会先做一些检查,阻塞和非阻塞函数反应不同,记得收到对方关闭消息,一定要关闭自己的socket.
  // 接收消息前会先检查发送缓存区,如果正在发送,那么等待发送缓冲区的数据发送完毕,期间网络出错返回SOCKET_ERROR.
  // 阻塞模式下:按上面检查,recv收到数据完毕(协议会把一整个TCP包接收完毕,大包会重组后才算完毕)才返回,没收到则一直等待。
  // 非阻塞模式下:按上面检查,recv没收到马上返回不会阻塞,收到接收完毕才返回。
  // 返回值小于0的SOCKET_ERROR检查是否EAGAIN 接收期间网络断开,非阻塞下没有收到数据的返回10035错误码。
  // 返回值等于0,表示对方socket已经正常断开。
  // 返回值不等于请求的缓存值,那么再次接收。
  int nRecvRes = recv(sClient, szMessage, MSGSIZE, 0);  
  if(nRecvRes > 0)
  {
   szMessage[nRecvRes] = '\0';
   printf("Bytes receive : %s\n", szMessage);
  }
  else if(nRecvRes== 0 || (nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
  {
    printf("Connection Close.\n");
 closesocket(sClient); //  调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
 ReleaseWSAData();
  }
  else if(nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
  {
     // 非阻塞类型返回
 continue;
  }
  else
  {
   printf("Unknow recv error code: %d\n", WSAGetLastError());
   closesocket(sClient); //  调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
   ReleaseWSAData();
  }

// 非阻塞window下
int nRet = recv(
                            pClient->m_Socket,
                            pBuff,
                            nFreeSize, 0);
                        if(nRet <= 0)
                        {
                              // 分析数据
                            if(WSAGetLastError() != WSAEWOULDBLOCK)
                            {
                           printf("recive faild nRet == SOCKET_ERROR errorcode = %d,ret=%d", WSAGetLastError(),nRet);
                                closesocket(pClient->m_Socket);
                                pClient->m_Socket = INVALID_SOCKET;
                                return 0;
                            }
                            break;
                        }
                        if (!pClient->m_RBuffer.MarkData(nRet))
                        {
                            closesocket(pClient->m_Socket);
                            pClient->m_Socket = INVALID_SOCKET;
                            return 0;
                        }

// 非阻塞linux下
int nRet = recv(
                        pConn->m_Socket,
                        pBuff,
                        nFreeSize, 0);
                // 接收完了分析接收到的数据
                    if(nRet <= 0)
                    {
                      // 分析数据
                        if((nRet == 0)||(errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN))
                        {
                            printf("nRet = %d, nFreeSize = %d, pBuff = %d", nRet, nFreeSize, (int)pBuff);
                            printf("read date faild(%d)(%s)  Close()!!!!!!!!!", errno, strerror(errno));
                            fflush(stdout);
                            closesocket(pConn->m_Socket);
                            pConn->m_Socket = INVALID_SOCKET;
                            pClient->m_bThreadRun = false;
                            return 0;
                        }
                        break;
                    }
                     // 获取应用层接收缓存大小
                    else
                    {
                        if (!pConn->m_RBuffer.MarkData(nRet))
                        {
                            printf("read date faild read buff too larg (%d)  Close()!!!!!!!!!", pConn->m_RBuffer.GetBlockCount());
                            fflush(stdout);
                            closesocket(pConn->m_Socket);
                            pConn->m_Socket = INVALID_SOCKET;
                            pClient->m_bThreadRun = false;
                            return 0;
                        }
                    }

//5.  closesocket,无论是主动还是被动都要记得关闭socket才能完成TCP四次挥手
closesocket(sClient);

二、服务器端函数

//1. socket, 创建服务器自己的socket
::WSAStartup(0x0202,  &wsaData); 
 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// socket默认是阻塞模型的,可以设置为非阻塞模型的,socket函数将会马上返回有不同的表现,比较影响性能。
unsigned long ul=1;
 int ret=ioctlsocket(sClient, FIONBIO, (unsigned long *)&ul);//设置成非阻塞模式。  
 if(ret==SOCKET_ERROR)//设置失败。  
 {  
   printf("Set socket unblock socket fail!!!.\n");
 } 

// 设置可以在TIME_WAIT期间,也就是主动关闭时候发送确认给对方关闭被动->主动之间的数据流后,服务器可以重新使用地址
int opt =  1;
 if ( setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 )
 {
  printf("setsockopt Failed.\n");
  return false;
 }

//2. bind, 将创建的socket和服务器自己的地址绑定
 local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 local.sin_family = AF_INET;
 local.sin_port = htons(PORT);

 bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

//3. listen, 监听端口,可以指定同时可以监听到的连接数。
 // 同时连接的用户数量太多,服务器一下子不能处理那么多,
 // 3是内核TCP队列缓存的同时收到未处理的连接数量超过了将会丢弃,一般小于30.
 listen(sListen, 3);

// 4. accept a connection,接收到连接才存放到数组里面,否则一直阻塞 , 返回客户端的socket.
  // 这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。
  // 一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。
  sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
  printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

// 5. recv,从客户端接收数据,会先做一些检查,阻塞和非阻塞函数反应不同,记得收到对方关闭消息,一定要关闭对方的socket.
 // 接收消息前会先检查发送缓存区,如果正在发送,那么等待发送缓冲区的数据发送完毕,期间网络出错返回SOCKET_ERROR.
  // 阻塞模式下:按上面检查,recv收到数据完毕(协议会把一整个TCP包接收完毕,大包会重组后才算完毕)才返回,没收到则一直等待。
  // 非阻塞模式下:按上面检查,recv没收到马上返回不会阻塞,收到接收完毕才返回。
  // 返回值小于0的SOCKET_ERROR检查是否EAGAIN 接收期间网络断开,非阻塞下没有收到数据的返回10035错误码。
  // 返回值等于0,表示对方socket已经正常断开。
  // 返回值不等于请求的缓存值,那么再次接收。
  ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
    {

     // Client socket closed
     printf("Client socket %d closed.\n", g_CliSocketArr[i]);
     closesocket(g_CliSocketArr[i]);   //  调用closesocket避免四次挥手时候,被动端一直在CLOSE_WAIT状态。
     if (i < g_iTotalConn - 1)
     {          
      g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];// 将最后面的移动到删除的位置
     }
    }

//6.send, 给客户端发送数据会做一些检查,非阻塞和阻塞下该函数的表现不一样,拷贝出错或者网络有问题需要处理.   
  // 阻塞模式下:发送前会先检查发送缓存是否在发送数据是则等待,不是则检查内核发送缓存,比较大小,
  // 如果小于那么拷贝到发送缓存,否则等待。
  // 非阻塞模式下:先检查发送缓存是否在发送,是等待,不是马上拷贝发送,能拷贝多少就拷贝多少。
  // 拷贝到内核发送缓存出错,那么返回SOCKET_ERROR,等待或者拷贝的过程中网络断开也返回SOCKET_ERROR
  int nSendRes = send(sClient, szMessage, strlen(szMessage), 0);// strlen求得的字符串长度不包含'\0'
  if(SOCKET_ERROR == nSendRes)
  {
   printf("Send Copy data kernel buffer is too small or network shutdown!\n");
  }

//7.  closesocket,无论是主动还是被动都要记得关闭socket才能完成TCP四次挥手
closesocket(sClient);


你可能感兴趣的:(TCP/IP收发缓存_MSS/MTU算法机制_状态图_连接建立和断开之上的socket函数需要注意的问题)