您的位置:首页 > 理论基础 > 计算机网络

网络编程(1)跨平台的Socket同步阻塞工作模式例子

2014-02-13 22:43 363 查看
        同步阻塞是很经典的一种模式,也常是学习Socket编程的人弄的第一个例子。在这种模式下,Socket设置为阻塞模式,当程序中的Socket没有完成I/O操作时,

进程或线程会进入等待状态,直到操作完成或发生例外中止。

优点在于,占用的资源会很少,一旦返回,肯定有数据。

缺点在于,程序会一直等在那,不能做其他操作。不适合应用于有大量连接的服务器上。
我在实现这个模式时,为了方便对比Windows下与UNIX/Linux下这种基本的Socket的差异,把它们封装了一下。

使下面的例子代码能跨平台使用,并且这样做可以比较清楚的看出不同平台的细节差异。

在Windows下与AIX服务器通讯的效果如下:



一。 首先列下Socket编程主要流程:
Windows下:
服务端
  1、初始化Windows Socket库。
  2、创建Socket。
  3、绑定Socket。
  4、监听。
  5、Accept。
  6、接收、发送数据。
客户端
  1、初始化Windows Socket库。
  2、创建Socket。
  3、连接Socket。
  4、接收、发送数据。
Linux下除了无须初始Socket库外,其它大致一样。

二。封装的公共模块代码部份

common.h :
/*************************************************
Author: xiongchuanliang
Description: 公共模块定义
**************************************************/

#ifndef __COMMON_H__
#define __COMMON_H__

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#if defined(WIN32) || defined(__cplusplus_winrt)
#ifndef _MS_WINDOWS
#define _MS_WINDOWS
#endif // _MS_WINDOWS

#include <Windows.h>

typedef int					socklen_t;
#else
#include <errno.h>
#include <strings.h>

typedef unsigned char       BYTE;
typedef	int					SOCKET;
#define SOCKET_ERROR		-1
#define INVALID_SOCKET		-1
#endif

//Socket所使用的宏定义
#define SERVPORT	3003 /* SOCKET端口号 */
#define MAXDATASIZE 100  /* 每次最大数据传输量 */
#define BACKLOG		10	 /* 最大同时连接请求数 */

