Funkcja zwrotna w C to funkcja udostępniana innej funkcji w celu „oddzwonienia” w pewnym momencie, gdy inna funkcja wykonuje swoje zadanie.
Istnieją dwa sposoby wykorzystania wywołania zwrotnego : synchroniczny i asynchroniczny callback zwrotna. Synchroniczne wywołanie zwrotne jest dostarczane do innej funkcji, która wykona jakieś zadanie, a następnie wróci do dzwoniącego po zakończeniu zadania. Asynchroniczne wywołanie zwrotne jest dostarczane do innej funkcji, która ma rozpocząć zadanie, a następnie wrócić do obiektu wywołującego z zadaniem prawdopodobnie nie zakończonym.
Synchroniczne wywołanie zwrotne jest zwykle używane w celu zapewnienia delegata innej funkcji, do której druga funkcja deleguje pewien krok zadania. Klasycznymi przykładami tej delegacji są funkcje bsearch()
i qsort()
biblioteka standardowa C. Obie te funkcje przyjmują wywołanie zwrotne, które jest używane podczas zadania, które funkcja zapewnia, tak że typ wyszukiwanych danych, w przypadku bsearch()
lub sortowania, w przypadku qsort()
, nie musi być znany funkcji będącej używany.
Na przykład tutaj jest mały przykładowy program bsearch()
wykorzystujący różne funkcje porównawcze, synchroniczne wywołania zwrotne. Umożliwiając nam delegowanie porównania danych do funkcji zwrotnej, bsearch()
funkcja ta pozwala nam zdecydować w czasie wykonywania, jakiego rodzaju porównania chcemy użyć. Jest to synchroniczne, ponieważ gdy bsearch()
funkcja zwraca, zadanie jest zakończone.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int iValue;
int kValue;
char label[6];
} MyData;
int cmpMyData_iValue (MyData *item1, MyData *item2)
{
if (item1->iValue < item2->iValue) return -1;
if (item1->iValue > item2->iValue) return 1;
return 0;
}
int cmpMyData_kValue (MyData *item1, MyData *item2)
{
if (item1->kValue < item2->kValue) return -1;
if (item1->kValue > item2->kValue) return 1;
return 0;
}
int cmpMyData_label (MyData *item1, MyData *item2)
{
return strcmp (item1->label, item2->label);
}
void bsearch_results (MyData *srch, MyData *found)
{
if (found) {
printf ("found - iValue = %d, kValue = %d, label = %s\n", found->iValue, found->kValue, found->label);
} else {
printf ("item not found, iValue = %d, kValue = %d, label = %s\n", srch->iValue, srch->kValue, srch->label);
}
}
int main ()
{
MyData dataList[256] = {0};
{
int i;
for (i = 0; i < 20; i++) {
dataList[i].iValue = i + 100;
dataList[i].kValue = i + 1000;
sprintf (dataList[i].label, "%2.2d", i + 10);
}
}
// ... some code then we do a search
{
MyData srchItem = { 105, 1018, "13"};
MyData *foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_iValue );
bsearch_results (&srchItem, foundItem);
foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_kValue );
bsearch_results (&srchItem, foundItem);
foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_label );
bsearch_results (&srchItem, foundItem);
}
}
Asynchroniczne wywołanie zwrotne różni się tym, że gdy wywoływana funkcja, której dostarczamy, powraca, zadanie może nie zostać ukończone. Ten typ wywołania zwrotnego jest często używany w przypadku asynchronicznych operacji we / wy, w których operacja we / wy jest uruchamiana, a po jej zakończeniu wywoływana jest funkcja zwrotna.
W poniższym programie tworzymy gniazdo do nasłuchiwania żądań połączenia TCP, a po odebraniu żądania funkcja nasłuchująca wywołuje dostarczoną funkcję zwrotną. Tę prostą aplikację można uruchomić, uruchamiając ją w jednym oknie, podczas gdy telnet
narzędzie lub przeglądarka internetowa próbuje połączyć się w innym oknie.
Podniosłem większość kodu WinSock z przykładu dostarczonego przez Microsoft z accept()
funkcją pod adresem https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx
Aplikacja ta rozpoczyna się listen()
na lokalnym komputerze, 127.0.0.1, za pomocą portu 8282, więc można użyć jednej telnet 127.0.0.1 8282
lub http://127.0.0.1:8282/
.
Ta przykładowa aplikacja została utworzona jako aplikacja konsolowa w programie Visual Studio 2017 Community Edition i korzysta z wersji gniazd Microsoft WinSock. W przypadku aplikacji dla systemu Linux funkcje WinSock musiałyby zostać zastąpione alternatywami dla systemu Linux, a biblioteka wątków systemu Windows byłaby używana pthreads
zamiast tego.
#include <stdio.h>
#include <winsock2.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
// Need to link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")
// function for the thread we are going to start up with _beginthreadex().
// this function/thread will create a listen server waiting for a TCP
// connection request to come into the designated port.
// _stdcall modifier required by _beginthreadex().
int _stdcall ioThread(void (*pOutput)())
{
//----------------------
// Initialize Winsock.
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
printf("WSAStartup failed with error: %ld\n", iResult);
return 1;
}
//----------------------
// Create a SOCKET for listening for
// incoming connection requests.
SOCKET ListenSocket;
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET) {
wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
//----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port for the socket that is being bound.
struct sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(8282);
if (bind(ListenSocket, (SOCKADDR *)& service, sizeof(service)) == SOCKET_ERROR) {
printf("bind failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//----------------------
// Listen for incoming connection requests.
// on the created socket
if (listen(ListenSocket, 1) == SOCKET_ERROR) {
printf("listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//----------------------
// Create a SOCKET for accepting incoming requests.
SOCKET AcceptSocket;
printf("Waiting for client to connect...\n");
//----------------------
// Accept the connection.
AcceptSocket = accept(ListenSocket, NULL, NULL);
if (AcceptSocket == INVALID_SOCKET) {
printf("accept failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
else
pOutput (); // we have a connection request so do the callback
// No longer need server socket
closesocket(ListenSocket);
WSACleanup();
return 0;
}
// our callback which is invoked whenever a connection is made.
void printOut(void)
{
printf("connection received.\n");
}
#include <process.h>
int main()
{
// start up our listen server and provide a callback
_beginthreadex(NULL, 0, ioThread, printOut, 0, NULL);
// do other things while waiting for a connection. In this case
// just sleep for a while.
Sleep(30000);
}