Praktiniai patarimai
Dokumentacija
Unixe/Linuxe yra nuostabi komanda man (santrumpa nuo "manual"). Jei norite sužinoti, kokiame C header faile yra deklaruota funkcija recv ir kokius parametrus ji ima, terminale surinkite
$ man recv
Javai šis fokusas nesuveiks ir teks naudotis Google.
Kompiliavimas Linuxe/Unixe
Tarkime, kad turite C programą sudarytą iš trijų modulių su atitinkamais .h failais (modulis1.c, modulis1.h ir t.t.) Ji kompiliuojama taip:
$ gcc -Wall -o manoprograma modulis1.c modulis2.c modulis3.c
o leidžiama
$ ./manoprograma parametrai
C++e viskas tas pats, tik vietoje gcc reikia leisti g++.
Kadangi kiekvieną kartą rinkti tą patį komandų rinkinį atsibos, siūlau susikurti failiuką Makefile su tokiu turiniu:
CFLAGS = -Wall manoprograma: modulis1.c modulis2.c modulis3.c gcc $(CFLAGS) -o $@ $^ # Pastaba: viršuje esanti eilutė privalo prasidėti TAB simboliu!
Tuomet kompiliavimui pakaks komandos make.
Kompiliavimas Windows terpėje
Jei naudojate Dev-C++, nueikite į Tools -> Compiler Options ir ten uždėkite varnelę "Add these commands to the linker command line" ir tekstiniame laukelyje įrašykite -lwsock32. Arba, vietoje to galite savo C++ programoje įrašyti kompiliatoriaus direktyvą
#pragma comment(lib, "wsock32.lib")
Windows programų kompiliavimas Linuxe
Jei programą rašėte Windowsuose, o atsiskaitinėjate Linux klasėje, parsiųskite šį winsock.h failą ir pakeiskite
#include <winsock.h>
į
#include "winsock.h"
O taip pat išmeskite #pragma eilutę.
Gal suveiks.
Pranešimų ribos - problema
Dauguma studentų renkasi paprasčiausią variantą -- klientą ir serverį, bendraujanti TCP protokolu. Dauguma studentų daro tą pačią klaidą -- pamiršta, kad TCP protokolas siunčia baitų srautą ir neturi pranešimo sąvokos. Vadinasi, jei programa A padarys
send(sock_fd, "Pranešimas 1", strlen("Pranešimas 1"), 0); send(sock_fd, "Pranešimas 2", strlen("Pranešimas 2"), 0);
o programa B darys
char buf1[MAXSIZE]; char buf2[MAXSIZE]; int len; ... len = recv(sock_fd, buf, sizeof(buf1), 0); buf1[len] = '\0'; printf("Gavau: [%s]\n", buf1); len = recv(sock_fd, buf, sizeof(buf2), 0); buf2[len] = '\0'; printf("Gavau: [%s]\n", buf2);
tai nėra jokių garantijų, jog buf1 turinys bus "Pranešimas 1", o buf2 turinys bus "Pranešimas 2".
TCP protokolas siunčiamus ir gaunamus pranešimus gali skaldyti ir klijuoti taip, kaip jam patinka. Antroji programa greičiausiai gaus suklijuotus abu pranešimus.
Gavau: [Pranešimas 1Pranešimas 2] Gavau: []
Jei siunčiančioji programa darys ilgą pauzę tarp pirmojo ir antrojo pranešimo arba, dar geriau, bandys pati laukti programos B atsakymo, tuomet pranešimų „suklijavimo“ tikimybė smarkiai krinta ir atrodys, kad programa veikia sekmingai.
Jei jūsų pranešimai trumpi, tikimybė, kad jie pakeliui bus suskaidyti į mažesnius, irgi labai maža. Gal tik džiunglėse per satelitinį mobulųjį telefoną su prastos kokybės ryšiu ir atitinkamai maža MSS reikšme antroji programa gali imti ir pamatyti
Gavau: [Praneš] Gavau: [imas 1]
Pranešimų ribos - sprendimas
Norėdami patikimai atskirti savo programos pranešimų ribas TCP protokolo pateikiamame baitų sraute susigalvokite savo protokolą. Pavyzdžiai:
- Kiekvienas pranešimas yra eilutė teksto, užbaigta '\n' simboliu. Aš rekomenduoju rinktis šį variantą.
- Kiekvienas pranešimas yra eilutė teksto, užbaigta '\0' simboliu.
- Kiekvienas pranešimas yra fiksuoto ilgio (pvz. 256 baitai).
- Kiekvieno pranešimo pradžioje yra dviejų baitų pločio pranešimo ilgis tinklo baitų tvarka, po kurio seka pats pranešimas.
Jei rinksitės "pranešimas yra eilutė teksto" variantą, galėsite savo programas testuoti (debuginti) naudodami standartines telnet ar netcat programėles, arba perimti jų siunčiamus duomenis ir juos lengvai interpretuoti naudodami, pvz., TCPWatch.
Eilutinis protokolas - C
Siuntimas yra labai paprastas:
/** * Send a message. The message should not contain any '\n' characters. * Example: * send_line(sock_fd, "Hello, world!"); */ void send_line(int sock_fd, char * buf) { send(sock_fd, buf, strlen(buf), 0); send(sock_fd, "\n", 1, 0); }
Gavimas yra kiek sudėtingesnis, bet nelabai.
/** * Receive a message terminated by a newline. Returns an empty string if the * connection is lost of closed. If the message is longer than fits into * bufsize bytes, the first part of the message is returned, and the rest is * lost forever. * * Example: * char buf[BUF_SIZE]; * recv_line(sock_fd, buf, sizeof(buf)); * if (!*buf) { * printf("Connection lost!\n"); * return; * } else if (strncmp(buf, "EHLO", 4) == 0) { * ... * } */ void recv_line(int sock_fd, char * buf, int bufsize) { int len = 0; while (1) { int n = recv(sock_fd, &buf[len], 1, 0); if (n <= 0 || buf[len] == '\n') break; if (len < bufsize - 1) len++; } buf[len] = '\0'; }
(Funkcijas ką tik parašiau, bet netestavau. Net nebandžiau kompiliuoti.)
Eilutinis protokolas - Java
Socket klasė duoda jums InputStream ir OutputStream klasių objektus. Sumaitinkite InputStreamą į InputStreamReader, o šį į BufferedReader, ir tuomet galite kviesti pastarojo readLine metodą nerašinėdami rankutėmis jokių skaitymo ciklų. Rašymo pusėje irgi turėtų būti analogiški wrapperiai.