-
2007-02-05
Windows网络编程经验小结 - [winsock]
Windows网络编程经验小结
转自:CSDN网友的强贴,其ID:gdy119 (夜风微凉)
1. 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000; //1秒//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);
系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收
数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置
connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞
的函数调用中作用不大)BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序
满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)?struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
Note:
1. 在设置了逗留延时,用于一个非阻塞的socket是作用不大的,最好不用;2. 如果想要程序不经历SO_LINGER需要设置SO_DONTLINGER,或者设置l_onoff=0;
10.还一个用的比较少的是在SDI或者是Dialog的程序中,可以记录socket的调试信息:
(前不久做过这个函数的测试,调式信息可以保存,包括socket建立时候的参数,采用的
具体协议,以及出错的代码都可以记录下来)
BOOL bDebug=TRUE;
setsockopt(s,SOL_SOCKET,SO_DEBUG,(const char*)&bDebug,sizeof(BOOL));
11.附加:往往通过setsockopt()设置了缓冲区大小,但还不能满足数据的传输需求,
我的习惯是自己写个处理网络缓冲的类,动态分配内存;下面我将这个类写出,希望对
初学者有所帮助:
//仿照String 改写而成
//=====================================================================
// 二进制数据,主要用于收发网络缓冲区的数据
// CNetIOBuffer 以 MFC 类 CString 的源代码作为蓝本改写而成,用法与 CString 类似,
// 但是 CNetIOBuffer 中存放的是纯粹的二进制数据,'' 并不作为它的结束标志。
// 其数据长度可以通过 GetLength() 获得,缓冲区地址可以通过运算符 LPBYTE 获得。
//=====================================================================
// Copyright (c) All-Vision Corporation. All rights reserved.
// Module: NetObject
// File: SimpleIOBuffer.h
// Author: gdy119
// Email : 8751webmaster@126.com
// Date: 2004.11.26
//=====================================================================
// NetIOBuffer.h
#ifndef _NETIOBUFFER_H
#define _NETIOBUFFER_H
//=====================================================================
#define MAX_BUFFER_LENGTH 1024*1024
//=====================================================================
//主要用来处理网络缓冲的数据
class CNetIOBuffer
{
protected:
LPBYTE m_pbinData;
int m_nLength;
int m_nTotalLength;
CRITICAL_SECTIONm_cs;
void Initvalibers();
public:
CNetIOBuffer();
CNetIOBuffer(const LPBYTE lbbyte, int nLength);
CNetIOBuffer(const CNetIOBuffer&binarySrc);
virtual ~CNetIOBuffer();
//=====================================================================
BOOL CopyData(const LPBYTE lbbyte, int nLength);
BOOL ConcatData(const LPBYTE lbbyte, int nLength);
void ResetIoBuffer();
int GetLength() const;
BOOL SetLength(int nLen);
LPBYTE GetCurPos();
int GetRemainLen();
BOOL IsEmpty() const;
operator LPBYTE() const;
static GetMaxLength() { return MAX_BUFFER_LENGTH; }
const CNetIOBuffer& operator=(const CNetIOBuffer& buffSrc);
};
#endif //
// NetOBuffer.cpp: implementation of the CNetIOBuffer class.
//=====================================================================
#include "stdafx.h"
#include "NetIOBuffer.h"
//=====================================================================
//=====================================================================
// Construction/Destruction
CNetIOBuffer::CNetIOBuffer()
{
Initvalibers();
}
CNetIOBuffer::CNetIOBuffer(const LPBYTE lbbyte, int nLength)
{
Initvalibers();
CopyData(lbbyte, nLength);
}
CNetIOBuffer::~CNetIOBuffer()
{
delete []m_pbinData;
m_pbinData=NULL;
DeleteCriticalSection(&m_cs);
}
CNetIOBuffer::CNetIOBuffer(const CNetIOBuffer&binarySrc)
{
Initvalibers();</
-
2006-11-27
8.2.5完成端口模型 - [winsock]
8.2.5完成端口模型
创建完成端口对象 CreateCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, DWORD CompletionKey, DWORD NumberOfConcurrentThreads);
CompletionKey:则指定要与某个特定套接字句柄关联在一起的“单句柄数据”
1.工作者线程与完成端口
StartWinsock();
//step 1
//create an IO completion port.CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0,0);
//step 2;
//Determine how many processors are on the systemGetSystemInfo(&SystemInfo);
//Step3
//create worker threads based on the number of processors available on the system .For this simple case, we create one worker thread for each processor.
for(i=0; i < SystemInfo.dwNumberOfProcessors; i++)
{
HANDLE ThreadHandle;
//create a server worker thread and pass the completion port to the thread, Note:the ServerWorkerThread procedure is not defined in this listing.
ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,0, &ThreadID);//close the thread handle
CloseHandle(ThreadHandle);
}//step 4
//create a listening socket
Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));//prepare socket for listening
listen(Listen, 5);while (TRUE)
{
//step 5
//Accept connections and assign to completion port.
Accept = WSAAccept(Listen, NULL, NULL, NULL, 0);//step 6
//create per-handle data information structure to associate with the socket
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));printf("Socket number %d connected\n",Accept);
PerHandleData->Socket = Accept;//step 7
//Associate the accepted socket with the completion port.
CreateIoCompletionPort((HANDLE)Accept,CompletionPort, (DWORD)PerHandleData,0);//创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。
//step 8
//start processing IO on the accepted socket. post oen or more WSASend() or WSARecv() calls on the socket using overlapped IO
WSARecv(...)''
}2.完成端口和重叠IO
需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort, LPDWORD lpNumberOfBytesTransferred, LPDWORD lpCompletionKey, LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);3.单句柄数据和单IO操作数据
typedef struct
{
OVERLAPPED Overlapped;
WSABUF DataBuf;
CHAR Buffer[DATA_BUFSIZE];
bool OperationType;
} PER_IO_OPERATION_DATA;DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE).CompletionPortID;
DWORD ByteTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD SendBytes, RecvBytes;
DWORD Flags;while (TRUE)
{
//Wait for IO to complete on any socket associated with the completion port
GetQueuedCompletionStatus(CompletionPort,&ByteTransferred, (LPDWORD)&PerHandleData,(LPOVERLAPPED*)&PerIoData, INFINITE);//socket and clean up the per-handle data and per-io opertion data associated with the socket
if (BytesTransferred ==0&&(PerIoData->OperationType == RECV_POSTED||PerHandleData->OperationType == SEND_POSTED))
{
//A zero bytesTransferred indicates that the socket has been closed by the peer, so you should close the socket.
//Note: Per-handle data was used to reference the socket associated withe IO operation.
closesocket(PerHandleData->Socket);GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}//service the completed IO request, You can determine which IO request has just completed by looking at the OperationType field contained in the
//the per-io operation data.
if (PerIoData->OperationType==RECV_POSTED)
{
//Do sth with the received data in perIoData->Buffer
}//post another WSASend or WSARecv operation .
//As an example , we will post another WSARecv() IO operationFlags = 0;
//Set up the per-IO operation data for the next overlapped call
ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));PerIoData->DataBuf.len = DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer;
PerIoData->OperationType = RECV_POSTED;WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf),1,&RecvBytes, &Flags, &(PerIoData->Overlapped),NULL);
}
}如何正确地关闭IO端口-特别是同时运行一个或者多个线程,在几个不同的套接字上执行IO操作的时候,要避免的一个重要的问题是在进行重叠IO操作的同时,
强行释放一个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket,任何尚未进行的重叠IO操作都会完成。
一旦所有套接字句柄都已经关闭,便需在完成端口上终止所有的工作者者线程的运行,PostQueuedCompletionStatus. -
2006-11-27
8.2.3WSAEventSelect - [winsock]
8.2.3WSAEventSelect
应用程序在一个或者多个套接字上,接收以事件为基础的网络事件通知。网络事件投递至一个事件对象句柄。
事件通知
WSAVENT WSACreateEvent(void);
BOOL WSAResetEvent(WSAEVENT hEvent);
BOOL WSACloseEvent(WSAEVENT hEvent);
int WSAEventSelect(SOCKET S, WSAEVENT hEventObject, long lNetworkEvents);DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);
Index = WSAWaitForMultipleEvents();
MyEvent = EventArray[index - WSA_WAIT_EVENT_0];
int WSAEnumNetworkEvents(SOCKET s, WSAEVET hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);SOCKET Socket[WSA_MAXIMUM_MAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_MAIT_EVENTS];
SOCKET Accept, Listen;
DWORD EventTotal = 0;
DWORD Index;//Set up a TCP socket for listening on port 5150;
Listen = socket(PF_INET, SOCKET_STREAM, 0);InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5105);bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));
NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent, FD_ACCEP|FD_CLOSE);
listen(Listen, 5);
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;while(TRUE)
{
//Wait for network events on all sockets
Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(SocketArray[Index - WSAWAIT_EVENT_0],EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents);
//check for FD_ACCEPT messages
if(NetWorkEvents.lNetworkEvents&FD_ACCEPT)
{
if(NetworkEvent.iErrorCode[FD_ACCEPT_BIT]!=0)
{
printf("FD_ACCEPT failed with error%d\n", NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]);
break;
}
//Accept a new connection and it to the socket and event lists
Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL, NULL);
//We can not process more than WSA_MAXIMUM_WAIT_EVENTS sockets, so close the accepted socket
if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf("Too many connecttions");
closesocket(Accept);
break;
}
NewEvent = WSACreateEvent();
WSAEventSelect(Accept, NewEvent, FD_READ|FD_WRITE|FD_CLOSE);
Event[EventTotal] = NewEvent;
Socket[EventTotal] = Accept;
EventTotal++;
printf("socket &d connected\n",ACCept);
}//Process FD_READ notification
if(NewWorkEvent.iErrorCode[FD_READ_BIT]!0)
{
if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0)
{
printf("FD_READ failed with error %dn", NetworkEvent.iErrorCode[FD_READ_BIT]);
break;
}//Read data from socket
recv(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
}//Process FD_WRITE notification
if(NetworkEvents.lNetworkEvents&FD_WRITE)
{
if(NetworkEvent.iErrorCode[FD_WRITE_BIT]!=0)
{
printf("FD_WRITE failed with error %d\n",
NetworkEvents.iErrorCode[FD_WRITE_BIT]);
break;
}
send(Socket[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer),0);
}
if(NetworkEvents.lNetworkEvents&FD_CLOSE)
{
if(NetworkEvent.iErrorCode[FD_CLOSE_BIT]!=0)
{
printf("FD_CLOSE failed with error &d\n",
NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
break;
}
}
closesocket(Socket[index - WSA_WAIT_EVET_0]);
//Remove socket and associated event from the socket and evnet array and decrement eventtatal
CompressArrays(Event, socket,&EventTotal);
}
} -
2006-11-26
第8章 WinsockIO方法 - [winsock]
第8章 WinsockIO方法
套接字模式:锁定和非锁定
8.1套接字模式
生产者-消费者
8.1.2非锁定模式
SOCKET s;
unsigned long ul = 1;
int nRet;s = socket(AF_INET, SOCK_STREAM, 0);
nRet = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
if(nRet == SOCKET_ERROR)
{
....
}8.2.1 select模型
利用这个函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。
int select(int nfds,
fd_set FAR* readfds,
fd_set FAR* writefds,
fd_set FAR* exceptfds,
const struct timeval FAR* timeout);FD_CLR(s,*set);从set中删除套接字s。
FD_ISSET(s,*set);检查s是否在set中。
FD_SET(s,*set);将s加入集合set。
FD_ZERO(*set);将set初始化。SOCKET s;
fd_set fdread;
int ret;//create a socket and accept a connection
//Manage I/O on the socket
while(1)
{
FD_ZERO(&fdread);
FD_SET(s,&fdread);
if((ret = select(0, &fdread, NULL, NULL, NULL)== SOCKET_ERROR)
{ //ERROR }
if(ret >0)
{
if(FD_ISSER(s, &fdread))
{
//a read event has occurred on socket s.
}
}
} -
2006-11-24
第7章 Winsock基础 - [winsock]
第7章 Winsock基础
7.1Winsock的初始化
int WSAStartup(WORD wVersionRequested, LPWSASATA lpWSData);
7.2错误检查和控制
int WSAGetLastError(void);
7.3面向连接的协议
7.3.1服务器API函数
1.bind 将指定的套接字同一个已经知道的地址绑定在一起。
2.listen(SOCKET s, int backlog);
3.accept和WSAAccept(SOCKET s, struct socket FAR*addr, int FAR* addrlen);
addr 返回客户机的ip地址信息
accept返回一个新的套接字,它对应于已经接受的那个客户机连接,对于该客户机后续的所有操作,都使用这个新套接字。
原来的套接字仍然用于接受客户机连接而处于监听模式。int CALLBACK ConditionFunc(
LPWSABUF lpCallerId,
LPWSABUF lpCallerData,
LPQQS lpSOOS,
LPQOS lpGOOS,
LPWSABUF lpCalleeId,
LPWSABUF lpCalleeData,
GROUP FAR*g,
DWORD dwCallbackData);lpCallerId: 指定建立连接的协议 包含建立连接的那个客户机的IP地址。
lpCallerData:包含了随连接一道,由客户机发出的任何连接数据。
lpSQOS,lpGQOS:对客户机请求的任何一个服务质量参数进行指定。
lpCalleeId:结构中包含与客户机需要与之连接的本地地址。
服务器将传递给条件的参数处理完之后,必须指出CF_ACCEPT, CF_REJECT , CF_DEFER.7.3.2客户机API函数
1)创建套接字
2)解析服务器名字(以基层协议为准)
3)connect WSAConnect初始化一个链接TCP状态
每个套接字的初始化都是closed,如果客户机初始化一个链接就会向服务器发送一个SYN包,同时将客户机的套接字状态设置为SYN_SENT。服务器收到SYNB包后,就会发送一个SYN-ACK包
。作为客户机,需要一个ACK包对它作出反应。此时客户机的套接字就会变成ESTABLISHED状态。如果服务器一直不发送SYN-ACK包,客户机就会超时并返回CLOSED状态。若一个服务器套接字同一个本地接口和端口绑定起来,并在上面监听,那么套接字的状态是LISTEN。客户机试图与之连接时,服务器会收到一个SYN包,并用一个SYN-ACK包做出相应。服务器套接字
变成SYN-RCVD,最后客户机一个ACK包,令服务器套接字变成ESTABLISHED状态。主动关闭套接字closesocket,shutdown时会向对方发送一个FIN包,而且套接字的专题变为FIN-WAIT-1。正常情况下通信对方会回应一个ACK包,套接字的状态变成FIN-WAIT-2。如果对方也关闭了连接,便会发出一个FIN包。我的机器则会相应一个ACK包,并将已方套接字的状态至为TIME-WAIT。
7.3.3数据传输
所有关系到收发数据的缓冲都属于简单哦案的char类型。WideCharToMultiByte
int send(SOCKET S,
const char FAR* buf,
int len,
int flags);
带外数据:对已经建立链接的流套接字上的应用来说,如果需要发送的数据比流上的普通数据重要得多,便可将这些重要的数据标记为到外数据out-of-band OOB.位于连接另一端的应用可通过一个独立的逻辑信道来接收和处理OOB数据。
int recv(SOCKET s, char FAR* buf, int len, int flags);7.3.4流协议
大多面向连接的协议同时也是流式传输协议。不能保证对请求的数据量进行读取或者写入。也就是当send recv时不能保证数据的全部发送和接收。char sendbuff[2048];
int nBytes = 2048;
int nLeft;
int idx;nLeft = nBytes;
idx = 0;
while(nLeft>0)
{
ret = send(s, &sendbuff[idx], nLeft,0);
if(ret==SOCKET_ERROR)
{
//ERROR
}
nLeft -= ret;
idx += ret;
}
7.3.5中断连接
1.shutdown(SOCKET s, int how); SD_REVEIVE SD_SEND SD_BOTH 表示不再接收,发送和收发。
2.closesocket(SOCKET s); 释放套接字描述符相关联的资源,包括丢弃所有等候处理的数据。7.4无连接协议
7.4.1接收端
int recvfrom(SOCKET S, char FAR* buf, int len, int flags, struct socketaddr FAR* from, int FAR* fromlen);
7.4.2发送端
int sendto(SOCKET s, const char FAR* buf, int len, int flags, const struct socketaddr FAR* to, int tolen);7.4.3基于消息的协议
面向连接的协议同时也是流式协议,无连接协议几乎都是基于消息的。7.5其它API函数
1.getpeername() 用于获得通信方的套接字地址信息。
2.getsockname()返回的是指定套接字的本地接口的地址信息。7.6Windows CE