//打印错误信息
static void PrintError(const char* useMsg)
{
const size_t MaxLen = 500;
char szMsg[MaxLen]={0};

if(useMsg == NULL)
{
printf("ERROR: NULL POINT!\n");
return;
}

#ifdef _MS_WINDOWS
switch (WSAGetLastError())
{
case WSANOTINITIALISED:
strcpy_s(szMsg,MaxLen,"A successful WSAStartup call must occur before using this function. ");
break;
case WSAENETDOWN:
strcpy_s(szMsg,MaxLen,"The network subsystem has failed. ");
break;
case WSAEACCES:
strcpy_s(szMsg,MaxLen,"The requested address is a broadcast address, \
but the appropriate flag was not set. Call setsockopt \
with the SO_BROADCAST parameter to allow the use of the broadcast address. ");
break;
case WSAEINVAL:
strcpy_s(szMsg,MaxLen,"An unknown flag was specified, or MSG_OOB was specified \
for a socket with SO_OOBINLINE enabled. ");
break;
case WSAEINTR:
strcpy_s(szMsg,MaxLen,"A blocking Windows Sockets 1.1 call was canceled through WSACancelBlockingCall. ");
break;
case WSAEINPROGRESS:
strcpy_s(szMsg,MaxLen,"A blocking Windows Sockets 1.1 call is in progress, \
or the service provider is still processing a callback function. ");
break;
case WSAEFAULT:
strcpy_s(szMsg,MaxLen,"The buf or to parameters are not part of the user\
address space, or the tolen parameter is too small. ");
break;
case WSAENETRESET:
strcpy_s(szMsg,MaxLen,"The connection has been broken due to keep-alive activity \
detecting a failure while the operation was in progress. ");
break;
case WSAENOBUFS:
strcpy_s(szMsg,MaxLen,"No buffer space is available. ");
break;
case WSAENOTCONN:
strcpy_s(szMsg,MaxLen,"The socket is not connected (connection-oriented sockets only). ");
break;
case WSAENOTSOCK:
strcpy_s(szMsg,MaxLen,"The descriptor is not a socket. ");
break;
case WSAEOPNOTSUPP:
strcpy_s(szMsg,MaxLen,"MSG_OOB was specified, but the socket is not stream-style\
such as type SOCK_STREAM, OOB data is not supported in \
the communication domain associated with this socket, or the socket is \
unidirectional and supports only receive operations. ");
break;
case WSAESHUTDOWN:
strcpy_s(szMsg,MaxLen,"The socket has been shut down; it is not possible to sendto on a socket \
after shutdown has been invoked with how set to SD_SEND or SD_BOTH. ");
break;
case WSAEWOULDBLOCK:
strcpy_s(szMsg,MaxLen,"The socket is marked as nonblocking and the requested operation would block. ");
break;
case WSAEMSGSIZE:
strcpy_s(szMsg,MaxLen,"The socket is message oriented, and the message is larger than the maximum \
supported by the underlying transport. ");
break;
case WSAEHOSTUNREACH:
strcpy_s(szMsg,MaxLen,"The remote host cannot be reached from this host at this time. ");
break;
case WSAECONNABORTED:
strcpy_s(szMsg,MaxLen,"The virtual circuit was terminated due to a time-out or other failure. \
The application should close the socket as it is no longer usable. ");
break;
case WSAECONNRESET:
strcpy_s(szMsg,MaxLen,"The virtual circuit was reset by the remote side executing a hard or \
abortive close. For UPD sockets, the remote host was unable to deliver \
a previously sent UDP datagram and responded with a \"Port Unreachable\" ICMP packet. \
The application should close the socket as it is no longer usable. ");
break;
case WSAEADDRNOTAVAIL:
strcpy_s(szMsg,MaxLen,"The remote address is not a valid address, for example, ADDR_ANY. ");
break;
case WSAEAFNOSUPPORT:
strcpy_s(szMsg,MaxLen,"Addresses in the specified family cannot be used with this socket. ");
break;
case WSAEDESTADDRREQ:
strcpy_s(szMsg,MaxLen,"A destination address is required. ");
break;
case WSAENETUNREACH:
strcpy_s(szMsg,MaxLen,"The network cannot be reached from this host at this time. ");
break;
case WSAETIMEDOUT:
strcpy_s(szMsg,MaxLen,"The connection has been dropped, because of a network failure\
or because the system on the other end went down without notice. ");
break;
default:
strcpy_s(szMsg,MaxLen,"Unknown socket error. ");
break;
}
printf("ERROR: %s\n %s\n", useMsg,szMsg);
#else
perror(useMsg);
#endif
}

#endif // __COMMON_H__


iosocket.h:
/*************************************************
Author: xiongchuanliang
Description: Socket库的初始化及清理
Windows除定义头文件外还需要WSAStartup及WSACleanup套接字库
UNIX/Linux只需定义好头文件即可。
CInitSock类的做法参考自<<Windows-网络与通信程序设计>>
**************************************************/

#ifndef __INITSOCK_H__
#define __INITSOCK_H__

#include <stdio.h>
#include <string.h>

#if defined(WIN32) || defined(__cplusplus_winrt)
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
//#include <Windows.h>
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h> //同步非阻塞模型

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socketvar.h>

#include <sys/select.h>	//异步阻塞 select
#include <sys/time.h>
#include <sys/stat.h>

#endif
//#include <Windows.h>
#include "common.h"

