TCP.02.SELECT模型

2023-05-16

文章目录

  • SELECT模型简介
  • SELECT模型流程
  • SELECT原理
  • SELECT代码实现
    • fd_set 数组及基本操作
    • SELECT函数
    • 参数2(重点)
    • 参数3
    • 参数4
  • 关闭所有SOCKET句柄
  • 处理控制台窗口关闭事件
  • 整体代码
  • 思考:
  • 升级vs2019说明

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
基于TCP/IP的网络编程还有5种模型:
SELECT模型
事件选择模型
异步选择模型
重叠IO模型
完成端口模型

这次先讲第一种。

SELECT模型简介

针对多个客户端连接服务器时,服务器不能同时响应多个客户端的情况,SELECT模型就是用来解决服务器的accept、recv函数等待阻塞的问题的(客户端不需要使用这个模型)。注意这里函数执行时阻塞这个问题并没有解决,只是不等待阻塞了。(执行阻塞和等待阻塞的区别)
换句话解释,recv执行过程中,从内存拷贝东西的过程中,是执行阻塞状态,无法中途响应别的请求;但是执行recv函数的时候,如果没有消息,这个时候是等待阻塞状态,SELECT可以让SOCKET句柄在等待阻塞的时候响应别的有消息的SOCKET请求。当然执行阻塞也是有解决方案的,后面讲。

SELECT模型流程

  1. 打开网络库

  2. 校验版本

  3. 创建SOCKET

  4. 绑定地址与端口

  5. 开始监听

  6. SELECT

可以看到除了最后一步,其他和普通模型是一样的,因此可以直接看最后一个步骤如何做。

SELECT原理

1、每个客户端都有SOCKET(多个),服务器也有自己的SOCKET(单个),将所有的socket装进SOCKET数组里
2、通过select函数,遍历1中的SOCKET数组,当某个SOCKET有响应,SELECT就会通过其参数/返回值反馈出来。
3、根据SOCKET类型进行不同操作:
如果是服务器SOCKET,说明有客户端连接,调用accept
如果是客户端SOCKET,说明有客户端请求通信,调用send或者recv

SELECT代码实现

使用上一节的基本CS通信中的server代码,并将监听成功到最后清理内存之间的代码删除掉。

fd_set 数组及基本操作

先定义一个SOCKET数组,这个在SOCKET库中提供了相应的类型:

typedef struct fd_set {
  u_int  fd_count;
  SOCKET fd_array[FD_SETSIZE];
} fd_set, FD_SET, *PFD_SET, *LPFD_SET;

fd_count表示数组中包含多少个SOCKET句柄
数组大小默认最大值为:FD_SETSIZE=64

/*
 * Select uses arrays of SOCKETs.  These macros manipulate such
 * arrays.  FD_SETSIZE may be defined by the user before including
 * this file, but the default here should be >= 64.
 *
 * CAVEAT IMPLEMENTOR and USER: THESE MACROS AND TYPES MUST BE
 * INCLUDED IN WINSOCK2.H EXACTLY AS SHOWN HERE.
 * 这里的意思是有ifndef宏定义判断,如果在调用头文件之前有定义,这个定义就会失效
 */
#ifndef FD_SETSIZE
#define FD_SETSIZE      64
#endif /* FD_SETSIZE */

根据上面的说明,我们可以在调用winsock2.h前预定义FD_SETSIZE新的值

#define FD_SETSIZE 100
#include <winsock2.h>

虽然修改FD_SETSIZE可以让服务器处理更多的客户端,但是考虑到内存,响应时间等限制,还是不要玩太大,否则容易出现莫名其妙的错误。理论上来看,由于这个玩意是数组,里面进行遍历操作,只能按位置进行轮询,数组太大当然响应时间会变长。64去掉一个服务器SOCKET,能和63个客户端同时玩,应该够了。大量并发用户后面会有专门的模型来处理。

对于fd_set 数组,WINSOCK也相应的提供了4个函数来进行操作:

//清空fd_set数组
	FD_ZERO();

其原理并不去删除数组内容,而是直接把fd_set中的fd_count设置为0。

//添加一个SOCKET句柄
	FD_SET(socketServer,&clientSockets)

FD_SET添加相同的句柄会被忽略,如果数组满了则无法添加。

