Обратный вызов в C - это функция, которая предоставляется другой функции для «обратного вызова» в некоторый момент, когда другая функция выполняет свою задачу.
Существует два способа использования обратного вызова : синхронный обратный вызов и асинхронный обратный вызов. Синхронный обратный вызов предоставляется другой функции, которая собирается выполнить некоторую задачу, а затем возвращается к вызывающей стороне с выполнением задачи. Асинхронный обратный вызов предоставляется другой функции, которая собирается запустить задачу, а затем вернуться к вызывающей стороне с задачей, возможно, не завершенной.
Синхронный обратный вызов обычно используется для предоставления делегата другой функции, которой другая функция делегирует некоторый этап задачи. Классическими примерами этого делегирования являются функции bsearch()
и qsort()
из стандартной библиотеки Си. Обе эти функции принимают обратный вызов, который используется во время задачи, которую предоставляет функция, так что тип данных, в которых выполняется поиск, в случае bsearch()
или сортировка, в случае qsort()
, не должен быть известен функции, являющейся используемый.
Например, вот небольшой пример программы с bsearch()
использованием различных функций сравнения, синхронных обратных вызовов. Позволяя нам делегировать сравнение данных функции обратного вызова, bsearch()
функция позволяет нам во время выполнения решить, какой тип сравнения мы хотим использовать. Это синхронно, потому что, когда bsearch()
функция возвращает, задача завершена.
#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);
}
}
Асинхронный обратный вызов отличается тем, что когда возвращается вызываемая функция, которой мы предоставляем обратный вызов, задача может быть не выполнена. Этот тип обратного вызова часто используется с асинхронным вводом-выводом, в котором операция ввода-вывода начинается, а затем, когда она завершается, вызывается обратный вызов.
В следующей программе мы создаем сокет для прослушивания запросов соединения TCP, и когда запрос получен, функция, выполняющая прослушивание, затем вызывает предоставленную функцию обратного вызова. Это простое приложение можно запустить, запустив его в одном окне, используя telnet
утилиту или веб-браузер, чтобы попытаться подключиться в другом окне.
Я взял большую часть кода WinSock из примера, который Microsoft предоставляет с помощью accept()
функции по адресу https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx
Это приложение запускает listen()
на локальном хосте 127.0.0.1, используя порт 8282, чтобы вы могли использовать либо telnet 127.0.0.1 8282
или http://127.0.0.1:8282/
.
Этот пример приложения был создан как консольное приложение с Visual Studio 2017 Community Edition и использует версию сокетов Microsoft WinSock. Для приложения Linux функции WinSock должны быть заменены альтернативами Linux, и pthreads
вместо них будет использоваться библиотека потоков Windows .
#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);
}