class CInitSock
{
public:
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
{
#ifdef _MS_WINDOWS
//初始化WS2_32.dll
WSADATA wsaData;
WORD sockVersion = MAKEWORD(minorVer, majorVer);
if(WSAStartup(sockVersion, &wsaData) != 0)
{
printf("WSAStartup() failed.\n");
exit(EXIT_FAILURE);
}
#endif
}
~CInitSock()
{
#ifdef _MS_WINDOWS
//解除与Socket库的绑定并且释放Socket库所占用的系统资源
if(WSACleanup( )== SOCKET_ERROR)
{
printf("WSACleanup() failed.\n");
}
#endif
}
};
#endif // __INITSOCK_H__

三。同步阻塞工作模式的实现代码

服务端代码:
/*************************************************
Author: xiongchuanliang
Description: 同步阻塞工作模式例子_服务端代码
编译命令:
Linux:
g++ -o tcpserver tcpserver.cpp -m64 -I./common
AIX:
g++ -maix64 -o tcpserver tcpserver.cpp -I../common/
**************************************************/

// 服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "initsock.h"
#include "common.h"

CInitSock initSock;

int main(int argc, char* argv[])
{
//创建套接字
SOCKET sListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sListen == INVALID_SOCKET)
{
PrintError("socket() failed.\n");
exit(EXIT_FAILURE);
}

//绑定本地IP和端口到套接字
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT); //大于1024且小于65535
#ifdef _MS_WINDOWS
server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
#else
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero),8);
#endif

//接收数据的超时时间
//#ifdef _MS_WINDOWS
//    int timeout=1000;  //1s
//	setsockopt(sListen,SOL_SOCKET, SO_REUSEADDR, (const char*)&timeout,sizeof(timeout));
//#else
//	struct timeval timeout ={1,0}; //1S SO_RCVTIMEO
//	setsockopt(sListen,SOL_SOCKET, SO_REUSEADDR, (const char*)&timeout,sizeof(timeout));
//#endif

//SO_REUSEADDR : 使bind函数能允许地址立即重用
//当一个socket绑定了一个端口,此socket正常关闭或程序异常退出后
//的一段时间内,此端口会依然维持原来的绑定状态,其他程序无法绑定
//此端口,此选项可以防止出现这种情况,即ddress already in use错误。
int on = 1;
setsockopt( sListen, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on) );

if(bind(sListen,(struct sockaddr *)&server_addr,sizeof(struct sockaddr)) == SOCKET_ERROR)
{
PrintError("bind() failed.");
exit(EXIT_FAILURE);
}

//开始监听
// listen(套接字,监听队列中允许保持的尚未处理的最大连接数量)
// listen仅用在支持连接的套接字上,如SOCK_STREAM类型的套接字
// 如果连接数超过BACKLOG,客户端将收到WSAECONNREFUSED错误
if(listen(sListen, BACKLOG) == SOCKET_ERROR)
{
PrintError("sListen() failed.");
exit(EXIT_FAILURE);
}

//循环接收数据
SOCKET sClient;
struct sockaddr_in remoteAddr;
socklen_t nAddrlen = sizeof(struct sockaddr_in); //sizeof(remoteAddr);

char recvData[MAXDATASIZE]={0};
while(true)
{
printf("等待客户端连接中...\n");
sClient = accept(sListen,(struct sockaddr *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
PrintError("accept() failed.");
continue;
}
printf("接收到一个客户端连接:%s \n",inet_ntoa(remoteAddr.sin_addr));

//接收数据
int recvbytes = recv(sClient, recvData, MAXDATASIZE, 0);
if( recvbytes == 0)
{
printf("recv() no data!\n");
}else if( recvbytes < 0)
{
PrintError("recv() failed");
}else if(recvbytes > 0)
{
recvData[recvbytes]='\0';
printf("收到信息:%s\n",recvData);
}

//发送数据到客户端
char * sendData = "客户端,你好啊!\n";
send(sClient, sendData, strlen(sendData), 0);

//关闭套接字
#ifdef _MS_WINDOWS
closesocket(sClient);
#else
close(sClient);
#endif

if(recvbytes > 0) //表示recv()有接收到数据
{
//当信息为quit或exit时,退出监听,结束服务
if( strcmp(recvData,"quit") == 0 || strcmp(recvData,"exit") == 0)
{
printf("退出监听,结束服务\n");
break;
}
}
//重新清空缓冲区
memset(recvData,0,sizeof(recvData));
}
//关闭监听套接字
#ifdef _MS_WINDOWS
closesocket(sListen);
#else
close(sListen);
#endif
exit(EXIT_SUCCESS);
}

