跨平台Socket通讯程序(Windows+Linux)底层封装
【摘要】编写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在windows下和linux下的区别
一些文章也已涉及,这里,也是综合一下,并加上自己的理解。
项目 | Windows | Linux |
主要头文件 | winsock.h/winsock2.h | sys/socket.h fcntl.h errno.h |
链接库 | ws2_32.dll/lib | 连接是使用参数:-lstdc 运行时需要libstdc++.so.5,可在/usr/lib目录中创建一个链接。 |
初始化及退出 | 初始化需要调用WSAStartup,退出需调用WSACleanup | 无 |
关闭Socket | closesocket | 与文件操作相同close |
Socket类型 | SOCKET | 与文件句柄相同int |
错误查看 | WSAGetLastError | 全局变量errno |
设置非阻塞模式 | int i=1 ioctlsocket(sockethandle,FIONBIO,&i) | fcntl(ockethandle,F_SETFL, O_NONBLOCK) |
send/recv函数最后一个参数 | 一般设置为0 | 可以有多种组合:MSG_NOSIGNAL,MSG_DONTWAIT,MSG_WAITALL |
send的异常 | 当连接断开,还发数据的时候,不仅send()的返回值会有反映,而且还会像系统发送一个异常消息,如果不作处理,程序会退 出。为此,send()函数的最后一个参数可以设置MSG_NOSIGNAL,禁止send()函数向系统发送异常消息。 | |
WSA宏 | 除了可以使用标准的socket函数外,微软自己有许多以WSA开始的函数,作为对标准socket函数的封装(可能微软感觉这些函数更好用一些吧) |
以下给出源代码。
sock_wrap.h代码如下,其中用到了platform.h,定义_WIN32_PLATFROM_和_LINUX_PLATFROM_两个宏。
- #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
#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)的线程辅助程序”。
- #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);
- }
#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,本文的意思是,如果发生接收/发送错误,直接从函数的返回值大致判断下一步的动作。
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连接,当接收方发觉由”丢包“时,作为”对齐“信息包之用。