C++Windows socket编程 TCP通信

SERVER端
代码:

#include <Winsock2.h>
#include <iostream>

using namespace std;

void main()
{
	/*
	1.加载套接字库函数WSAstartup ,
	加载套接字库,进行套接字库的版本协商。
	有两个参数,第一个是WORD,用来指定版本号。
	第二个参数是返回值,指向WSAData结构体的指针,接收windows sockets实现的详细的细节
	*/
	WORD wVersionRequested;//定义WORD类型变量
	WSADATA wsaData;//WORD类型,用于指定准备加载的winsock库的版本,可用MAKEWORD(x,y)方便的获得wVersionRequested的正确值。x高字节副版本,y低字节主版本
	int err;

	/*
	补充:
	WSADATA结构体定义如下:`在这里插入代码片`
	typedef struct WSAData{
	WORD wVersion;//低字节主版本
	WORD wHighVersion;//高字节副版本
	char szDescription [WSADECRIPTION_LEN+1];//没用
	char szSystemStatus[WSASYS_STATUS_LEN+1];//没用
	unsigned short iMaxSockets;//不可使用
	unsigned short iMaxLdpDg;//不可使用
	char FAR*lpVendorInfo;//没有用到
	}WSADATA,*LPWSADATA;
	对于每一个WSAStartup的成功调用(成功加载winsock.DLL后),在最后都对应一个WSACleanup调用,以便释放为该应用程序分配的资源。
	*/

	wVersionRequested=MAKEWORD(1,1);//MAKEWORD宏,请求1.1版本的winsock库
	err=WSAStartup(wVersionRequested,&wsaData);//&wsaData指向WSADATA的指针,WSAStartup用其加载的库版本的相关信息填写在这个结构中。(后来程序出错,1.1版本不行了,换成2.2可以)

	if(err!=0){
		return;
	}

	//判断返回的wVerion值低字节是否为1和高字节是否为1,如果不是请求的winsock 1.1版本,程序调用WSACleanup(),终止对winsock库的使用,然后返回。
	if(LOBYTE(wsaData.wVersion)!=1||
		HIBYTE(wsaData.wHighVersion)!=1){
			WSACleanup();
			return;
	}


	/*2.创建套接字
	SOCKET socket(int af,int type,int protocol);
	socket类型返回套接字的描述符,这个类型实际上指定的是一个整型。
	该函数接收三个参数。
	af:指定地址族,对于TCP/IP协议的套接字,它只能是AF_INET(也可以写成PF_INET).
	type:socket类型,对于1.1版本的socket,只支持两种类型的套接字:SOCK_STREAM 指定产生流式套接字
	                                                             SOCK_RGRAM产生数据报套接字
	protocol:与特定的地址家族相关的协议,如果指定为0,那么它就会根据地址格式和套接字类别,自动为你选一个合适的协议。
	返回值:函数调用成功,返回一个新的SOCKET数据类型的套接字描述符。
	                失败,返回一个INVALID_SOCKET,错误信息可以通过WSAGetLastError函数返回。
	*/

	SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);//定义一个变量接收返回的描述符

	/*3.绑定本地地址和端口上
	int bind(
	SOCKET s,
	const struct sockaddr FAR * name,
	int namelen
	);
	将一个地址和一个套接字关联起来。
	3个参数
	s:指定要绑定的套接字。
	*name:该套接字的本地地址信息,指向sockaddr结构的指针变量。
	namelen:指定该地址结构的长度。

	补充:
	struct sockaddr{
	u_short sa_family;//指定地址家族 AF_INET
	char sa_data[14];//要求一块内存分配区,占位,区域并指定与协议相关的具体地址信息。
	};

	在TCP/IP中,可以使用sockaddr_in结构,替换sockaddr,以方便我们填写地址信息。

	tips:
	如果我们只想让套接字使用多个IP中的一个地址,就必须指定实际地址,
	要做到这一点,可以使用inet_addr()函数,
	这个函数需要一个字符串作为其参数,该字符串制定了以点分十进制格式表示的IP地址如(192.168.0.15),有返回值。

	*/

	SOCKADDR_IN addrSrv;//定义一个变量,地址结构体的变量。
	/*
	u_long htonl(
	u_long hostlong
	);//将u_long类型从主机字节序到TCP/IP网络字节序的转换。

	u_=short htonl(
	u_short hostshort
	);//将u_short类型从主机字节序到TCP/IP网络字节序的转换。

	*/
	//对地址结构体的成员变量进行赋值,需要u_long类型。
	addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//将IP地址设置为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。

	addrSrv.sin_family=AF_INET;//地址族

	addrSrv.sin_port=htons(6000);//2个字节的用u_short类型的,注意端口选择,1024以上的端口

	//绑定本地地址和端口上。
	bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//(套接字,需要指针 取地址 强制类型转换,指定地址结构大小)


	//4.调用listen函数,将套接字设置为监听模式。
	listen(sockSrv,5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);

	//做一个死循环,作为服务器端程序来说,它需要不断的等待客户端连接请求的到来
	while(1)
	{
		/*
		SOCKET accept(
		SOCKET s,//描述符
		struct sockaddr FAR * addr,//指向buff的指针,用来接收连接实体的地址,
		//即当客户端向服务端发起连接时,在接收连接时,通过这个参数保存发起连接的客户端的IP地址信息和端口信息
		int FAR *addrlen //返回值,指向一个整型的指针,用来包含所返回的地址字节的长度。
		);

		int send(
		SOCKET s;//套接字
		const char FAR *buf,//包含了将要传送的数据
		int len,//buf中数据长度
		int flags //会影响send的调用行为,设为0即可。
		);

		*/
		//调用accept函数以等待客户端连接,并接收客户端连接请求。
		SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClient,&len);//(监听套接字,结构体指针,指针 len的地址)

		char sendBuf[100]=”你好,我是服务端。”;//定义一个字符数组

		/*C语言的写法:
// 将一个数据格式化到Buf当中
		sprintf(sendBuf,"Welcome %s to http://www.baidu.com",
			inet_ntoa(addrClient.sin_addr));//inet_ntoa接收in_adrin类型的参数,返回IP地址。*/

		//发送(套接字,数据,数据长度 +1是为了在接收端增加一个‘/0'结尾,0)
		send(sockConn,sendBuf,strlen(sendBuf)+1,0);

		char recvBuf[100];//从客户端接收数据

		//接收(套接字,数据,0)
		recv(sockConn,recvBuf,100,0);

		//将接收的数据打印出来
		cout<<recvBuf<<endl;//printf("%s\n",recvBuf);

		//完成后,调用函数关闭套接字释放资源(死循环),如果不是死循环,在后面要关闭监听套接字:调用WSAcleanup()终止winsock库使用。
		closesocket(sockConn);


}
	}
	/*
	注:
	在程序中使用了winsock库的函数,要包含一个文件:Winsock2.h
	                               还要链接一个库文件:ws2_32.lib
								   这是使用动态链接库需要做的事情。
	在开始:
	#include <Winsock2.h>
    链接库文件:project->project settings->Link->object/library modules:最后输入 ws2_32.lib注意格式
	*/

