跨平台(Windows+Linux)的Socket通讯程序(一)—底层封装

【摘要】编写Socket通讯程序是一个老话题。本文重点介绍Windows平台和Linux平台Socket通讯的不同,采用C++,编制了一个简单的跨平台的Socket通讯库。
一、Socket通讯的基础知识
Socket通讯是两个计算机之间最基本的通讯方法,有TCP和UDP两种协议。关于这两种协议的区别,不少文章已有详述,这里,稍微总结一下:
1.TCP是面向连接的,是“流”式的,意即通讯两端建立了一个“数码流管”,该流无头无尾,接收端保证接收顺序,但不保证包的分割。
2.UDP是面向无连接的,是“包”式的,意即通讯两端自由发送数据包,接收端不保证接收顺序,但保证包的分割与发送端一致。
正是基于上述二者的不同,在编程上,它们的区别如下:对TCP连接,服务器端过程(bind->listen->accept->send/receive)与客户端不相同(connect->send/receive),对UDP连接,二者似乎更对等一些(服务器端仅需要bind)。

二、跨平台的Socket辅助程序
以下给出源代码。
sock_wrap.h代码如下,其中用到了platform.h,定义_WIN32_PLATFROM_和_LINUX_PLATFROM_两个宏。
[cpp] view plain copy
#ifndef _SOCK_WRAP_H_  
#define _SOCK_WRAP_H_  
  
#include "platform.h"  
  
#if defined(_WIN32_PLATFROM_)  
#include <winsock2.h>  
typedef SOCKET HSocket;  
#endif  
  
#if defined(_LINUX_PLATFORM_)  
#include <netinet/in.h>  
#include <sys/socket.h>  
#include <sys/types.h>  
  
typedef int HSocket;  
#define SOCKET_ERROR  (-1)  
#define INVALID_SOCKET  0  
#endif  
  
  
typedef struct  
{  
    int block;  
    int sendbuffersize;  
    int recvbuffersize;  
    int lingertimeout;  
    int recvtimeout;  
    int sendtimeout;  
} socketoption_t;  
  
typedef struct  
{  
   int nbytes;  
   int nresult;  
} transresult_t;  
  
int InitializeSocketEnvironment();  
void FreeSocketEnvironment();  
void GetAddressFrom(sockaddr_in *addr, const char *ip, int port);  
void GetIpAddress(char *ip, sockaddr_in *addr);  
bool IsValidSocketHandle(HSocket handle);  
int GetLastSocketError();  
  
HSocket SocketOpen(int tcpudp);  
void SocketClose(HSocket &handle);  
  
int SocketBlock(HSocket hs, bool bblock);  
int SocketTimeOut(HSocket hs, int recvtimeout, int sendtimeout, int lingertimeout);  
  
int SocketBind(HSocket hs, sockaddr_in *addr);  
HSocket SocketAccept(HSocket hs, sockaddr_in *addr);  
int SocketListen(HSocket hs, int maxconn);  
  
void SocketSend(HSocket hs, const char *ptr, int nbytes, transresult_t &rt);  
void SocketRecv(HSocket hs, char *ptr, int nbytes, transresult_t &rt);  
void SocketTryRecv(HSocket hs, char *ptr, int nbytes, int milliseconds, transresult_t &rt);  
void SocketTrySend(HSocket hs, const char *ptr, int nbytes, int milliseconds, transresult_t &rt);  
  
void SocketClearRecvBuffer(HSocket hs);  
  
class CSockWrap  
{  
public:  
    CSockWrap(int tcpudp);  
    ~CSockWrap();  
    void SetAddress(const char *ip, int port);  
    void SetAddress(sockaddr_in *addr);  
    int SetTimeOut(int recvtimeout, int sendtimeout, int lingertimeout);  
    int SetBufferSize(int recvbuffersize, int sendbuffersize);  
    int SetBlock(bool bblock);  
  
    HSocket  GetHandle () { return m_hSocket;}  
    void Reopen(bool bForceClose);  
    void Close();  
    transresult_t Send(void *ptr, int nbytes);  
    transresult_t Recv(void *ptr, int nbytes );  
    transresult_t TrySend(void *ptr, int nbytes, int milliseconds);  
    transresult_t TryRecv(void *ptr, int nbytes, int  milliseconds );  
    void ClearRecvBuffer();  
  
protected:  
    HSocket  m_hSocket;  
    sockaddr_in m_stAddr;  
    int m_tcpudp;  
};  
  
  
#endif   

sock_wrap.cpp代码如下,其中引用了lightThread.h和spantime.h,它们的代码见“跨平台(Windows+Linux)的线程辅助程序”。
[cpp] view plain copy
#include "platform.h"  
  
