关于windows下,基于socket的tcp服务器端的学习笔记

#include
#include
#include // windows socket套节字编程
#pragma comment( lib, “ws2_32.lib”) // windows socket 32 动态库 没有64位

// 网络学习: 20191120
// 查看网络最高版本库的方法
// 1、头文件
// 2、WSAStartup

int main()
{
printf(" 网络编程学习:\n ");

// 打开网络库
// int WSAAPI WSAStartup(_In_ WORD wVersionRequested, _Out_ LPW);	
WORD wdVersion = MAKEWORD( 2, 2);	// 支持的最高版本
WSADATA wdSockMsg;
memset( &wdSockMsg, 0x00, sizeof(wdSockMsg));
//	int a = *((char *)&wdVersion);	// 第一个地址
//	int b = *((char *)&wdVersion + 1);	//	第二个地址	
// LPWSADATA pSocketMsg = (LPWSADATA)malloc( sizeof(WSADATA) );
// 如果传入大于系统的版本,WSAStartup会返回系统支持的最大版本,但是程序不会报错
// 如果传入小于系统的版本,比如0版本。WSAStartup就会报错;
int iRet = WSAStartup( wdVersion, &wdSockMsg);	// windows socket 异步 启动 
if ( 0 != iRet )	// 文件出错
{
	perror( "WSAStartup" );

	switch ( iRet )
	{
	case WSASYSNOTREADY: // 因为它使用提供网络服务的系统目前无效,WSAStartup 目前不能正常工作。 1、重启电脑 2、查看一下网络库
		printf("重启电脑,或者检查网络库");
		break;
	case WSAVERNOTSUPPORTED:	// 不支持请求的 Windows 套接字版本。 
		printf("检查网络库");		
		break;
	case WSAEINPROGRESS:	// 目前正在执行一个阻止性操作。 
		printf("请重新启动");
		break;
	case WSAEPROCLIM:	//	一个 Windows 套接字操作可能在可以同时使用的应用程序数目上有限制。 
		printf("请关闭不必要的软件,以为软件运行提供资源");
		break;
	case WSAEFAULT:	//	系统检测到在一个调用中尝试使用指针参数时的无效指针地址。 
		printf("参数错误");
		break;
	}
	return iRet;
}

// 版本校验
if ( HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2 )
{
	// 说明版本不对
	// 清理网络库
	WSACleanup();
	// 提示错误
	printf("网络库版本不对\n");
	return 0;
}

// 什么是socket?
// socket是将底层复杂的协议体系,执行流程,进行封装,封装完成的结果,就是一个socket
// 也就是说SOCKET是我们调用协议进行通讯 操作接口

// 意义
// 将复杂的协议和我们编程人员分开,我们直接操作一个SOCKET就行,对于底层的协议、过程细节,完全不知道,大大开发了开发人员
// 网络编程难就难在协议本身的复杂性,简单就简单在我们编程人员完成不用考虑

// 本质
// socket就是一种数据类型,转定义看类型 就是一个整型/但是这个数是唯一的 标识着我当前的应用程序、协议特点等信息

// 应用
// 网络通信函数全部使用socket
// 每个客户端有一个socket,服务端有一个socket,通信的时候,就需要这个socket做参数,给谁通信,你要传递谁的socket

// 所以 网络编程,理论层面SOCKET是网络封装的精华,代码层面就是不停的使用SOCKET这个变量,所以又叫SOCKET编程
// 3个参数 第一个参数 地址协议   第二个参数  套接字类型  第三个参数  协议类型

// 第一个参数 地址协议
// AF_INET 2	IPv4 地址 Internet协议版本(IPv4)协议系列。 0.0.0.0-255.255.255.255 点分十进制表示
//				4字节32位表示 个数块不够 就是无无符号int类型的范围0-4294967295
// AF_INET6 23	IPv6 Internet协议版本6地址系列 2001:0.32.38:DFE1:63:FEEB
//				16字节 128位表示 这个是地球没一寸IP
// AF_BTH 32	蓝牙地址系列 如果计算机安装了蓝牙适配器和驱动程序,则Windows XP SP2或更高版本支持此地址系统 GB:2D:BC:A9:BC:12
// AF_IRDA 26	 红外数据协会(IrDA)地址系列  仅当计算机安装了红外端口和驱动程序的时候,才支持地址系列
// 通讯地址不仅仅只有IP地址

// 第二个参数 套接字类型
// SOCK_STREAM 1 一种套接字的类型,提供具有OOB数据传输机制的顺序,可靠、双向、基于连接字节流。此套接字类型可以传输到控制协议(TCP)作为Interent地址系列(AF_INET或AF_INET 6)
// SOCK_DGRAM  2 一种支持数据的套接字类型,它是固定(通常很小)最大长度的无连接,不可靠的缓冲区,此套接字类型使用用户报文协议(UDP)作为Interent地址系列(AF_INET或AF_INET 6)
// SOCK_RAW    3 一种套接字的类型,提供了允许应用程序操作下一个上层协议的套接字,要操作IPv4标头,必须在套接字上设置IP_HDRINCL套节字选项,要操作IPv6的头,必须在套接字上设置IPV6_HDRINCL套接字选项
// SOCK_RDM    4 一种套接字的类型,提供可靠的数据报,这种类型的一个提示Windows中实用了多播(PGM) 多播协议实现,通常称为可靠的多播节目,仅仅安装了可靠多播协议时才支持多类型值
// SOCK_SEQPACKET 5 一种套接字类型,提供了基于数据的伪装数据包

// 第三个参数  协议类型
// IPPROTO_TCP 传输控制协议(TCP),当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM是这是一个可能值
// IPPROTO_UDP 传输数据报协议(UDP),当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM时,这是一个可能值
// IPPROTO_ICMP Internet控制协议(ICMP),当af参数为AF_UNSPEC,AF_IENT和AF_INET6且类型的参数为SOCK_RAW或为指定时,这是一个可能的值
// IPPROTO_IGMP Internet组管理协议(IGMP),当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定是,这个一个可能的值
// IPPROTO_RM   用于可靠多播PGM协议,当af参数为AF_INET且类型参数为SOCK_RDM时,这是一个可能的值,在针对Windows Vista及更高版本发布的Windows SDK上,此仅在安装了可靠多播协议时才支持次协议值

// 整理下 通过参数3得到一个事,参数1,2,3三者是配套,是一套参数,不是随便填的,及使用不同的协议,要添加配套的参数
// 想要使用一个协议,咱们设备支持才行,比如红外;
// 参数3中,有可能有这个词,所以说一般,参数3可以填0,系统可以帮我们选择参数

// 返回值 成功返回可用的socket  不用就一定使用要销毁套接字 closesocket(socketServer);
// 失败返回 INVALID_SOCKET 关闭网络库 WSACleanup() 可用WSAGetLaterroe() 返回错误码

// 当系统库里面不存在的时候,我们给据提供的源码库和头文件引入来解决
SOCKET socketServer = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( INVALID_SOCKET == socketServer )
{
	int a = WSAGetLastError();	// 支持所有的函数 离最近的一个函数的错误号

	// 清理网络库
	WSACleanup();
	return 0;
}

// bind函数
// 作用 给我们的socket绑定端口号与具体的地址 地址 找到电脑只有一个
//		端口号 每一种的端口号都是唯一的 同一个软件可以占用多个端口
// 参数
// 参数1 上一个函数socket,绑定了协议信息(地址类型,套接字类型,协议类型),咱们bind函数就是绑定实质的地址,端口号
// 参数2 结构体 地址类型,装IP的地址 端口号
//		 结构体的类型 sockaddr 参数使用的方法 SOCKADDR_IN sockAddress;
//											  sockAddress.sin_family = AF_INET;
//											  sockAddress.sin_addr_s_addr = inet_addr("127.0.0.1");
//											  sockAddress.sin_port = htons(12345);
//		给参数使用方法						  (sockaddr *)&sockAddress强转加到参数2
//		参数2的大小 sizeof(参数2)
//
//		端口号:本质就是一个整数 0 - 65535 IP是我们的机器的地址,端口是我们具体软件的通讯端口,一个软件可以占用多个端口,比如一个软件可以聊天,可以下载,可以看视屏,那么这些不同的通讯内容往往有自己的协议,各自的端口
//				IP是公司,端口是各个部门的地址
//		填写那个值呢?  理论上这个值在0-65535都可以 实际介于0-1023,为系统保留占用端口  21端口被分配给FTP(文件传输服务)
//																						25端口分配给SMIP(简单邮件传输协议)服务
//																						80端口分配给HTTP服务

//		所以 我们不写这个范围的 我们的范围是 1024 - 65535 稍微大一点, 1万多
//		但注意的一点是,端口号是唯一的,比如1234已经被别的软件占用了,那么你在使用1234这个端口号,那么就回你绑定失败,因为已经占用了
//		那大家如何查看自己要用的端口号有没有被占用呢? 打开运行cmd输入netstat -ano 查看被使用的所有端口
//													   netstat  -ano|findstr "12345" 检测我们要使用的端口号你是否被使用 使用了就会显示使用的程序,未被是用就啥也不显示
//		
//		127.0.0.1 回送地址 本地回环地址 本地网络测试
//		192.168.xxx.xxx 可以在控制台输入指令 ipconfig就能看到了
//						或者在网络设置中,能找到这个地址
//
//		返回值说明:	成功返回0
//						失败返回SOCKET_ERROR 具体通过 int WSAGetLastError(void)获得
//		closesocket(socketServer);
//		WSACleanup();

struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);	//	端口
si.sin_addr.s_addr = inet_addr("127.0.0.1");	// inet_addr 将点分十进制转换成无符号的long
if ( SOCKET_ERROR == bind( socketServer, (struct sockaddr *)&si, sizeof(struct sockaddr)))
{
	//	出错le
	int a = WSAGetLastError();
	// 释放
	closesocket(socketServer);
	// 清理网络库
	WSACleanup();
	return -1;
}

