Chapter 21 of TCP/IP network programming

Posted by Boxerman on Mon, 17 Jan 2022 07:31:07 +0100

Understanding the asynchronous notification I/O model

Synchronous and asynchronous

The key of synchronization is the call and return time of function, as well as the start and completion time of data transmission.

  • Data transmission starts at the moment when the send function is called, and the data transmission is completed at the moment when the send function is executed (returned) (data is completely transmitted to the output buffer, and the operating system completes network transmission).
  • The moment when the recv function is called starts to receive data, and the moment when the recv function is completed (returned).

Asynchronous, inconsistent.
Asynchronous I/O means that the return time of I/O function is inconsistent with the completion time of data sending and receiving.

Disadvantages of synchronous I/O and asynchronous solutions

During I/O synchronization, functions cannot return and cannot perform other tasks.
Asynchronous I/O immediately returns functions, which can perform other tasks and use the CPU more effectively.

Asynchronous notification I/O model

The asynchronous working mode is "Notification I/O".
Notify I/O: notify that the input buffer receives data and needs to be read, and that the output buffer is empty, so data can be sent.

Select notifies I/O in a synchronous manner. The time point at which I/O is required or available (the time point at which I/O related events occur) is consistent with the return time point of the select function.

In asynchronous notification I/O, the function that specifies the I/O monitoring object and the function that actually verifies the state change are separated from each other. After specifying the monitoring object, you can leave to perform other tasks, and finally come back to verify the state change.

Implement asynchronous notification I/O model

WSAAsyncSelect function, you need to specify a Windows handle to get the events (UI related content), which will not be covered in this book.

WSAEventSelect function

I/O status change

  • I/O state change of socket
  • Socket I/O related events occurred
#include <winsock2.h>

//Success 0, failure SOCKET_ERROR
int WSAEventSelect(
	SOCKET s, 
	WSAEVENT hEventObject, 	//Event object handle
	long lNetworkEvents		//Monitored event type information, or operation
);

As long as one of the events specified in lNetworkEvents occurs in the socket passing in parameter s, the WSAEventSelect function changes the kernel object referred to by the hEventObject handle to the signaled state. This function is a function connecting the event object and socket. It works in the form of asynchronous notification and returns directly after calling.

Event type information:

  • FD_READ: readable
  • FD_WRITE: writable
  • FD_OOB: out of band data received
  • FD_ACCEPT: new connection
  • FD_CLOSE: disconnect

Creation of event object in manual reset mode

The CreateEvent function can create an auto reset or manual reset mode event object.
Wsa createevent only creates event objects in manual reset mode and non signaled state.

#include <winsock2.h>

//#define WSAEVENT HANDLE

//Failed WSA_INVALID_EVENT
WSAEVENT WSACreateEvent(void);

//Destroy the event object created by the above function
BOOL WSACloseEvent(WSAEVENT hEvent);

Verify that an event has occurred

#include <winsock2.h>

//Failed WSA_INVALID_EVENT
DWORD WSAWaitForMultipleEvents(
	DWORD cEvents,//The number of event objects that verify whether they are transferred to the signaled state
	const WSAEVENT *lphEvents,//Event object handle array
	BOOL fWaitAll,//If TRUE, only the signaled status of all event objects is returned; FALSE, the signaled status of any event object is returned
	DWORD dwTimeout,//Timeout (1 / 1000 second), WSA_INFINITE has been waiting
	BOOL fAlertable//When TRUE, enter the alertable wait state
);
//Return value minus WSA_WAIT_EVENT_0, get the corresponding index of the event object handle converted to the signaled state (event object handle array).
//Multiple signaled state event objects get smaller values (only one signaled state event object handle can be obtained at a time).
//Timeout return WAIT_TIMEOUT

Up to 64 event objects can be passed. If you need to monitor more handles, you can only create a thread or extend the array that holds handles, and call the above function multiple times.

Gets the event object for all signaled states.

int posInfo, startIdx, i;
posInfo = WSAWaitForMultipleEvents(nmuOfSock, hEventArray, FALSE, WSA_INFINITE, FALSE);
startIdx = posInfo-WSA_WAIT_EVENT_0;
for(i=startIdx; i<numOfSock, i++) {
	int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArray[i], TRUE, 0, FALSE)-WSA_WAIT_EVENT_0;
}

Distinguish event types

#include <winsock2.h>

//Success 0, failure SOCKET_ERROR
int WSAEnumNetworkEvents(
	SOCKET s,
	WSAEVENT hEventObject, //The event object handle of the signaled state associated with the socket
	LPWSANETWORKEVENTS lpNetworkEvents//Save event type information and error information
);

The above function changes the manual reset mode event object to the non signaled state without calling the ResetEvent function separately.