#include <stdio.h>  
#include <string.h>  
#include <fcntl.h>  
#include "lightthread.h"  
#include "sock_wrap.h"  
#include "TimeSpan.h"  
  
#define INVALIDSOCKHANDLE   INVALID_SOCKET  
  
#if defined(_WIN32_PLATFROM_)  
#include <windows.h>  
#define ISSOCKHANDLE(x)  (x!=INVALID_SOCKET)  
#define BLOCKREADWRITE      0  
#define NONBLOCKREADWRITE   0  
#define SENDNOSIGNAL        0  
#define ETRYAGAIN(x)     (x==WSAEWOULDBLOCK||x==WSAETIMEDOUT)  
#define gxsprintf   sprintf_s  
  
#endif  
  
  
#if defined(_LINUX_PLATFORM_)  
#include <stdlib.h>  
#include <errno.h>  
#include <unistd.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#define ISSOCKHANDLE(x)    (x>0)  
#define BLOCKREADWRITE      MSG_WAITALL  
#define NONBLOCKREADWRITE   MSG_DONTWAIT  
#define SENDNOSIGNAL        MSG_NOSIGNAL  
#define ETRYAGAIN(x)        (x==EAGAIN||x==EWOULDBLOCK)  
#define gxsprintf           snprintf  
  
#endif  
  
  
void GetAddressFrom(sockaddr_in *addr, const char *ip, int port)  
{  
    memset(addr, 0, sizeof(sockaddr_in));  
    addr->sin_family = AF_INET;            /*地址类型为AF_INET*/  
    if(ip)  
    {  
        addr->sin_addr.s_addr = inet_addr(ip);  
    }  
    else  
    {  
        /*网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址, 
        这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址*/  
        addr->sin_addr.s_addr = htonl(INADDR_ANY);  
    }  
    addr->sin_port = htons(port);   /*端口号*/  
}  
void GetIpAddress(char *ip, sockaddr_in *addr)  
{  
    unsigned char *p =(unsigned char *)( &(addr->sin_addr));  
    gxsprintf(ip, 17, "%u.%u.%u.%u", *p,*(p+1), *(p+2), *(p+3) );  
}  
  
int GetLastSocketError()  
{  
#if defined(_WIN32_PLATFROM_)  
    return WSAGetLastError();  
#endif  
  
#if defined(_LINUX_PLATFORM_)  
    return errno;  
#endif  
}  
  
bool IsValidSocketHandle(HSocket handle)  
{  
    return ISSOCKHANDLE(handle);  
}  
  
void SocketClose(HSocket &handle)  
{  
    if(ISSOCKHANDLE(handle))  
    {  
#if defined(_WIN32_PLATFROM_)  
        closesocket(handle);  
#endif  
  
#if defined(_LINUX_PLATFORM_)  
        close(handle);  
#endif  
        handle = INVALIDSOCKHANDLE;  
    }  
}  
  
HSocket SocketOpen(int tcpudp)  
{  
    int protocol = 0;  
    HSocket hs;  
#if defined(_WIN32_PLATFROM_)  
    if(tcpudp== SOCK_STREAM) protocol=IPPROTO_TCP;  
    else if (tcpudp== SOCK_DGRAM) protocol = IPPROTO_UDP;  
#endif  
    hs = socket(AF_INET, tcpudp, protocol);  
    return hs;  
}  
int SocketBind(HSocket hs, sockaddr_in *paddr)  
{  
    return bind(hs, (struct sockaddr *)paddr, sizeof(sockaddr_in));  
}  
int SocketListen(HSocket hs, int maxconn)  
{  
    return listen(hs,maxconn);  
}  
HSocket SocketAccept(HSocket hs, sockaddr_in *paddr)  
{  
#if defined(_WIN32_PLATFROM_)  
    int cliaddr_len = sizeof(sockaddr_in);  
#endif  
#if defined(_LINUX_PLATFORM_)  
    socklen_t cliaddr_len = sizeof(sockaddr_in);  
#endif  
    return accept(hs, (struct sockaddr *)paddr, &cliaddr_len);  
}  
//  
// if timeout occurs, nbytes=-1, nresult=1  
// if socket error, nbyte=-1, nresult=-1  
// if the other side has disconnected in either block mode or nonblock mode, nbytes=0, nresult=-1  
// otherwise nbytes= the count of bytes sent , nresult=0  
void SocketSend(HSocket hs, const char *ptr, int nbytes, transresult_t &rt)  
{  
    rt.nbytes = 0;  
    rt.nresult = 0;  
    if(!ptr|| nbytes<1) return;  
  
    //Linux: flag can be MSG_DONTWAIT, MSG_WAITALL, 使用MSG_WAITALL的时候, socket 必须是处于阻塞模式下,否则WAITALL不能起作用  
    rt.nbytes = send(hs, ptr, nbytes, BLOCKREADWRITE|SENDNOSIGNAL);  
    if(rt.nbytes>0)  
    {  
        rt.nresult = (rt.nbytes == nbytes)?0:1;  
    }  
    else if(rt.nbytes==0)  
    {  
       rt.nresult=-1;  
    }  
    else  
    {  
        rt.nresult = GetLastSocketError();  
        rt.nresult = ETRYAGAIN(rt.nresult)? 1:-1;  
    }  
}  
  
  
  