//	开始监听 
//	int WSAAPI listen(SOCKET s,int backlog);
//	作用  将套接字置于正在监听传入直接连接的状态
//	参数1 服务器的socket,也就是socket函数创建的
//	参数2 挂起连接队列的最大长度 
//		  就是说,比如100个用户连接请求,但是系统一次只能处理20个,那么剩下来的80个不能不理人家,所以系统就创建记录暂时不能处理,一会儿处理连接请求,依次由先后顺序处理,
//		  但这个队列有多大?比如2,那么允许2个人在排队,这个不可能无限大,那内存就不够了
//		
//		  我们可以手动设置这个参数,但别太大,可能2-10多 -20多
//		  我们一般填写这个参数 SOMAXCONN	作用是让系统自动选择最合适的这个数 
//		  不同的系统函数环境不一样,所以这个合适的数也不一样
//	WSAAPI	调用约定 这个我们可以忽略,这是给系统看到,给咋们没有关系
//		  决定三 函数名字的编译方式
//				 参数的入栈顺序
//				 函数的调用时间
//	返回值  成功 返回0
//			失败 返回 SOCKET_ERROR
//			用 WSAGetLastError(); 获取错误码
//			
//			释放
//			closesocket(socketServer);
//			WSACleanup();
if ( SOCKET_ERROR ==  listen(socketServer, SOMAXCONN))
{
	//	出错le
	int a = WSAGetLastError();
	// 释放
	closesocket(socketServer);
	// 清理网络库
	WSACleanup();
	return -1;
}