type struct _WSANETWORKEVENTS {
	long lNetworkEvents;
	int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

View the type of event that occurred

WSANETWORKEVENTS netEvents;

WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.lNetworkEvents & FD_ACCEPT) {
	//
}
if(netEvents.lNetworkEvents & FD_READ) {
	//
}
if(netEvents.lNetworkEvents & FD_WRITE) {
	//
}
if(netEvents.lNetworkEvents & FD_CLOSE) {
	//
}

The iErrorCode array holds error information.

  • FD_ Read error, view iErrorCode[FD_READ_BIT]
  • FD_ Write error, view iErrorCode[FD_WRITE_BIT]
  • FD_ XXX error, view iErrorCode[FD_XXX_BIT]
WSANETWORKEVENTS netEvents;

WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.iErrorCode[FD_READ_BIT] != 0) {
	//
}

Asynchronous notification I/O model echo server

AsyncNotiEchoServ_win.c

#include <stdio.h>
#include <string.h>
#include <windock2.h>

#define BUF_SIZE 100
void CompressSockets(SOCKET hSockArr[], int idx, int total);
void CompressEvents(WSAEVENT hEventArr[], int ind, int total);
void error_handling(char *message);

int main(int argc, char *argv[]) {
	WSADATA wsaData;
	SOCKET serv_sock;
	SOCKET clnt_sock;

	SOCKADDR_IN serv_addr;
	SOCKADDR_IN clnt_addr;
	int addr_size;

	SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS];
	WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];
	WSAEVENT newEvent;
	WSANETWORKEVENTS netEvents;

	int numOfClntSock=0;
	int strLen, i;
	int posInfo, startIdx;
	int clntAdrLen;
	char msg[BUF_SIZE];
	
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
		error_handling("WSAStartup() error!");

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock==INVALID_SOCKET)
		error_handling("socket() error");

	addr_size = sizeof(SOCKADDR_IN);
	
	memset(&serv_addr, 0, addr_size);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(atoi(argv[1]));
	
	if(bind(serv_sock, (SOCKADDR*)&serv_addr, addr_size)==SOCKET_ERROR)
		error_handling("bind() error");
		
	if(listen(serv_sock, 5)==SOCKET_ERROR)
		error_handling("listen() error");
		
	newEvent = WSACreateEvent();
	if(WSAEventSelect(serv_sock, newEvent, FD_ACCEPT)==SOCKET_ERROR)
		error_handling("WSAEventSelect() error");
	
	hSockArr[numOfClntSock] = serv_sock;
	hEventArr[numOfClntSock] = newEvent;
	numOfClntSock++;
	
	while(1) {
		posInfo = WSAWaitForMultipleEvents(numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);
		startIdx = posInfo-WSA_WAIT_EVENT_0;

		for(i=startIdx; i<numOfClntSock; i++) {
			int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);
			if(sigEventIdx==WSA_WAIT_FAILED || sigEventIdx==WSA_WAIT_TIMEOUT) {
				continue;
			} else {
				sigEventIdx = i;
				WASEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents);
				if(netEvents.lNetworkEvents & FD_ACCCEPT) {
					if(netEvents.iErrorCode[FD_ACCEPT_BIT]!=0) {
						puts("ACCEPT Error");
						break;
					}
					clnt_sock = accept(hSockArr[sigEventIdx], (SOCKADDR*)&clnt_addr, &addr_size);
					newEvent = WSACreateEvent();
					WSAEventSelect(clnt_sock, newEvent, FD_READ|FD_CLOSE);
					
					hSockArr[numOfClntSock] = clnt_sock;
					hEventArr[numOfClntSock] = newEvent;
					numOfClntSock++;
					puts("connected new client...");
				}
				if(netEvents.lNetworkEvents & FD_READ) {
					if(netEvents.iErrorCode[FD_READ_BIT]!=0) {
						puts("READ Error");
						break;
					}
					strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);
					send(hSockArr[sigEventIdx], msg, strLen, 0);
				}
				if(netEvents.lNetworkEvents & FD_CLOSE) {
					if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0) {
						puts("CLOSE Error");
						break;
					}
					WSACloseEvent(hEventArr[sigEventIdx]);
					closesocket(hSockArr[sigEventIdx]);

					numOfClntSock--;
					CompressSockets(hSockArr, sigEventIdx, numOfClntSock);
					CompressEvents(hEventArr, sigEventIdx, numOfClntSock);
				}
			}
		}
	}
	
	WSACleanup();
	return 0;
}

void CompressSockets(SOCKET hSockArr[], int idx, int total) {
	int i;
	for(i=idx; i<total; i++)
		hSockArr[i] = hSockArr[i+1];
}

void CompressEvents(WSAEVENT hEventArr[], int idx, int total) {
	int i;
	for(i=idx; i<total; i++)
		hEventArr[i] = hEventArr[i+1];
}

void error_handling(char *message) {
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
cl /EHsc AsyncNotiEchoServ_win.c /Fe:AsyncNotiEchoServ_win.exe
AsyncNotiEchoServ_win 9190

Topics: C++ network Network Protocol TCP/IP