//删除指定SOCKET句柄
	FD_CLR(socketServer,&clientSockets)

FD_CLR删除指定SOCKET句柄后,会将在该句柄后面所有的元素依次向前移动一位。但是SOCKET句柄内存需要手动释放(要接着调用closesocket)。

//判断某个SOCKET句柄是否在数组中
	FD_ISSET(socketServer,&clientSockets);

其实现原理是调用__WSAFDIsSet内部函数来判断

int __WSAFDIsSet(
  SOCKET fd,
  fd_set *
);

存在返回非零,不存在返回零。

SELECT函数

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select
看下调用方式:

int WSAAPI select(
  int           nfds,
  fd_set        *readfds,
  fd_set        *writefds,
  fd_set        *exceptfds,
  const timeval *timeout
);

参数1nfds:为兼容Berkeley socket标准而保留的无用参数,默认写0即可。
参数2:*readfds,检查是否有可读的SOCKET,当select函数调用时,将要轮询的SOCKET数组放到FD_SET(这里是传址调用)后丢给系统,系统将有请求的SOCKET句柄(需要服务器recv)再填到FD_SET里面丢回来(当然这里的请求有两种,前面提到过,如果是服务器句柄,就需要accept,如果是客户端句柄就需要recv);
参数3: *writefds,检查是否有可写的SOCKET,和上面功能类似,只不过针对send,调用select后,这个地址里面就是可以send数据的客户端SOCKET句柄,但是由于对于send这个功能,服务器是没有执行阻塞的(我想怎么发送就怎么发送消息,无需经过客户端同意)因此这个参数用得不多。
参数4:*exceptfds,检查是否有出错的SOCKET,当select函数调用时,将要轮询的SOCKET数组放到FD_SET(这里是传址调用)后丢给系统,系统将有错误的SOCKET句柄再填到FD_SET里面丢回来。然后可以用下面这个函数得到具体错误码:

int WSAAPI getsockopt(
  SOCKET s,
  int    level,
  int    optname,
  char   *optval,
  int    *optlen
);

参数5:*timeout,是timeval结构体,用来设置轮询FD_SET完无请求情况下的最大等待时间间隔(最大的含义就是最多等这么久,如果等待中间发生请求则终止等待,直接返回)

typedef struct timeval {
  long tv_sec;
  long tv_usec;
} TIMEVAL, *PTIMEVAL, *LPTIMEVAL;

两个成员都是用来设置时间间隔的,第一个是秒;第二个是微秒(百万分之一秒,千分之一毫秒)

状态*timeout设置值select状态
非阻塞0 0轮询FD_SET完无请求后,无等待,立刻返回
半阻塞4 2轮询FD_SET完无请求后,最多等待4秒2微秒后返回(等待期间有请求则立刻返回)
全阻塞NULL轮询FD_SET完无请求后,不返回,等待有请求后才返回

select的全阻塞状态并不代表服务器在等待某个客户端请求,而是等待FD_SET里面所有的客户端请求。select函数除了有等待之外,每个状态都有执行阻塞。

select返回值:
0:客户端在等待时间内无请求,可以进行下一次select操作;
>0:有客户端请求,此时要分服务器或者客户端分别进行判断;
SOCKET_ERROR:发生错误,利用WSAGetLastError()获取错误码。

注意:select函数的2.3.4的参数是传址引用,因此不能直接把要轮询的FD_SET丢进去,否则会被覆盖。

参数2(重点)