// if timeout occurs, nbytes=-1, nresult=1  
// if socket error, nbyte=-1, nresult=-1  
// if the other side has disconnected in either block mode or nonblock mode, nbytes=0, nresult=-1  
void SocketRecv(HSocket hs, char *ptr, int nbytes, transresult_t &rt)  
{  
    rt.nbytes = 0;  
    rt.nresult = 0;  
    if(!ptr|| nbytes<1) return;  
  
    rt.nbytes = recv(hs, ptr, nbytes, BLOCKREADWRITE);  
    if(rt.nbytes>0)  
    {  
        return;  
    }  
    else if(rt.nbytes==0)  
    {  
       rt.nresult=-1;  
    }  
    else  
    {  
        rt.nresult = GetLastSocketError();  
        rt.nresult = ETRYAGAIN(rt.nresult)? 1:-1;  
    }  
  
}  
//  nbytes= the count of bytes sent  
// if timeout occurs, nresult=1  
// if socket error,  nresult=-1,  
// if the other side has disconnected in either block mode or nonblock mode, nresult=-2  
void SocketTrySend(HSocket hs, const char *ptr, int nbytes, int milliseconds, transresult_t &rt)  
{  
    rt.nbytes = 0;  
    rt.nresult = 0;  
    if(!ptr|| nbytes<1) return;  
  
  
    int n;  
    CMyTimeSpan start;  
    while(1)  
    {  
        n = send(hs, ptr+rt.nbytes, nbytes, NONBLOCKREADWRITE|SENDNOSIGNAL);  
        if(n>0)  
        {  
            rt.nbytes += n;  
            nbytes -= n;  
            if(rt.nbytes >= nbytes) {    rt.nresult = 0;  break; }  
        }  
        else if( n==0)  
        {  
            rt.nresult= -2;  
            break;  
        }  
        else  
        {  
            n = GetLastSocketError();  
            if(ETRYAGAIN(n))  
            {  
                CLightThread::DiscardTimeSlice();  
            }  
            else  
            {  
                rt.nresult = -1;  
                break;  
            }  
        }  
        if(start.GetSpaninMilliseconds()>milliseconds)  { rt.nresult= 1; break;}  
    }  
}  
// if timeout occurs, nbytes=-1, nresult=1  
// if socket error, nbyte=-1, nresult=-1  
// if the other side has disconnected in either block mode or nonblock mode, nbytes=0, nresult=-1  
void SocketTryRecv(HSocket hs, char *ptr, int nbytes, int milliseconds, transresult_t &rt)  
{  
    rt.nbytes = 0;  
    rt.nresult = 0;  
    if(!ptr|| nbytes<1) return;  
  
    if(milliseconds>2)  
    {  
        CMyTimeSpan start;  
        while(1)  
        {  
            rt.nbytes = recv(hs, ptr, nbytes, NONBLOCKREADWRITE);  
            if(rt.nbytes>0)  
            {  
               break;  
            }  
            else if(rt.nbytes==0)  
            {  
                rt.nresult = -1;  
                break;  
            }  
            else  
            {  
                rt.nresult = GetLastSocketError();  
                if( ETRYAGAIN(rt.nresult))  
                {  
                   if(start.GetSpaninMilliseconds()>milliseconds)  { rt.nresult= 1; break;}  
                   CLightThread::DiscardTimeSlice();  
                }  
                else  
                {  
                    rt.nresult = -1;  
                    break;  
                }  
            }  
  
        }  
    }  
    else  
    {  
        SocketRecv(hs, ptr, nbytes, rt);  
    }  
}  
  
