winsock之 TCP/IP的简单编程
2014-04-08 13:36
288 查看
问题记录:
1.写这个代码的时候犯了一个最低级的问题搞错了优先级。++的优先级要大于==的优先级要大于=的优先级,最好用括号来避免这种问题。
2.在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项。一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。这个套接字选项通知内核,如果端口忙,但TCP状态位于TIME_WAIT,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时SO_REUSEADDR选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。
3.服务器开启keepalive功能,并修改了默认的keepalive参数。目的是检测客户端是否还处于连接。
Server.cpp
Client.cpp
1.写这个代码的时候犯了一个最低级的问题搞错了优先级。++的优先级要大于==的优先级要大于=的优先级,最好用括号来避免这种问题。
2.在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项。一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。这个套接字选项通知内核,如果端口忙,但TCP状态位于TIME_WAIT,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时SO_REUSEADDR选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。
3.服务器开启keepalive功能,并修改了默认的keepalive参数。目的是检测客户端是否还处于连接。
Server.cpp
//使用WinSock2库之前,要做准备工作,即调用WSAStartup初始化WinSock库。初始化后,才能使用WinSock2库 //使用完WinSock2库后,要做清理工作,即调用WSACleanup清理 //这个工程的目的是:启动服务器监听客户端的连接请求,如果服务器收到客户端的连接请求,执行accept接受客户端的连接请求返回一个与其客户端通信的socket。然后使用这个socket与其客户端进行通信。 #include <iostream> using namespace std; #include <tchar.h> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib") #include <MSTcpIP.h> #define PORT 8080 //服务器的端口打算采用8080 int main() { SetConsoleTitle(_T("Server.exe")); //初始WinSock2库 WORD wVersionRequest=MAKEWORD(2,2); //副版本2,主版本2 WSADATA wsaData; int ret; ret=WSAStartup(wVersionRequest,&wsaData); if(ret !=0) { cerr<<"初始WinSock2库失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; exit(1); } //验证加载的版本 if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2) { cerr<<"使用的socket库版本不对!"<<endl; WSACleanup(); exit(1); } SOCKET listenSocket; //监听socket //创建一个用于TCP/IPV4的流式套接字 listenSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(listenSocket ==INVALID_SOCKET) { cerr<<"创建sokcet失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; WSACleanup(); exit(1); } //设置sock选项,使其重用指定端口 BOOL bReuseaddr = TRUE; ret=setsockopt(listenSocket,SOL_SOCKET,SO_REUSEADDR,(char*)&bReuseaddr,sizeof(bReuseaddr)); if(ret ==SOCKET_ERROR) { cerr<<"设置socket的SO_REUSEADDR选项失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(listenSocket); WSACleanup(); exit(1); } //设置服务端的套接字的地址,即设置地址族,IP地址和端口号 SOCKADDR_IN serverAddr; int serverAddrLen=sizeof(serverAddr); memset(&serverAddr,0,serverAddrLen); serverAddr.sin_family=AF_INET; serverAddr.sin_addr.s_addr=htonl(INADDR_ANY); serverAddr.sin_port=htons(PORT); //将listenSokect与指定的套接字地址绑定,即占有指定的端口号和IP地址 ret=bind(listenSocket,(sockaddr*)&serverAddr,serverAddrLen); if(ret ==SOCKET_ERROR) { cerr<<"绑定失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(listenSocket); WSACleanup(); exit(1); } //将listenSokect设置为监听状态,即开启服务端的监听状态,用于监听客户端的连接请求 int backlog=5; //等待连接的队列的数目 ret=listen(listenSocket,backlog); if(ret ==SOCKET_ERROR) { cerr<<"启动监听功能失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(listenSocket); WSACleanup(); exit(1); } SOCKET connectSocket; //用于与连接的客户端进行通信的socket SOCKADDR_IN connectAddr; //与其连接的客户端的地址 int connectAddrlen=sizeof(connectAddr); while(1) { // accept 会阻塞进程,直到有客户端连接上来为止 //接受客户端的连接请求,并返回一个与该客户端通信的socket connectSocket=accept(listenSocket,(sockaddr*)&connectAddr,&connectAddrlen); if(connectSocket ==INVALID_SOCKET) { cerr<<"创建与这个客户端通信的socket失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(listenSocket); WSACleanup(); exit(1); } //开启keepalive功能,并修改默认的keepalive //用于心跳包检测 //开启keepalive BOOL bKeepAlive=TRUE; ret=setsockopt(connectSocket,SOL_SOCKET,SO_KEEPALIVE,(char*)&bKeepAlive,sizeof(bKeepAlive)); if(ret ==SOCKET_ERROR) { cerr<<"开启keepalive功能失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(listenSocket); WSACleanup(); exit(1); } //修改keepalive参数 tcp_keepalive aliveIn; aliveIn.onoff=1; aliveIn.keepalivetime=600*1000; //当TCP连接有10分钟没有数据就开始发送数据 aliveIn.keepaliveinterval=1000; //当发送第一个心跳包后,每隔1毫秒发送心跳包,经过若干次的重试,如果心跳包都没有返回,那么就得出结论:TCP连接已经断开) tcp_keepalive aliveOut; unsigned long uiBytesReturn=0; ret=WSAIoctl(listenSocket,SIO_KEEPALIVE_VALS,&aliveIn,sizeof(aliveIn),&aliveOut,sizeof(aliveOut),&uiBytesReturn,NULL,NULL); if(ret ==SOCKET_ERROR) { cerr<<"修改keepalive参数失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(listenSocket); WSACleanup(); exit(1); } //服务端发送一个数据包给客户端,然后等待客户端收到后发送一个数据过来,接受客户端回应的数据 char connectIpAddress[50]; strcpy(connectIpAddress,inet_ntoa(connectAddr.sin_addr)); char sendBuf[100]; sprintf(sendBuf,"欢迎 ip: %s 的用户连接, 这里是 Mransin 的服务器\n",connectIpAddress); int sendDataLen=strlen(sendBuf)+1; send(connectSocket,sendBuf,sendDataLen,0); int recvDataLen; //实际接受到的数据的字节数 char recvBuf[200]; //用于存放接受的数据 memset(recvBuf,0,sizeof(recvBuf)); //先将用于接受数据的缓冲区的所有字节置0 int recvDataMaxLen=sizeof(recvBuf); recvDataLen=recv(connectSocket,recvBuf,recvDataMaxLen,0); if(recvDataLen ==SOCKET_ERROR) { cerr<<"接受数据失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(connectSocket); closesocket(listenSocket); WSACleanup(); exit(1); } cout<<recvBuf<<endl; //输出接受的数据内容 //关闭connetSocket closesocket(connectSocket); } //关闭listenSocket closesocket(listenSocket); //清理WinSock2库 ret=WSACleanup(); if(ret ==SOCKET_ERROR) { cerr<<"清理失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; exit(1); } return 0; }
Client.cpp
//使用WinSock2库之前,要做准备工作,即调用WSAStartup初始化WinSock2库。初始化后,才能使用WinSock2库 //使用完WinSock2库后,要做清理工作,即调用WSACleanup清理 //这个工程的目的:启动客户端,并向指定的服务器发送连接请求,当连接成功后,与服务端进行通信。 #include <iostream> using namespace std; #include <tchar.h> #include <WinSock2.h> #pragma comment (lib, "ws2_32.lib") #define PORT 8080 //服务端采用的端口是8080 int main() { SetConsoleTitle(_T("Client.exe")); //初始WinSock2库 WORD wVersionRequest=MAKEWORD(2,2); WSADATA wsaData; int ret; ret=WSAStartup(wVersionRequest,&wsaData); if(ret !=0) { cerr<<"初始WinSock2库失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; exit(1); } //验证加载的版本 if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2) { cerr<<"socket库版本不对!"<<endl; WSACleanup(); exit(1); } //创建一个TCP/IPV4的流式套接字,用于与服务端通信 SOCKET clientSocket; clientSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(clientSocket ==INVALID_SOCKET) { cerr<<"创建socket失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; WSACleanup(); exit(1); } //设置要连接的服务端的套接字地址,设置目标连接端的ip地址和端口号 SOCKADDR_IN serverAddr; memset(&serverAddr,0,sizeof(serverAddr)); serverAddr.sin_family=AF_INET; serverAddr.sin_port=htons(PORT); serverAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //连接服务端 //连接不成功返回-1 ret=connect(clientSocket,(sockaddr*)&serverAddr,sizeof(serverAddr)); if(ret ==SOCKET_ERROR) { cerr<<"连接服务端失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(clientSocket); WSACleanup(); exit(1); } //接受一个由服务端发来的数据,收到后并回复一个数据给服务端 char recvBuf[100]; memset(recvBuf,0,sizeof(recvBuf));//先将用于接受数据的缓冲区的所有字节置0 int recvDataMaxLen=sizeof(recvBuf); int recvDataLen; //实际接受的数据数目 recvDataLen=recv(clientSocket,recvBuf,recvDataMaxLen,0); if(recvDataLen ==SOCKET_ERROR) { cerr<<"接受数据失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; closesocket(clientSocket); WSACleanup(); exit(1); } cout<<recvBuf<<endl; char sendBuf[]={"你好啊,服务端!"}; int sendDataLen=strlen(sendBuf)+1; if(send(clientSocket,sendBuf,sendDataLen,0) ==SOCKET_ERROR) { cerr<<""<<endl; closesocket(clientSocket); WSACleanup(); exit(1); } //关闭clientSocket closesocket(clientSocket); //清理WinSock2库 ret=WSACleanup(); if(ret ==SOCKET_ERROR) { cerr<<"清理失败!"<<endl; cerr<<"error code:"<<WSAGetLastError()<<endl; } return 0; }
相关文章推荐
- Winsock;TCP/IP编程;模板代码;VC++;单线程;
- [C++] Windows下的socket编程(这是一个简单的TCP/IP例子)
- TCP/IP Winsock编程要点
- [C++] Windows下的socket编程(这是一个简单的TCP/IP例子)
- [C++] Windows下的socket编程(这是一个简单的TCP/IP例子)
- Winsock 初探II 简单TCP/IP 和UDP/IP 程序
- TCP/IP Winsock编程要点
- 三十天学不会TCP,UDP/IP网络编程-UDP,从简单的开始
- 开始Winsock编程-简单的TCP客户端
- TCP-IP学习笔记二:NIO的网络编程Buffer简单使用
- socket编程实现简单DNS协议实现获取域名ip(TCP)
- TCP/IP Winsock编程要点
- TCP/IP Winsock编程要点
- TCP/IP Winsock编程要点
- TCP/IP Winsock编程要点
- TCP-IP学习笔记五:Netty使用--简单通信编程1
- 三十天学不会TCP,UDP/IP网络编程-UDP,从简单的开始
- VC++ Socket编程 简单的Tcp/ip服务器
- TCP-IP学习笔记六:Netty使用--简单通信编程2
- TCP/IP Winsock编程要点