为了加深理解,这里将select(0,&readSocket,NULL,NULL,&st);的参数3和参数4设置为NULL,来观察参数2的使用。

	fd_set allSockets;
	//清空fd_set数组
	FD_ZERO(&allSockets);

	//添加服务器SOCKET句柄
	FD_SET(socketServer,&allSockets);

	struct timeval st;//等待间隔
	st.tv_sec = 3;
	st.tv_usec = 4;

	while(1)
	{

		//防止轮询数组被传址引用清空
		fd_set readSocket = allSockets;

		int nRes = select(0,&readSocket,NULL,NULL,&st);//这里只处理参数2

		if (nRes == 0)//没有请求
		{
			continue;
		}
		else if (nRes > 0)//有请求,进一步判断句柄类型
		{
			//循环处理有读请求的句柄
			for(int i = 0; i < readSocket.fd_count; i++)
			{
				//对服务器句柄进行accept
				if(readSocket.fd_array[i] == socketServer)
				{
					SOCKET clientSocket = accept(socketServer,NULL,NULL);
					if (clientSocket == INVALID_SOCKET)
					{
						//取错误码后继续取下一个请求句柄
						int err = WSAGetLastError();//取错误码
						printf("获取客户端句柄失败错误码为:%d\n",err);
						continue;
					}

					//将新加入的客户端丢进轮询数组
					FD_SET(clientSocket,&allSockets);
					printf("获取客户端句柄成功,数组中共有%d个句柄\n",allSockets.fd_count);
				}
				else//处理客户端
				{
					char buff[1500] = {0};
					//接收客户端消息
					int recverr = recv(readSocket.fd_array[i],buff,1500,0);
					if (recverr == 0)
					{
						//客户端已经掉线,通常客户端正常执行WSACleanup();属于正常掉线,否则属于异常掉线。
						//记录轮询数组中客户端句柄
						//要从轮询数组中去掉该客户端句柄
						//释放记录的客户端句柄
						printf("客户端已下线\n");
						SOCKET freeSocket = readSocket.fd_array[i];
						FD_CLR(readSocket.fd_array[i],&allSockets);
						closesocket(freeSocket);
					}
					else if(recverr == SOCKET_ERROR)//SOCKET_ERROR处理,强行关闭客户端也会走这里10054
					{
						int err = WSAGetLastError();//取错误码
						printf("循环处理客户端句柄失败错误码为:%d\n",err);	
						
						//记录轮询数组中客户端句柄
						//要从轮询数组中去掉该客户端句柄
						//释放记录的客户端句柄
						SOCKET freeSocket = readSocket.fd_array[i];
						FD_CLR(readSocket.fd_array[i],&allSockets);
						closesocket(freeSocket);
						continue;
					}
					else
					{						
						//打印收到的消息
						printf("客户端收到消息为:%s\n",buff);
					}
					printf("接收客户端消息成功,数组中共有%d个句柄\n",allSockets.fd_count);
				}
			}
		}
		else//发送错误
		{
			//
		}
	}

参数3

上面其实也有提到过,参数3是用来获取可写的客户端SOCKET集合的,这里,当客户端连接服务器后,就是可写的,因此可以不需要使用参数3来进行send操作。

		fd_set sendSockets = allSockets;

		int nRes = select(0,&recvSockets,&sendSockets,NULL,&st);+
		//循环处理有写请求的句柄
		for(i = 0; i < sendSockets.fd_count; i++)
		{
			//由于服务器不会自己和自己send消息
			//因此,在sendSockets集合中是没有服务器句柄的
			//可以用下面语句进行打印
			//printf("服务器句柄是d%,d%可写\n",socketServer,sendSockets.fd_array[i]);
			
			
			//处理send
			if( send(sendSockets.fd_array[i],"hello",5) == SOCKET_ERROR)
			{
				int senderr = WSAGetLsatError();
			}
		}

这里注意:
send函数如果成功则返回发送的字节数
失败则返回小于0的SOCKET_ERROR,一般不会出现等于0 的情况(除非发送消息长度为0)

参数4

这里要用到一个常用函数,这个函数在这里是用来获取错误信息,实际应用中还可以用来获取很多相关的其他信息(是否广播、连接时间等):

int WSAAPI getsockopt(
  SOCKET s,
  int    level,
  int    optname,
  char   *optval,
  int    *optlen
);

参数1:要获取信息的SOCKET句柄
参数2:获取信息的level(我感觉可以理解为类型)
参数3:获取信息的具体属性,这个和上面的level有关,不同level对应的属性不一样
参数4:传址调用,错误信息
参数5:传址调用,错误信息长度

			fd_set errorSockets = allSockets;

			int nRes = select(0,&recvSockets,&sendSockets,&errorSockets,&st);
			
			//循环处理有错误的句柄
			for(i = 0; i < errorSockets.fd_count; i++)
			{
				char errbuf[100] = {0};
				int errbuflen = 99;
				if(getsockopt(errorSockets.fd_array[i],SOL_SOCKET,SO_ERROR,errbuf,&errbuflen) == SOCKET_ERROR);
				{
					printf("getsocketopt无法获取相关信息");
					int senderr = WSAGetLastError();
					//错误处理
				}
			}

