Varios modos de programación de Socket
El principio básico es: primero establecer una conexión de socket y luego operarla, por ejemplo, leer datos del socket. Debido a que la transmisión de la red lleva una cierta cantidad de tiempo, incluso si la red está abierta, la operación de recepción de datos llevará tiempo. Para un programa simple de un solo subproceso, el proceso de recepción de datos no puede manejar otras operaciones. Por ejemplo, para un programa de ventana, cuando recibe datos, hacer clic en el botón o cerrar la ventana no será efectivo. Sus deficiencias son obvias. Solo puede procesar un socket por subproceso, lo cual está bien para la enseñanza, pero no es efectivo en el uso real. Modelo selecto Para manejar múltiples conexiones de enchufe, personas inteligentes inventaron el modelo selecto. Este modelo utiliza una colección para administrar las conexiones de socket y consulta el estado del socket en la colección cada vez para lograr la capacidad de manejar múltiples conexiones. Su prototipo de función es int select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set. FAR * exceptfds, const struct timeval FAR * timeout). Por ejemplo, para determinar si un socket tiene datos legibles, primero vaciamos una colección fdread, luego agregamos el socket a la colección, llamamos a select(0,&fdread,NULL,NULL,NULL) y luego determinamos si el socket todavía está allí en fdread, si todavía está allí, significa que hay datos para leer. Los modelos de lectura y bloqueo de datos son los mismos, llamando a la función recv. Sin embargo, cada capacidad de recopilación tiene un límite, que es 64 de forma predeterminada. Por supuesto, puede redefinir su tamaño, pero todavía hay un límite máximo que no puede exceder si lo establece usted mismo. Aunque el modelo seleccionado puede manejar múltiples conexiones, la gestión de colecciones es algo engorrosa. Modelo de selección asincrónica Cualquiera que esté familiarizado con el sistema operativo Windows sabe que su procesamiento de ventanas se basa en mensajes. La gente ha inventado un nuevo modelo de red: el modelo WSAAsyncSelect, que es un modelo de selección asincrónico. Este modelo vincula un mensaje a cada socket. Cuando ocurre un evento de socket preestablecido en el socket, el sistema operativo enviará este mensaje a la aplicación para procesar el evento de socket. Su prototipo de función es int WSAAsynSelect(SOCKET s, HWND hWnd, unsigned int. wMsg, evento largo). hWnd especifica el identificador para recibir el mensaje, wMsg especifica el ID del mensaje, lEvent establece el evento de red de interés en bits, ingresa WSAAsyncSelect(s,hwnd,WM_SOCKET, FD_CONNECT | FD_READ | FD_CLOSE). La ventaja de este modelo es que puede manejar muchas conexiones al mismo tiempo con poca sobrecarga del sistema y no requiere ninguna gestión de recopilación. La desventaja es obvia, incluso si su programa no necesita una ventana, debe definir una ventana específicamente para el modelo WSAAsyncSelect. Además, permitir que una sola ventana maneje miles de eventos de operación de sockets probablemente se convierta en un cuello de botella en el rendimiento. Modelo de selección de eventos Similar al modelo WSAAsynSelect, la gente también inventó el modelo WSAAventSelect, el modelo de selección de eventos. Como puedes adivinar por el nombre, está basado en eventos. Cuando ocurre un evento de socket de interés en el modelo WSAAsynSelect, el sistema enviará el mensaje correspondiente. Cuando ocurre un evento de socket de interés en el modelo WSAEventSelect, el sistema configurará el evento WSAEVENT correspondiente para señalización. Quizás no tenga muy claro cuál es el evento sokect y el evento WSAEVENT ordinario. Los eventos de socket son eventos relacionados con operaciones de socket, como FD_READ, FD_WRITE, FD_ACCEPT, etc. El evento WSAEVENT es un evento tradicional. Este evento tiene dos estados, señalizado (señalizado) y no señalizado (no señalizado).
La llamada comunicación significa que el evento ha sucedido y la falta de comunicación significa que aún no ha sucedido. Cada vez que establecemos una conexión, le vinculamos un evento. Cuando la conexión cambia, el evento se convertirá en el estado de señalización. Entonces, ¿quién aceptará este cambio de acontecimientos? Esperamos que ocurran eventos a través de una función WSAWaitForMultipleEvents(...) En la matriz de eventos pasada, si solo ocurre un evento, la función regresará (también se puede configurar para que regrese solo cuando ocurran todos los eventos, lo cual es inútil). aquí)), el valor de retorno es el número de secuencia de la matriz del evento, para que sepamos qué evento ocurrió, es decir, el socket correspondiente al evento tiene un evento de operación de socket. Este modelo tiene ventajas obvias sobre el modelo WSAAsynSelect y no requiere una ventana. La única desventaja es que este modelo solo puede esperar 64 eventos a la vez. Esta limitación hace que cuando se trata de múltiples sockets, sea necesario organizar un grupo de subprocesos, y la escalabilidad no es tan buena como la del modelo superpuesto que se analizará más adelante. Modelo de E/S superpuestas (E/S superpuestas) El modelo de E/S superpuestas (E/S superpuestas) permite que las aplicaciones logren un mejor rendimiento del sistema. El principio de diseño básico del modelo superpuesto es permitir que las aplicaciones utilicen estructuras de datos superpuestas para entregar una o más solicitudes de E/S de Winsock a la vez. ¿Qué es exactamente un modelo superpuesto? Se puede comparar con el modelo WSAEventSelect (de hecho, no es apropiado, lo hablaré más adelante). El modelo de selección de eventos vincula un evento a cada conexión de socket, mientras que el modelo de superposición vincula una superposición a cada conexión de socket. Cuando ocurre un evento de socket en la conexión, se actualiza la superposición correspondiente. De hecho, la ventaja de la superposición es que, al actualizar la superposición, también transfiere los datos de la red al área de caché especificada por la implementación. Sabemos que el modelo de red anterior requiere que los usuarios reciban datos a través de la función recv, lo que reduce la eficiencia. Usemos una analogía. El modelo WSAEventSelect es como una notificación de paquete en la oficina de correos. Después de recibir la notificación, el usuario debe ir a la oficina de correos para recoger el paquete. El modelo superpuesto es como la entrega puerta a puerta. Cuando el cartero le envía una notificación, también coloca el paquete en el almacén que usted designó con anticipación. El modelo superpuesto se divide en dos modos: notificación de eventos y rutina de finalización. Antes de analizar estos dos modos, echemos un vistazo a la estructura de datos superpuesta: typedef struct WSAOVERLAPPED{DWORD Internal;DWORD InternalHigh;DWORD Offset;DWORD OffsetHigh;WSAEVENT hEvent;}WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;En esta estructura de datos, Internal, InternalHigh , Offset y OffsetHigh son utilizados por el sistema. Los usuarios no necesitan preocuparse por ellos. Lo único que les importa es hEvent. Si se utiliza el modo de notificación de eventos, hEvent apunta al identificador de evento correspondiente. Si es el modo de rutina de finalización, hEvent se establece en NULL. Veamos ahora el modo de notificación de eventos. Primero, cree un evento hEvent, cree una estructura superpuesta AcceptOverlapped y configure AcceptOverlapped.hEvent = hEvent es el caché de datos que configuramos de antemano. Llamar a WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,NULL) se superpondrá y unirá AcceptSocket y AcceptOverlapped. Cuando se reciben los datos, hEvent se configurará en señalización y los datos se colocarán en DataBuf. Luego recibimos la notificación del evento a través de WSAWaitForMultipleEvents(...). Debemos señalar aquí que, dado que se basa en la notificación de eventos, tiene un límite superior para el procesamiento de eventos, que generalmente es 64. La diferencia entre la rutina de finalización y el modo de notificación de eventos es que cuando ocurre el evento de socket correspondiente, el sistema llamará a la función de devolución de llamada especificada por el usuario de antemano en lugar de configurar el evento.
De hecho, se trata de establecer el último parámetro de WSARecv en un puntero de función. El prototipo de la función de devolución de llamada es el siguiente: void CALLBACK CompletionROUTINE(DWORD dwError,DWORD cbTransferred,LPWSAOVERLAPPED lpOverlapped,DWORD dwFlags); donde cbTransferred representa el número de bytes transferidos y lpOverlapped es el puntero de superposición donde ocurre el evento de socket. Llamamos a WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,WorkerRoutine) para vincular AcceptSocket a la rutina WorkRoutine. Aquí hay un pequeño consejo. Cuando creamos múltiples conexiones de socket, es mejor juntar los búferes de datos superpuestos y correspondientes en una estructura de datos grande. De esta manera, podemos encontrarlos directamente a través del puntero lpOverlapped en la rutina. área de caché. Cabe señalar aquí que no se puede utilizar el mismo búfer de datos para varias superposiciones. En este caso, se producirá confusión de datos cuando se procesen varias superposiciones. Modelo de puerto de finalización Introduzcamos un modelo de red diseñado específicamente para manejar numerosas conexiones de socket: puerto de finalización. Debido a que agregar un socket a un puerto de finalización requiere mucho trabajo y los pasos de inicialización de otros métodos son mucho más simples, el modelo de puerto de finalización puede parecer demasiado complejo para los principiantes. Sin embargo, una vez que descubras lo que está pasando, descubrirás que los pasos no son tan complicados. El llamado puerto de finalización es en realidad un mecanismo de construcción de E/S utilizado por Windows. Además de los identificadores de socket, también puede aceptar otras cosas. Antes de usar este modo, primero debe crear un objeto de puerto de finalización de E/S. La función se define de la siguiente manera: HANDLE CreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,DWORD CompletionKey,DWORD NumberOfConcurrentThreads). ) se utiliza para crear un objeto de puerto de finalización. 2) Asocie una manija con el puerto de terminación. A través del parámetro NumberOfConcurrentThreads, podemos especificar la cantidad de subprocesos que se ejecutan al mismo tiempo. Idealmente, esperamos que cada procesador sea responsable de ejecutar un subproceso, proporcionar servicios para el puerto de finalización y evitar cambios de tarea de subproceso demasiado frecuentes. Para una conexión de socket, vinculamos la conexión Accept al puerto de finalización CompletionPort a través de CreateIoCompletionPort((HANDLE)Accept,CompletionPort, (DWORD)PerHandleData,0. Los subprocesos correspondientes a CompetionPort consultan continuamente su conexión de socket asociada a través de GetQueuedCompletionStatus. Si se completa alguna operación de E/S, de ser así, realice el procesamiento de datos correspondiente y luego entregue la conexión del socket nuevamente a través de WSARecv para continuar trabajando. Los puertos de finalización funcionan muy bien en términos de rendimiento y escalabilidad, y no hay límite en la cantidad de conexiones de socket asociadas.