//	accept函数
//	函数原型  SOCKET WSAAPI accept( SOCKET s, socketaddr *addr,int *addrlen)
//	作用 accept函数允许在套接字上进入连接尝试
//	listen监听客户端来的链接,accept将客户端的信息绑定到一个socket上,也就是将给客户端返回一个socket通过返回值返回给客户端的socket
//	一次只能创建一个,有几个客户端链接,就要调用几次
//	
//	参数1 我们上面创建的自己的socket socket先处于监听状态,然后来的链接都在由这个管理,我们取客户端的信息,就是通过这个我们自己的socket
//	参数2 struct socketaddr的结构体  客户端的地址 端口的结构体
//		和bind的第二个参数是一样的
//		意义:系统帮我们监听这客户的动态,肯定会记录客户端的信息,也就是IP地址,和端口号,并通过这个结构体记录
//		SOCKETADDR_IN socketClient 这个我们不填写,系统帮我们填写,也即传址调用
//		参数2 3 也都可以设置成NULL,那就不直接得到客户端的地址,端口号等
//		对此可以通过函数得到客户端信息		getpeername(newSocket, (struct socketaddr*)&clientMsg, &nLen);
//		得到本地服务器信息	getsockname( sSocket, (sockaddr *)&addr, &nLen);
//	参数3 参数2的大小
//	返回值 成功 返回值就是给客服端的socket 与客户端通讯就靠这个
//		失败 返回INVALID_SOCKET  WSAGetLastError()得到的错误码
//		释放空间 closesocket( socketServer );
//				 WSACleanup();
//		
//	accept	1、阻塞、同步  这和函数是阻塞的、没有客户端链接,就一直卡在这,等着
//			2、多个链接 一次只能一个,5个就要5次循环
//			
//	创建客户端链接
struct sockaddr_in clientMsg;
int len = sizeof(struct sockaddr);
SOCKET socketClient = accept( socketServer, (struct sockaddr *)&clientMsg, &len);
if ( INVALID_SOCKET == socketClient )
{
	printf("客户端链接失败");
	//	出错le
	int a = WSAGetLastError();
	// 释放
	closesocket(socketServer);
	// 清理网络库
	WSACleanup();
	return -1;
}