关闭所有SOCKET句柄

释放数组中所有的句柄,由于服务器句柄也在里面,因此服务器句柄不需要再写代码单独关闭。

//释放所有句柄
	for(u_int i = 0; i < allSockets.fd_count; i++)
	{
		closesocket(allSockets.fd_array[i]);
	}

处理控制台窗口关闭事件

当在运行程序的过程中,如果是直接用鼠标关闭窗口,属于非正常关闭,这个时候程序最后的释放句柄代码没有得到执行,容易造成内存泄露,这个时候,我们需要使用hook请求,通过操作系统来处理关闭窗口事件。
https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler

BOOL WINAPI SetConsoleCtrlHandler(
  _In_opt_ PHANDLER_ROUTINE HandlerRoutine,
  _In_     BOOL             Add
);

第一个参数是程序关闭时额外执行的函数名称
第二个参数通常设置为TRUE,代表系统是否执行额外的函数名称
函数(回调函数callback function)定义要遵守一定的规则:

BOOL WINAPI HandlerRoutine(
  _In_ DWORD dwCtrlType
);

整体代码

//如果需要增加Select模型处理客户端数量,可修改下面一行代码
//#define FD_SETSIZE 128
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")

//用来保存所有客户端+服务器Socket句柄的集合
fd_set allSockets;


BOOL WINAPI cls(DWORD dwCtrlType)
{
	switch (dwCtrlType)
	{
	case CTRL_CLOSE_EVENT:
		//释放所有句柄
		for (u_int i = 0; i < allSockets.fd_count; i++)
		{
			closesocket(allSockets.fd_array[i]);
		}
		WSACleanup();//清理网络库

	}

	return TRUE;
}

