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

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

//使用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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: