Một cuộc gọi lại trong C là một chức năng được cung cấp cho một chức năng khác để "gọi lại" tại một thời điểm nào đó khi chức năng kia đang thực hiện nhiệm vụ của mình.
Có hai cách mà một cuộc gọi lại được sử dụng : gọi lại đồng bộ và gọi lại không đồng bộ. Một cuộc gọi lại đồng bộ được cung cấp cho một chức năng khác sẽ thực hiện một số nhiệm vụ và sau đó trả lại cho người gọi với nhiệm vụ đã hoàn thành. Một cuộc gọi lại không đồng bộ được cung cấp cho một chức năng khác sẽ bắt đầu một tác vụ và sau đó trả về cho người gọi với nhiệm vụ có thể không hoàn thành.
Một cuộc gọi lại đồng bộ thường được sử dụng để cung cấp một ủy nhiệm cho một chức năng khác mà chức năng kia ủy nhiệm một số bước của nhiệm vụ. Các ví dụ cổ điển của phái đoàn này là các chức năng bsearch()
và qsort()
từ Thư viện chuẩn C. Cả hai hàm này đều có một cuộc gọi lại được sử dụng trong tác vụ mà hàm đang cung cấp để loại dữ liệu được tìm kiếm, trong trường hợp bsearch()
hoặc được sắp xếp, trong trường hợp qsort()
, không cần biết bởi hàm đang được đã sử dụng.
Ví dụ ở đây là một chương trình mẫu nhỏ với bsearch()
việc sử dụng các hàm so sánh khác nhau, các cuộc gọi lại đồng bộ. Bằng cách cho phép chúng ta ủy quyền so sánh dữ liệu cho hàm gọi lại, bsearch()
hàm cho phép chúng ta quyết định trong thời gian chạy loại so sánh nào chúng ta muốn sử dụng. Điều này là đồng bộ bởi vì khi bsearch()
hàm trả về nhiệm vụ hoàn thành.
#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);
}
}
Một cuộc gọi lại không đồng bộ khác ở chỗ khi hàm được gọi mà chúng ta cung cấp trả về gọi lại, tác vụ có thể không được hoàn thành. Kiểu gọi lại này thường được sử dụng với I / O không đồng bộ trong đó một thao tác I / O được bắt đầu và sau đó khi nó được hoàn thành, cuộc gọi lại được gọi.
Trong chương trình sau, chúng tôi tạo một ổ cắm để lắng nghe các yêu cầu kết nối TCP và khi nhận được yêu cầu, chức năng thực hiện nghe sẽ gọi chức năng gọi lại được cung cấp. Ứng dụng đơn giản này có thể được thực hiện bằng cách chạy nó trong một cửa sổ trong khi sử dụng telnet
tiện ích hoặc trình duyệt web để cố gắng kết nối trong một cửa sổ khác.
Tôi đã gỡ bỏ hầu hết mã WinSock từ ví dụ Microsoft cung cấp với accept()
chức năng tại https://msdn.microsoft.com/en-us/l Library / windows / desktop / ms737526 (v = vs85) .aspx
Ứng dụng này khởi động listen()
máy chủ cục bộ, 127.0.0.1, sử dụng cổng 8282 để bạn có thể sử dụng telnet 127.0.0.1 8282
hoặc http://127.0.0.1:8282/
.
Ứng dụng mẫu này được tạo ra dưới dạng một ứng dụng bảng điều khiển với Visual Studio 2017 Community Edition và nó đang sử dụng phiên bản ổ cắm Microsoft WinSock. Đối với một ứng dụng Linux, các chức năng WinSock sẽ cần được thay thế bằng các lựa chọn thay thế của Linux và thư viện các luồng của Windows sẽ sử dụng pthreads
thay thế.
#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);
}