场景:
1. 当使用socket通讯时,客户端或者服务端需要1对多的情况,为了维护多个连接,简单的blocking模式是
满足不了要求的,所以这时候需要简单的select I/O 模型基本能解决问题,因为它的recv和send并不是
立即返回的,所以它其实还是属于blocking模式.
非阻塞模式需要调用:
nRet = ioctlsocket(s, FIONBIO, (unsigned long *) &ul);
2. 阻塞模式的编码其实是有挂起程序的风险的,因为read,send都有可能让程序得不到及时的响应导致
丢失数据.
3. socket模式,I/O模型这几种要研究明白,适用范围是个很花时间的事情.
介绍:
1. socket mode(或者说socket operating mode)里有两种模式,一种是blocking mode,一种是non-blocking mode.
而这两种模式各自分为了好几种I/O model(模型).对winsock来说就有6种I/O模型,所以不要搞混了模式(mode)和
模型(model).
6中I/O模型: blocking, select, WSAAsyncSelect, WSAEventSelect, overlapped, and completion port
2. Berkeley套接字接口(Berkeley socket),winsock1.1向后兼容Berkeley socket的,所以移植到其他平台也比较方便.
参考阅读: http://zh.wikipedia.org/zh-cn/Berkeley_sockets.
The select model was incorporated into Winsock 1.1 to allow applications that want to avoid blocking on socket calls the capability to manage multiple sockets in an organized manner.
中文: select模型并入Winsock 1.1就是为了允许应用程序想回避socket阻塞,它提供了一种有组织的方式管理多个socket连接.
socket_select_client.cpp
// socket_select_client.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <winsock2.h>
#include <stdint.h>
#include <windows.h>
#include <iostream>
using namespace std;
SOCKET GetNewSocket()
{
SOCKET server_socket;
fd_set fdread,fdwrite;
int ret;
// Create a socket, and accept a connection
server_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(server_socket == INVALID_SOCKET )
{
return INVALID_SOCKET;
}
return server_socket;
}
int ConnectToDevice(SOCKET server_socket,uint16_t port)
{
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(port);
int r = ::connect( server_socket, (sockaddr*)&server, sizeof(server) );
if ( r == SOCKET_ERROR )
{
DWORD dwError = ::WSAGetLastError();
cout << "connect error" << endl;
if ( dwError != WSAEWOULDBLOCK )
{
cout << "dwError != WSAEWOULDBLOCK" << endl;
closesocket(server_socket);
return INVALID_SOCKET;
}
}
cout << "connect" << endl;
return 0;
}
DWORD WINAPI SendThread(PVOID pvParam)
{
cout << "SendThread" << endl;
SOCKET s = *(SOCKET*)pvParam;
const int kLen = 1024;
char buf[kLen];
int i = 1;
while(true)
{
cout << "i: " << i << endl;
memset(buf,i,kLen);
send(s,buf,kLen,0);
Sleep(2000);
++i;
}
return 0;
}
void StartClient()
{
SOCKET s = GetNewSocket();
ConnectToDevice(s,8083);
//1.启动发送线程.
DWORD dwThreadID;
HANDLE hThread = CreateThread(NULL,0,SendThread,(PVOID)&s,0,&dwThreadID);
CloseHandle(hThread);
fd_set fdread;
char buffer[1024];
while(true)
{
FD_ZERO(&fdread);
FD_SET(s, &fdread);
TIMEVAL tv = {1,0};
int ret = select(0, &fdread, NULL, NULL, &tv);
for(int i = 0; i< ret; ++i)
{
int readed = 0;
//1.同server
readed = recv(s,buffer,1024,0);
if(readed == SOCKET_ERROR)
{
int code = WSAGetLastError();
cout << "code: " << code << endl;
//1.socket已经断开
break;
}else if(readed == 0)
{
// If the connection has been gracefully closed,
// the return value is zero.
break;
}else
{
//1.读取数据
cout << "readed: " << readed << endl;
}
}
}
closesocket(s);
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
StartClient();
WSACleanup();
return 0;
}
socket_select_server.cpp
// test_socket_select.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <winsock2.h>
#include <windows.h>
#include <iostream>
using namespace std;
void StartServer()
{
fd_set fdread,fdwrite;
SOCKET server_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
int value = 8083;
server.sin_port = htons(value);
int ret = bind(server_socket, (sockaddr*)&server, sizeof(server));
cout << "binding port..." << value << endl;
if(ret == SOCKET_ERROR)
{
int code = WSAGetLastError();
cout << "error binding code: " << code << endl;
closesocket(server_socket);
return;
}
ret = listen(server_socket, 4);
int nSize = sizeof(server);
NewConn:
SOCKET client_conn = accept(server_socket,( sockaddr*) &server, &nSize);
char buf[64];
int buf_size = 64;
getsockopt(client_conn, SOL_SOCKET,SO_MAX_MSG_SIZE,buf,&buf_size);
if( client_conn == INVALID_SOCKET )
{
closesocket(server_socket);
return;
}
cout << "accept" << endl;
const int kLen = 512;
char buffer[kLen];
ZeroMemory(buffer, kLen);
while(true)
{
FD_ZERO(&fdread);
//1.加入fdread,如果有多个连接,也是这种方式加入fdread.
//2.获取客户端连接其实是需要一个独立线程去监听的获取并加入队列的.
FD_SET(client_conn, &fdread);
//1.The select function returns the total number of socket handles
// that are ready and contained in the fd_set structures,
// zero if the time limit expired, or SOCKET_ERROR if an error occurred
if ((ret = select(0, &fdread, NULL, NULL, NULL)) == SOCKET_ERROR)
{
closesocket(client_conn);
goto NewConn;
}
for(int i = 0; i <ret; ++i)
{
//1.这里其实应该是根据i遍历fdread的,比如conns[i],而不是client_conn.
//1.可以用vector来存储SOCKET.有时间的童鞋自己写吧.
//1.Nonzero if s is a member of the set. Otherwise, zero.
if (FD_ISSET(client_conn, &fdread))
{
cout << "get fdread" << endl;
int readed = 0;
readed = recv(client_conn,buffer,kLen,0);
if(readed == SOCKET_ERROR)
{
int code = WSAGetLastError();
cout << "code: " << code << endl;
//1.socket已经断开
closesocket(client_conn);
goto NewConn;
}else if(readed == 0)
{
// If the connection has been gracefully closed,
// the return value is zero.
closesocket(client_conn);
goto NewConn;
}else
{
//1.读取数据
cout << "readed: " << readed << " :" << (int)buffer[0] << endl;
//1.发数据
send(client_conn,buffer,readed,0);
}
}
}
}
closesocket(server_socket);
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
StartServer();
WSACleanup();
return 0;
}
输出:
项目下载地址:
http://download.csdn.net/detail/infoworld/8456649
参考:
1. 《Network Programming for Microsoft Windows 2nd》
2. MSDN