Table of Contents
מבוא לתכנות Socket ב C
בניית יישום צ'אט מאפס היא אחת הדרכים הטובות ביותר לשלוט על יסודות תכנות רשת.שימוש בשקעים C נותן לך שליטה ישירה על כל אינטראקציה ברשת, מחיבור להעברת נתונים.פרויקט זה הולך באמצעות יצירת שרת צ'אט רב-קליש ותוכנית לקוח שמתקשר בזמן אמת.לאורך הדרך, תלמד כיצד ליצור שקעים, לקשור אותם לנמלים, להקשיב לחיבורים, לקבל, ולשלוח הודעות, ולקבל הודעות מרובות, כמו שרתי משחקים באופן ישיר, כמו גם לשרתים.
אנו מניחים ידע בסיסי של תכנות C (variables, לולאות, פונקציות, נקודות) וסביבת עבודה עם C מהפך C (GCC) ומערכת הפעלה POSIX (לינוקס, macOS, או WSL על Windows). כל דוגמאות הקוד לעקוב אחר ממשק ה- BSD socket API, שהוא תקן לתכנות ברשת במערכות דומות.
הבנת C Sockets
[המכונה] היא נקודת מוצא לתקשורת בין שני מכונות ברשת.ב- API של POSIX, cket מיוצג על ידי קובץ Integer.הסוגים הנפוצים ביותר הם FLT:0stream socketsssektureFLT:1 (SODP STREAM) באמצעות TCP ו-FpLT:2datagram sockulrated (SOCK ) באמצעות זרם אמין, השתמשנו-TK-upit (SOK) באמצעות יישום).
פונקציות הליבה המרכיבים את ה- API של ה-Socket הן:
- (ב) ,0) - יוצר שקע חדש וחוזר קובץ.
- (FLT:1) - עמית עם כתובת IP מסוימת ומספר נמל.
- (ב) ,2 , ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
- (FLT 3: 3) - חסימת עד שלקוח מתחבר, מחזירה שקע חדש עבור לקוח ספציפי זה.
- (ב) , (ב) - לקוח אשר שימש כלקוח כדי לפתוח חיבור לשרת.
- (ב) ו[[1924]], [[1924]]]], [[1924]]]] ו[[1924]]
כל התפקידים הללו מוצהרים ב-FLT:7 (הראשים הנוספים כמו FLT:8) ו- (עבור המרת כתובת IP) נדרשים גם הם, הבנה של ראשים אלה והמבנים שהם מגדירים הוא חיוני.המבנה של ה-FLT:11 מחזיק את הכתובת, הנמל (ברשת לפי סדר), וכתובת IP.
לקבלת התייחסות עמוקה יותר, ייעוץ (FLT:0) מדריך של רשת תכנות תכנות רשתי FLT:1 - משאב קלאסי המסביר כל פונקציה עם דוגמאות ברורות.
הקמת שרת
השרת הוא המרכז המרכזי של יישום הצ'אט.זה מקשיב לחיבורי לקוחות, מקבל הודעות מכל לקוח, ומדרדר הודעות אלה לכל שאר הלקוחות המחוברים.
יצירת ה-Socket
הצעד הראשון הוא ליצור שקע עם הפונקציה 1414:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket failed");
exit(EXIT_FAILURE);
}
- (ב) ויקרא י"א: ויקרא כ"ד): "נפגשו בני משפחה ל- IPv4".
- (ב) ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
- (ב) ⁇ 0 ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
תמיד לבדוק את ערך ההחזר; כשל יצירת שקע חייב להיות מטופל בחסד.התפקיד של ה-FLT:16 הדפס את תיאור השגיאה של ממריצים.
עקבו אחרי Socket
לאחר מכן, אנו מחברים את השקע לכתובת מקומית ונמל.לקוחות יתחברו לכתובת זו.
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // Bind to all local interfaces
server_addr.sin_port = htons(8080); // Port number
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
(FLT:0)inaDDR ANYFLT:1hil מאפשר לשרת לקבל קשרים על כל כתובת IP שהוקצה למארח.מספר הנמל (80 בדוגמה זו) נבחר להימנע משירותים ידועים; ערכים מעל 1024 אופייניים לתוכניות ברמת המשתמש.
הקשבה לקישורים
לאחר המחייב, השרת חייב להקשיב לחיבורים הבאים.ה-FLT:19 קורא למקומות את השקע למצב פסיבי שבו הוא יהפוך לבקשות חיבור נכנסות.
if (listen(server_fd, 5) < 0) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
הטיעון השני (5) הוא הגבלוג - אורך מקסימלי של תור חיבורים מועדפים.ערך גבוה יותר עשוי לשמש לשרתים עסוקים, אך המערכת מטילה גבול עליון (לעתים קרובות FLT:21).
קבלת קשרי לקוחות
כדי לקבל לקוח, אנו קוראים לוולוה (FLT:22) בתוך לולאה.זה מחזיר קובץ פיקסלים חדש עבור לקוח ספציפי זה, בעוד השרת המקורי שקע ממשיך להקשיב.
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
// In a real server, handle the error without exiting
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("New connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
עבור כל לקוח מקובל, אנו מקבלים את ה- IP והנמל של הלקוח.מידע זה שימושי עבור כניסה ובקרת גישה פוטנציאלית.השקע הלקוח מוכן לאחר מכן להעברת נתונים.
מידע על לקוח
לאחר שלקוח מחובר, השרת חייב להיות מסוגל לקרוא הודעות ולדר אותן.עבור שרת פשוט המטפל לקוח אחד בכל פעם, התבנית האופיינית היא להתקשר ל-FLT:24 על השקע של הלקוח, עם זאת, כדי לתמוך באמת לקוחות מרובים, אתה צריך מטבע.
פיתוח הלקוח
הלקוח פשוט יותר: הוא יוצר שקע, מתחבר לשרת, ואז נכנס לולאה שבה הוא קורא קלט משתמש ושולח אותו לשרת, ובמקביל מקבל הודעות שידור מהשרת.
יצירת וחיבור
ראשית, שקע הלקוח נוצר בדיוק כפי שהשרת עשה.אז הוא מחבר באמצעות כתובת IP של השרת ונמל:
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
// Convert IP address from text to binary
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("invalid address / address not supported");
close(sock);
exit(EXIT_FAILURE);
}
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("connection failed");
close(sock);
exit(EXIT_FAILURE);
}
כאן (FLT:26) הופך את מחרוזת כתובת ה- IP המתקראת לאדם לצורה בינארית.
החלפת Loop
לאחר חיבור, הלקוח צריך לשלוח הודעות מתוקף המשתמש ולקבל הודעות מהשרת בו זמנית.גישה נפוצה היא להשתמש בשני חוטים: אחד לקריאה מ stdin ולשלוח, אחר לקבלת מהשקע. Alternatively, אתה יכול להשתמש ללא חסימת I/O או מערכת FLT:28 שיחת טלפון. לקוח מינימלי שולח רק הודעות (ומתעלם) הוא קל, אבל עבור צ'אט אמיתי אנחנו צריכים תקשורת דו-זמנית.
ה-[[1924]], [[1924]], [[1924]], [[1924]]]], [[1924]]]], [[1924]]]], [[1924]]]]]], [[1924]]]]
// Fork to handle send and receive separately
if (fork() == 0) {
char buffer[1024];
while (1) {
memset(buffer, 0, sizeof(buffer));
int n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) {
printf("Server disconnected\n");
break;
}
printf("%s", buffer);
}
close(sock);
exit(0);
} else {
char message[1024];
while (fgets(message, sizeof(message), stdin) != NULL) {
send(sock, message, strlen(message), 0);
memset(message, 0, sizeof(message));
}
close(sock);
}
דפוס זה פועל, אך ביישום אמיתי תצטרך להתמודד עם הודעה המסתמנת כראוי, כמו TCP הוא פרוטוקול זרם - הודעות עשוי להיות מפוצל או מקודמת.פתרון פשוט הוא להשתמש בדל כגון: FLT:32 או לשלוח את אורך ההודעה קודם.
טיפול לקוחות מרובים
השרת חייב לנהל לקוחות רבים בבת אחת, שתי שיטות פופולריות הן FLT:0 (הנקראות "FLT" 1 (עמוד אחד ללקוח) ו-FLT:2I / O multixingFLT 3: 3 (באמצעות FLT:33 או FLT 34).
שימוש ב-Sles (pthread)
במודל המפוסח, הלולאה העיקרית מקבלת לקוח ויוצרת חוט חדש המוקדש ללקוח זה.החוט מטפל בקבלת הודעות ומדרדר אותם.זה פשוט באופן קונספטואלי ועובד היטב עבור עשרות לקוחות, אבל חוט מעל הראש יכול להפוך לבעיה עם אלפי קשרים.
void *handle_client(void *arg) {
int client_fd = *(int*)arg;
free(arg); // Free the heap‑allocated int
char buffer[1024];
while (1) {
memset(buffer, 0, sizeof(buffer));
int n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) {
// Client disconnected
break;
}
// Broadcast to all other clients
// (requires a mutex‑protected list of client fds)
}
close(client_fd);
return NULL;
}
while (1) {
int *pclient = malloc(sizeof(int));
*pclient = accept(server_fd, ...);
pthread_t tid;
pthread_create(&tid, NULL, handle_client, pclient);
pthread_detach(tid); // Automatically clean up on exit
}
זכור להגן על נתונים משותפים (כמו רשימת שקעים מחוברים) עם mutex.com, להיזהר עם בטיחות חוט של FLT:36 - זה בדרך כלל בטוח, אבל אתה חייב להימנע במקביל כותב לאותו שקע.
שימוש ב-()
מערכת ההפעלה (FLT:37) מאפשרת חוט אחד לפקח על מספר רב של תיאורי קבצים עבור קריאה, עצבנות או שגיאות.זה נמנע מראש של חוטים רבים ויעיל עבור מספר מתון של חיבורים. השרת שומר קבוצה של תיאורי קבצים (server socket + כל שקעים לקוח).
fd_set readfds;
int max_fd = server_fd;
while (1) {
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
for (int i = 0; i < client_count; i++) {
FD_SET(clients[i], &readfds);
if (clients[i] > max_fd) max_fd = clients[i];
}
int activity = select(max_fd + 1, &readfds, NULL, NULL, NULL);
if (activity < 0) { perror("select"); break; }
if (FD_ISSET(server_fd, &readfds)) {
// Accept new client
int new_socket = accept(server_fd, ...);
clients[client_count++] = new_socket;
}
for (int i = 0; i < client_count; i++) {
int sd = clients[i];
if (FD_ISSET(sd, &readfds)) {
char buffer[1024];
int n = recv(sd, buffer, sizeof(buffer), 0);
if (n <= 0) {
// Remove client
close(sd);
clients[i] = clients[--client_count];
i--;
} else {
// Broadcast buffer to all other clients
}
}
}
}
(ה) יתרון אחד של [13] הוא יכולת, אך הוא מוגבל על ידי קונסולת ה-:41 (בדרך כלל 1024) עבור סקאלות גבוהה יותר, שימוש ב-FLT:42 או FLT:43 (במיוחד לינוקס) .
יישום פרוטוקול צ'אט פשוט
כדי להפוך את הצ'אט קוהרנטי, אתה צריך פרוטוקול מינימלי.ללא פרוטוקול, הודעות מלקוחות שונים יכולים להתבלט על חוט.במינימום, להחליט על פורמט הודעה. גישה פשוטה: כל הודעה היא קו אחד סיום עם (FLT:44 השרת רשם את שם השולח או מזהה לפני השידור.
[Alice] Hello everyone!
[Bob] Hi Alice!
כאשר לקוח שולח הודעה, השרת קורא את השורה (באמצעות דלמיטר) ולאחר מכן שולח אותה לכל שאר הלקוחות, באופן אופציונלי עם תיקון.הודעה המסתנן היא קריטית. שיטה חזקה היא לשלוח ראש קבוע הכולל את אורך ההודעה, ואחריו עומס השכר.זה מונע בעיות עם קריאה חלקית.
struct chat_message {
uint32_t length; // in network byte order
char data[1024]; // up to 1024 bytes
};
השרת קורא בדיוק 4 מצעים כדי לקבל את אורך, ואז קורא כי רבים מדפים עבור הנתונים.זה מבטיח הודעות מתקבלות חתיכה אחת.
טעויות ועיסוקים טובים
תכנות רשת מלא מקרים קצה.כמה שיטות חיוניות:
- (ה) עיין ב[[המאה ה-20]], ב[[1924]] וב[[1924]], [[1924]], [[1924]], [[1924]], [[1924]], [[1924]],]]
- (ב) , (ב) , (ב) , ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
- הגדר את האפשרות השקעית (FLT:56) על שקע השרת כדי למנוע "תכתובת כבר בשימוש" שגיאות בעת הפעלת מחדש.
- (ב) ,ב[[1924]], [[1924]], [[1924]]]]]], [[1924]]]], [[1924]]]]]]
- השתמש ב- I/O או Timeouts עם I/O או Timeouts עם חסימה (בשיתוף פעולה) כדי למנוע מהשרת להיתלות על לקוח מת.
- תמיד אפס מציצים לפני הקריאה כדי להבטיח שלא תשלחו נתונים שמאליים.
- ניקוי משאבים - זיכרון צמוד וזיכרון מוקצה חינם - כדי למנוע דליפות.
לצליל עמוק יותר לטיפול בטעויות וטכניקות מתקדמות, קרא את ה-FLT:0 POSIX מפרט לחיבור (ראה)FLT:1 ופונקציות קשורות.
הרחבת הבקשה
לאחר שיש לך צ'אט רב-קלי עבודה, אתה יכול להוסיף תכונות כדי להפוך את זה יותר שימושי:
- (ב) (ב) ,0 משתמשים ב-Fireph1) - לקוחות מספקים שם משתמש לחיבור; השרת מאמת ומאחסן אותו.
- (ב) ,0) הודעות פרטיות (FLT:1) - מאפשרות "טקסט שם משתמש" הוראות השרת רק ללקוח היעד.
- (ב) ,0) ראוטום/תמיכה בתקשורים (בשורה 1) – לקוחות יכולים להצטרף לחדרים; הודעות משודרות רק בתוך חדר.
- (ב) ,0) העברת קבצים בינאריים על ידי יישום ערוץ נתונים נפרד או על ידי שליחת metadata קובץ ו ע"י העברת חומרים גולמיים על אותו שקע.
- (ב) ,0) , קידוד (אנ') (ב) , השתמש ב- OpenSSL או ב-TLS כדי לאבטח תקשורת.
- (FLT:0) ממשק משתמש גריפיתי 1FLT: כתוב לקוח GUI (באמצעות GTK, Qt, או אפילו חזית אינטרנט עם גשר אחורי).
- (ב) ,0) העברת מידע על ידי תפוצה (בתרגום חופשי:0)
כל הרחבה מלמדת מושגים בעלי ערך: עיצוב פרוטוקול, קונקפיד, אבטחה וניסיון למשתמש.הבסיס שאתה בונה עכשיו יתמוך בכל אחד מהשיפורים האלה.
מסקנה
יצירת יישום צ'אט פשוט באמצעות C sockets הוא פרויקט מעולה עבור כל תכנות רשת למידה.למדת כיצד ליצור שרתים ולקוחות, לנהל קשרים מרובים עם חוטים או FLT:60, לטפל בנתונים גולמיים על פני TCP, וליישם פרוטוקול צ'אט בסיסי.הדוגמה הקוד לספק נקודת התחלה שניתן לערוך, לבדוק ולשנות.
לפרקטיקה נוספת, שקול ליישם שרת HTTP פשוטה או כלי שיתוף קבצים עמיתים ל-peer.the Skills are transferable ישירות. and remember - Always Read the Document.FLT:0 the socket (7) אדם עמוד הבית 1 הוא בן לוויה קבוע שלך.