void SocketClearRecvBuffer(HSocket hs)  
{  
#if defined(_WIN32_PLATFROM_)  
    struct timeval tmOut;  
    tmOut.tv_sec = 0;  
    tmOut.tv_usec = 0;  
    fd_set    fds;  
    FD_ZERO(&fds);  
    FD_SET(hs, &fds);  
    int   nRet = 1;  
    char tmp[100];  
    int rt;  
    while(nRet>0)  
    {  
        nRet= select(FD_SETSIZE, &fds, NULL, NULL, &tmOut);  
        if(nRet>0)  
        {  
           nRet = recv(hs, tmp, 100,0);  
        }  
    }  
#endif  
  
#if defined(_LINUX_PLATFORM_)  
   char tmp[100];  
   while(recv(hs, tmp, 100, NONBLOCKREADWRITE)> 0);  
#endif  
}  
  
int SocketBlock(HSocket hs, bool bblock)  
{  
    unsigned long mode;  
    if( ISSOCKHANDLE(hs))  
    {  
#if defined(_WIN32_PLATFROM_)  
        mode = bblock?0:1;  
        return ioctlsocket(hs,FIONBIO,&mode);  
#endif  
  
#if defined(_LINUX_PLATFORM_)  
        mode = fcntl(hs, F_GETFL, 0);                  //获取文件的flags值。  
        //设置成阻塞模式      非阻塞模式  
        return bblock?fcntl(hs,F_SETFL, mode&~O_NONBLOCK): fcntl(hs, F_SETFL, mode | O_NONBLOCK);  
#endif  
    }  
    return -1;  
}  
  
int SocketTimeOut(HSocket hs, int recvtimeout, int sendtimeout, int lingertimeout)   //in milliseconds  
{  
    int rt=-1;  
    if (ISSOCKHANDLE(hs) )  
    {  
        rt=0;  
#if defined(_WIN32_PLATFROM_)  
        if(lingertimeout>-1)  
        {  
            struct linger  lin;  
            lin.l_onoff = lingertimeout;  
            lin.l_linger = lingertimeout ;  
            rt = setsockopt(hs,SOL_SOCKET,SO_DONTLINGER,(const char*)&lin,sizeof(linger)) == 0 ? 0:0x1;  
        }  
        if(recvtimeout>0 && rt == 0)  
        {  
            rt = rt | (setsockopt(hs,SOL_SOCKET,SO_RCVTIMEO,(char *)&recvtimeout,sizeof(int))==0?0:0x2);  
        }  
        if(sendtimeout>0 && rt == 0)  
        {  
            rt = rt | (setsockopt(hs,SOL_SOCKET, SO_SNDTIMEO, (char *)&sendtimeout,sizeof(int))==0?0:0x4);  
        }  
#endif  
  
#if defined(_LINUX_PLATFORM_)  
   struct timeval timeout;  
        if(lingertimeout>-1)  
        {  
            struct linger  lin;  
            lin.l_onoff = lingertimeout>0?1:0;  
            lin.l_linger = lingertimeout/1000 ;  
            rt = setsockopt(hs,SOL_SOCKET,SO_LINGER,(const char*)&lin,sizeof(linger)) == 0 ? 0:0x1;  
        }  
        if(recvtimeout>0 && rt == 0)  
        {  
            timeout.tv_sec = recvtimeout/1000;  
            timeout.tv_usec = (recvtimeout % 1000)*1000;  
            rt = rt | (setsockopt(hs,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout))==0?0:0x2);  
        }  
        if(sendtimeout>0 && rt == 0)  
        {  
            timeout.tv_sec = sendtimeout/1000;  
            timeout.tv_usec = (sendtimeout % 1000)*1000;  
            rt = rt | (setsockopt(hs,SOL_SOCKET, SO_SNDTIMEO, &timeout,sizeof(timeout))==0?0:0x4);  
        }  
#endif  
    }  
    return rt;  
}  
  
  
int InitializeSocketEnvironment()  
{  
#if defined(_WIN32_PLATFROM_)  
    WSADATA  Ws;  
    //Init Windows Socket  
    if ( WSAStartup(MAKEWORD(2,2), &Ws) != 0 )  
    {  
        return -1;  
    }  
#endif  
    return 0;  
}  
void FreeSocketEnvironment()  
{  
#if defined(_WIN32_PLATFROM_)  
    WSACleanup();  
#endif  
}  
//==============================================================================================================  
//================================================================================================================  
CSockWrap::CSockWrap(int tcpudp)  
{  
    memset(&m_stAddr, 0, sizeof(sockaddr_in));  
    m_tcpudp = tcpudp;  
    m_hSocket = INVALIDSOCKHANDLE;  
    Reopen(false);  
}  
  
  
CSockWrap::~CSockWrap()  
{  
    SocketClose(m_hSocket);  
}  
void CSockWrap::Reopen(bool bForceClose)  
{  
  
    if (ISSOCKHANDLE(m_hSocket) && bForceClose) SocketClose(m_hSocket);  
    if (!ISSOCKHANDLE(m_hSocket) )  
    {  
        m_hSocket=SocketOpen(m_tcpudp);  
    }  
  
}  
void CSockWrap::SetAddress(const char *ip, int port)  
{  
    GetAddressFrom(&m_stAddr, ip, port);  
}  
void CSockWrap::SetAddress(sockaddr_in *addr)  
{  
    memcpy(&m_stAddr, addr, sizeof(sockaddr_in));  
}  
  