printf("客户端链接成功");

//	与客户端收发消息
//	收	int recv(SOCKET s,char *buf,int len,int flag);
//	作用 得到客户端(参数1)发来的消息
//	原理 本质 复制  数据的接收都是由协议本身做的,也就是socket的底层做的,系统会有一段缓冲区,存储着接收到的数据
//		咋们外面调用recv的作用,就是通过socket找到这个缓冲区,并把数据复制到参数2,复制到参数3
//	参数1 客户端的socket,每一个客户对应唯一的socket
//	参数2 客户端的存储空间,也就是个字符数组 这个一般1500字节  网络传输的最大单元,1500字节。也就是客户端发过来的数据,一次最大就是1500字节,这是协议规定,这个数值也是根据很多情况,
//	总结出的最优值
//	所以客户端最多一组来1500字节,咋们这头1500读一次,够够的
//	参数3 想要读取到字节个数	一般参数2得字节数-1,把0字符串结尾留出来
//	参数4 数据的读取方式 0
//						正常逻辑来说	我们从系统缓冲区把数据读到我们的部分,读到我们buf中后,系统缓冲区的被删除掉,必然浪费空间,毕竟,通讯时间长的话,那就爆炸了
//								我们将缓冲区的数据读到我们自己的buf,根据需要处理相应的数据,这是我们可控的,完全玩弄于咋们自己的鼓掌,系统缓冲区的数据,咋们无可奈何,操作不了
//						读出就删的话,有很多好处 1、系统缓冲区读到的数据,比我们的buf多,那么我们读出,系统删掉,从而我们就可以依次的把所有数据读完 比如 系统缓冲区收到abcdefghl,咋们一次读4个字节,那么在我们的循环里,就每次读出abcdefghl
//												 如果不删,那么每次从头读  循环每次都读出出abcd,只读到这四个
//						正常逻辑填0  读完就删
//												 2 可以计算收到多少字节  返回值就是本次读出来的数据
//	
//	MSG_PEEK	窥视传入的数据,数据将复制到缓冲区中,但不会从输入队列中删除
//				这个东西是不是不建议被使用 第一、读数据不行 第二、那就无法计算计数了
//	
//	MSG_OOB		外带数据 意义:就是传输一段数据,在外带一个额外的特殊数据 相当于小声BB
//						 实际就不建议使用 1、TCP协议规范(RFC 793)中OOB的原始描述被“主机要求”规范取代(RFC 1122),但仍有许多机器具有RFC793 OOB实现
//										  2、既然另种数据,那咋们就send两次,另一方recv两次就行了,何必搞得那么神神秘秘,浪费计算机精力
//	MSG_WAITALL	直到系统缓冲区字节数满足参数3所请求字节数,才开始读取
//						
//	返回值 读出字节的大小  在recv函数卡着,等着客户端发来数据  即阻塞,同步,阻塞的
//	客户端下线,这端返回0  释放客户端socket
//	执行失败,返回SOCKER_ERROR  WSAGetLastError()的到错误码
//			根据错误码信息做相应的处理	重启/等待/不用理会

