<< Prev <<Sebelumnya | Beej's Guide to Network Programming Biija Panduan Pemrograman Jaringan | Next >> Berikutnya>> |
6. Client-Server Background 6. Client-Server Latar Belakang
It's a client-server world, baby. Ini adalah client-server dunia, bayi. Just about everything on the network deals with client processes talking to server processes and vice-versa. Hanya tentang segala sesuatu di penawaran jaringan dengan proses klien berbicara dengan proses server dan sebaliknya. Take telnet , for instance. Ambil telnet, misalnya. When you connect to a remote host on port 23 with telnet (the client), a program on that host (called telnetd , the server) springs to life. Bila Anda terhubung ke sebuah host remote pada port 23 dengan telnet (klien), sebuah program pada host (telnetd disebut, server) mata air untuk hidup. It handles the incoming telnet connection, sets you up with a login prompt, etc. Ini menangani koneksi telnet yang masuk, membuat Anda dengan sebuah prompt login, dll
Note that the client-server pair can speak SOCK_STREAM , SOCK_DGRAM , or anything else (as long as they're speaking the same thing.) Some good examples of client-server pairs are telnet / telnetd , ftp / ftpd , or Firefox / Apache . Perhatikan bahwa pasangan client-server dapat berbicara SOCK_STREAM, SOCK_DGRAM, atau apapun (selama mereka berbicara hal yang sama.) Beberapa contoh yang baik dari klien-server pasangan telnet / telnetd, ftp / ftpd, atau Firefox / Apache . Every time you use ftp , there's a remote program, ftpd , that serves you. Setiap kali Anda menggunakan ftp, ada program remote, ftpd, yang melayani Anda.
Often, there will only be one server on a machine, and that server will handle multiple clients using Seringkali, hanya akan ada satu server pada mesin, dan server yang akan menangani beberapa klien menggunakan fork() . fork (). The basic routine is: server will wait for a connection, accept() it, and fork() a child process to handle it. Rutinitas dasar adalah: server akan menunggu untuk koneksi, menerima () itu, dan garpu () proses anak untuk menanganinya. This is what our sample server does in the next section. Ini adalah apa server sampel kami tidak dalam bagian berikutnya.
6.1. A Simple Stream Server 6.1. Streaming Server Sederhana
All this server does is send the string " Hello, World!\n " out over a stream connection. Semua server ini dilakukan adalah mengirim string "Hello, World \ n" keluar melalui koneksi stream. All you need to do to test this server is run it in one window, and telnet to it from another with: Yang perlu Anda lakukan untuk menguji server ini dijalankan dalam satu jendela, dan telnet ke sana dari yang lain dengan:$ telnet remotehostname 3490 $ 3490 remotehostname telnet
where remotehostname is the name of the machine you're running it on. mana remotehostname adalah nama dari mesin Anda menjalankan pada. The server code : Kode server :
/* ** server.c -- a stream socket server demo */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/wait.h> #include <signal.h> #define PORT "3490" // the port users will be connecting to #define BACKLOG 10 // how many pending connections queue will hold void sigchld_handler(int s) { while(waitpid(-1, NULL, WNOHANG) > 0); } // get sockaddr, IPv4 or IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int main(void) { int sockfd, new_fd; // listen on sock_fd, new connection on new_fd struct addrinfo hints, *servinfo, *p; struct sockaddr_storage their_addr; // connector's address information socklen_t sin_size; struct sigaction sa; int yes=1; char s[INET6_ADDRSTRLEN]; int rv; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // use my IP if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } // loop through all the results and bind to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("server: socket"); continue; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("server: bind"); continue; } break; } if (p == NULL) { fprintf(stderr, "server: failed to bind\n"); return 2; } freeaddrinfo(servinfo); // all done with this structure if (listen(sockfd, BACKLOG) == -1) { perror("listen"); exit(1); } sa.sa_handler = sigchld_handler; // reap all dead processes sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); } printf("server: waiting for connections...\n"); while(1) { // main accept() loop sin_size = sizeof their_addr; new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); if (new_fd == -1) { perror("accept"); continue; } inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s); printf("server: got connection from %s\n", s); if (!fork()) { // this is the child process close(sockfd); // child doesn't need the listener if (send(new_fd, "Hello, world!", 13, 0) == -1) perror("send"); close(new_fd); exit(0); } close(new_fd); // parent doesn't need this } return 0; } / * ** Server.c - soket streaming server * demo / # include # include # include # include <unistd.h> <errno.h> # include <string.h > # include # include <sys/types.h> <sys/socket.h> # include # include <netinet/in.h> <netdb.h> # include <arpa/inet.h> # include <sys / menunggu . h> # include # define PORT <signal.h> "3490" / / para pengguna port akan menghubungkan ke # define Backlog 10 / / berapa banyak antrian koneksi yang tertunda akan mengadakan sigchld_handler void (int s) {while (waitpid (- 1, NULL, WNOHANG)> 0);} / / mendapatkan sockaddr, IPv4 atau IPv6: void * get_in_addr (struct sockaddr * sa) {if (sa-> sa_family == AF_INET) {return & (((struct sockaddr_in *) sa) -> sin_addr);} return & (((struct sockaddr_in6 *) sa) -> sin6_addr);} int main (void) {int sockfd, new_fd; / / mendengarkan pada sock_fd, sambungan baru pada petunjuk struct new_fd addrinfo, * servinfo, * p; struct their_addr sockaddr_storage; / / alamat informasi konektor sin_size socklen_t; struct sigaction sa; int yes = 1; arang s [INET6_ADDRSTRLEN]; int rv; memset (& petunjuk, 0, sizeof petunjuk); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; / / menggunakan IP saya jika ((rv = getaddrinfo (NULL, PORT, & petunjuk, & servinfo)) = 0!) {fprintf (stderr, "getaddrinfo:% s \ n ", gai_strerror (rv)); return 1;} / / loop melalui semua hasil dan mengikat dengan yang pertama kita bisa untuk (p = servinfo; p = NULL;! p = p-> ai_next) {if ((sockfd = socket (p-> ai_family, p-> ai_socktype, p-> ai_protocol)) == -1) {perror ("server: soket"); melanjutkan;} if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, & ya, sizeof (int)) == -1) {perror ("setsockopt"); exit (1);} if (bind (sockfd, p-> ai_addr, p-> ai_addrlen) == -1) {close (sockfd); perror ("server: mengikat"); melanjutkan;} break;} if (p == NULL) {fprintf (stderr, "server: gagal untuk mengikat \ n"); kembali 2;} freeaddrinfo (servinfo); / / semua dilakukan dengan struktur ini jika (mendengarkan (sockfd, backlog) == -1) {perror ("mendengarkan"); exit (1);} sa.sa_handler = sigchld_handler; / / meraup semua proses mati sigemptyset (& sa.sa_mask); sa.sa_flags = SA_RESTART, jika (sigaction (SIGCHLD, & sa, NULL) == -1) {perror ("sigaction"); exit (1);} printf ("server: menunggu untuk koneksi ... \ n") ; sementara (1) {/ / main menerima () loop = sizeof their_addr sin_size; new_fd = menerima (sockfd, (struct sockaddr *) & their_addr, & sin_size), jika (new_fd == -1) {perror ("menerima"); melanjutkan;} inet_ntop (their_addr.ss_family, get_in_addr ((struct sockaddr *) & their_addr), s, sizeof s); printf ("server: punya koneksi dari% s \ n", s); if (fork ()!) { / / ini adalah proses anak dekat (sockfd); / / anak tidak perlu pendengar jika (mengirim (new_fd, "Halo, dunia!", 13, 0) == -1) perror ("kirim"); dekat (new_fd); exit (0);} dekat (new_fd); / / orang tua tidak perlu ini kembali} 0;}
In case you're curious, I have the code in one big main() function for (I feel) syntactic clarity. Dalam kasus Anda penasaran, saya memiliki kode dalam satu utama besar () fungsi untuk (Saya merasa) kejelasan sintaksis. Feel free to split it into smaller functions if it makes you feel better. Jangan ragu untuk membagi ke dalam fungsi yang lebih kecil jika itu membuat Anda merasa lebih baik. (Also, this whole (Juga, ini seluruh sigaction() thing might be new to you—that's ok. sigaction () mungkin hal baru bagi Anda-itu ok. The code that's there is responsible for reaping Kode yang ada di sana bertanggung jawab untuk menuai zombie processes that appear as the fork() ed child processes exit. zombie proses yang muncul sebagai fork () ed anak proses keluar. If you make lots of zombies and don't reap them, your system administrator will become agitated.) Jika Anda membuat banyak zombie dan tidak menuai mereka, administrator sistem Anda akan menjadi gelisah.)
You can get the data from this server by using the client listed in the next section. Anda bisa mendapatkan data dari server ini dengan menggunakan klien yang tercantum dalam bagian berikutnya.
6.2. A Simple Stream Client 6.2. Sebuah Klien Streaming Sederhana
This guy's even easier than the server. Orang ini bahkan lebih mudah daripada server. All this client does is connect to the host you specify on the command line, port 3490. Semua klien ini adalah menyambung ke host Anda tentukan pada baris perintah, port 3490. It gets the string that the server sends. Ia mendapat string yang server mengirimkan.The client source : Sumber klien :
/* / * ** client.c -- a stream socket client demo ** Client.c - soket aliran demo klien */ * / #include <stdio.h> # Include #include <stdlib.h> # Include #include <unistd.h> # Include <unistd.h> #include <errno.h> # Include <errno.h> #include <string.h> # Include <string.h> #include <netdb.h> # Include <netdb.h> #include <sys/types.h> # Include <sys/types.h> #include <netinet/in.h> # Include <netinet/in.h> #include <sys/socket.h> # Include <sys/socket.h> #include <arpa/inet.h> # Include <arpa/inet.h> #define PORT "3490" // the port client will be connecting to # Define PORT "3490" / / klien port akan menghubungkan ke #define MAXDATASIZE 100 // max number of bytes we can get at once # Define MAXDATASIZE 100 / / max jumlah byte yang kita bisa sekaligus // get sockaddr, IPv4 or IPv6: / / Mendapatkan sockaddr, IPv4 atau IPv6: void *get_in_addr(struct sockaddr *sa) void * get_in_addr (struct sockaddr * sa) { { if (sa->sa_family == AF_INET) { if (sa-> sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); kembali & (((struct sockaddr_in *) sa) -> sin_addr); } } return &(((struct sockaddr_in6*)sa)->sin6_addr); kembali & (((struct sockaddr_in6 *) sa) -> sin6_addr); } } int main(int argc, char *argv[]) int main (int argc, char * argv []) { { int sockfd, numbytes; int sockfd, numbytes; char buf[MAXDATASIZE]; buf char [MAXDATASIZE]; struct addrinfo hints, *servinfo, *p; struct petunjuk addrinfo, * servinfo, * p; int rv; int rv; char s[INET6_ADDRSTRLEN]; arang s [INET6_ADDRSTRLEN]; if (argc != 2) { if (argc = 2!) { fprintf(stderr,"usage: client hostname\n"); fprintf (stderr, "penggunaan: hostname client \ n"); exit(1); exit (1); } } memset(&hints, 0, sizeof hints); memset (& petunjuk, 0, sizeof petunjuk); hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) { if ((rv = getaddrinfo (argv [1], PORT, & petunjuk, & servinfo)) = 0!) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); fprintf (stderr, "getaddrinfo:% s \ n", gai_strerror (rv)); return 1; return 1; } } // loop through all the results and connect to the first we can / / Loop melalui semua hasil dan terhubung dengan yang pertama kita dapat for(p = servinfo; p != NULL; p = p->ai_next) { untuk (p = servinfo; p = NULL;! p = p-> ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, if ((sockfd = socket (p-> ai_family, p-> ai_socktype, p->ai_protocol)) == -1) { p-> ai_protocol)) == -1) { perror("client: socket"); perror ("klien: socket"); continue; melanjutkan; } } if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { if (connect (sockfd, p-> ai_addr, p-> ai_addrlen) == -1) { close(sockfd); dekat (sockfd); perror("client: connect"); perror ("klien: koneksi"); continue; melanjutkan; } } break; break; } } if (p == NULL) { if (p == NULL) { fprintf(stderr, "client: failed to connect\n"); fprintf (stderr, "Klien: gagal untuk menghubungkan \ n"); return 2; kembali 2; } } inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), inet_ntop (p-> ai_family, get_in_addr ((struct sockaddr *) p-> ai_addr), s, sizeof s); s, sizeof s); printf("client: connecting to %s\n", s); printf ("Klien: terhubung ke% s \ n", s); freeaddrinfo(servinfo); // all done with this structure freeaddrinfo (servinfo); / / semua dilakukan dengan struktur ini if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) { if ((numbytes = recv (sockfd, buf, MAXDATASIZE-1, 0)) == -1) { perror("recv"); perror ("recv"); exit(1); exit (1); } } buf[numbytes] = '\0'; buf [numbytes] = '\ 0'; printf("client: received '%s'\n",buf); printf ("Klien: menerima \ '% s' n", buf); close(sockfd); dekat (sockfd); return 0; return 0; } }Notice that if you don't run the server before you run the client, connect() returns Perhatikan bahwa jika Anda tidak menjalankan server sebelum Anda menjalankan klien, menghubungkan () mengembalikan "Connection refused". "Koneksi ditolak". Very useful. Sangat berguna.
6.3. Datagram Sockets 6.3. Datagram Socket
We've already covered the basics of UDP datagram sockets with our discussion of sendto() and recvfrom() , above, so I'll just present a couple of sample programs: talker.c and listener.c . Kami sudah membahas dasar-dasar soket UDP datagram dengan diskusi kita tentang sendto () dan recvfrom (), di atas, jadi saya hanya akan menyajikan beberapa contoh program: talker.c dan listener.c.listener sits on a machine waiting for an incoming packet on port 4950. talker sends a packet to that port, on the specified machine, that contains whatever the user enters on the command line. pendengar duduk pada mesin menunggu sebuah paket masuk pada port 4950. pembicara yang mengirimkan sebuah paket ke port itu, pada mesin tertentu, yang berisi apa pun pengguna masuk pada baris perintah.
Here is the source for listener.c : Berikut adalah sumber untuk listener.c :
/* ** listener.c -- a datagram sockets "server" demo */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define MYPORT "4950" // the port users will be connecting to #define MAXBUFLEN 100 // get sockaddr, IPv4 or IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int main(void) { int sockfd; struct addrinfo hints, *servinfo, *p; int rv; int numbytes; struct sockaddr_storage their_addr; char buf[MAXBUFLEN]; socklen_t addr_len; char s[INET6_ADDRSTRLEN]; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4 hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; // use my IP if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } // loop through all the results and bind to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("listener: socket"); continue; } if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("listener: bind"); continue; } break; } if (p == NULL) { fprintf(stderr, "listener: failed to bind socket\n"); return 2; } freeaddrinfo(servinfo); printf("listener: waiting to recvfrom...\n"); addr_len = sizeof their_addr; if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) { perror("recvfrom"); exit(1); } printf("listener: got packet from %s\n", inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s)); printf("listener: packet is %d bytes long\n", numbytes); buf[numbytes] = '\0'; printf("listener: packet contains \"%s\"\n", buf); close(sockfd); return 0; } / * ** Listener.c - datagram socket "server" demo * / # include # include # include # include <unistd.h> <errno.h> # menyertakan string < h>. # include # include <sys/types.h> <sys/socket.h> # include # include <netinet/in.h> <arpa/inet.h> # include # define <netdb.h> MYPORT " 4950 "/ / para pengguna port akan menghubungkan ke # define MAXBUFLEN 100 / / mendapatkan sockaddr, IPv4 atau IPv6: void * get_in_addr (struct sockaddr * sa) {if (sa-> sa_family == AF_INET) {return & ((( struct sockaddr_in *) sa) -> sin_addr); - int rv;;} int main (void) {int sockfd; struct petunjuk addrinfo, * servinfo, * p> sin6_addr)} return & (((struct sockaddr_in6 *) sa) int numbytes; struct their_addr sockaddr_storage; buf char [MAXBUFLEN]; socklen_t addr_len; arang s [INET6_ADDRSTRLEN]; memset (& petunjuk, 0, sizeof petunjuk); hints.ai_family = AF_UNSPEC; / / set AF_INET untuk memaksa IPv4 hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; / / menggunakan IP saya jika ((rv = getaddrinfo (NULL, MYPORT, & petunjuk, & servinfo)) = 0!) {fprintf (stderr, "getaddrinfo:% s \ n", gai_strerror ( rv)); return 1;} / / loop melalui semua hasil dan mengikat dengan yang pertama kita bisa untuk (p = servinfo; p = NULL;! p = p-> ai_next) {if ((sockfd = socket (p- > ai_family, p-> ai_socktype, p-> ai_protocol)) == -1) {perror ("pendengar: socket"); melanjutkan;} if (bind (sockfd, p-> ai_addr, p-> ai_addrlen) == -1) {close (sockfd); perror ("pendengar: mengikat"); melanjutkan;} break;} if (p == NULL) {fprintf (stderr, "pendengar: gagal untuk mengikat socket \ n"); kembali 2 ;} freeaddrinfo (servinfo); printf ("pendengar: menunggu untuk recvfrom ... \ n"); addr_len = sizeof their_addr, jika ((numbytes = recvfrom (sockfd, buf, MAXBUFLEN-1, 0, (struct sockaddr *) & their_addr, & addr_len)) == -1) {perror ("recvfrom"); exit (1);} printf ("pendengar: mendapat paket dari% s \ n", inet_ntop (their_addr.ss_family, get_in_addr ((struct sockaddr * ) & their_addr), s, sizeof s)); printf ("pendengar: paket adalah% d byte panjang \ n", numbytes); buf [numbytes] = '\ 0'; printf ("pendengar: paket berisi \"% s \ "\ n", buf); dekat (sockfd); return 0;}
Notice that in our call to getaddrinfo() we're finally using SOCK_DGRAM . Perhatikan bahwa dalam panggilan kita untuk getaddrinfo () kita akhirnya menggunakan SOCK_DGRAM. Also, note that there's no need to listen() or accept() . Juga, perhatikan bahwa tidak perlu untuk mendengarkan () atau menerima (). This is one of the perks of using unconnected datagram sockets! Ini adalah salah satu manfaat dari menggunakan soket datagram tidak tersambung! Next comes the source for talker.c : Selanjutnya muncul sumber untuk talker.c :
/* / * ** talker.c -- a datagram "client" demo ** Talker.c - datagram "client" demo */ * / #include <stdio.h> # Include #include <stdlib.h> # Include #include <unistd.h> # Include <unistd.h> #include <errno.h> # Include <errno.h> #include <string.h> # Include <string.h> #include <sys/types.h> # Include <sys/types.h> #include <sys/socket.h> # Include <sys/socket.h> #include <netinet/in.h> # Include <netinet/in.h> #include <arpa/inet.h> # Include <arpa/inet.h> #include <netdb.h> # Include <netdb.h> #define SERVERPORT "4950" // the port users will be connecting to # Define SERVERPORT "4950" / / para pengguna port akan menghubungkan ke int main(int argc, char *argv[]) int main (int argc, char * argv []) { { int sockfd; int sockfd; struct addrinfo hints, *servinfo, *p; struct petunjuk addrinfo, * servinfo, * p; int rv; int rv; int numbytes; int numbytes; if (argc != 3) { if (argc = 3!) { fprintf(stderr,"usage: talker hostname message\n"); fprintf (stderr, "penggunaan: Pesan pembicara hostname \ n"); exit(1); exit (1); } } memset(&hints, 0, sizeof hints); memset (& petunjuk, 0, sizeof petunjuk); hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_socktype = SOCK_DGRAM; if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) { if ((rv = getaddrinfo (argv [1], SERVERPORT, & petunjuk, & servinfo)) = 0!) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); fprintf (stderr, "getaddrinfo:% s \ n", gai_strerror (rv)); return 1; return 1; } } // loop through all the results and make a socket / / Loop melalui semua hasil dan membuat soket for(p = servinfo; p != NULL; p = p->ai_next) { untuk (p = servinfo; p = NULL;! p = p-> ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, if ((sockfd = socket (p-> ai_family, p-> ai_socktype, p->ai_protocol)) == -1) { p-> ai_protocol)) == -1) { perror("talker: socket"); perror ("pembicara: socket"); continue; melanjutkan; } } break; break; } } if (p == NULL) { if (p == NULL) { fprintf(stderr, "talker: failed to bind socket\n"); fprintf (stderr, "pembicara: gagal untuk mengikat socket \ n"); return 2; kembali 2; } } if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0, if ((numbytes = sendto (sockfd, argv [2], strlen (argv [2]), 0, p->ai_addr, p->ai_addrlen)) == -1) { p-> ai_addr, p-> ai_addrlen)) == -1) { perror("talker: sendto"); perror ("pembicara: sendto"); exit(1); exit (1); } } freeaddrinfo(servinfo); freeaddrinfo (servinfo); printf("talker: sent %d bytes to %s\n", numbytes, argv[1]); printf ("pembicara:% d byte dikirim ke% s \ n", numbytes, argv [1]); close(sockfd); dekat (sockfd); return 0; return 0; } }And that's all there is to it! Dan itu semua ada untuk itu! Run listener on some machine, then run talker on another. Jalankan pendengar pada mesin tertentu, kemudian jalankan pembicara yang lain. Watch them communicate! Menonton mereka berkomunikasi! Fun G-rated excitement for the entire nuclear family! Fun G-rated kegembiraan bagi seluruh keluarga inti!
You don't even have to run the server this time! Anda bahkan tidak harus menjalankan server saat ini! You can run talker by itself, and it just happily fires packets off into the ether where they disappear if no one is ready with a recvfrom() on the other side. Anda dapat menjalankan pembicara dengan sendirinya, dan hanya gembira kebakaran dari paket ke dalam eter di mana mereka menghilang jika tidak ada yang siap dengan recvfrom () di sisi lain. Remember: data sent using UDP datagram sockets isn't guaranteed to arrive! Ingat: data yang dikirim menggunakan UDP datagram socket tidak dijamin tiba!
Except for one more tiny detail that I've mentioned many times in the past: Kecuali untuk satu detail kecil lagi yang telah saya sebutkan berkali-kali di masa lalu: connected datagram sockets. terhubung soket datagram. I need to talk about this here, since we're in the datagram section of the document. Aku perlu bicara tentang ini di sini, karena kita berada di bagian datagram dokumen. Let's say that talker calls connect() and specifies the listener 's address. Mari kita mengatakan bahwa pembicara panggilan terhubung () dan menentukan alamat pendengar 's. From that point on, talker may only sent to and receive from the address specified by connect() . Sejak saat itu, pembicara hanya dapat dikirim ke dan menerima dari alamat yang ditentukan oleh koneksi (). For this reason, you don't have to use sendto() and recvfrom() ; you can simply use send() and recv() . Untuk alasan ini, Anda tidak harus menggunakan sendto () dan recvfrom (); Anda hanya dapat menggunakan send () dan recv ().
7. Slightly Advanced Techniques 7. Teknik Sedikit LanjutanThese aren't really advanced, but they're getting out of the more basic levels we've already covered. Ini tidak benar-benar maju, tapi mereka keluar dari tingkat yang lebih mendasar kita sudah tertutup. In fact, if you've gotten this far, you should consider yourself fairly accomplished in the basics of Unix network programming! Bahkan, jika Anda sudah sampai sejauh ini, Anda harus menganggap diri cukup dilakukan dalam dasar-dasar pemrograman jaringan Unix! Congratulations! Selamat! So here we go into the brave new world of some of the more esoteric things you might want to learn about sockets. Jadi di sini kita pergi ke dunia baru yang berani dari beberapa hal yang lebih esoteris Anda mungkin ingin belajar tentang soket. Have at it! Apakah itu! 7.1. Blocking 7.1. blokirBlocking. Memblokir. You've heard about it—now what the heck is it? Anda pernah mendengar tentang hal itu-sekarang apa sih itu? In a nutshell, "block" is techie jargon for "sleep". Singkatnya, "blok" adalah jargon Techie untuk "tidur". You probably noticed that when you run listener , above, it just sits there until a packet arrives. Anda mungkin memperhatikan bahwa ketika Anda menjalankan pendengar, di atas, itu hanya duduk di sana sampai paket tiba. What happened is that it called recvfrom() , there was no data, and so recvfrom() is said to "block" (that is, sleep there) until some data arrives. Apa yang terjadi adalah bahwa hal itu disebut recvfrom (), tidak ada data, dan sebagainya recvfrom () dikatakan "blok" (yaitu, tidur di sana) sampai beberapa data tiba.Lots of functions block. accept() blocks. Banyak blok fungsi blok menerima (.). All the recv() functions block. Semua recv () fungsi blok. The reason they can do this is because they're allowed to. Alasan mereka dapat melakukan ini adalah karena mereka diperbolehkan. When you first create the socket descriptor with socket() , the kernel sets it to blocking. Ketika Anda pertama kali membuat descriptor socket dengan socket (), kernel set untuk memblokir. If you don't want a socket to be blocking, you have to make a call to Jika Anda tidak ingin soket yang akan memblokir, Anda harus membuat panggilan ke fcntl() : fcntl (): #include <unistd.h> # Include <unistd.h> #include <fcntl.h> # Include <fcntl.h> . . . . . . sockfd = socket(PF_INET, SOCK_STREAM, 0); sockfd = socket (PF_INET, SOCK_STREAM, 0); fcntl(sockfd, F_SETFL, O_NONBLOCK); fcntl (sockfd, F_SETFL, O_NONBLOCK); . . . . . .By setting a socket to non-blocking, you can effectively "poll" the socket for information. Dengan menetapkan socket untuk non-blocking, Anda dapat secara efektif "jajak pendapat" soket untuk informasi. If you try to read from a non-blocking socket and there's no data there, it's not allowed to block—it will return -1 and errno will be set to Jika Anda mencoba untuk membaca dari soket non-blocking dan ada tidak ada data di sana, itu tidak diperbolehkan untuk memblokir-itu akan kembali -1, dan errno akan ditetapkan untuk EWOULDBLOCK . EWOULDBLOCK. Generally speaking, however, this type of polling is a bad idea. Secara umum, bagaimanapun, ini jenis pemungutan suara adalah ide yang buruk. If you put your program in a busy-wait looking for data on the socket, you'll suck up CPU time like it was going out of style. Jika Anda menempatkan program anda dalam sibuk-tunggu mencari data pada socket, Anda akan menyedot waktu CPU seperti itu akan keluar dari gaya. A more elegant solution for checking to see if there's data waiting to be read comes in the following section on Sebuah solusi yang lebih elegan untuk memeriksa untuk melihat apakah ada data yang menunggu untuk dibaca datang dalam bagian berikut pada select() . pilih (). 7.2. select() —Synchronous I/O Multiplexing 7.2. pilih ()-Synchronous I / O MultiplexingThis function is somewhat strange, but it's very useful. Fungsi ini agak aneh, tapi itu sangat berguna. Take the following situation: you are a server and you want to listen for incoming connections as well as keep reading from the connections you already have. Ambil situasi berikut: Anda server dan Anda ingin mendengarkan koneksi masuk serta terus membaca dari koneksi Anda sudah memiliki.No problem, you say, just an accept() and a couple of recv() s. Tidak masalah, Anda katakan, hanya menerima () dan beberapa recv () s. Not so fast, buster! Tidak begitu cepat, Bung! What if you're blocking on an accept() call? Bagaimana jika Anda memblokir pada panggilan menerima ()? How are you going to recv() data at the same time? Bagaimana Anda akan ke data recv () pada waktu yang sama? "Use non-blocking sockets!" "Gunakan non-blocking socket!" No way! Tidak! You don't want to be a CPU hog. Anda tidak ingin menjadi babi CPU. What, then? Lalu apa? select() gives you the power to monitor several sockets at the same time. pilih () memberikan Anda kekuatan untuk memantau beberapa soket pada waktu yang sama. It'll tell you which ones are ready for reading, which are ready for writing, and which sockets have raised exceptions, if you really want to know that. Ini akan memberitahu Anda mana yang siap untuk membaca, yang siap untuk menulis, dan yang telah meningkatkan soket pengecualian, jika Anda benar-benar ingin tahu. This being said, in modern times select() , though very portable, is one of the slowest methods for monitoring sockets. Ini dikatakan, di zaman modern pilih (), meskipun sangat portabel, adalah salah satu metode paling lambat untuk soket pemantauan. One possible alternative is libevent , or something similar, that encapsulates all the system-dependent stuff involved with getting socket notifications. Salah satu alternatif mungkin adalah libevent , atau sesuatu yang mirip, yang merangkum semua hal bergantung pada sistem yang terlibat dengan mendapatkan pemberitahuan soket. Without any further ado, I'll offer the synopsis of select() : Tanpa basa-basi lagi, saya akan menawarkan sinopsis pilih (): #include <sys/time.h> # Include <sys/time.h> #include <sys/types.h> # Include <sys/types.h> #include <unistd.h> # Include <unistd.h> int select(int numfds, fd_set *readfds, fd_set *writefds, int pilih (int numfds, fd_set * readfds, fd_set * writefds, fd_set *exceptfds, struct timeval *timeout); fd_set * exceptfds, struct timeval * timeout);The function monitors "sets" of file descriptors; in particular readfds , writefds , and exceptfds . Fungsi monitor "set" deskriptor file, dalam readfds khususnya, writefds, dan exceptfds. If you want to see if you can read from standard input and some socket descriptor, sockfd , just add the file descriptors 0 and sockfd to the set readfds . Jika Anda ingin melihat apakah Anda dapat membaca dari input standar dan beberapa deskriptor soket, sockfd, tambahkan saja file deskriptor 0 dan sockfd ke readfds ditetapkan. The parameter numfds should be set to the values of the highest file descriptor plus one. Para numfds Parameter harus di set ke nilai-nilai file descriptor tertinggi ditambah satu. In this example, it should be set to sockfd+1 , since it is assuredly higher than standard input ( 0 ). Dalam contoh ini, harus di set ke sockfd +1, karena pasti lebih tinggi dari standar input (0). When select() returns, readfds will be modified to reflect which of the file descriptors you selected which is ready for reading. Ketika pilih () mengembalikan, readfds akan dimodifikasi untuk mencerminkan yang mana dari deskriptor file yang dipilih yang siap untuk membaca. You can test them with the macro FD_ISSET() , below. Anda dapat menguji mereka dengan FD_ISSET makro (), di bawah ini. Before progressing much further, I'll talk about how to manipulate these sets. Sebelum maju lebih jauh, saya akan berbicara tentang bagaimana untuk memanipulasi set ini. Each set is of the type fd_set . Setiap set dari fd_set tipe. The following macros operate on this type: Makro berikut beroperasi pada tipe ini: The struct timeval has the follow fields: Struct timeval memiliki bidang berikut: struct timeval { struct timeval { int tv_sec; // seconds int tv_sec; / / detik int tv_usec; // microseconds int tv_usec; / / mikrodetik }; };Just set tv_sec to the number of seconds to wait, and set tv_usec to the number of microseconds to wait. Hanya mengatur tv_sec dengan jumlah detik untuk menunggu, dan mengatur tv_usec dengan jumlah mikrodetik untuk menunggu. Yes, that's micro seconds, not milliseconds. Ya, itu mikro detik, bukan milidetik. There are 1,000 microseconds in a millisecond, and 1,000 milliseconds in a second. Ada 1.000 mikrodetik dalam milidetik, dan 1.000 milidetik dalam satu detik. Thus, there are 1,000,000 microseconds in a second. Jadi, ada 1.000.000 mikrodetik dalam satu detik. Why is it "usec"? Mengapa "usec"? The "u" is supposed to look like the Greek letter μ (Mu) that we use for "micro". "U" seharusnya terlihat seperti μ huruf Yunani (Mu) yang kita gunakan untuk "mikro". Also, when the function returns, timeout might be updated to show the time still remaining. Juga, ketika kembali fungsi, batas waktu mungkin diperbarui untuk menunjukkan waktu yang masih tersisa. This depends on what flavor of Unix you're running. Hal ini tergantung pada apa rasa Unix kamu sedang berjalan. Yay! Yay! We have a microsecond resolution timer! Kami memiliki timer resolusi mikrodetik! Well, don't count on it. Yah, jangan berharap. You'll probably have to wait some part of your standard Unix timeslice no matter how small you set your struct timeval . Anda mungkin harus menunggu beberapa bagian dari timeslice standar Unix Anda tidak peduli seberapa kecil Anda mengatur struct timeval Anda. Other things of interest: If you set the fields in your struct timeval to 0 , select() will timeout immediately, effectively polling all the file descriptors in your sets. Hal-hal lain yang menarik: Jika Anda mengatur bidang dalam struct timeval Anda ke 0, pilih () akan batas waktu segera, efektif pemungutan suara semua deskriptor file di set Anda. If you set the parameter timeout to NULL, it will never timeout, and will wait until the first file descriptor is ready. Jika Anda mengatur timeout parameter untuk NULL, itu tidak akan pernah habis, dan akan menunggu sampai file descriptor pertama adalah siap. Finally, if you don't care about waiting for a certain set, you can just set it to NULL in the call to select() . Akhirnya, jika Anda tidak peduli tentang menunggu untuk satu set tertentu, Anda hanya dapat mengaturnya ke NULL dalam panggilan untuk memilih (). The following code snippet waits 2.5 seconds for something to appear on standard input: Potongan kode berikut menunggu 2,5 detik untuk sesuatu muncul pada masukan standar: /* / * ** select.c -- a select() demo ** Select.c - sebuah pilih () demo */ * / #include <stdio.h> # Include #include <sys/time.h> # Include <sys/time.h> #include <sys/types.h> # Include <sys/types.h> #include <unistd.h> # Include <unistd.h> #define STDIN 0 // file descriptor for standard input # Define STDIN deskriptor 0 / / file untuk standard input int main(void) int main (void) { { struct timeval tv; struct timeval tv; fd_set readfds; fd_set readfds; tv.tv_sec = 2; tv.tv_sec = 2; tv.tv_usec = 500000; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_ZERO (& readfds); FD_SET(STDIN, &readfds); FD_SET (STDIN, & readfds); // don't care about writefds and exceptfds: / / Tidak peduli writefds dan exceptfds: select(STDIN+1, &readfds, NULL, NULL, &tv); pilih (STDIN +1, & readfds, NULL, NULL, & tv); if (FD_ISSET(STDIN, &readfds)) if (FD_ISSET (STDIN, & readfds)) printf("A key was pressed!\n"); printf ("kunci A ditekan \ n"); else lain printf("Timed out.\n"); printf ("Jangka waktu keluar \ n."); return 0; return 0; } }If you're on a line buffered terminal, the key you hit should be RETURN or it will time out anyway. Jika Anda pada line terminal buffer, kunci anda menekan harus RETURN atau akan waktu pula. Now, some of you might think this is a great way to wait for data on a datagram socket—and you are right: it might be. Sekarang, beberapa dari Anda mungkin berpikir ini adalah cara yang bagus untuk menunggu data pada datagram socket-dan Anda benar: itu mungkin. Some Unices can use select in this manner, and some can't. Beberapa mesin-mesin Unix dapat menggunakan memilih dengan cara ini, dan beberapa tidak bisa. You should see what your local man page says on the matter if you want to attempt it. Anda harus melihat apa halaman Anda pria lokal mengatakan pada hal jika Anda ingin mencobanya. Some Unices update the time in your struct timeval to reflect the amount of time still remaining before a timeout. Beberapa beragam Unix memperbarui waktu di struct timeval Anda untuk mencerminkan jumlah waktu masih tersisa sebelum timeout. But others do not. Tetapi sebagian lainnya tidak. Don't rely on that occurring if you want to be portable. Jangan mengandalkan bahwa terjadi jika Anda ingin menjadi portabel. (Use (Gunakan gettimeofday() if you need to track time elapsed. gettimeofday () jika Anda perlu untuk melacak waktu berlalu. It's a bummer, I know, but that's the way it is.) Gelandangan itu, saya tahu, tapi itulah cara itu.) What happens if a socket in the read set closes the connection? Apa yang terjadi jika soket dalam mengatur membaca menutup koneksi? Well, in that case, select() returns with that socket descriptor set as "ready to read". Nah, dalam kasus itu, pilih () mengembalikan dengan deskriptor soket ditetapkan sebagai "siap membaca". When you actually do recv() from it, recv() will return 0 . Ketika Anda benar-benar melakukan recv () dari itu, recv () akan mengembalikan 0. That's how you know the client has closed the connection. Itulah cara Anda tahu klien telah menutup koneksi. One more note of interest about select() : if you have a socket that is Satu lagi catatan yang menarik tentang pilih (): jika Anda memiliki soket yang listen() ing, you can check to see if there is a new connection by putting that socket's file descriptor in the readfds set. mendengarkan () ing, Anda dapat memeriksa untuk melihat apakah ada koneksi baru dengan menempatkan file descriptor yang soket di set readfds. And that, my friends, is a quick overview of the almighty select() function. Dan itu, teman-teman saya, adalah gambaran singkat dari pilih Mahakuasa () fungsi. But, by popular demand, here is an in-depth example. Tapi, oleh permintaan populer, di sini adalah contoh yang mendalam. Unfortunately, the difference between the dirt-simple example, above, and this one here is significant. Sayangnya, perbedaan antara kotoran-contoh sederhana, di atas, dan ini satu di sini adalah signifikan. But have a look, then read the description that follows it. Tapi kita lihat, baca penjelasan yang mengikutinya. This program acts like a simple multi-user chat server. Program ini bertindak seperti server multi-user chatting sederhana. Start it running in one window, then telnet to it (" telnet hostname 9034 ") from multiple other windows. Mulai berjalan dalam satu jendela, kemudian telnet ke sana ("9034 nama host telnet") dari beberapa jendela lain. When you type something in one telnet session, it should appear in all the others. Ketika anda mengetik sesuatu dalam satu sesi telnet, harus muncul dalam semua yang lain. /* / * ** selectserver.c -- a cheezy multiperson chat server ** Selectserver.c - sebuah server yang cheezy multiperson chatting */ * / #include <stdio.h> # Include #include <stdlib.h> # Include #include <string.h> # Include <string.h> #include <unistd.h> # Include <unistd.h> #include <sys/types.h> # Include <sys/types.h> #include <sys/socket.h> # Include <sys/socket.h> #include <netinet/in.h> # Include <netinet/in.h> #include <arpa/inet.h> # Include <arpa/inet.h> #include <netdb.h> # Include <netdb.h> #define PORT "9034" // port we're listening on # Define PORT "9034" / / port yang kita sedang mendengarkan pada // get sockaddr, IPv4 or IPv6: / / Mendapatkan sockaddr, IPv4 atau IPv6: void *get_in_addr(struct sockaddr *sa) void * get_in_addr (struct sockaddr * sa) { { if (sa->sa_family == AF_INET) { if (sa-> sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); kembali & (((struct sockaddr_in *) sa) -> sin_addr); } } return &(((struct sockaddr_in6*)sa)->sin6_addr); kembali & (((struct sockaddr_in6 *) sa) -> sin6_addr); } } int main(void) int main (void) { { fd_set master; // master file descriptor list fd_set master; / / master file descriptor list fd_set read_fds; // temp file descriptor list for select() fd_set read_fds; file / / temp deskriptor daftar untuk pilih () int fdmax; // maximum file descriptor number int fdmax; / / jumlah maksimum deskriptor file yang int listener; // listening socket descriptor int pendengar; / / deskriptor soket mendengarkan int newfd; // newly accept()ed socket descriptor int newfd; / / baru menerima () ed socket descriptor struct sockaddr_storage remoteaddr; // client address struct sockaddr_storage remoteaddr, alamat / / klien socklen_t addrlen; addrlen socklen_t; char buf[256]; // buffer for client data arang buf [256]; / / buffer untuk data klien int nbytes; int nbytes; char remoteIP[INET6_ADDRSTRLEN]; arang remoteip [INET6_ADDRSTRLEN]; int yes=1; // for setsockopt() SO_REUSEADDR, below int yes = 1; / / untuk setsockopt () SO_REUSEADDR, di bawah ini int i, j, rv; int i, j, rv; struct addrinfo hints, *ai, *p; struct petunjuk addrinfo, * ai, * p; FD_ZERO(&master); // clear the master and temp sets FD_ZERO (& master); / / bersihkan master dan set temporer FD_ZERO(&read_fds); FD_ZERO (& read_fds); // get us a socket and bind it / / Membuat kita soket dan mengikat memset(&hints, 0, sizeof hints); memset (& petunjuk, 0, sizeof petunjuk); hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; hints.ai_flags = AI_PASSIVE; if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) { if ((rv = getaddrinfo (NULL, PORT, & petunjuk, & ai)) = 0!) { fprintf(stderr, "selectserver: %s\n", gai_strerror(rv)); fprintf (stderr, "selectserver:% s \ n", gai_strerror (rv)); exit(1); exit (1); } } for(p = ai; p != NULL; p = p->ai_next) { untuk (p = ai; p = NULL;! p = p-> ai_next) { listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol); pendengar = socket (p-> ai_family, p-> ai_socktype, p-> ai_protocol); if (listener < 0) { if (pendengar <0) { continue; melanjutkan; } } // lose the pesky "address already in use" error message / / Kehilangan "alamat telah digunakan" sial pesan kesalahan setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); setsockopt (pendengar, SOL_SOCKET, SO_REUSEADDR, & ya, sizeof (int)); if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) { if (bind (listener, p-> ai_addr, p-> ai_addrlen) <0) { close(listener); dekat (pendengar); continue; melanjutkan; } } break; break; } } // if we got here, it means we didn't get bound / / Jika kita sampai di sini, itu berarti kita tidak mendapatkan terikat if (p == NULL) { if (p == NULL) { fprintf(stderr, "selectserver: failed to bind\n"); fprintf (stderr, "selectserver: gagal untuk mengikat \ n"); exit(2); exit (2); } } freeaddrinfo(ai); // all done with this freeaddrinfo (ai); / / semua dilakukan dengan ini // listen / / Mendengarkan if (listen(listener, 10) == -1) { if (mendengarkan pendengar (, 10) == -1) { perror("listen"); perror ("mendengarkan"); exit(3); exit (3); } } // add the listener to the master set / / Tambahkan pendengar untuk master set FD_SET(listener, &master); FD_SET (pendengar, & master); // keep track of the biggest file descriptor / / Melacak file descriptor terbesar fdmax = listener; // so far, it's this one fdmax = pendengar; / / sejauh ini, itu salah satu ini // main loop / Loop / utama for(;;) { for (;;) { read_fds = master; // copy it read_fds = master; / / copy if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) { if (pilih (fdmax +1, & read_fds, NULL, NULL, NULL) == -1) { perror("select"); perror ("pilih"); exit(4); exit (4); } } // run through the existing connections looking for data to read / / Jalankan melalui koneksi yang ada mencari data untuk membaca for(i = 0; i <= fdmax; i++) { for (i = 0; i <= fdmax; i + +) { if (FD_ISSET(i, &read_fds)) { // we got one!! jika (FD_ISSET (i, & read_fds)) {/ / kita punya satu! if (i == listener) { jika (i == listener) { // handle new connections / / Menangani koneksi baru addrlen = sizeof remoteaddr; addrlen = sizeof remoteaddr; newfd = accept(listener, newfd = menerima pendengar (, (struct sockaddr *)&remoteaddr, (Struct sockaddr *) & remoteaddr, &addrlen); & Addrlen); if (newfd == -1) { if (newfd == -1) { perror("accept"); perror ("menerima"); } else { } Else { FD_SET(newfd, &master); // add to master set FD_SET (newfd, & master); / / menambahkan untuk menguasai set if (newfd > fdmax) { // keep track of the max if (newfd> fdmax) {/ / melacak max fdmax = newfd; fdmax = newfd; } } printf("selectserver: new connection from %s on " printf ("selectserver: koneksi baru dari% s pada" "socket %d\n", "% Soket d \ n", inet_ntop(remoteaddr.ss_family, inet_ntop (remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), get_in_addr ((struct sockaddr *) & remoteaddr), remoteIP, INET6_ADDRSTRLEN), remoteip, INET6_ADDRSTRLEN), newfd); newfd); } } } else { } Else { // handle data from a client / / Menangani data dari klien if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) { if ((nbytes = recv (i, buf, sizeof buf, 0)) <= 0) { // got error or connection closed by client / / Ada error atau koneksi ditutup oleh klien if (nbytes == 0) { if (nbytes == 0) { // connection closed / / Koneksi ditutup printf("selectserver: socket %d hung up\n", i); printf ("selectserver: socket% d menutup telepon \ n", i); } else { } Else { perror("recv"); perror ("recv"); } } close(i); // bye! dekat (i); / / bye! FD_CLR(i, &master); // remove from master set FD_CLR (i, & master); / / hapus dari master set } else { } Else { // we got some data from a client / / Kita punya beberapa data dari klien for(j = 0; j <= fdmax; j++) { for (j = 0; j <= fdmax; j + +) { // send to everyone! / / Kirim ke semua orang! if (FD_ISSET(j, &master)) { if (FD_ISSET (j, & master)) { // except the listener and ourselves / / Kecuali pendengar dan diri kita sendiri if (j != listener && j != i) { if (j = j pendengar & &! = i) { if (send(j, buf, nbytes, 0) == -1) { if (send (j, buf, nbytes, 0) == -1) { perror("send"); perror ("kirim"); } } } } } } } } } } } // END handle data from client } / Data menangani / AKHIR dari klien } // END got new incoming connection } / / AKHIR punya koneksi masuk yang baru } // END looping through file descriptors } / / END perulangan melalui file descriptor } // END for(;;)--and you thought it would never end! } / / END untuk dan Anda pikir itu tidak akan pernah berakhir! (;;)-- return 0; return 0; } }Notice I have two file descriptor sets in the code: master and read_fds . Perhatikan Aku punya dua set file descriptor dalam kode: master dan read_fds. The first, master , holds all the socket descriptors that are currently connected, as well as the socket descriptor that is listening for new connections. Master, pertama, memegang semua deskriptor soket yang sedang terhubung, serta deskriptor soket yang mendengarkan untuk koneksi baru. The reason I have the master set is that select() actually changes the set you pass into it to reflect which sockets are ready to read. Alasan saya memiliki master set adalah bahwa pilih () benar-benar mengubah set Anda lulus ke dalamnya untuk mencerminkan yang soket siap untuk membaca. Since I have to keep track of the connections from one call of select() to the next, I must store these safely away somewhere. Karena saya harus melacak koneksi dari satu panggilan pilih () untuk berikutnya, aku harus menyimpan ini aman di suatu tempat. At the last minute, I copy the master into the read_fds , and then call select() . Pada menit terakhir, saya master copy ke read_fds, dan kemudian memanggil pilih (). But doesn't this mean that every time I get a new connection, I have to add it to the master set? Tapi tidak ini berarti bahwa setiap kali saya mendapatkan sambungan baru, saya harus menambahkannya ke master set? Yup! Yup! And every time a connection closes, I have to remove it from the master set? Dan setiap kali sambungan menutup, aku harus menghapus itu dari master set? Yes, it does. Ya, itu tidak. Notice I check to see when the listener socket is ready to read. Perhatikan saya cek untuk melihat ketika soket pendengar siap untuk membaca. When it is, it means I have a new connection pending, and I accept() it and add it to the master set. Ketika itu, itu berarti saya memiliki koneksi baru tertunda, dan saya menerima () dan menambahkannya ke master set. Similarly, when a client connection is ready to read, and recv() returns 0 , I know the client has closed the connection, and I must remove it from the master set. Demikian pula, ketika koneksi klien siap untuk membaca, dan recv () mengembalikan 0, aku tahu klien telah menutup sambungan, dan saya harus menghapusnya dari master set. If the client recv() returns non-zero, though, I know some data has been received. Jika klien recv () mengembalikan bukan nol, meskipun, saya tahu beberapa data telah diterima. So I get it, and then go through the master list and send that data to all the rest of the connected clients. Jadi saya mendapatkannya, dan kemudian pergi melalui daftar induk dan mengirim data ke semua sisa klien yang terhubung. And that, my friends, is a less-than-simple overview of the almighty select() function. Dan itu, teman-teman saya, ini adalah gambaran yang kurang sederhana pilih Mahakuasa () fungsi. In addition, here is a bonus afterthought: there is another function called Selain itu, di sini adalah renungan bonus: ada fungsi lain yang disebut poll() which behaves much the same way select() does, but with a different system for managing the file descriptor sets. Check it out! jajak pendapat () yang berperilaku dengan cara yang sama pilih () tidak, tetapi dengan sistem yang berbeda untuk mengelola set file descriptor tersebut. Check it out! 7.3. Handling Partial send() s 7.3. Penanganan parsial mengirim () sRemember back in the section about send() , above, when I said that send() might not send all the bytes you asked it to? Ingat kembali di bagian tentang send () , di atas, ketika saya mengatakan bahwa mengirim () tidak mungkin mengirim semua byte yang Anda memintanya untuk? That is, you want it to send 512 bytes, but it returns 412. Artinya, Anda ingin untuk mengirim 512 byte, tapi kembali 412. What happened to the remaining 100 bytes? Apa yang terjadi pada 100 byte yang tersisa?Well, they're still in your little buffer waiting to be sent out. Yah, mereka masih dalam buffer kecil Anda menunggu untuk dikirim keluar. Due to circumstances beyond your control, the kernel decided not to send all the data out in one chunk, and now, my friend, it's up to you to get the data out there. Karena keadaan di luar kontrol Anda, kernel memutuskan untuk tidak mengirim semua data dalam satu potongan, dan sekarang, teman saya, itu terserah Anda untuk mendapatkan data di luar sana. You could write a function like this to do it, too: Anda bisa menulis fungsi seperti ini untuk melakukannya juga: #include <sys/types.h> # Include <sys/types.h> #include <sys/socket.h> # Include <sys/socket.h> int sendall(int s, char *buf, int *len) int sendall (int s, char * buf, int len *) { { int total = 0; // how many bytes we've sent int total = 0; / / berapa banyak byte yang dikirim kami int bytesleft = *len; // how many we have left to send int len bytesleft = *; / / berapa banyak kita miliki untuk mengirim int n; int n; while(total < *len) { sementara (total <* len) { n = send(s, buf+total, bytesleft, 0); n = send (s, buf + total, bytesleft, 0); if (n == -1) { break; } if (n == -1) {break;} total += n; total + = n; bytesleft -= n; bytesleft -= n; } } *len = total; // return number actually sent here * Len = total; nomor / / mengembalikan benar-benar dikirim ke sini return n==-1?-1:0; // return -1 on failure, 0 on success kembali n ==- 1 -1:0;? / / mengembalikan -1 pada kegagalan, 0 pada keberhasilan } }In this example, s is the socket you want to send the data to, buf is the buffer containing the data, and len is a pointer to an int containing the number of bytes in the buffer. Dalam contoh ini, s adalah soket Anda ingin mengirim data ke, buf adalah buffer berisi data, dan len adalah pointer ke int berisi jumlah byte dalam buffer. The function returns -1 on error (and errno is still set from the call to send() .) Also, the number of bytes actually sent is returned in len . Fungsi ini mengembalikan -1 pada kesalahan (dan errno masih diatur dari panggilan untuk mengirim ().) Juga, jumlah byte yang dikirim kembali pada len. This will be the same number of bytes you asked it to send, unless there was an error. sendall() will do it's best, huffing and puffing, to send the data out, but if there's an error, it gets back to you right away. Ini akan menjadi jumlah yang sama byte Anda diminta untuk mengirim, kecuali ada kesalahan sendall () akan melakukan yang terbaik, terengah-engah, untuk mengirim data keluar,. Tapi kalau ada kesalahan, itu akan kembali kepada Anda yang benar pergi. For completeness, here's a sample call to the function: Untuk kelengkapan, inilah panggilan sampel untuk fungsi: char buf[10] = "Beej!"; buf char [10] = "Biija!"; int len; int len; len = strlen(buf); len = strlen (buf); if (sendall(s, buf, &len) == -1) { if (sendall (s, buf, len &) == -1) { perror("sendall"); perror ("sendall"); printf("We only sent %d bytes because of the error!\n", len); printf ("Kami hanya mengirim byte% d karena kesalahan \ n", len); } }What happens on the receiver's end when part of a packet arrives? Apa yang terjadi pada akhir penerima ketika bagian dari sebuah paket tiba? If the packets are variable length, how does the receiver know when one packet ends and another begins? Jika paket yang panjang variabel, bagaimana penerima tahu kapan satu paket berakhir dan yang lain dimulai? Yes, real-world scenarios are a royal pain in the Ya, dunia nyata skenario yang sakit kerajaan di donkeys. keledai. You probably have to Anda mungkin harus encapsulate (remember that from the data encapsulation section way back there at the beginning?) Read on for details! merangkum (ingat bahwa dari data yang enkapsulasi bagian perjalanan kembali ada di awal?) Baca terus untuk detail! 7.4. Serialization—How to Pack Data 7.4. Serialisasi-Bagaimana data PackIt's easy enough to send text data across the network, you're finding, but what happens if you want to send some "binary" data like int s or float s? Ini cukup mudah untuk mengirim data teks di seluruh jaringan, Anda menemukan, tetapi apa yang terjadi jika Anda ingin mengirim beberapa "biner" data seperti int atau float s s? It turns out you have a few options. Ternyata Anda memiliki beberapa pilihan.
[ Curtain raises ] [Tirai menimbulkan] Beej says, "I prefer Method Three, above!" Biija berkata, "Aku lebih suka Metode Tiga, di atas!" [ THE END ] [AKHIR] (Before I begin this section in earnest, I should tell you that there are libraries out there for doing this, and rolling your own and remaining portable and error-free is quite a challenge. So hunt around and do your homework before deciding to implement this stuff yourself. I include the information here for those curious about how things like this work.) (Sebelum saya mulai bagian ini dengan sungguh-sungguh, saya harus memberitahu Anda bahwa ada perpustakaan di luar sana untuk melakukan hal ini, dan rolling Anda sendiri dan yang tersisa portabel dan bebas dari kesalahan cukup tantangan. Jadi berburu di sekitar dan melakukan pekerjaan rumah Anda sebelum memutuskan untuk mengimplementasikan hal ini sendiri saya termasuk. informasi di sini bagi mereka yang ingin tahu tentang bagaimana hal-hal seperti pekerjaan ini.) Actually all the methods, above, have their drawbacks and advantages, but, like I said, in general, I prefer the third method. Sebenarnya semua metode di atas, memiliki kelemahan mereka dan keuntungan, tapi, seperti saya katakan, secara umum, saya lebih memilih metode ketiga. First, though, let's talk about some of the drawbacks and advantages to the other two. Pertama, meskipun, mari kita bicara tentang beberapa kelemahan dan keuntungan dua lainnya. The first method, encoding the numbers as text before sending, has the advantage that you can easily print and read the data that's coming over the wire. Metode pertama, pengkodean angka sebagai teks sebelum mengirimnya, memiliki keuntungan yang Anda dapat dengan mudah mencetak dan membaca data yang datang melalui kawat. Sometimes a human-readable protocol is excellent to use in a non-bandwidth-intensive situation, such as with Kadang-kadang sebuah protokol terbaca-manusia yang sangat baik untuk digunakan dalam situasi non-bandwidth-intensif, seperti dengan Internet Relay Chat (IRC) . Internet Relay Chat (IRC) . However, it has the disadvantage that it is slow to convert, and the results almost always take up more space than the original number! Namun, ia memiliki kelemahan yang lambat untuk mengubah, dan hasilnya hampir selalu mengambil ruang lebih dari jumlah yang asli! Method two: passing the raw data. Cara dua: lewat data mentah. This one is quite easy (but dangerous!): just take a pointer to the data to send, and call send with it. Satu ini cukup mudah (tapi berbahaya!): Hanya mengambil pointer ke data untuk mengirim, dan menyebutnya kirim dengan itu. double d = 3490.15926535; double d = 3490.15926535; send(s, &d, sizeof d, 0); /* DANGER--non-portable! kirim (s, & d, sizeof d, 0); / * BAHAYA - non-portabel! */ * /The receiver gets it like this: Penerima mendapatkan seperti ini: double d; double d; recv(s, &d, sizeof d, 0); /* DANGER--non-portable! recv (s, & d, sizeof d, 0); / * BAHAYA - non-portabel! */ * /Fast, simple—what's not to like? Cepat, sederhana-apa yang tidak disukai? Well, it turns out that not all architectures represent a double (or int for that matter) with the same bit representation or even the same byte ordering! Nah, ternyata tidak semua arsitektur mewakili ganda (atau int dalam hal ini) dengan representasi bit yang sama atau bahkan byte yang sama memesan! The code is decidedly non-portable. Kode ini jelas non-portabel. (Hey—maybe you don't need portability, in which case this is nice and fast.) (Hei-mungkin Anda tidak perlu portabilitas, dalam hal ini adalah bagus dan cepat.) When packing integer types, we've already seen how the Ketika kemasan tipe data integer, kita sudah melihat bagaimana htons() -class of functions can help keep things portable by transforming the numbers into htons ()-kelas fungsi dapat membantu menjaga hal-hal portabel dengan mengubah angka-angka ke Network Byte Order, and how that's the Right Thing to do. Jaringan Byte Order, dan bagaimana itu Hal yang Benar untuk melakukan. Unfortunately, there are no similar functions for float types. Sayangnya, tidak ada fungsi yang sama untuk jenis mengapung. Is all hope lost? Adalah harapan semua hilang? Fear not! Jangan takut! (Were you afraid there for a second? No? Not even a little bit?) There is something we can do: we can pack (or "marshal", or "serialize", or one of a thousand million other names) the data into a known binary format that the receiver can unpack on the remote side. (Apakah Anda khawatir ada untuk kedua Tidak ada pun tidak sedikit???) Ada sesuatu yang bisa kita lakukan: kita bisa pak (atau "marshal", atau "cerita", atau satu dari seribu juta nama lainnya) data ke dalam format biner diketahui bahwa penerima dapat membongkar pada sisi remote. What do I mean by "known binary format"? Apa yang saya maksud dengan "format biner dikenal"? Well, we've already seen the htons() example, right? Nah, kita sudah melihat htons () misalnya, kan? It changes (or "encodes", if you want to think of it that way) a number from whatever the host format is into Network Byte Order. Ini perubahan (atau "encode", jika Anda ingin memikirkan cara itu) nomor dari apapun format host ke dalam Ordo Jaringan Byte. To reverse (unencode) the number, the receiver calls ntohs() . Untuk membalik (unencode) jumlah, penerima panggilan ntohs (). But didn't I just get finished saying there wasn't any such function for other non-integer types? Tapi aku tidak hanya mendapatkan selesai mengatakan tidak ada fungsi seperti untuk lainnya non-integer jenis? Yes. Ya. I did. Saya lakukan. And since there's no standard way in C to do this, it's a bit of a pickle (that a gratuitous pun there for you Python fans). Dan karena tidak ada cara standar dalam C untuk melakukan hal ini, itu sedikit acar (pelesetan beralasan bahwa di sana untuk Anda penggemar Python). The thing to do is to pack the data into a known format and send that over the wire for decoding. Hal yang harus dilakukan adalah untuk paket data ke dalam format yang dikenal dan mengirim bahwa selama kawat untuk decoding. For example, to pack float s, here's something quick and dirty with plenty of room for improvement: Misalnya, untuk paket mengambang s, inilah sesuatu yang cepat dan kotor dengan banyak ruang untuk perbaikan: #include <stdint.h> # Include <stdint.h> uint32_t htonf(float f) uint32_t htonf (float f) { { uint32_t p; p uint32_t; uint32_t sign; tanda uint32_t; if (f < 0) { sign = 1; f = -f; } if (f <0) {tanda = 1; f =-f;} else { sign = 0; } else {tanda = 0;} p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31); // whole part and sign p = ((((uint32_t) f) & 0x7fff) <<16) | (tanda <<31); / / keseluruhan bagian dan tanda p |= (uint32_t)(((f - (int)f) * 65536.0f))&0xffff; // fraction p | = (uint32_t) (((f - (int) f) * 65536.0f)) &0xffff; / / fraksi return p; kembali p; } } float ntohf(uint32_t p) mengambang ntohf (p uint32_t) { { float f = ((p>>16)&0x7fff); // whole part mengambang f = ((p>> 16) & 0x7fff); / / keseluruhan bagian f += (p&0xffff) / 65536.0f; // fraction f + = (p & 0xffff) / 65536.0f; / / fraksi if (((p>>31)&0x1) == 0x1) { f = -f; } // sign bit set jika (((p>> 31) & 0x1) == 0x1) {f =-f;} / / tanda bit set return f; kembali f; } }The above code is sort of a naive implementation that stores a float in a 32-bit number. Kode di atas adalah semacam implementasi naif yang menyimpan mengapung di sebuah 32-bit. The high bit (31) is used to store the sign of the number ("1" means negative), and the next seven bits (30-16) are used to store the whole number portion of the float . Bit tinggi (31) digunakan untuk menyimpan tanda nomor ("1" berarti negatif), dan tujuh bit berikutnya (30-16) digunakan untuk menyimpan bagian seluruh jumlah float. Finally, the remaining bits (15-0) are used to store the fractional portion of the number. Akhirnya, bit sisanya (15-0) digunakan untuk menyimpan bagian pecahan dari jumlah tersebut. Usage is fairly straightforward: Penggunaan cukup mudah: #include <stdio.h> # Include int main(void) int main (void) { { float f = 3.1415926, f2; f = 3.1415926 mengapung, f2; uint32_t netf; uint32_t netf; netf = htonf(f); // convert to "network" form netf = htonf (f); / / mengkonversi untuk membentuk "jaringan" f2 = ntohf(netf); // convert back to test f2 = ntohf (netf); / / mengkonversi kembali untuk menguji printf("Original: %f\n", f); // 3.141593 printf ("Asli:% f \ n", f); / / 3,141593 printf(" Network: 0x%08X\n", netf); // 0x0003243F printf ("Jaringan: 0x% 08X \ n", netf); / / 0x0003243F printf("Unpacked: %f\n", f2); // 3.141586 printf ("membongkar:% f \ n", f2); / / 3,141586 return 0; return 0; } }On the plus side, it's small, simple, and fast. Di sisi positifnya, itu kecil, sederhana, dan cepat. On the minus side, it's not an efficient use of space and the range is severely restricted—try storing a number greater-than 32767 in there and it won't be very happy! Di sisi minus, itu bukan efisiensi penggunaan ruang dan jangkauan sangat terbatas-coba menyimpan nomor yang lebih besar dari 32767 di sana dan itu tidak akan sangat bahagia! You can also see in the above example that the last couple decimal places are not correctly preserved. Anda juga dapat melihat pada contoh di atas bahwa tempat-tempat desimal terakhir pasangan tidak benar diawetkan. What can we do instead? Apa yang bisa kita lakukan sebagai gantinya? Well, The Standard for storing floating point numbers is known as Nah, Standar untuk menyimpan angka floating point dikenal sebagai IEEE-754 . IEEE-754 . Most computers use this format internally for doing floating point math, so in those cases, strictly speaking, conversion wouldn't need to be done. Kebanyakan komputer menggunakan format ini secara internal untuk melakukan floating point matematika, sehingga dalam kasus-kasus, tegasnya, konversi tidak perlu dilakukan. But if you want your source code to be portable, that's an assumption you can't necessarily make. Tetapi jika Anda ingin kode sumber Anda untuk menjadi portabel, itu sebuah asumsi Anda tidak bisa selalu membuat. (On the other hand, if you want things to be fast, you should optimize this out on platforms that don't need to do it! That's what htons() and its ilk do.) (Di sisi lain, jika Anda ingin hal yang harus cepat, Anda harus mengoptimalkan hal ini pada platform yang tidak perlu melakukannya Itulah yang htons () dan sejenisnya lakukan.!) Here's some code that encodes floats and doubles into IEEE-754 format . Berikut adalah beberapa kode yang mengkodekan mengapung dan ganda ke IEEE-754 Format . (Mostly—it doesn't encode NaN or Infinity, but it could be modified to do that.) (Sebagian besar-tidak mengkodekan NaN atau tak terhingga, tapi dapat dimodifikasi untuk melakukan itu.) #define pack754_32(f) (pack754((f), 32, 8)) # Define pack754_32 (f) (pack754 ((f), 32, 8)) #define pack754_64(f) (pack754((f), 64, 11)) # Define pack754_64 (f) (pack754 ((f), 64, 11)) #define unpack754_32(i) (unpack754((i), 32, 8)) # Define unpack754_32 (i) (unpack754 ((i), 32, 8)) #define unpack754_64(i) (unpack754((i), 64, 11)) # Define unpack754_64 (i) (unpack754 ((i), 64, 11)) uint64_t pack754(long double f, unsigned bits, unsigned expbits) uint64_t pack754 (f ganda panjang, bit unsigned, unsigned expbits) { { long double fnorm; fnorm ganda panjang; int shift; int pergeseran; long long sign, exp, significand; panjang tanda, exp, significand; unsigned significandbits = bits - expbits - 1; // -1 for sign bit unsigned significandbits = bit - expbits - 1; / / -1 untuk sedikit tanda if (f == 0.0) return 0; // get this special case out of the way if (f == 0,0) return 0; / / mendapatkan ini kasus khusus dari jalan // check sign and begin normalization / / Cek tanda dan memulai normalisasi if (f < 0) { sign = 1; fnorm = -f; } if (f <0) {tanda = 1; fnorm =-f;} else { sign = 0; fnorm = f; } else {tanda = 0; fnorm = f;} // get the normalized form of f and track the exponent / / Mendapatkan formulir normalisasi dari f dan melacak eksponen shift = 0; pergeseran = 0; while(fnorm >= 2.0) { fnorm /= 2.0; shift++; } sementara (fnorm> = 2.0) {fnorm / = 2,0; pergeseran + +;} while(fnorm < 1.0) { fnorm *= 2.0; shift--; } sementara (fnorm <1,0) {fnorm *= 2.0; pergeseran -;} fnorm = fnorm - 1.0; fnorm = fnorm - 1,0; // calculate the binary form (non-float) of the significand data / / Menghitung bentuk biner (non-float) dari data significand significand = fnorm * ((1LL<<significandbits) + 0.5f); significand = fnorm * ((1LL <<significandbits) + 0.5f); // get the biased exponent / / Mendapatkan eksponen bias exp = shift + ((1<<(expbits-1)) - 1); // shift + bias exp = shift + ((1 <<(expbits-1)) - 1); / / shift + Bias // return the final answer / / Mengembalikan jawaban akhir return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand; kembali (tanda <<(bit-1)) | (exp <<(bit-expbits-1)) | significand; } } long double unpack754(uint64_t i, unsigned bits, unsigned expbits) panjang ganda unpack754 (i uint64_t, bit unsigned, unsigned expbits) { { long double result; hasil ganda yang panjang; long long shift; pergeseran jangka panjang; unsigned bias; unsigned Bias; unsigned significandbits = bits - expbits - 1; // -1 for sign bit unsigned significandbits = bit - expbits - 1; / / -1 untuk sedikit tanda if (i == 0) return 0.0; jika (i == 0) return 0.0; // pull the significand / / Tarik significand yang result = (i&((1LL<<significandbits)-1)); // mask hasil = (i & ((1LL <<significandbits) -1)); / / mask result /= (1LL<<significandbits); // convert back to float hasil / = (1LL <<significandbits); / / mengkonversi kembali mengapung result += 1.0f; // add the one back on hasil + = 1.0f; / / tambahkan satu kembali pada // deal with the exponent / / Berurusan dengan eksponen bias = (1<<(expbits-1)) - 1; Bias = (1 <<(expbits-1)) - 1; shift = ((i>>significandbits)&((1LL<<expbits)-1)) - bias; pergeseran = ((i>> significandbits) & ((1LL <<expbits) -1)) - Bias; while(shift > 0) { result *= 2.0; shift--; } sementara (pergeseran> 0) {hasil *= 2.0; pergeseran -;} while(shift < 0) { result /= 2.0; shift++; } sementara (pergeseran <0) {hasil / = 2,0; pergeseran + +;} // sign it / / Menandatanganinya result *= (i>>(bits-1))&1? hasil *= (i>> (bit-1)) & 1? -1.0: 1.0; -1.0: 1,0; return result; kembali hasil; } }I put some handy macros up there at the top for packing and unpacking 32-bit (probably a float ) and 64-bit (probably a double ) numbers, but the pack754() function could be called directly and told to encode bits -worth of data ( expbits of which are reserved for the normalized number's exponent.) Aku menaruh beberapa macro berguna di atas sana di bagian atas untuk pengepakan dan membongkar 32-bit (mungkin pelampung) dan 64-bit (mungkin ganda) nomor, tetapi pack754 () fungsi bisa disebut secara langsung dan menyuruh untuk mengkodekan bit-senilai data (expbits yang disediakan untuk eksponen jumlah yang normal.) Here's sample usage: Berikut contoh penggunaan: #include <stdio.h> # Include #include <stdint.h> // defines uintN_t types # Include / <stdint.h> / mendefinisikan jenis uintN_t #include <inttypes.h> // defines PRIx macros # Include <inttypes.h> / / mendefinisikan macro PRIX int main(void) int main (void) { { float f = 3.1415926, f2; f = 3.1415926 mengapung, f2; double d = 3.14159265358979323, d2; double d = 3,14159265358979323, d2; uint32_t fi; fi uint32_t; uint64_t di; uint64_t di; fi = pack754_32(f); fi = pack754_32 (f); f2 = unpack754_32(fi); f2 = unpack754_32 (fi); di = pack754_64(d); di = pack754_64 (d); d2 = unpack754_64(di); d2 = unpack754_64 (di); printf("float before : %.7f\n", f); printf ("mengapung sebelumnya: 0,7% f \ n", f); printf("float encoded: 0x%08" PRIx32 "\n", fi); printf ("float dikodekan: 0x% 08" PRIx32 "\ n", fi); printf("float after : %.7f\n\n", f2); printf ("mengambang setelah:% .7 f \ n \ n", f2); printf("double before : %.20lf\n", d); printf ("ganda sebelum:% .20 Jika \ n", d); printf("double encoded: 0x%016" PRIx64 "\n", di); printf ("dikodekan ganda: 0x% 016" PRIx64 "\ n", di); printf("double after : %.20lf\n", d2); printf ("ganda setelah:% .20 Jika \ n", d2); return 0; return 0; } }The above code produces this output: Kode di atas menghasilkan output ini: float before : 3.1415925 mengapung sebelumnya: 3.1415925 float encoded: 0x40490FDA mengapung dikodekan: 0x40490FDA float after : 3.1415925 mengapung setelah: 3.1415925 double before : 3.14159265358979311600 ganda sebelum: 3,14159265358979311600 double encoded: 0x400921FB54442D18 dikodekan ganda: 0x400921FB54442D18 double after : 3.14159265358979311600 ganda setelah: 3,14159265358979311600Another question you might have is how do you pack struct s? Pertanyaan lain yang mungkin Anda miliki adalah bagaimana Anda pak struct s? Unfortunately for you, the compiler is free to put padding all over the place in a struct , and that means you can't portably send the whole thing over the wire in one chunk. Sayangnya bagi Anda, compiler bebas untuk meletakkan bantalan di seluruh tempat di struct, dan itu berarti Anda tidak dapat portable mengirim semuanya melalui kawat dalam satu potongan. (Aren't you getting sick of hearing "can't do this", "can't do that"? Sorry! To quote a friend, "Whenever anything goes wrong, I always blame Microsoft." This one might not be Microsoft's fault, admittedly, but my friend's statement is completely true.) (Bukankah Anda mendapatkan muak mendengar "tidak bisa melakukan ini", "tidak bisa melakukan itu" Maaf!? Untuk kutipan teman, "Setiap kali ada yang tidak beres, saya selalu menyalahkan Microsoft." Yang satu ini mungkin tidak Microsoft kesalahan, diakui, tetapi pernyataan teman saya benar-benar benar.) Back to it: the best way to send the struct over the wire is to pack each field independently and then unpack them into the struct when they arrive on the other side. Kembali ke: cara terbaik untuk mengirim struct di atas kawat adalah untuk paket setiap bidang secara independen dan kemudian ekstrak ke dalam struct ketika mereka tiba di sisi lain. That's a lot of work, is what you're thinking. Itu banyak pekerjaan, adalah apa yang Anda pikirkan. Yes, it is. Ya, itu. One thing you can do is write a helper function to help pack the data for you. Satu hal yang dapat Anda lakukan adalah menulis fungsi pembantu untuk membantu paket data untuk Anda. It'll be fun! Ini akan menyenangkan! Really! Benar-benar! In the book " The Practice of Programming " by Kernighan and Pike, they implement printf() -like functions called pack() and unpack() that do exactly this. Dalam buku " Praktek Pemrograman "oleh Kernighan dan Pike, mereka menerapkan printf ()-seperti fungsi disebut paket () dan ekstrak () yang melakukan hal ini. I'd link to them, but apparently those functions aren't online with the rest of the source from the book. Saya akan link ke mereka, tapi rupanya fungsi-fungsi yang tidak online dengan sisa sumber dari buku. (The Practice of Programming is an excellent read. Zeus saves a kitten every time I recommend it.) (Praktik Pemrograman adalah membaca yang sangat baik Zeus menyimpan kucing setiap kali saya sarankan itu..) At this point, I'm going to drop a pointer to the BSD-licensed Typed Parameter Language C API which I've never used, but looks completely respectable. Pada titik ini, aku akan menjatuhkan pointer ke lisensi BSD- Parameter Bahasa Diketik C API yang saya tidak pernah pakai, tapi tampak benar-benar terhormat. Python and Perl programmers will want to check out their language's pack() and unpack() functions for accomplishing the same thing. Python dan Perl programmer akan ingin memeriksa paket bahasa mereka () dan ekstrak () fungsi untuk mencapai hal yang sama. And Java has a big-ol' Serializable interface that can be used in a similar way. Dan Jawa memiliki besar-ol 'antarmuka Serializable yang dapat digunakan dalam cara yang sama. But if you want to write your own packing utility in C, K&P's trick is to use variable argument lists to make printf() -like functions to build the packets. Here's a version I cooked up on my own based on that which hopefully will be enough to give you an idea of how such a thing can work. Tetapi jika Anda ingin menulis utilitas sendiri kemasan Anda di C, trik K & P adalah dengan menggunakan daftar argumen variabel untuk membuat printf ()-seperti fungsi untuk membangun paket. Berikut adalah versi saya memasak sendiri berdasarkan apa yang diharapkan akan cukup untuk memberikan gambaran bagaimana hal itu dapat bekerja. (This code references the pack754() functions, above. The packi*() functions operate like the familiar htons() family, except they pack into a char array instead of another integer.) (Referensi Kode ini yang pack754 () fungsi, di atas. Packi * () fungsi beroperasi seperti akrab htons () keluarga, kecuali mereka masukkan ke dalam array char bukan bilangan bulat yang lain.) #include <ctype.h> # Include <ctype.h> #include <stdarg.h> # Include <stdarg.h> #include <string.h> # Include <string.h> #include <stdint.h> # Include <stdint.h> #include <inttypes.h> # Include <inttypes.h> // various bits for floating point types-- / / Berbagai bit untuk jenis floating point - // varies for different architectures / / Bervariasi untuk arsitektur yang berbeda typedef float float32_t; typedef mengapung float32_t; typedef double float64_t; float64_t typedef ganda; /* / * ** packi16() -- store a 16-bit int into a char buffer (like htons()) ** Packi16 () - toko int 16-bit dalam buffer char (seperti htons ()) */ * / void packi16(unsigned char *buf, unsigned int i) kekosongan packi16 (unsigned char * buf, unsigned int i) { { *buf++ = i>>8; *buf++ = i; * Buf + + = i>> 8; * buf + + = i; } } /* / * ** packi32() -- store a 32-bit int into a char buffer (like htonl()) ** Packi32 () - toko int 32-bit dalam buffer char (seperti htonl ()) */ * / void packi32(unsigned char *buf, unsigned long i) kekosongan packi32 (* buf unsigned char, unsigned long i) { { *buf++ = i>>24; *buf++ = i>>16; * Buf + + = i>> 24; * buf + + = i>> 16; *buf++ = i>>8; *buf++ = i; * Buf + + = i>> 8; * buf + + = i; } } /* / * ** unpacki16() -- unpack a 16-bit int from a char buffer (like ntohs()) ** Unpacki16 () - ekstrak int 16-bit dari buffer char (seperti ntohs ()) */ * / unsigned int unpacki16(unsigned char *buf) unsigned int unpacki16 (unsigned char * buf) { { return (buf[0]<<8) | buf[1]; kembali (buf [0] <<8) | buf [1]; } } /* / * ** unpacki32() -- unpack a 32-bit int from a char buffer (like ntohl()) ** Unpacki32 () - ekstrak int 32-bit dari buffer char (seperti ntohl ()) */ * / unsigned long unpacki32(unsigned char *buf) unsigned panjang unpacki32 (unsigned char * buf) { { return (buf[0]<<24) | (buf[1]<<16) | (buf[2]<<8) | buf[3]; kembali (buf [0] <<24) | (buf [1] <<16) | (buf [2] <<8) | buf [3]; } } /* / * ** pack() -- store data dictated by the format string in the buffer ** Pack () - menyimpan data didikte oleh format string dalam buffer ** ** ** h - 16-bit l - 32-bit ** H - 16-bit l - 32-bit ** c - 8-bit char f - float, 32-bit ** C - f arang 8-bit - mengambang, 32-bit ** s - string (16-bit length is automatically prepended) ** S - string (16-bit panjang secara otomatis prepended) */ * / int32_t pack(unsigned char *buf, char *format, ...) int32_t paket (* buf unsigned char, char * format, ...) { { va_list ap; va_list ap; int16_t h; int16_t h; int32_t l; l int32_t; int8_t c; c int8_t; float32_t f; float32_t f; char *s; char * s; int32_t size = 0, len; Ukuran int32_t = 0, len; va_start(ap, format); va_start (ap, format); for(; *format != '\0'; format++) { for (; * format = '\ 0';! Format + +) { switch(*format) { switch (* format) { case 'h': // 16-bit kasus 'h': / / 16-bit size += 2; ukuran + = 2; h = (int16_t)va_arg(ap, int); // promoted h = (int16_t) va_arg (ap, int); / / dipromosikan packi16(buf, h); packi16 (buf, h); buf += 2; buf + = 2; break; break; case 'l': // 32-bit 'l' kasus: / / 32-bit size += 4; ukuran + = 4; l = va_arg(ap, int32_t); l = va_arg (ap, int32_t); packi32(buf, l); packi32 (buf, l); buf += 4; buf + = 4; break; break; case 'c': // 8-bit kasus 'c': / / 8-bit size += 1; Ukuran + = 1; c = (int8_t)va_arg(ap, int); // promoted c = (int8_t) va_arg (ap, int); / / dipromosikan *buf++ = (c>>0)&0xff; * Buf + + = (c>> 0) &0xff; break; break; case 'f': // float kasus 'f': / / mengapung size += 4; ukuran + = 4; f = (float32_t)va_arg(ap, double); // promoted f = (float32_t) va_arg (ap, ganda); / / dipromosikan l = pack754_32(f); // convert to IEEE 754 l = pack754_32 (f); / / mengkonversi ke IEEE 754 packi32(buf, l); packi32 (buf, l); buf += 4; buf + = 4; break; break; case 's': // string kasus 's': / / string s = va_arg(ap, char*); s = va_arg (ap, char *); len = strlen(s); len = strlen (s); size += len + 2; ukuran + = len + 2; packi16(buf, len); packi16 (buf, len); buf += 2; buf + = 2; memcpy(buf, s, len); memcpy (buf, s, len); buf += len; buf + = len; break; break; } } } } va_end(ap); va_end (ap); return size; kembali ukuran; } } /* / * ** unpack() -- unpack data dictated by the format string into the buffer ** Membongkar () - ekstrak data yang didikte oleh format string ke dalam buffer */ * / void unpack(unsigned char *buf, char *format, ...) batal membongkar (* buf unsigned char, char * format, ...) { { va_list ap; va_list ap; int16_t *h; int16_t * h; int32_t *l; int32_t * l; int32_t pf; pf int32_t; int8_t *c; int8_t * c; float32_t *f; float32_t * f; char *s; char * s; int32_t len, count, maxstrlen=0; int32_t len, menghitung, maxstrlen = 0; va_start(ap, format); va_start (ap, format); for(; *format != '\0'; format++) { for (; * format = '\ 0';! Format + +) { switch(*format) { switch (* format) { case 'h': // 16-bit kasus 'h': / / 16-bit h = va_arg(ap, int16_t*); h = va_arg (ap, int16_t *); *h = unpacki16(buf); * H = unpacki16 (buf); buf += 2; buf + = 2; break; break; case 'l': // 32-bit 'l' kasus: / / 32-bit l = va_arg(ap, int32_t*); l = va_arg (ap, * int32_t); *l = unpacki32(buf); * L = unpacki32 (buf); buf += 4; buf + = 4; break; break; case 'c': // 8-bit kasus 'c': / / 8-bit c = va_arg(ap, int8_t*); c = va_arg (ap, * int8_t); *c = *buf++; * C = * buf + +; break; break; case 'f': // float kasus 'f': / / mengapung f = va_arg(ap, float32_t*); f = va_arg (ap, float32_t *); pf = unpacki32(buf); pf = unpacki32 (buf); buf += 4; buf + = 4; *f = unpack754_32(pf); * F = unpack754_32 (pf); break; break; case 's': // string kasus 's': / / string s = va_arg(ap, char*); s = va_arg (ap, char *); len = unpacki16(buf); len = unpacki16 (buf); buf += 2; buf + = 2; if (maxstrlen > 0 && len > maxstrlen) count = maxstrlen - 1; if (maxstrlen> 0 & & len> maxstrlen) count = maxstrlen - 1; else count = len; lagi count = len; memcpy(s, buf, count); memcpy (s, buf, count); s[count] = '\0'; s [count] = '\ 0'; buf += len; buf + = len; break; break; default: default: if (isdigit(*format)) { // track max str len if (isdigit (* Format)) {/ / track max str len maxstrlen = maxstrlen * 10 + (*format-'0'); maxstrlen = maxstrlen * 10 + (* Format-'0 '); } } } } if (!isdigit(*format)) maxstrlen = 0; if (isdigit (* format)!) maxstrlen = 0; } } va_end(ap); va_end (ap); } }And here is a demonstration program of the above code that packs some data into buf and then unpacks it into variables. Dan di sini adalah program demonstrasi dari kode di atas yang bungkus beberapa data ke buf dan kemudian membongkar ke variabel. Note that when calling unpack() with a string argument (format specifier " s "), it's wise to put a maximum length count in front of it to prevent a buffer overrun, eg " 96s ". Perhatikan bahwa saat memanggil membongkar () dengan argumen string (format specifier "s"), adalah bijaksana untuk menempatkan hitungan panjang maksimum di depannya untuk mencegah buffer overrun, misalnya "96s". Be wary when unpacking data you get over the network—a malicious user might send badly-constructed packets in an effort to attack your system! Berhati-hatilah ketika membongkar data yang anda peroleh melalui jaringan-pengguna yang jahat bisa mengirim paket buruk-dibangun dalam upaya untuk menyerang sistem Anda! #include <stdio.h> # Include // various bits for floating point types-- / / Berbagai bit untuk jenis floating point - // varies for different architectures / / Bervariasi untuk arsitektur yang berbeda typedef float float32_t; typedef mengapung float32_t; typedef double float64_t; float64_t typedef ganda; int main(void) int main (void) { { unsigned char buf[1024]; unsigned char buf [1024]; int8_t magic; int8_t ajaib; int16_t monkeycount; int16_t monkeycount; int32_t altitude; ketinggian int32_t; float32_t absurdityfactor; absurdityfactor float32_t; char *s = "Great unmitigated Zot! You've found the Runestaff!"; char * s = "Zot tak tanggung-tanggung Besar Anda telah menemukan Runestaff!!"; char s2[96]; arang s2 [96]; int16_t packetsize, ps2; int16_t packetsize, ps2; packetsize = pack(buf, "chhlsf", (int8_t)'B', (int16_t)0, (int16_t)37, packetsize = pack (buf, "chhlsf", (int8_t) 'B', (int16_t) 0, (int16_t) 37, (int32_t)-5, s, (float32_t)-3490.6677); (Int32_t) -5, s, (float32_t) -3.490,6677); packi16(buf+1, packetsize); // store packet size in packet for kicks packi16 (buf +1, packetsize); / / menyimpan ukuran paket dalam paket untuk iseng printf("packet is %" PRId32 " bytes\n", packetsize); printf ("paket%" PRId32 "byte \ n", packetsize); unpack(buf, "chhl96sf", &magic, &ps2, &monkeycount, &altitude, s2, membongkar (buf, "chhl96sf", & ajaib, & ps2, & monkeycount, & ketinggian, s2, &absurdityfactor); & Absurdityfactor); printf("'%c' %" PRId32" %" PRId16 " %" PRId32 printf ("% '% c'" PRId32 "%" PRId16 "%" PRId32 " \"%s\" %f\n", magic, ps2, monkeycount, "\"% S \ "% f \ n", sihir, ps2, monkeycount, altitude, s2, absurdityfactor); ketinggian, s2, absurdityfactor); return 0; return 0; } }Whether you roll your own code or use someone else's, it's a good idea to have a general set of data packing routines for the sake of keeping bugs in check, rather than packing each bit by hand each time. Apakah Anda roll kode Anda sendiri atau menggunakan orang lain, itu ide yang baik untuk memiliki satu set data yang umum dari rutinitas kemasan demi menjaga bug di cek, daripada kemasan setiap bit dengan tangan setiap kali. When packing the data, what's a good format to use? Ketika kemasan data, apa format yang baik untuk digunakan? Excellent question. Pertanyaan yang sangat baik. Fortunately, Untungnya, RFC 4506 , the External Data Representation Standard, already defines binary formats for a bunch of different types, like floating point types, integer types, arrays, raw data, etc. I suggest conforming to that if you're going to roll the data yourself. RFC 4506 , Standar Representasi Data Eksternal, sudah mendefinisikan format biner untuk sekelompok dari berbagai jenis, seperti jenis floating point, tipe data integer, array, data mentah, dll saya sarankan sesuai dengan bahwa jika Anda akan menggulung data diri . But you're not obligated to. Tapi kau tidak diwajibkan untuk. The Packet Police are not right outside your door. Polisi paket yang tidak tepat di luar pintu Anda. At least, I don't think they are. Setidaknya, saya tidak berpikir mereka. In any case, encoding the data somehow or another before you send it is the right way of doing things! Dalam kasus apapun, pengkodean data entah bagaimana atau yang lain sebelum Anda mengirim itu adalah cara yang benar dalam melakukan sesuatu! 7.5. Son of Data Encapsulation 7.5. Anak Enkapsulasi dataWhat does it really mean to encapsulate data, anyway? Apa benar-benar berarti untuk merangkum data, sih? In the simplest case, it means you'll stick a header on there with either some identifying information or a packet length, or both. Dalam kasus yang paling sederhana, itu berarti Anda akan tetap sebuah header di sana dengan baik beberapa informasi mengidentifikasi atau panjang paket, atau keduanya.What should your header look like? Apa yang harus header terlihat seperti? Well, it's just some binary data that represents whatever you feel is necessary to complete your project. Yah, itu hanya beberapa data biner yang mewakili apapun yang Anda merasa perlu untuk menyelesaikan proyek Anda. Wow. Wow. That's vague. Itu jelas. Okay. Oke. For instance, let's say you have a multi-user chat program that uses SOCK_STREAM s. Sebagai contoh, katakanlah Anda memiliki program multi-user chat yang menggunakan SOCK_STREAM s. When a user types ("says") something, two pieces of information need to be transmitted to the server: what was said and who said it. Ketika jenis pengguna ("kata") sesuatu, dua potong informasi perlu dikirim ke server: apa yang dikatakan dan yang mengatakan itu. So far so good? Sejauh ini begitu baik? "What's the problem?" "Apa masalahnya?" you're asking. Anda bertanya. The problem is that the messages can be of varying lengths. Masalahnya adalah bahwa pesan dapat dari berbagai panjang. One person named "tom" might say, "Hi", and another person named "Benjamin" might say, "Hey guys what is up?" Satu orang bernama "tom" mungkin berkata, "Hai", dan orang lain bernama "Benjamin" mungkin berkata, "Hei guys apa terserah?" So you send() all this stuff to the clients as it comes in. Your outgoing data stream looks like this: Jadi Anda mengirim () semua hal ini kepada klien karena masuk aliran data keluar Anda terlihat seperti ini: tom H i B enjamin H eyguyswhatisup ? tom H i B enjamin H eyguyswhatisup? And so on. Dan seterusnya. How does the client know when one message starts and another stops? Bagaimana klien mengetahui kapan satu pesan mulai dan berhenti yang lain? You could, if you wanted, make all messages the same length and just call the Anda bisa, jika Anda ingin, membuat semua pesan panjang yang sama dan hanya panggilan sendall() we implemented, above . sendall () kita dilaksanakan, di atas . But that wastes bandwidth! Tapi bahwa bandwidth limbah! We don't want to send() 1024 bytes just so "tom" can say "Hi". Kami tidak ingin mengirim () 1024 byte hanya supaya "tom" dapat mengatakan "Hai". So we encapsulate the data in a tiny header and packet structure. Jadi kita mengenkapsulasi data dalam sebuah struktur header dan paket kecil. Both the client and server know how to pack and unpack (sometimes referred to as "marshal" and "unmarshal") this data. Baik klien dan server tahu bagaimana untuk berkemas dan membongkar (kadang-kadang disebut sebagai "marshal" dan "unmarshal") data ini. Don't look now, but we're starting to define a protocol that describes how a client and server communicate! Jangan melihat sekarang, tapi kami mulai mendefinisikan sebuah protokol yang menggambarkan bagaimana sebuah klien dan server berkomunikasi! In this case, let's assume the user name is a fixed length of 8 characters, padded with '\0' . Dalam hal ini, mari kita asumsikan nama pengguna adalah panjang tetap dari 8 karakter, diisi dengan '\ 0'. And then let's assume the data is variable length, up to a maximum of 128 characters. Dan kemudian mari kita asumsikan data panjang variabel, sampai maksimum 128 karakter. Let's have a look a sample packet structure that we might use in this situation: Mari kita lihat struktur sampel paket yang kita gunakan dalam situasi ini:
Using the above packet definition, the first packet would consist of the following information (in hex and ASCII): Menggunakan definisi paket di atas, paket pertama akan terdiri dari informasi berikut (dalam hex dan ASCII): 0A 74 6F 6D 00 00 00 00 00 48 69 6F 6D 74 0A 00 00 00 00 00 48 69 (length) T om (padding) H i (Panjang) T om (padding) H iAnd the second is similar: Dan yang kedua adalah serupa: 18 42 65 6E 6A 61 6D 69 6E 48 65 79 20 67 75 79 73 20 77 ... 18 42 65 6E 61 6D 6A 69 6E 48 65 79 20 67 75 79 73 20 77 ... (length) B enjamin H eyguysw ... (Panjang) B enjamin H eyguysw ...(The length is stored in Network Byte Order, of course. In this case, it's only one byte so it doesn't matter, but generally speaking you'll want all your binary integers to be stored in Network Byte Order in your packets.) (Panjang disimpan dalam Jaringan Orde Byte, tentu saja Dalam hal ini, itu hanya satu byte sehingga tidak masalah,. Tetapi umumnya Anda akan ingin semua bilangan bulat biner Anda akan disimpan di Orde Jaringan Byte dalam paket Anda. ) When you're sending this data, you should be safe and use a command similar to sendall() , above, so you know all the data is sent, even if it takes multiple calls to send() to get it all out. Ketika Anda mengirim data ini, Anda harus aman dan menggunakan perintah serupa untuk sendall () , di atas, sehingga Anda tahu semua data yang dikirim, bahkan jika itu membutuhkan beberapa panggilan untuk mengirim () untuk mendapatkan semuanya keluar. Likewise, when you're receiving this data, you need to do a bit of extra work. Demikian juga, ketika Anda menerima data ini, Anda perlu melakukan sedikit pekerjaan ekstra. To be safe, you should assume that you might receive a partial packet (like maybe we receive " 18 42 65 6E 6A " from Benjamin, above, but that's all we get in this call to recv() ). Untuk amannya, Anda harus mengasumsikan bahwa Anda mungkin menerima paket parsial (seperti mungkin kita menerima "18 6E 42 65 6A" dari Benyamin, di atas, tapi itu semua kita dalam hal ini panggilan untuk recv ()). We need to call recv() over and over again until the packet is completely received. Kita perlu menelepon recv () lagi dan lagi sampai paket benar-benar diterima. But how? Tapi bagaimana? Well, we know the number of bytes we need to receive in total for the packet to be complete, since that number is tacked on the front of the packet. Yah, kita tahu jumlah byte yang kita butuhkan untuk menerima secara total untuk paket akan selesai, karena angka yang tertempel di bagian depan paket. We also know the maximum packet size is 1+8+128, or 137 bytes (because that's how we defined the packet.) Kita juga tahu ukuran paket maksimum adalah 1 +8 128, atau 137 byte (karena itulah bagaimana kita mendefinisikan paket.) There are actually a couple things you can do here. Sebenarnya ada beberapa hal yang dapat Anda lakukan di sini. Since you know every packet starts off with a length, you can call recv() just to get the packet length. Karena Anda tahu setiap paket dimulai dengan panjang, Anda dapat menghubungi recv () hanya untuk mendapatkan panjang paket. Then once you have that, you can call it again specifying exactly the remaining length of the packet (possibly repeatedly to get all the data) until you have the complete packet. Kemudian setelah Anda memiliki itu, Anda dapat menghubungi lagi menentukan persis sisa panjang paket (mungkin berulang kali untuk mendapatkan semua data) sampai Anda memiliki paket lengkap. The advantage of this method is that you only need a buffer large enough for one packet, while the disadvantage is that you need to call recv() at least twice to get all the data. Keuntungan dari metode ini adalah bahwa Anda hanya membutuhkan penyangga yang cukup besar untuk satu paket, sementara kelemahan adalah bahwa Anda perlu menelepon recv () setidaknya dua kali untuk mendapatkan semua data. Another option is just to call recv() and say the amount you're willing to receive is the maximum number of bytes in a packet. Pilihan lain adalah hanya untuk panggilan recv () dan mengatakan jumlah yang Anda bersedia untuk menerima adalah jumlah maksimum byte dalam paket. Then whatever you get, stick it onto the back of a buffer, and finally check to see if the packet is complete. Lalu apa pun yang Anda dapatkan, tongkat itu ke belakang dari buffer, dan akhirnya memeriksa untuk melihat apakah paket yang lengkap. Of course, you might get some of the next packet, so you'll need to have room for that. Tentu saja, Anda mungkin mendapatkan beberapa paket berikutnya, sehingga Anda harus memiliki ruang untuk itu. What you can do is declare an array big enough for two packets. Apa yang dapat Anda lakukan adalah mendeklarasikan sebuah array yang cukup besar untuk dua paket. This is your work array where you will reconstruct packets as they arrive. Ini adalah pekerjaan array Anda di mana Anda akan merekonstruksi paket saat mereka tiba. Every time you recv() data, you'll append it into the work buffer and check to see if the packet is complete. Setiap kali Anda recv () data, Anda akan tambahkan ke dalam buffer kerja dan memeriksa untuk melihat apakah paket yang lengkap. That is, the number of bytes in the buffer is greater than or equal to the length specified in the header (+1, because the length in the header doesn't include the byte for the length itself.) If the number of bytes in the buffer is less than 1, the packet is not complete, obviously. Artinya, jumlah byte dalam buffer lebih besar dari atau sama dengan panjang tertentu di header (+1, karena panjang di header tidak termasuk byte untuk panjang itu sendiri.) Jika jumlah byte dalam buffer kurang dari 1, paket tidak lengkap, jelas. You have to make a special case for this, though, since the first byte is garbage and you can't rely on it for the correct packet length. Anda harus membuat kasus khusus untuk ini, meskipun, karena byte pertama adalah sampah dan Anda tidak bisa mengandalkan itu untuk panjang paket yang benar. Once the packet is complete, you can do with it what you will. Setelah paket selesai, Anda dapat melakukannya dengan itu apa yang akan Anda. Use it, and remove it from your work buffer. Menggunakannya, dan menghapusnya dari buffer pekerjaan Anda. Whew! Wah! Are you juggling that in your head yet? Apakah Anda menyulap bahwa di kepala Anda belum? Well, here's the second of the one-two punch: you might have read past the end of one packet and onto the next in a single recv() call. Nah, inilah kedua pukulan satu-dua: Anda mungkin telah membaca melewati ujung satu paket dan ke berikutnya dalam recv tunggal () call. That is, you have a work buffer with one complete packet, and an incomplete part of the next packet! Artinya, Anda memiliki buffer bekerja dengan satu paket lengkap, dan bagian yang tidak lengkap dari paket berikutnya! Bloody heck. Berdarah sih. (But this is why you made your work buffer large enough to hold two packets—in case this happened!) (Tapi ini adalah mengapa Anda membuat penyangga pekerjaan Anda cukup besar untuk menampung dua paket-dalam hal ini terjadi!) Since you know the length of the first packet from the header, and you've been keeping track of the number of bytes in the work buffer, you can subtract and calculate how many of the bytes in the work buffer belong to the second (incomplete) packet. Karena Anda tahu panjang dari paket pertama dari header, dan Anda telah melacak jumlah byte dalam buffer bekerja, Anda dapat mengurangi dan menghitung berapa banyak byte dalam buffer pekerjaan milik kedua (tidak lengkap ) paket. When you've handled the first one, you can clear it out of the work buffer and move the partial second packet down the to front of the buffer so it's all ready to go for the next recv() . Bila Anda sudah menangani yang pertama, Anda dapat menghapus itu dari buffer kerja dan memindahkan paket kedua parsial ke depan ke buffer sehingga semua siap untuk pergi untuk recv berikutnya (). (Some of you readers will note that actually moving the partial second packet to the beginning of the work buffer takes time, and the program can be coded to not require this by using a circular buffer. Unfortunately for the rest of you, a discussion on circular buffers is beyond the scope of this article. If you're still curious, grab a data structures book and go from there.) (Beberapa dari Anda pembaca akan mencatat bahwa benar-benar bergerak paket kedua parsial ke awal buffer kerja membutuhkan waktu, dan program ini dapat dikodekan untuk tidak memerlukan ini dengan menggunakan circular buffer Sayangnya untuk sisa Anda., Diskusi tentang buffer lingkaran adalah di luar lingkup artikel ini Jika Anda masih penasaran, ambil buku data struktur dan pergi dari sana..) I never said it was easy. Aku tidak pernah mengatakan itu mudah. Ok, I did say it was easy. Ok, saya tidak mengatakan itu mudah. And it is; you just need practice and pretty soon it'll come to you naturally. Dan, Anda hanya perlu latihan dan segera itu akan datang ke Anda alami. By Dengan Excalibur I swear it! Excalibur aku bersumpah! 7.6. Broadcast Packets—Hello, World! 7.6. Broadcast Paket-Halo, Dunia!So far, this guide has talked about sending data from one host to one other host. Sejauh ini, panduan ini telah berbicara tentang pengiriman data dari satu host ke satu host lain. But it is possible, I insist, that you can, with the proper authority, send data to multiple hosts at the same time ! Tetapi adalah mungkin, aku bersikeras, bahwa Anda bisa, dengan otoritas yang tepat, mengirim data ke beberapa host pada saat yang sama!With Dengan UDP (only UDP, not TCP) and standard IPv4, this is done through a mechanism called UDP (hanya UDP, TCP tidak) dan standar IPv4, ini dilakukan melalui mekanisme yang disebut broadcasting . penyiaran. With IPv6, broadcasting isn't supported, and you have to resort to the often superior technique of multicasting , which, sadly I won't be discussing at this time. Dengan IPv6, penyiaran tidak didukung, dan Anda harus resor untuk teknik sering unggul multicasting, yang, sayangnya saya tidak akan membahas saat ini. But enough of the starry-eyed future—we're stuck in the 32-bit present. Tapi cukup dari terjebak berbintang bermata masa depan-kita di masa kini 32-bit. But wait! Tapi tunggu! You can't just run off and start broadcasting willy-nilly; You have to Anda tidak bisa hanya lari dan mulai penyiaran mau tak mau, Anda harus set the socket option mengatur pilihan soket SO_BROADCAST before you can send a broadcast packet out on the network. SO_BROADCAST sebelum Anda dapat mengirim paket disiarkan pada jaringan. It's like a one of those little plastic covers they put over the missile launch switch! Ini seperti salah satu plastik kecil mencakup mereka diletakkan di atas tombol peluncuran rudal! That's just how much power you hold in your hands! Begitulah banyak kekuatan yang Anda pegang di tangan Anda! But seriously, though, there is a danger to using broadcast packets, and that is: every system that receives a broadcast packet must undo all the onion-skin layers of data encapsulation until it finds out what port the data is destined to. Tapi serius, meskipun, ada bahaya untuk menggunakan paket-paket broadcast, dan itu adalah: setiap sistem yang menerima paket broadcast harus membatalkan semua bawang-kulit lapisan enkapsulasi data sampai menemukan apa data port ini ditakdirkan untuk. And then it hands the data over or discards it. Dan kemudian tangan data di atas atau membuang itu. In either case, it's a lot of work for each machine that receives the broadcast packet, and since it is all of them on the local network, that could be a lot of machines doing a lot of unnecessary work. Dalam kedua kasus, itu banyak bekerja untuk setiap mesin yang menerima paket broadcast, dan sejak itu mereka semua di jaringan lokal, yang bisa menjadi banyak mesin melakukan banyak pekerjaan yang tidak perlu. When the game Doom first came out, this was a complaint about its network code. Ketika Doom game pertama keluar, ini adalah keluhan tentang kode jaringannya. Now, there is more than one way to skin a cat... Sekarang, ada lebih dari satu cara untuk kulit kucing ... wait a minute. tunggu dulu. Is there really more than one way to skin a cat? Apakah ada benar-benar lebih dari satu cara untuk kulit kucing? What kind of expression is that? Apa jenis ekspresi itu? Uh, and likewise, there is more than one way to send a broadcast packet. Eh, dan juga, ada lebih dari satu cara untuk mengirim paket siaran. So, to get to the meat and potatoes of the whole thing: how do you specify the destination address for a broadcast message? Jadi, untuk sampai ke daging dan kentang dari seluruh hal: bagaimana Anda menentukan alamat tujuan untuk broadcast message? There are two common ways: Ada dua cara umum:
$ talker 192.168.1.2 foo $ Foo pembicara 192.168.1.2 sent 3 bytes to 192.168.1.2 dikirim 3 byte untuk 192.168.1.2 $ talker 192.168.1.255 foo $ Foo pembicara 192.168.1.255 sendto: Permission denied sendto: Permission denied $ talker 255.255.255.255 foo $ Foo pembicara 255.255.255.255 sendto: Permission denied sendto: Permission deniedYes, it's not happy at all...because we didn't set the SO_BROADCAST socket option. Ya, itu tidak bahagia sama sekali ... karena kita tidak mengatur pilihan soket SO_BROADCAST. Do that, and now you can sendto() anywhere you want! Lakukan itu, dan sekarang Anda dapat sendto () mana pun Anda inginkan! In fact, that's the only difference between a UDP application that can broadcast and one that can't. Bahkan, itulah satu-satunya perbedaan antara aplikasi UDP yang dapat disiarkan dan satu yang tidak bisa. So let's take the old talker application and add one section that sets the SO_BROADCAST socket option. Jadi mari kita ambil aplikasi pembicara tua dan menambahkan satu bagian yang menentukan pilihan soket SO_BROADCAST. We'll call this program broadcaster.c : Kita akan menyebutnya program ini broadcaster.c : /* / * ** broadcaster.c -- a datagram "client" like talker.c, except ** Broadcaster.c - datagram "client" seperti talker.c, kecuali ** this one can broadcast ** Satu ini dapat disiarkan */ * / #include <stdio.h> # Include #include <stdlib.h> # Include #include <unistd.h> # Include <unistd.h> #include <errno.h> # Include <errno.h> #include <string.h> # Include <string.h> #include <sys/types.h> # Include <sys/types.h> #include <sys/socket.h> # Include <sys/socket.h> #include <netinet/in.h> # Include <netinet/in.h> #include <arpa/inet.h> # Include <arpa/inet.h> #include <netdb.h> # Include <netdb.h> #define SERVERPORT 4950 // the port users will be connecting to # Define SERVERPORT 4950 / / pengguna port akan menghubungkan ke int main(int argc, char *argv[]) int main (int argc, char * argv []) { { int sockfd; int sockfd; struct sockaddr_in their_addr; // connector's address information struct sockaddr_in their_addr; / / informasi alamat konektor struct hostent *he; struct hostent * ia; int numbytes; int numbytes; int broadcast = 1; siaran int = 1; //char broadcast = '1'; // if that doesn't work, try this / / Siaran char = '1 '; / / jika itu tidak bekerja, coba ini if (argc != 3) { if (argc = 3!) { fprintf(stderr,"usage: broadcaster hostname message\n"); fprintf (stderr, "penggunaan: penyiar pesan hostname \ n"); exit(1); exit (1); } } if ((he=gethostbyname(argv[1])) == NULL) { // get the host info if ((dia = gethostbyname (argv [1])) == NULL) {/ / mendapatkan host info perror("gethostbyname"); perror ("gethostbyname"); exit(1); exit (1); } } if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { if ((sockfd = socket (AF_INET, SOCK_DGRAM, 0)) == -1) { perror("socket"); perror ("socket"); exit(1); exit (1); } } // this call is what allows broadcast packets to be sent: / / Panggilan ini adalah apa yang memungkinkan paket siaran akan dikirim: if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, if (setsockopt (sockfd, SOL_SOCKET, SO_BROADCAST, & siaran, sizeof broadcast) == -1) { sizeof broadcast) == -1) { perror("setsockopt (SO_BROADCAST)"); perror ("setsockopt (SO_BROADCAST)"); exit(1); exit (1); } } their_addr.sin_family = AF_INET; // host byte order their_addr.sin_family = AF_INET; urutan byte / / host their_addr.sin_port = htons(SERVERPORT); // short, network byte order their_addr.sin_port = htons (SERVERPORT); / / pendek, urutan byte jaringan their_addr.sin_addr = *((struct in_addr *)he->h_addr); their_addr.sin_addr = * ((struct in_addr *) he-> h_addr); memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero); memset (their_addr.sin_zero, '\ 0', sizeof their_addr.sin_zero); if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, if ((numbytes = sendto (sockfd, argv [2], strlen (argv [2]), 0, (struct sockaddr *)&their_addr, sizeof their_addr)) == -1) { (Struct sockaddr *) & their_addr, sizeof their_addr)) == -1) { perror("sendto"); perror ("sendto"); exit(1); exit (1); } } printf("sent %d bytes to %s\n", numbytes, printf ("% d byte dikirim ke% s \ n", numbytes, inet_ntoa(their_addr.sin_addr)); inet_ntoa (their_addr.sin_addr)); close(sockfd); dekat (sockfd); return 0; return 0; } }What's different between this and a "normal" UDP client/server situation? Apa yang berbeda antara ini dan yang "normal" UDP klien / server situasi? Nothing! Tidak ada! (With the exception of the client being allowed to send broadcast packets in this case.) As such, go ahead and run the old UDP listener program in one window, and broadcaster in another. (Dengan pengecualian dari klien diizinkan untuk mengirim paket siaran dalam kasus ini.) Dengan demikian, pergi ke depan dan menjalankan UDP tua pendengar program dalam satu jendela, dan penyiar yang lain. You should be now be able to do all those sends that failed, above. Anda harus sekarang bisa melakukan semua yang gagal mengirim, di atas. $ broadcaster 192.168.1.2 foo $ Penyiar 192.168.1.2 foo sent 3 bytes to 192.168.1.2 dikirim 3 byte untuk 192.168.1.2 $ broadcaster 192.168.1.255 foo $ Foo penyiar 192.168.1.255 sent 3 bytes to 192.168.1.255 dikirim 3 byte untuk 192.168.1.255 $ broadcaster 255.255.255.255 foo $ Foo penyiar 255.255.255.255 sent 3 bytes to 255.255.255.255 dikirim 3 byte untuk 255.255.255.255And you should see listener responding that it got the packets. Dan Anda harus melihat pendengar menanggapi bahwa itu mendapat paket. (If listener doesn't respond, it could be because it's bound to an IPv6 address. Try changing the AF_UNSPEC in listener.c to AF_INET to force IPv4.) (Jika pendengar tidak merespon, bisa jadi karena itu terikat ke sebuah alamat IPv6 Cobalah mengubah AF_UNSPEC di listener.c untuk AF_INET untuk memaksa IPv4..) Well, that's kind of exciting. Yah, itu semacam menyenangkan. But now fire up listener on another machine next to you on the same network so that you have two copies going, one on each machine, and run broadcaster again with your broadcast address... Tapi sekarang jalankan pendengar pada mesin lain di sebelah Anda di jaringan yang sama sehingga Anda memiliki dua salinan terjadi, satu di setiap mesin, dan menjalankan penyiar lagi dengan alamat broadcast anda ... Hey! Hei! Both listener s get the packet even though you only called sendto() once! Kedua pendengar dapatkan paket meskipun Anda hanya disebut sendto () sekali! Cool! Keren! If the listener gets data you send directly to it, but not data on the broadcast address, it could be that you have a Jika pendengar mendapatkan data yang Anda kirim langsung ke sana, tetapi tidak data pada alamat broadcast, bisa jadi bahwa Anda memiliki firewall on your local machine that is blocking the packets. firewall pada mesin lokal Anda yang memblokir paket-paket. (Yes, (Ya, Pat and Pat dan Bapper, thank you for realizing before I did that this is why my sample code wasn't working. Bapper, terima kasih untuk mewujudkan sebelum aku bahwa ini adalah mengapa kode sampel saya tidak bekerja. I told you I'd mention you in the guide, and here you are. Saya bilang bahwa saya akan menyebutkan Anda dalam panduan ini, dan di sini Anda. So nyah .) Jadi nyah.) Again, be careful with broadcast packets. Sekali lagi, berhati-hatilah dengan paket-paket broadcast. Since every machine on the LAN will be forced to deal with the packet whether it recvfrom() s it or not, it can present quite a load to the entire computing network. Karena setiap mesin di LAN akan dipaksa untuk berurusan dengan paket apakah itu recvfrom () s atau tidak, dapat hadir cukup beban ke jaringan komputasi seluruh. They are definitely to be used sparingly and appropriately. Mereka pasti akan digunakan dengan hemat dan tepat.
|