client端
增加新的工程到工作区
选择 Win32 Console Application
位置和TCPSRV平级
工程名:TcpClient

选择工程,现在有两个工程,注意工作区的主工程。
添加一个源文件tcpClient

代码:

#include <Winsock2.h>
#include <iostream>

using namespace std;

void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested=MAKEWORD(2,2);
	err=WSAStartup(wVersionRequested,&wsaData);

	if(err!=0){
		return;
	}
	
	if(LOBYTE(wsaData.wVersion)!=2||
		HIBYTE(wsaData.wHighVersion)!=2){
			WSACleanup();
			return;
	}
//以上部分和服务器端一模一样,不再赘述。

//接下来创建套接字,参数:指定地址族,类型 基于TCP的都采用流式套接字,第三个参数为0
	SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0);
/*
对于客户端来说,他不需要去绑定,可以直接去连接服务器端,使用connect函数。
int connect(
   SOCKET s,   // 套接字
   const struct sockaddr FAR * name,  //地址结构体指针,用来设定你所连接的服务器的地址信息
   int namelen   // 地址结构体的长度
   );
*/

//首先定义一个地址结构体的变量
	SOCKADDR_IN addrSrv;
//接着,对它的成员进行赋值
	addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//注意S_addr是u_long类型,将点分十进制表示的IP地址的字符串转换成u_long类型,使用inet_addr.