客户端代码:

/*************************************************
Author: xiongchuanliang
Description: 同步阻塞工作模式例子_客户端代码
编译命令:
Linux:
g++ -o tcpclient tcpclient.cpp -m64 -I./common
AIX:
g++ -maix64 -o tcpserver tcpserver.cpp -I../common/

./tcpclient 127.0.0.1 ssss
**************************************************/

// 客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "initsock.h"
#include "common.h"

//指定要连接的服务器ip
#define SERVIP	"192.16.8.206"  //"127.0.0.1"

CInitSock initSock;

int main(int argc, char* argv[])
{
//取出传入参数
const size_t MaxLen = 500;
char  testMsg[MaxLen]={0};
if(argc < 2)
{
printf("需要带上发送给服务端的信息,再运行。\n");
exit(EXIT_FAILURE);
}else{
#ifdef _MS_WINDOWS
strcpy_s(testMsg,MaxLen,argv[1]);
#else
strncpy(testMsg,argv[1],strlen(argv[1]));
#endif
printf("将发送的信息: %s \n",testMsg);
}

//建立套接字
SOCKET sclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
PrintError("invalid() failed");
exit(EXIT_FAILURE);
}

//指定要连接的服务器地址和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVPORT);
#ifdef _MS_WINDOWS
server_addr.sin_addr.S_un.S_addr = inet_addr(SERVIP);
#else
server_addr.sin_addr.s_addr =inet_addr(SERVIP);
memset(&(server_addr.sin_zero),0,8);
#endif
//将套接字连接上服务器
if( connect(sclient,(struct sockaddr *)&server_addr,sizeof(struct sockaddr) ) == SOCKET_ERROR)
{
PrintError("connect() failed");
exit(EXIT_FAILURE);
}

//发送数据到服务端
send(sclient,testMsg,strlen(testMsg),0);

//接收返回的数据
char recvData[MAXDATASIZE] = {0};
int recvbytes = recv(sclient,recvData,MAXDATASIZE,0);
if( recvbytes == 0)
{
printf("recv() no data!\n");
}else if( recvbytes < 0)
{
PrintError("recv() failed");
exit(EXIT_FAILURE);
}else if( recvbytes > 0)
{
recvData[recvbytes]='\0';
printf("服务端返回的信息:%s\n",recvData);
}

//关闭套接字,结束此次TCP会话
#ifdef _MS_WINDOWS
closesocket(sclient);
#else
close(sclient);
#endif
exit(EXIT_SUCCESS);
}


四。总结
在这个简单的例子中,对比看Windows与UNIX/Linux,其实会发现两者差别真不大。
最主要的差别是下面两个,蛮有意思的。

#ifdef _MS_WINDOWS
server_addr.sin_addr.S_un.S_addr = inet_addr(SERVIP);
#else
server_addr.sin_addr.s_addr =inet_addr(SERVIP);
memset(&(server_addr.sin_zero),0,8);
#endif

#ifdef _MS_WINDOWS
closesocket(sclient);
#else
close(sclient);
#endif


补充一个小问题:

当初始化Windows Socket库时,一定要把Windows.h头文件声明放在后面,否则会报错.原因在于新老版本头文件间,如果反了,会重复定义然后哗哗的报错地。

       须按下面的方式声明:

#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <Windows.h>

MAIL: xcl_168@aliyun.com

BLOG: http://blog.csdn.net/xcl168
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息