תקשורת ב- Linux ו- Unix מנגנון תקשורת ב- Linux סכמת שרת לקוח: client/server
מודל השכבות רשת תקשורת: חיבורים המאפשרים תקשורת בין מחשבים פרוטוקולי תקשורת מאפשרים העברת נתונים ברשת פועלים בשכבות שונות, כשלכל שכבה תפקיד משלה. אוסף פרוטוקולי התקשורת הנפוץ ביותר נקרא TCP/IP מכיל 4 שכבות Application )telnet, ftp ( אפליקציות המשתמשות ברשת Transport (TCP, UDP) תקשורת בין תהליכים )ולא מחשבים( Internet (IP) ניתוב חבילות בין תחנות )לא שכנות( Data Link העברת חבילה בין תחנות שכנות 2
Linux של Networking API Linux תומכת במספר רב של ארכיטקטורות רשת. נתמקד בתמיכה של Linux בפרוטוקולי :TCP/IP קריאות מערכת המאפשרות יצירת תקשורת עם תהליכים מרוחקים. מבני נתונים המשמשים את קריאות המערכת הנ"ל כל קריאות המערכת שנראה משתמשות ב- sockets. תחילה נגדיר את ה- socket, עבור כל הקריאות צריך להוסיף ואח"כ נחבר אותו לפורט המתאים #include <sys/types.h> #include <sys/socket.h> : 3
הממשק: socket חדש יצירת int socket(int family, int type, int protocol) פרמטרים: family ארכיטקטורת הרשת לביצוע התקשורת type מודל התקשורת ברשת. )AF_INET( stream מבוססת,connection oriented עבור תקשורת SOCK_STREAM ואמינה )ממומשת ע"י )TCP protocol מגדיר את פרוטוקול התקשורת של שכבת ה.Transport פרמטר 0 ערך חזרה: החדש בוחר את פרוטוקול ברירת המחדל במקרה של הצלחה, מחזיר )SOCK_STREAM עבור TCP( socket המצביע ל- descriptor שייך ל- PDT של התהליך כמו פתוחים,,pipes... descriptors אחרים המצביעים על קבצים 4
הממשק: socket קישור של לפורט int bind(int sockfd, struct sockaddr * my_addr, int addrlen) פרמטרים: sockfd ה- descriptor של ה- socket אותו מחברים. my_addr כתובת אליה מקשרים. הכתובת מכילה את כתובת ה- IP של המחשב המקומי ואת מספר ה- port אליו יקושר ה- socket addrlen אורך של my_addr בבתים. ערך חזרה: במקרה של הצלחה, 1- במקרה של כישלון. 0 5
הממשק: האזנה הצהרת כוונה לקבל בקשות תקשורת והגדרת אורך תור מקסימלי של בקשות ממתינות. int listen(int sockfd, int num) פרמטרים: sockfd num ממתינות מזהה של ה- socket מספר מקסימלי של בקשות התחברות ערך חזרה: במקרה של הצלחה, 1- במקרה של כישלון. 0 6
הממשק: קבלת בקשות מחכה על ה- socket הנתון לבקשות תקשורת. אם יש בקשות הממתינות בתור: מוציא בקשה מהתור יוצר socket חדש ומקצה לו descriptor חדש. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) פרמטרים: sockfd ה- descriptor של ה- socket עליו מחכים לבקשות תקשורת Addr כתובת שולח בקשת תקשורת שהתקבלה, מוצב בתוך הקריאה. addrlen אורך הכתובת בבתים. ערך חזרה: במקרה של הצלחה ה- descriptor של ה- socket החדש שנוצר, אחרת.-1 7
הממשק: התחברות מנסה ליצור תקשורת עם תהליך שמקשיב על הכתובת serv_addr )מורכבת מכתובת IP ומספר )port int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen) פרמטרים: socket של ה- descriptor sockfd serv_addrהכתובת עליה מקשיב התהליך איתו מנסים להתקשר addrlen אורך הכתובת בבתים. ערך חזרה: 0 במקרה של הצלחה, 1- במקרה של כישלון. 8
הממשק: socket כתיבה ל אפשר לכתוב ל- socket באמצעות קריאת,write כמו לקבצים. ה- descriptor שמועבר הוא descriptor של ה- socket השולח, וההודעה תועבר ל- socket אליו מקשיב התהליך השני. פעולה מיוחדת, עם דגלים לאפשרויות שליחה מיוחדות int send(int sockfd,const void *msg, size_t len, int flags) ערך חזרה: במקרה של הצלחה, מספר הבתים שנשלחו, אחרת 1-. 9
הממשק: socket קריאה מה ניתן לקרוא מה- socket באמצעות קריאת,read כמו לקבצים. ה- descriptor שמועבר הוא descriptor של ה- socket הקורא, שחייב להיות מחובר )connected( ל- socket אחר. פעולה מיוחדת, עם דגלים לאפשרויות קריאה מיוחדות ערך חזרה: int recv(int sockfd, void *buf, size_t len, int flags) במקרה של הצלחה, מספר הבתים שנקראו, אחרת 1-. 10
הממשק: סגירת socket סגירת התקשורת עם המחשב המרוחק ושחרור ה- שהצביע על ה- socket. descriptor int close (int sockfd) ה- descriptor של ה- socket לסגירה. פרמטר: sockfd ערך חזרה: 0 כישלון. במקרה של הצלחה, 1- במקרה של 11
סכמת שרת לקוח: client/server תהליך אחד, הלקוח, מבקש שירות מתהליך אחר, השרת. למשל, שרתי.ftp,web,telnet השרת מטפל בבקשות של מספר לקוחות בו זמנית. לדוגמה, שרת telnet "מקשיב" על פורט 23 לבקשות התחברות מלקוחות )שזהותם לא ידועה מראש( כשמתקבלת בקשת התחברות נוצר telnet session ללקוח שהתחבר. ב- sockets : נשתמש מאפשרים לאפליקציה להבדיל בין telnet sessions שונים. לכל session מתאים זוג sockets בשרת ובלקוח. בין השרת 12
יצירת connection באופן לא סימטרי השרת יוצר socket מחבר אותו לפורט עליו הוא מקשיב מחכה על ה socket לבקשות תקשורת נכנסות הלקוח יוצר socket מנסה להתחבר אל כתובת IP ופורט של השרת כאשר connection מוקם, התקשורת היא סימטרית: שני הצדדים יכולים לשלוח הודעות אחד לשני דרך ה socket כל TCP session שנוצר כך מוגדר באופן חד ערכי ע"י רביעיה )source IP, source Port, destination IP, destination Port( 13
Client side: sd=socket() מימוש של שרת-לקוח עם sockets Server side: sd=socket() bind(sd, port) listen)sd, ( connect(sd, dst) new_sd=accept(sd) write)sd, ( read)sd, ( write)new_sd, ( read)new_sd, ( close(sd) close(new_sd) 14
מימוש שרת-לקוח עם :sockets הסברים השרת חייב לקרוא ל bind() כדי לחבר את ה sd שלו ל port מסוים )אחרת הוא יחכה לבקשות על port אקראי( בדרך כלל כמה לקוחות שולחים בקשות לתקשורת עם השרת. בקשות אלה נשמרות ע"י ה kernel בתוך תור. כאמור listen מגדירה את אורך התור. השרת מקבל כל פעם בקשה אחת ע"י accept() כאשר הטיפול בבקשה הוא ארוך, כדאי אחרי accept() לעשות fork() ולתת לבן לטפל בבקשה ולאב לקבל את הבקשות האחרות. אפשרות נוספת היא טיפול בבקשות ע"י חוטים חדשים 15
מציאת כתובת מציאת כתובת IP של מחשב ברשת לפי שם המחשב.)hostname( struct hostent * gethostbyname(const char *hostname); פרמטרים: hostname שם המחשב ערך חזרה: * hostent - struct מצביע למבנה נתונים המתאר את המחשב המבוקש. דוגמא: struct hostent *h = gethostbyname( t2.technion.ac.il ) של t2 IP יכיל את כתובת ה h->h_addr h->h_addr אורך ה h->h_lenght t2.technion.ac.il יכיל את המחרוזת h->h_name 16
החלפת Endians במעבדים שונים, יש סדר שונה בין LSB (least significant byte) ו byte).msb (most significant כדי שנוכל להעביר אינפורמציה בין מחשבים שונים ברשת, צריכים להעביר את המספרים long( )int, short, ליצוג אחיד. בזה מטפלות 4 הפונקציות הבאות: u_long htonl(u_long); // host to network long (32 bits) u_short htons(u_short); // host to network short (16 bits) u_long ntohl(u_long); // network to host long (32 bits) u_short ntohs(u_short); // network to host short (16 bits) 17
מבני נתונים לכתובות struct sockaddr_in { short int sin_family; //Address family, AF_xxx unsigned short int sin_port; // Port number struct in_addr sin_addr; // Internet address unsigned char sin_zero[8]; // for allignments }; struct sockaddr { unsigned short sa_family; // address family, AF_xxx char sa_data[14]; // 14 bytes of protocol address }; struct in_addr { unsigned long s_addr; //32-bit long,(4 bytes) IP address } 18
קוד שרת 1 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> void error(char *msg) { perror(msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, newsockfd, portno, clilen; char buffer[256]; struct sockaddr_in serv_addr, cli_addr; int n; if (argc < 2) { fprintf(stderr,"error, no port provided\n"); exit(1); } 19
קוד שרת 2 sockfd = socket(af_inet,sock_stream,0); if (sockfd < 0) error("error opening socket"); bzero((char *) &serv_addr, sizeof(serv_addr)); portno = atoi(argv[1]); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno); if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) error("error on binding"); listen(sockfd,5); clilen = sizeof(cli_addr); 20
קוד שרת 3 newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) error("error on accept"); bzero(buffer,256); n = read(newsockfd, buffer, 255); if (n < 0) error("error reading from socket"); printf("here is the message: %s\n",buffer); n = write(newsockfd,"i got your message",18); if (n < 0) error("error writing to socket"); close(newsockfd); close(sockfd); //or goto accept to wait for another clients return 0; האם הקוד הנ "ל תמיד יעבוד נכון? 21
קוד לקוח 1 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> void error(char *msg) { perror(msg); exit(0); } int main(int argc, char *argv[]) { int sockfd, portno, n; struct sockaddr_in serv_addr; struct hostent *server; char buffer[256]; 22
קוד לקוח 2 if (argc < 3) { fprintf(stderr,"usage %s hostname port\n", argv[0]); exit(0); } sockfd = socket(af_inet, SOCK_STREAM, 0); if (sockfd < 0) error("error opening socket"); portno = atoi(argv[2]); server = gethostbyname(argv[1]); if (server == NULL) { fprintf(stderr,"error, no such host\n"); exit(0); } bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; 23
קוד לקוח 3 } bcopy((char *)server->h_addr, (char *) &serv_addr.sin_addr.s_addr, server->h_length); serv_addr.sin_port = htons(portno); if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)( error("error connecting"); n = write(sockfd, HELLO THERE, sizeof( HELLO THERE )); if (n < 0) error("error writing to socket"); bzero(buffer,256); n = read(sockfd,buffer,255); if (n < 0) error("error reading from socket"); printf("%s\n",buffer); close(sockfd); return 0; האם הקוד הנ"ל תמיד יעבוד נכון? 24