TCP/IP协议的套接字类型及异步winsock编程例子
流套接字(SOCK_STREAM):流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。
数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
原始套接字(SOCK_RAW):原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。
表12. 3 Linux 支持的套接字地址族
套接字地址族 | 描述 |
UNIX | UNIX 域套接字 |
INET | 通过 TCP/IP 协议支持的 Internet 地址族 |
AX25 | Amater radio X25 |
APPLETALK | Appletalk DDP |
IPX | Novell IPX |
X25 | X25 |
表12.4 Linux 所支持的 BSD 套接字类型
BSD 套接字类型 | 描述 |
流(stream) | 这种套接字提供了可靠的双向顺序数据流,可保证数据不会在传输过程中丢失、破坏或重复出现。流套接字通过 INET 地址族的 TCP 协议实现。 |
数据报(datagram) | 这种套接字也提供双向的数据传输,但是并不对数据的传输提供担保,也就是说,数据可能会以错误的顺序传递,甚至丢失或破坏。这种类型的套接字通过 INET 地址族的 UDP 协议实现。 |
原始(raw) | 利用这种类型的套接字,进程可以直接访问底层协议(因此称为原始)。例如,可在某个以太网设备上打开原始套接字,然后获取原始的 IP 数据传输信息。 |
可靠发送的消息 | 和数据报套接字类似,但保证数据被正确传输到目的端。 |
顺序数据包 | 和流套接字类似,但数据包大小是固定的。 |
数据包(packet) | 这并不是标准的 BSD 套接字类型,它是 Linux 专有的 BSD 套接字扩展,可允许进程直接在设备级访问数据包。 |
例子1:(winsock异步编程)
//这是一个winsock异步编程的例子
//这个服务监听1000端口,可以使用 telnet localhost 1000进行测试,最大允许20个连接
#include <stdio.h>
#include <Winsock2.h>
#pragma comment(lib, "WS2_32.LIB")
#define MAX_SOCKETS 20
#define MAX_BACKLOG 1
#define PORT 1000
SOCKET Startlisten(int port)
{
WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
return 0;
}
SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
return 0;
}
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port= htons(port); //保证字节顺序
addr.sin_addr.s_addr= htonl(INADDR_ANY);
nResult=bind(sock,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
return 0;
}
nResult=listen(sock,MAX_BACKLOG); //最多 MAX_BACKLOG 个 Pending 连接
if(nResult==SOCKET_ERROR)
{
return 0;
}
printf("Please try: telnet localhost 1000/n");
return sock;
}
int main()
{
//init winsock env
WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
//create the first event
WSAEVENT eventList[MAX_SOCKETS+1];
int eventIndex=0;
eventList[eventIndex]=WSACreateEvent();
//start listen on 1000
SOCKET sockList[MAX_SOCKETS+1];
sockList[eventIndex]=Startlisten(PORT);
//use WSAEventSelect to set the server to running on async mode
int rc = WSAEventSelect(sockList[eventIndex], eventList[eventIndex], FD_READ|FD_WRITE|FD_ACCEPT|FD_CLOSE|FD_CONNECT);
int index;
sockaddr_in client;
//waiting on the events to deal with connect requests or network read event
while( (index=WSAWaitForMultipleEvents(eventIndex+1,eventList,false,WSA_INFINITE,true)) != WSA_WAIT_FAILED)
{
index -= WSA_WAIT_EVENT_0;
WSANETWORKEVENTS type;
WSAEnumNetworkEvents(sockList[index],eventList[index],&type);
switch(type.lNetworkEvents)
{
case FD_ACCEPT:
{
int len=sizeof(sockaddr);
if (eventIndex < MAX_SOCKETS)
{
++eventIndex;
sockList[eventIndex] =accept(sockList[index],(sockaddr*)&client,&len);
eventList[eventIndex]=WSACreateEvent();
rc = WSAEventSelect(sockList[eventIndex], eventList[eventIndex], FD_READ|FD_WRITE|FD_CLOSE|FD_CONNECT);
printf("connected from %s:%d/n",inet_ntoa( client.sin_addr ),client.sin_port);
}
}
break;
case FD_READ:
{
char mess;
rc =recv(sockList[index],&mess,1,0);
//ctrl+c == 3
if (mess == 3 || rc ==SOCKET_ERROR)
{
shutdown(sockList[index],SD_SEND);
break;
}
rc=send(sockList[index],&mess,sizeof(mess),0);
printf("%c",mess);
}
break;
case FD_WRITE:
{
char buf[256];
sprintf(buf,"hello,you are the %d client./n",eventIndex);
rc=send(sockList[index],buf,strlen(buf),0);
}
break;
case FD_CLOSE:
{
int len=sizeof(sockaddr);
getpeername(sockList[index],(sockaddr*)&client,&len);
printf("Closed from %s:%d/n",inet_ntoa( client.sin_addr ),client.sin_port);
closesocket(sockList[index]);
}
break;
}
}
return 0;
}
例子2:
//
// WSAEventSelect文件
include "initsock.h"
#include <stdio.h>
#include <iostream.h>
#include <windows.h>
// 初始化Winsock库
CInitSock theSock;
int main()
{
// 事件句柄和套节字句柄表
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;
USHORT nPort = 4567; // 此服务器监听的端口号
// 创建监听套节字
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(nPort);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf(" Failed bind() /n");
return -1;
}
::listen(sListen, 5);
// 创建事件对象,并关联到新的套节字
WSAEVENT event = ::WSACreateEvent();
::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);
// 添加到表中
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = sListen;
nEventTotal ;
// 处理网络事件
while(TRUE)
{
// 在所有事件对象上等待
int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
// 对每个事件调用WSAWaitForMultipleEvents函数,以便确定它的状态
nIndex = nIndex - WSA_WAIT_EVENT_0;
for(int i=nIndex; i<nEventTotal; i )
{
nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
{
continue;
}
else
{
// 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件
WSANETWORKEVENTS event;
::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);
if(event.lNetworkEvents & FD_ACCEPT) // 处理FD_ACCEPT通知消息
{
if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf(" Too many connections! /n");
continue;
}
SOCKET sNew = ::accept(sockArray[i], NULL, NULL);
WSAEVENT event = ::WSACreateEvent();
::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);
// 添加到表中
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = sNew;
nEventTotal ;
}
}
else if(event.lNetworkEvents & FD_READ) // 处理FD_READ通知消息
{
if(event.iErrorCode[FD_READ_BIT] == 0)
{
char szText[256];
int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
if(nRecv > 0)
{
szText[nRecv] = '/0';
printf("接收到数据:%s /n", szText);
}
}
}
else if(event.lNetworkEvents & FD_CLOSE) // 处理FD_CLOSE通知消息
{
if(event.iErrorCode[FD_CLOSE_BIT] == 0)
{
::closesocket(sockArray[i]);
for(int j=i; j<nEventTotal-1; j )
{
sockArray[j] = sockArray[j 1];
sockArray[j] = sockArray[j 1];
}
nEventTotal--;
}
}
else if(event.lNetworkEvents & FD_WRITE) // 处理FD_WRITE通知消息
{
}
}
}
}
return 0;
}
以上特殊颜色的是不懂的地方.
【1】第一个 [int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);]
中nIndex返回的是什么呀,是像FD_READ 这样的吗
【2】那为什么还要 [nIndex = nIndex - WSA_WAIT_EVENT_0;]呀,这是做什么的.为什么要减WSA_WAIT_EVENT_0
【3】减过之后为什么还要 [for(int i=nIndex; i <nEventTotal; i )]循环,循环的时候为什么不是从i=0开始呀.而是从i=nIndex开始
网友回复:#include "initsock.h"
中就包含了
::WSAStartup
::WSACleanup()
所以不必考虑这些了
网友回复:1、返回值是从WSA_WAIT_EVENT_0 到 (WSA_WAIT_EVENT_0 cEvents - 1),表示WSAWaitForMultipleEvents()函数在等待的某一个事件已经触发
2、nIndex = nIndex - WSA_WAIT_EVENT_0是因为获得的索引要去访问eventArray[WSA_MAXIMUM_WAIT_EVENTS]数组,当然得从0开始
3、访问数组当然从0开始索引
网友回复:这个是因为WSAWaitForMultipleEvents返回的时候有可能是多个event被signal了
而返回的是第一个signaled的event的index,这个时候需要详细的检查之后的event。同时对这些signaled的event进行处理.........而nindex之前的event可以确定是没有singaled,可以不考虑............
网友回复:那为什么要【nIndex = nIndex - WSA_WAIT_EVENT_0;】呢,
为什么不直接[nIndex =nIndex 1]这样不就可以检查后面的是否也有事件发生吗
【nIndex = nIndex - WSA_WAIT_EVENT_0;】代表什么,是什么意思呀.
就这点不明白了
网友回复:【1】第一个 [int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);]
中nIndex返回的是什么呀,是像FD_READ 这样的吗
如果等待成功,返回的是大于等于WSA_WAIT_EVENT_0的一个值,表示所等待的事件中哪一个触发了。
【2】那为什么还要 [nIndex = nIndex - WSA_WAIT_EVENT_0;]呀,这是做什么的.为什么要减WSA_WAIT_EVENT_0
减去WSA_WAIT_EVENT_0,等到的就是触发的事件在eventArray数组中的下标。
【3】减过之后为什么还要 [for(int i=nIndex; i <nEventTotal; i )]循环,循环的时候为什么不是从i=0开始呀.而是从i=nIndex开始
从这开始循环,目的是检查还有哪些事件也触发了,因为WSAWaitForMultipleEvents的返回值是数组中第1个(按下标)触发的事件。
网友回复:nIndex是表示eventArray中的第几个事件被等到,与FD-READ无关了,然后通过调用WSAEnumNetworkEvents获取具体的网络事件
至于第2个nIndex = nIndex - WSA_WAIT_EVENT_0其实我觉得一般无关紧要的,因为通常都是从0开始的数组,更正确的说nIndex - WSA_WAIT_EVENT_0才是第几个事件被等到,也就是eventArray下标
for(int i=nIndex; i <nEventTotal; i )接着下面的事件等呀,总不能等到一个以后又从头开始吧,那后面总没机会了……
网友回复:恩,不太明白,感兴趣话题
网友回复:WSA_WAIT_EVENT_0 是不是0呀
网友回复:是0,但不要管它是多少,该减的时候就减,这是好的编程习惯。
网友回复:我明白了.谢谢各位了
网友回复:结贴:
1.2楼的是不是有些地方说的不对呀
2.3楼的给我的启示最大,也就明白为什么了,所以在4楼我就把我不懂的说了下
3.5,6楼说得不错,只是,我在2楼时已经明白了,也就还有一个就是4楼那个问题了.
4.9楼确定了我的问题