网络编程(1)跨平台的Socket同步阻塞工作模式例子
2014-02-13 22:43
363 查看
同步阻塞是很经典的一种模式,也常是学习Socket编程的人弄的第一个例子。在这种模式下,Socket设置为阻塞模式,当程序中的Socket没有完成I/O操作时,
进程或线程会进入等待状态,直到操作完成或发生例外中止。
优点在于,占用的资源会很少,一旦返回,肯定有数据。
缺点在于,程序会一直等在那,不能做其他操作。不适合应用于有大量连接的服务器上。
我在实现这个模式时,为了方便对比Windows下与UNIX/Linux下这种基本的Socket的差异,把它们封装了一下。
使下面的例子代码能跨平台使用,并且这样做可以比较清楚的看出不同平台的细节差异。
在Windows下与AIX服务器通讯的效果如下:
![](https://img-blog.csdn.net/20140213223238625?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGNsMTY4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一。 首先列下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 :
iosocket.h:
三。同步阻塞工作模式的实现代码
服务端代码:
客户端代码:
四。总结
在这个简单的例子中,对比看Windows与UNIX/Linux,其实会发现两者差别真不大。
最主要的差别是下面两个,蛮有意思的。
补充一个小问题:
当初始化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
进程或线程会进入等待状态,直到操作完成或发生例外中止。
优点在于,占用的资源会很少,一旦返回,肯定有数据。
缺点在于,程序会一直等在那,不能做其他操作。不适合应用于有大量连接的服务器上。
我在实现这个模式时,为了方便对比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
相关文章推荐
- 网络编程(2)Socket同步非阻塞工作模式
- Unix网络编程之socket阻塞与非阻塞模式下函数调用结果分析
- 《聊聊Socket、TCP/IP、HTTP、FTP及网络编程》 / 《聊聊同步、异步、阻塞与非阻塞》
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- Linux socket 阻塞与非阻塞,同步与异步、I/O模型
- Linux下同步模式、异步模式、阻塞调用、非阻塞调用
- IO模式——同步(阻塞、非阻塞)、异步
- socket阻塞与非阻塞,同步与异步、I/O模型
- socket阻塞与非阻塞,同步与异步、I/O模型
- socket阻塞与非阻塞,同步与异步,select,pool,epool
- Linux编程的socket阻塞与非阻塞,同步与异步、I/O模型
- socket阻塞与非阻塞,同步与异步、I/O模型
- 关于同步、异步、阻塞、非阻塞一个通俗易懂的例子
- socket阻塞与非阻塞,同步与异步
- Linux下同步模式、异步模式、阻塞调用、非阻塞调用总结 .
- I/O模式 同步、异步、阻塞、非阻塞
- socket阻塞与非阻塞,同步与异步、I/O模型
- Windows网络编程之(一)Socket阻塞模式(TCP和UDP)
- socket阻塞与非阻塞,同步与异步、I/O模型
- socket编程的同步、异步与阻塞、非阻塞示例详解之二