char buf[1500] = {0};
int res = recv( socketClient, buf, 1499, 0);
if ( 0 == res )		// 连接中断
{
	printf("连接中断");
}
else if ( SOCKET_ERROR == res)
{
	// 出错了
	int a = WSAGetLastError();
	// 根据实际情况进行处理
}
else
{
	printf("%d %s\n", res, buf);
}

//	send 函数
//
//	函数原型 int WSAAPI send( SOCKET s,const char* buf, int len,int flags);
//	作用 向目标函数发送数据
//	本质 send函数将我们的数据复制粘贴进系统的协议发送缓冲区,计算机司机发出去  最大的传输单元是1500字节
//	参数1	目标socket,每个客户端对应唯一的socket
//	参数2	给对方发送的字符串 这个不要超过1500字节
//			发送时候,协议要进行包装,加上协议信息,也叫协议头,或者包头,咋们在理论部分,会非常详细的介绍协议头,以及功能
//			这个大小不同的协议不一样,链路层14字节,ip头20字节,tcp头20字节,数据尾还有状态确认,加起来也几十个字节,所以实际咋们的数据位,不能写1500个,要留出来,那就1024字节,或者最多1400,就差不多了
//			当然大家这个不一定每次正好这么多字节,比如聊天,一句话就是十个八个的汉字,别多于1400字节最好
//			超过1500系统咋办?
//			系统会分片处理,比如2000字节
//			系统分成两个包 1400+包头==1500 假设包头100字节
//			60 + 包头 == 700
//			分两次发送出去
//			结果:1、系统要给咋们分包在打包,在发送,客户端接收到了,还得拆包,组合数据。从而增加了系统的工作,降低了效率
//			2、有的协议,就把分片后的二包数据直接丢了
//			
//			
//	参数3	字节个数 1400
//	参数4	写0就行
//			其他 MSG_OOB 意义通recv 就不使用了
//			MSG_OOB		外带数据 意义:就是传输一段数据,在外带一个额外的特殊数据 相当于小声BB
//						 实际就不建议使用 1、TCP协议规范(RFC 793)中OOB的原始描述被“主机要求”规范取代(RFC 1122),但仍有许多机器具有RFC793 OOB实现
//										  2、既然另种数据,那咋们就send两次,另一方recv两次就行了,何必搞得那么神神秘秘,浪费计算机精力
//			MSG_DONTROUTE	指定数据不受路由器限制,Windows套节字服务提供程序可以选择忽略此标识
//	返回值	成功返回写入的字节数
//			执行失败,返回SOCKET_ERROR  WSAGetError得到错误码
//			根据错误码做相应的处理 重启 等待 不用理会
//			

if ( SOCKET_ERROR == send(socketClient, "abcd", sizeof("abcd"), 0) )
{
	// 出错了
	int a = WSAGetLastError();
	// 根据实际情况进行处理
}


closesocket( socketClient );
closesocket( socketServer );
// 清理网络库
WSACleanup();

// free(pSocketMsg );
// pSocketMsg = NULL;
system(“pause”);
return 0;
}

你可能感兴趣的:(网络)