/*地址设置成127.0.0.1 本地回路地址,即不管你的机器上有没有网卡,都可以用这个IP地址。有时在自己计算机上编写程序,可以采用127.0.0.1这个IP做一些测试。如果你编写的程序要在两台计算机上通信,这个地方则填写该服务器的IP地址。*/
	addrSrv.sin_family=AF_INET;
	addrSrv.sin_port=htons(6000);//端口设为服务器应用程序在哪一个端口等待连接,与服务器保持一致。

//调用connect进行连接,参数:套接字,地址需要转换,长度
	connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

//当我们建立连接后就可以接受服务器端发过来的数据
	char recvBuf[100];//定义一个字符数组
//调用recv接收数据
	recv(sockClient,recvBuf,100,0);
//将我们收到的数据打印出来
	cout<<recvBuf<<endl;//printf("%s\n",recvBuf);

//向服务器端发送数据,参数:注意长度多加1
	send(sockClient,"This is 喵喵客户端",strlen("This is 喵喵客户端")+1,0);
    
//当通信完成后,需要调用close
	closesocket(sockClient);
//终止对套接字的使用(非死循环)
	WSACleanup();

}

最后注意先启动server端,再启动client端
测试结果:
设置启动顺序:

设置启动顺序
启动server:
启动server
设置启动顺序:
设置启动顺序
启动client,并查看客户端:启动服务端
查看服务端:
查看服务端
客户端可以多次启动:
多次启动客户端结果
为了方便查看,下面是纯代码:
server:

#include <Winsock2.h>
#include <iostream>

using namespace std;

void main()
{

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested=MAKEWORD(2,2);
	err=WSAStartup(wVersionRequested,&wsaData);

	if(err!=0){
		return;
	}

	
	if(LOBYTE(wsaData.wVersion)!=2||
		HIBYTE(wsaData.wHighVersion)!=2){
			WSACleanup();
			return;
	}

	SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	addrSrv.sin_family=AF_INET;
	addrSrv.sin_port=htons(6000);
	bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

	listen(sockSrv,5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);

	while(1)
	{	
		SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClient,&len);

		char sendBuf[100]="你好,我是服务端";	
		//sprintf(sendBuf,"Welcome %s to http://www.baidu.com",
		//inet_ntoa(addrClient.sin_addr));
		
		send(sockConn,sendBuf,strlen(sendBuf)+1,0);

		char recvBuf[100];	
		recv(sockConn,recvBuf,100,0);		
		cout<<recvBuf<<endl;
		closesocket(sockConn);
}
	}

client:

#include <Winsock2.h>
#include <iostream>

using namespace std;

void main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested=MAKEWORD(2,2);
	err=WSAStartup(wVersionRequested,&wsaData);

	if(err!=0){
		return;
	}
	
	if(LOBYTE(wsaData.wVersion)!=2||
		HIBYTE(wsaData.wHighVersion)!=2){
			WSACleanup();
			return;
	}

	SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
	addrSrv.sin_family=AF_INET;
	addrSrv.sin_port=htons(6000);

	connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

	char recvBuf[100];
	recv(sockClient,recvBuf,100,0);
	cout<<recvBuf<<endl;//printf("%s\n",recvBuf);

	send(sockClient,"This is 喵喵客户端",strlen("This is 喵喵客户端")+1,0);
    
	closesocket(sockClient);
	WSACleanup();

}

猜你喜欢

转载自blog.csdn.net/qq_43633973/article/details/88915398
今日推荐