Сокеты - это одно из средств коммуникации между процессами. Главное отличие сокетов от других средств межпроцессного взаимодействия, таких как пайпы и очереди сообщений, состоит в том, что обменивающимся информацией процессам не обязательно находится на одном компьютере. В этой статье я постараюсь объяснить принципы работы с сокетами, и написать простые TCP клиент и сервер, которые после добавления необходимых функций могут быть использованы в реальных проектах.
Итак, алгоритм работы клиента:
socket() - создание сокета
connect() - его привязка к адресу и порту, установка соединения
write() - посылка запроса
read() - прием ответа
close() - закрытие сокета
int socket(int domain, int type, int protocol);
Параметр domain определяет семейство протоколов, которое будет использоваться для взаимодействия (список возможных значений можно посмотреть в bits/socket.h). Мы будем использовать семейство протоколов IP, поэтому положим этот параметр равным AF_INET. Второй параметр определяет тип сокета, т.е будет ли передаваться информация дейтаграммами с гарантией или без гарантии доставки, с установкой логического соединения, или еще как-нибудь. В рамках семейства IP протоколов реализованы передача дейтаграмм без гарантии доставки (протокол UDP) и надежная передача информации с установкой логического соединения (протокол TCP). Именно его мы и будем использовать. Для этого в качестве type укажем константу SOCK_STREAM. Третий параметр служит для указания конкретного протокола для данного типа сокета. Мы укажем в качестве этого параметра 0, т.к этот протокол единственный. В случае успешного завершения возвращается неотрицательное число - дескриптор, в случае неудачи возвращается -1.
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
sockfd - дескриптор сокета, т.е. число которое возвратил системный вызов socket().
*serv_addr - указатель на структуру, содержащую информацию о сокете.
addrlen - размер этой структуры.
В случае успеха connect возвращает 0, а в случае неудачи -1.
Структура sockaddr выглядит следующим образом
struct sockaddr { unsigned short sa_family; char sa_data[14]; };
Для семейства AF_INET эта структура переопределена как
struct sockaddr_in{ short sin_family; //семейство протоколов – AF_INET unsigned short sin_port; //номер порта struct in_addr sin_addr; //адрес char sin_zero[8]; //не используется, должно быть заполнено нулями };
Номер порта нужно задавать, используя сетевой порядок байт. Для преобразования между обычным и сетевым порядками служат функции htons и ntohs. Для преобразования адреса из символьного представления (127.0.0.1) в числовое служит функция inet_ntoa, обратная функция - inet_aton.
Для записи в сокет и чтения из него используются функции write и read, точно так же как и при работе с файлами.
int close(int sockfd);
Этот системный вызов закрывает дескриптор сокета, тем самым, завершая соединение.
Теперь мы можем написать простой TCP клиент.
#define IP "127.0.0.1" #define PORT 123 #define BUFLEN 1024 #include <sys> #include <sys> #include <netinet> #include <arpa> #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> int main() { int sockfd; int num; char buffer[BUFLEN]; struct sockaddr_in servaddr; //Обнуляем буффер bzero(buffer,BUFLEN); //Создаем сокет if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) { perror("socket"); exit(1); } //Заполняем структуру sockaddr bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); inet_aton(IP, &servaddr.sin_addr); //Пытаемся приконнектится if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { perror("connect"); close(sockfd); exit(1); } //Запрашиваем строку у пользователя и посылаем ее серверу fgets(buffer,BUFLEN,stdin); if((num=write(sockfd,buffer,strlen(buffer)+1))<0) { perror("write"); close(sockfd); exit(1); } //Получаем и печатаем ответ if((num=read(sockfd,buffer,BUFLEN-1))<0) { perror("Can\'t read\n"); close(sockfd); exit(1); } printf("%s",buffer); //Закрываем соединение close(sockfd); return 0; }
Теперь приступим к написанию нашего сервера.
Алгоритм его работы:
socket() - создание сокета
bind() - привязка сокета к адресу
listen() - перевод сокета в пассивный режим
accept() - прием полностью установленного соединения
read() - чтение запроса
write() - посылка ответа
close() - закрытие сокета
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
С аргументами все очевидно. Возвращает 0 в случае успешного завершения, отрицательное число в противном случае. Для клиента вызов bind не требуется т.к. привязка сокета к адресу производится функцией connect.
int listen(int sockfd, int backlog);
Переводит сокет в слушающее состояние и устанавливает максимальный размер очереди соединений - параметр backlog.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Выбирает первый запрос на соединение из очереди. Записывает информацию о подключившемся клиенте в структуру *addr, и ее размер в *addrlen.
Код сервера:
#define BUFLEN 1024 #define PORT 123 #include <sys> #include <sys> #include <netinet> #include <arpa> #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> int main() { int sockfd,newsockfd; int clilen; int n; char buffer[BUFLEN]; struct sockaddr_in servaddr,cliaddr; //Создаем сокет if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) { perror("socket"); exit(1); } //Заполняем структуру servaddr.sin_family= AF_INET; servaddr.sin_port=htons(PORT); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //Принимаем соединения с любого адреса //Биндим сокет к адресу if(bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){ perror("bind"); close(sockfd); exit(1); } //Переводим сокет в слушающее состояние if(listen(sockfd, 5) < 0){ perror("listen"); close(sockfd); exit(1); } //Принимаем соединение clilen=sizeof(cliaddr); if((newsockfd=accept(sockfd,(struct sockaddr*)&cliaddr,&clilen))<0) { perror("accept"); close(sockfd); exit(1); } printf("Client %s connected\n",inet_ntoa(cliaddr.sin_addr)); while((n = read(newsockfd,buffer,BUFLEN-1))>0) { //Отправляем обратно принятые данные if((n=write(newsockfd,buffer,strlen(buffer)+1))<0) { perror("write"); close(sockfd); close(newsockfd); exit(1); } } if(n < 0) //При чтении произошла ошибка { perror("read"); close(sockfd); close(newsockfd); exit(1); } close(newsockfd); close(sockfd); return 0; }
Приведенные программы можно усовершенствовать. Например, сделать чтобы параметры задавались из командной строки; адреса можно было бы резолвить, используя функцию gethostbyname; сервер, используя функцию fork, мог параллельно обслуживать несколько клиентов. Более подробное описание всех использованных функций можно прочесть в манах.
© s7r34m 04.04.2006

linux не говно,при правильном обращении все будет гладко. 1) Для нее не...
Linux vs Windows