int main(void)
{
	SetConsoleCtrlHandler(cls, TRUE);
	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
	WORD wdVersion = MAKEWORD(2, 2);
	int a = *((char*)&wdVersion);
	int b = *((char*)&wdVersion + 1);

	//LPWSADATA lpw = (WSADATA*)malloc(sizeof(WSADATA));手动分配内存还要自己释放,太麻烦
	WSADATA wdScoket;
	//1、打开网络库
	int nRes = WSAStartup(wdVersion, &wdScoket);

	if (0 != nRes)
	{
		switch (nRes)
		{
		case WSASYSNOTREADY:
			printf("解决方案:重启。。。\n");
			break;
		case WSAVERNOTSUPPORTED:
			break;
		case WSAEINPROGRESS:
			break;
		case WSAEPROCLIM:
			break;
		case WSAEFAULT:
			break;
		}
		return 0;

	}
	printf("打开网络库成功!\n");

	//2、校验版本	
	if (2 != HIBYTE(wdScoket.wVersion) || 2 != LOBYTE(wdScoket.wVersion))
	{
		printf("版本有问题!\n");
		WSACleanup();
		return 0;
	}
	printf("版本校验成功!\n");

	//3、创建SOCKET
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (INVALID_SOCKET == socketServer)
	{
		int err = WSAGetLastError();
		printf("服务器创建SOCKET失败错误码为:%d\n", err);

		//清理网络库,不关闭句柄
		WSACleanup();
		return 0;
	}
	printf("服务器创建SOCKET成功!\n");

	struct sockaddr_in si;
	si.sin_family = AF_INET;
	si.sin_port = htons(12345);//用htons宏将整型转为端口号的无符号整型

	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	//4、绑定地址与端口
	if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
	{
		int err = WSAGetLastError();//取错误码
		printf("服务器bind失败错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库

		return 0;
	}
	printf("服务器端bind成功!\n");

	//5、开始监听
	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
	{
		int err = WSAGetLastError();//取错误码
		printf("服务器监听失败错误码为:%d\n", err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库

		return 0;
	}

	printf("服务器端监听成功!\n");




	//清零
	FD_ZERO(&allSockets);
	//服务器装进去
	FD_SET(socketServer, &allSockets);

	while (1)
	{

		//防止轮询数组被传址引用清空
		fd_set recvSockets = allSockets;
		fd_set sendSockets = allSockets;
		//由于服务器不会自己和自己send消息,因此这里要把服务器句柄从发送列表里面去掉
		FD_CLR(socketServer, &sendSockets);
		fd_set errorSockets = allSockets;


		//时间段
		struct timeval st;
		st.tv_sec = 3;
		st.tv_usec = 4;

		//select
		int nRes = select(0, &recvSockets, &sendSockets, &errorSockets, &st);

		if (0 == nRes) //没有响应的socket
		{
			continue;
		}
		else if (nRes > 0)
		{	
			//循环处理有读请求的句柄:可能是服务器句柄,可能是客户端句柄
			for (u_int i = 0; i < recvSockets.fd_count; i++)
			{
				//如果是服务器句柄有请求,则表明有新客户端需要进行accept
				if (recvSockets.fd_array[i] == socketServer)
				{
					//accept新客户端
					SOCKET socketClient = accept(socketServer, NULL, NULL);
					if (INVALID_SOCKET == socketClient)
					{
						//连接出错,取错误码后继续取下一个请求句柄
						int err = WSAGetLastError();//取错误码
						printf("accept客户端句柄失败错误码为:%d\n", err);
						continue;
					}

					//将新加入的客户端丢进fd_set集合
					FD_SET(socketClient, &allSockets);
					//这里可以send
					printf("服务器获取客户端句柄成功,fd_set中共有%d个句柄!\n", allSockets.fd_count);
				}
				else//处理客户端,recv消息
				{
					char strBuf[1500] = { 0 };
					//接收客户端消息
					int nRecv = recv(recvSockets.fd_array[i], strBuf, 1500, 0);
					//send
					if (0 == nRecv)
					{
						//客户端已经掉线
						//记录fd_set中掉线的客户端句柄
						//要从fd_set中去掉该客户端句柄
						//手工释放记录的客户端句柄
						SOCKET socketTemp = recvSockets.fd_array[i];
						FD_CLR(recvSockets.fd_array[i], &allSockets);
						//释放
						closesocket(socketTemp);
						printf("客户端已经掉线,fd_set中共有%d个句柄!\n", allSockets.fd_count);
					}
					else if (0 < nRecv)
					{
						//打印收到的消息
						printf("客户端收到消息为:%s\n", strBuf);
					}
					else ///SOCKET_ERROR处理,强行关闭客户端也会走这里10054
					{
						//强制下线也叫出错 10054
						//有时候也只会触发10053
						int recverr = WSAGetLastError();
						switch (recverr)
						{
						case 10053:
						{
							//记录轮询数组中客户端句柄
							//要从轮询数组中去掉该客户端句柄
							//释放记录的客户端句柄
							SOCKET socketTemp = recvSockets.fd_array[i];
							FD_CLR(recvSockets.fd_array[i], &allSockets);
							//释放
							closesocket(socketTemp);
							printf("10053客户端已经强关,fd_set中共有%d个句柄!\n", allSockets.fd_count);
						}
						case 10054:
						{
							//记录轮询数组中客户端句柄
							//要从轮询数组中去掉该客户端句柄
							//释放记录的客户端句柄
							SOCKET socketTemp = recvSockets.fd_array[i];
							FD_CLR(recvSockets.fd_array[i], &allSockets);
							//释放
							closesocket(socketTemp);
							printf("10054客户端已经强关,fd_set中共有%d个句柄!\n", allSockets.fd_count);
						}
						}
					}
				}
			}

			//循环处理有错误的句柄
			for (u_int i = 0; i < errorSockets.fd_count; i++)
			{
				char str[100] = { 0 };
				int len = 99;
				if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len))
				{
					printf("getsocketopt无法获取相关信息!\n");
					int getopterr = WSAGetLastError();
					//错误处理
					printf("服务器getsocketopt失败错误码为:%d\n", getopterr);

				}
				printf("%s\n", str);//打印getsocketopt获取的错误信息
			}

			//循环处理有写请求的句柄
			for (u_int i = 0; i < sendSockets.fd_count; i++)
			{
				//由于服务器不会自己和自己send消息
				//因此,在sendSockets集合中是没有服务器句柄的
				
				//printf("服务器%d,%d:可写\n", socketServer, writeSockets.fd_array[i]);

				//处理send,注意这里接收信息的客户端要设置接收长度和hello一致
				if (SOCKET_ERROR == send(sendSockets.fd_array[i], "hello", 5, 0))
				{
					int senderr = WSAGetLastError();
					//这里最好不打印错误码
					//printf("服务器send失败错误码为:%d\n", senderr);
				}
			}
		}
		else//发送错误
		{
			//获取错误码,根据错误码基分类处理

			//这里简单退出一下,不详细写
			break;
		}

		//可以定义异步输入标志,代替while循环判断条件,满足该条件即可退出循环

	}

	//删除指定SOCKET句柄
	//FD_CLR(socketServer,&allSockets);

	//判断某个SOCKET句柄是否在数组中
	//FD_ISSET(socketServer,&allSockets);


	//释放所有句柄
	for (u_int i = 0; i < allSockets.fd_count; i++)
	{
		closesocket(allSockets.fd_array[i]);
	}

	//+closesocket(socketServer);
	WSACleanup();

	//free(lpw);

	system("pause");
	return 0;
}

