TCP/IP Winsock
编程要点
作者:
Ackarlix
利用
Winsock
编程由同步和异步方式,同步方式逻辑清晰,编程专注于应用,在抢先式的多任务操作系统中
(WinNt
、
Win2K)
采用多线程方式效率基本达到异步方式的水平,应此以下为同步方式编程要点。
1 、快速通信
Winsock 的 Nagle 算法将降低小数据报的发送速度,而系统默认是使用 Nagle 算法 , 使用
1 、快速通信
Winsock 的 Nagle 算法将降低小数据报的发送速度,而系统默认是使用 Nagle 算法 , 使用
int setsockopt(
SOCKET s,
int level,
int optname,
const char FAR *optval,
int optlen ); 函数关闭它 |
例子:
SOCKET sConnect; sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); int bNodelay = 1; int err; err = setsockopt( sConnect, IPPROTO_TCP, TCP_NODELAY, (char *)&bNodelay, sizoeof(bNodelay));// 不采用延时算法 if (err != NO_ERROR) TRACE ("setsockopt failed for some reason/n");; |
2 、 SOCKET 的 SegMentSize 和收发缓冲
TCPSegMentSize 是发送接受时单个数据报的最大长度,系统默认为 1460 ,收发缓冲大小为 8192 。
在 SOCK_STREAM 方式下,如果单次发送数据超过 1460 ,系统将分成多个数据报传送,在对方接受到的将是一个数据流,应用程序需要增加断帧的判断。当然可以采用修改注册表的方式改变 1460 的大小,但 MicrcoSoft 认为 1460 是最佳效率的参数,不建议修改。
在工控系统中,建议关闭 Nagle 算法,每次发送数据小于 1460 个字节(推荐 1400 ),这样每次发送的是一个完整的数据报,减少对方对数据流的断帧处理。
3 、同步方式中减少断网时 connect 函数的阻塞时间
同步方式中的断网时 connect 的阻塞时间为 20 秒左右,可采用 gethostbyaddr 事先判断到服务主机的路径是否是通的,或者先 ping 一下对方主机的 IP 地址。
A 、采用 gethostbyaddr 阻塞时间不管成功与否为 4 秒左右。
例子:
LONG lPort=3024; struct sockaddr_in ServerHostAddr;// 服务主机地址 ServerHostAddr.sin_family=AF_INET; ServerHostAddr.sin_port=::htons(u_short(lPort)); ServerHostAddr.sin_addr.s_addr=::inet_addr("192.168.1.3"); HOSTENT* pResult=gethostbyaddr((const char *) & (ServerHostAddr.sin_addr.s_addr),4,AF_INET); if(NULL==pResult) { int nErrorCode=WSAGetLastError(); TRACE("gethostbyaddr errorcode=%d",nErrorCode); } else { TRACE("gethostbyaddr %s/n",pResult->h_name);; } |
B 、采用 PING 方式时间约 2 秒左右
暂略
4 、同步方式中解决 recv , send 阻塞问题
采用 select 函数解决,在收发前先检查读写可用状态。
A 、读
例子:
TIMEVAL tv01 = {0, 1};//1ms
钟延迟
,
实际为
0-10
毫秒
int nSelectRet; int nErrorCode; FD_SET fdr = {1, sConnect}; nSelectRet=::select(0, &fdr, NULL, NULL, &tv01);// 检查可读状态 if(SOCKET_ERROR==nSelectRet) { nErrorCode=WSAGetLastError(); TRACE("select read status errorcode=%d",nErrorCode); ::closesocket(sConnect); goto 重新连接(客户方),或服务线程退出(服务方) ; } if(nSelectRet==0)// 超时发生,无可读数据 { 继续查读状态或向对方主动发送 } else { 读数据 } |
B 、写
TIMEVAL tv01 = {0, 1};//1ms
钟延迟
,
实际为
9-10
毫秒
int nSelectRet; int nErrorCode; FD_SET fdw = {1, sConnect}; nSelectRet=::select(0, NULL, NULL,&fdw, &tv01);// 检查可写状态 if(SOCKET_ERROR==nSelectRet) { nErrorCode=WSAGetLastError(); TRACE("select write status errorcode=%d",nErrorCode); ::closesocket(sConnect); //goto 重新连接(客户方),或服务线程退出(服务方) ; } if(nSelectRet==0)// 超时发生,缓冲满或 网络 忙 { // 继续查写状态或查读状态 } else { // 发送 } |
5 、改变 TCP 收发缓冲区大小
系统默认为 8192 ,利用如下方式可改变。
SOCKET sConnect; sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); int nrcvbuf=1024*20; int err=setsockopt( sConnect, SOL_SOCKET, SO_SNDBUF,// 写缓冲,读缓冲为 SO_RCVBUF (char *)&nrcvbuf, sizeof(nrcvbuf)); if (err != NO_ERROR) { TRACE("setsockopt Error!/n"); } 在设置缓冲时,检查是否真正设置成功用 int getsockopt( SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen ); |
6 、服务方同一端口多 IP 地址的 bind 和 listen
在可靠性要求高的应用中,要求使用双网和多 网络 通道,再服务方很容易实现,用如下方式可建立客户对本机所有 IP 地址在端口 3024 下的请求服务。
SOCKET hServerSocket_DS=INVALID_SOCKET; struct sockaddr_in HostAddr_DS;// 服务器主机地址 LONG lPort=3024; HostAddr_DS.sin_family=AF_INET; HostAddr_DS.sin_port=::htons(u_short(lPort)); HostAddr_DS.sin_addr.s_addr=htonl(INADDR_ANY); hServerSocket_DS=::socket( AF_INET, SOCK_STREAM,IPPROTO_TCP); if(hServerSocket_DS==INVALID_SOCKET) { AfxMessageBox(" 建立数据服务器 SOCKET 失败 !"); return FALSE; } if(SOCKET_ERROR==::bind(hServerSocket_DS,(struct sockaddr *)(&(HostAddr_DS)),sizeof(SOCKADDR))) { int nErrorCode=WSAGetLastError (); TRACE("bind error=%d/n",nErrorCode); AfxMessageBox("Socket Bind 错误 !"); return FALSE; } if(SOCKET_ERROR==::listen(hServerSocket_DS,10))//10 个客户 { AfxMessageBox("Socket listen 错误 !"); return FALSE; } AfxBeginThread(ServerThreadProc,NULL,THREAD_PRIORITY_NORMAL); |
在客户方要复杂一些,连接断后,重联不成功则应换下一个 IP 地址连接。也可采用同时连接好后备用的方式。
7 、用 TCP/IP Winsock 实现变种 Client/Server
传统的 Client/Server 为客户问、服务答,收发是成对出现的。而变种的 Client/Server 是指在连接时有客户和服务之分,建立好通信连接后,不再有严格的客户和服务之分,任何方都可主动发送,需要或不需要回答看应用而言,这种方式在工控行业很有用,比如 RTDB 作为 I/O Server 的客户,但 I/O Server 也可主动向 RTDB 发送开关状态变位、随即事件等信息。在很大程度上减少了 网络 通信负荷、提高了效率。
采用 1-6 的 TCP/IP 编程要点,在 Client 和 Server 方均已接收优先,适当控制时序就能实现。