int CSockWrap::SetTimeOut(int recvtimeout, int sendtimeout, int lingertimeout)   //in milliseconds  
{  
  return SocketTimeOut(m_hSocket, recvtimeout, sendtimeout, lingertimeout);  
}  
  
int CSockWrap::SetBufferSize(int recvbuffersize, int sendbuffersize)   //in bytes  
{  
    int rt=-1;  
    if (ISSOCKHANDLE(m_hSocket) )  
    {  
#if defined(_WIN32_PLATFROM_)  
        if(recvbuffersize>-1)  
        {  
            rt = setsockopt( m_hSocket, SOL_SOCKET, SO_RCVBUF, ( const char* )&recvbuffersize, sizeof( int ) );  
        }  
        if(sendbuffersize>-1)  
        {  
            rt = rt | (setsockopt(m_hSocket,SOL_SOCKET,SO_SNDBUF,(char *)&sendbuffersize,sizeof(int))==0?0:0x2);  
        }  
#endif  
    }  
    return rt;  
}  
  
int CSockWrap::SetBlock(bool bblock)  
{  
    return SocketBlock(m_hSocket, bblock);  
}  
transresult_t CSockWrap::Send(void *ptr, int nbytes)  
{  
    transresult_t rt;  
    SocketSend(m_hSocket, (const char *)ptr, nbytes,rt);  
    return rt;  
}  
transresult_t CSockWrap::Recv(void *ptr, int nbytes )  
{  
    transresult_t rt;  
    SocketRecv(m_hSocket, (char *)ptr, nbytes,rt);  
    return rt;  
}  
transresult_t CSockWrap::TrySend(void *ptr, int nbytes, int milliseconds)  
{  
    transresult_t rt;  
    SocketTrySend(m_hSocket, (const char *)ptr, nbytes,milliseconds, rt);  
    return rt;  
}  
transresult_t CSockWrap::TryRecv(void *ptr, int nbytes, int  milliseconds )  
{  
    transresult_t rt;  
    SocketTryRecv(m_hSocket, (char *)ptr, nbytes,milliseconds, rt);  
    return rt;  
}  
  
void CSockWrap::ClearRecvBuffer()  
{  
    SocketClearRecvBuffer(m_hSocket);  
}  
上面的辅助程序实际上包含了对一些常用的socket函数的封装和一个类CSockWrap,如果需要自己组建通讯逻辑,可以直接用这些C风格的函数,CSockWrap实际上就是这样一个应用。发送和接收函数的返回值有点复杂,是一个结构体transresult_t,本文的意思是,如果发生接收/发送错误,直接从函数的返回值大致判断下一步的动作。

四、关于Socket通讯过程的一些讨论
1.关于send函数。Socket中Send函数的意思是只要将应用程序的数据发送到网卡中就算成功,将发送端的网线拔掉与将接收端的网线拔掉,Send函数的返回可能不同,因此它的正常返回不能作为接收方是否收到的判断条件。如果需要确保对方收到信息,只能采用应答式,但这样做可能会降低双方的通讯效率。一般情况下,Send不会阻塞,除非网卡的发送缓冲区已经满了(发送端直接掉线)。
2.关于recv函数。Recv是最常用的阻塞函数,但通常情况下,应设置其为非阻塞(windows将整个Socket连接都设为非阻塞,linux可以有两种方式),因为,如果发送方已经掉线,或者还需要干别的事情,让Recv阻塞显然是不合适的。当然,也可以不用Recv,而用非阻塞的Select函数(本文没有涉及Select函数),其实它们的效果是一样的。
3.关于从send和recv函数的返回值来初步判断网络状态,见SocketSend等函数的注释。
4.采用UDP通讯时,数据包的内容不宜过大,所以UDP特别适合于命令的传输(一次的通讯量小,但可能频繁)。
5.SocketClearRecvBuffer函数一般用于TCP连接,当接收方发觉由”丢包“时,作为”对齐“信息包之用。

猜你喜欢

转载自blog.csdn.net/wushuangge/article/details/79230709