思考:

1.FD_SET数组中删除元素使用的是逻辑删除,即直接设置fd_count属性,这样做的好处是什么?
效率高,速度快
2.如何防止客户端异常退出?
可以在发送代码时通过判断输入特殊字符串就跳出循环,从而执行WSACleanup();

		scanf("%s",sendbuff);
		//正常退出循环
		if (sendbuff == '0')
		{
			break;
		}

3.select虽然解决了等待问题,但是没有解决执行阻塞问题,为什么要专门设置模型来解决执行阻塞?
因为recv、send函数在客户端数量较大的时候,调用次数非常多,因此执行阻塞必须要解决。
4.客户端可以使用之前的基本通信模型的代码,但是在客户端接收消息这里:

char recvbuff[1500]={0};
int res = recv(socketServer,recvbuff,sizeof(recvbuff),0);

由于服务器端发送过来的消息是:hello,长度只有5,客户端非要用长度为1500的长度的字符数据进行接收,就会出现:
在这里插入图片描述
因此要把客户端接收消息的代码中,长度改成具体消息的长度(这里是5)

int res = recv(socketServer,recvbuff,5,0);

或者把服务器发送消息部分代码也改成对应长度的字符数组。

升级vs2019说明

可能会出现
error D8016: “/ZI”和“/Gy-”命令行选项不兼容
的提示。
点击【项目】→【属性】→【C/C++】
两种解决方案:
法1、【常规】页面下,选下面红线中两个中的其中一个即可
在这里插入图片描述
法2、【代码生成】页面下
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP.02.SELECT模型 的相关文章

  • 使用 InputStream 通过 TCP 套接字接收多个图像

    每次我从相机捕获图像时 我试图将多个图像自动从我的 Android 手机一张一张地发送到服务器 PC 问题是read 函数仅在第一次时阻塞 因此 从技术上讲 只有一张图像被接收并完美显示 但在那之后当is read 回报 1 该功能不阻塞
  • TCP打孔问题

    我尝试使用 Python 3 中概述的原则为防火墙编写一个基本的 TCP 打孔器本文 http www bford info pub net p2pnat index html 不过 我无法连接任何东西 这是代码 usr bin pytho
  • Python pandas 按日期列表选择行

    如何通过日期列表选择数据框的多行 dates pd date range 20130101 periods 6 df pd DataFrame np random randn 6 4 index dates columns list ABC
  • TCP 连接寿命

    客户端 服务器 TCP 连接在野外可以持续多长时间 我希望它保持永久连接 但事情发生了 所以客户端将不得不重新连接 我什么时候可以说代码有问题而不是某些外部设备有问题 我同意赞 林克斯的观点 虽然无法保证 但假设不存在连接或带宽问题 您可以
  • Java TCP Echo 服务器 - 广播

    我有一个简单的回显服务器 我希望当连接的用户向服务器键入任何内容时 所有其他客户端和该客户端都会收到消息 MOD 它现在不会发送给所有客户端 但它应该发送 而且我只是不知道我的代码出了什么问题 所以现在它只会将消息 MOD 发送给发送消息的
  • 使用 jQuery 选择焦点文本在 Safari 和 Chrome 中不起作用

    我有以下 jQuery 代码 类似于这个问题 https stackoverflow com questions 480735 在 Firefox 和 IE 中工作 但在 Chrome 和 Safari 中失败 没有错误 只是不起作用 有解
  • XPath 选择具有特定属性值的元素?

    我在使用 XPath 选择节点时遇到问题 我将展示一个示例 由于实际数据量很大 xml 文件被缩短了 这是 XML 的子集
  • 使用 select_ 和starts_with R

    为什么这段代码不起作用 mtcars gt select starts with d Error in eval expr envir enclos could not find function starts with 这是简化的示例 我
  • 如何使用另一个表中存在的列名创建表?

    我正在研究 SQL Server 2016 我想知道是否可以创建一个永久或临时表 其列名存在于另一个表中 请参阅下表 MAINTAGS 及其代码 我希望 DOB 作为我的第一栏 POB 作为第二栏 依此类推 目前我的 MAINTAGS 表中
  • SQL语句帮助--选择今天下单的客户

    假设我有一个存储客户订单 ID 的表 例如 客户 ID 订单编号 订购日期 如何获取今天下单的所有客户 OrderDate 也将是一个 DateTime 就像是 SELECT DISTINCT CustomerID FROM TableNa
  • ConnectionTimeout 与 SocketTimeout

    我正在使用的库有问题 可能是图书馆的问题 也可能是我用错了 基本上 当我这样做时 超时以毫秒为单位 ignitedHttp setConnectionTimeout 1 v short ignitedHttp setSocketTimeou
  • PHP 通过 TCP/IP 发送消息

    我尝试通过 TCP IP 从 PHP 网站向 Arduino 发送消息 使用以下代码我可以从 php 网站发送消息 问题是 当第一次调用该网站时 消息不会立即发送 网站刷新几次后 消息就会到达 但逻辑上很多次 就像网站刷新量一样 已经尝试将
  • 10G 链路的 netcat 和 iperf 结果存在巨大差异

    我很困惑看到 netcat 和 iperf 结果之间的巨大差异 我有 10 G 链路连接我的服务器和客户端 iperf 的速度约为 10Gb s 但 netcat 的速度仅为约 280 MB s 可能是什么错误 对于 Iperf Serve
  • 使用 TcpClient 通过 C# 通过 TCP 发送多个文件

    我正在尝试使用 C TcpClient 通过 TCP 发送多个文件 对于单个文件来说它效果很好 但是当我有多个文件时 它只发送第一个文件 这是我的代码 发送文件 try TcpClient tcpClient new TcpClient N
  • 在本地主机上使用相同的 IP 和端口创建套接字

    我在 Linux 上看到奇怪的行为 我看到远程端和本地端都显示相同的 IP 和端口组合 以下是 netstat 输出 netstat anp 网络统计grep 6102 tcp 0 0 139 185 44 123 61020 0 0 0
  • Jmeter TCP Sampler - 如何重用线程之间的连接?

    我在 JMeter 的 ThreadGroup 下设置了一个 TCP 采样器 数据是从 CSV 文件中选取的 第一行数据用于认证 后续行为实际参数数据 像下面这样的东西 AAAAAAA21 BBBBBBBCCCCCCCDDDDDDD BBB
  • Java 客户端到服务器未知来源

    我有一个简单的乒乓球游戏 需要通过网络工作 服务器将创建一个带有球和 2 个球棒位置的游戏 当客户端连接到服务器时 服务器将创建一个名为 PongPlayerThread 的新类 它将处理客户端到服务器的输入和输出流 我的服务器工作100
  • C++ Socket选择和接收问题

    下面是我在套接字编程方面遇到问题的代码片段 在此之后select调用 如果我不在第 9 行放置睡眠 则在 Windows XP 上 第 11 行收到 1 个字节 而不是从服务器作为整数发送 4 个字节 当我检查 xmlSize 时 它 被设
  • 对卡在 CLOSE_WAIT 状态的连接进行故障排除

    我有一个在 Windows 上的 WebLogic 11g 中运行的 Java 应用程序 几天后它变得没有响应 我注意到的一个可疑症状是大量连接 大约 3000 个 出现在netstat即使服务器空闲 也具有 CLOSE WAIT 状态 由
  • 在 SQL 中,如何从 SELECT * FROM ... 中排除结果?

    我知道我的标题不太具有描述性 让我在这里详细解释一下 假设一个表有 26 个字段 例如字段 a 字段 z 我只想要一个选择查询只返回 15 个字段 所以 通常 我会执行 SELECT field a field b field o FROM

随机推荐