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

        同步阻塞是很经典的一种模式,也常是学习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



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
跨平台socket库是一种能在不同操作系统上实现网络通信的库。在计算机网络中,socket是一种抽象层,用于实现不同计算机之间的通信。它在不同操作系统上的实现可能有所不同,因此跨平台socket库的目的就是提供一种统一的接口,使开发者能够在不同操作系统上方便地实现网络通信。 跨平台socket库通常会提供一组函数和数据结构,用于创建、连接、发送和接收数据,以及关闭网络连接。它封装了不同操作系统下底层的socket API,通过提供一致的接口,使得开发者能够在不同的操作系统上编写通用的网络代码。这样一来,开发者无需关心不同操作系统之间的差异,只需使用跨平台socket库提供的统一接口即可。 跨平台socket库的实现通常需要考虑不同操作系统下的网络协议差异、数据类型转换、编码转换等问题。为了提高效率和可靠性,一些跨平台socket库还会采用异步IO、多线程或多进程等方式来处理并发连接和请求。 使用跨平台socket库的好处在于它能够简化网络编程的复杂性。开发者只需学习一种简单而一致的接口,就能够实现跨平台的网络通信。这减少了编写和维护不同操作系统下的网络代码的工作量,提高了开发效率。另外,跨平台socket库还能够使应用程序更易于移植和扩展,因为它可以在不同的操作系统和平台上工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值