Операционная система UNIX - Андрей Робачевский
Шрифт:
Интервал:
Закладка:
#include <tiuser.h>
int t_accept(int fd, int connfd, struct t_call *call);
Аргумент fd адресует транспортный узел, принявший запрос (тот же, что и для функции t_listen(3N)). Аргумент connfd адресует транспортный узел, для которого будет установлено соединение с удаленным узлом. За создание нового транспортного узла отвечает сама программа (т.е. необходим явный вызов функции t_open(3N)), при этом fd может по-прежнему использоваться для обслуживания поступающих запросов.
Как и в случае t_listen(3N), через аргумент call передается информация об удаленном транспортном узле.
После возврата из функции t_accept(3N) между двумя узлами (connfd и удаленным узлом-клиентом) образован виртуальный канал, готовый к передаче прикладных данных.
Для обмена прикладными данными после установления соединения используются две функции: t_rcv(3N) для получения и t_snd(3N) для передачи. Они имеют следующий вид:
#include <tiuser.h>
int t_rcv(int fildes, char *buf, unsigned nbytes, int* flags);
int t_snd(int fildes, char *buf, unsigned nbytes, int flags);
Первые три аргумента соответствуют аналогичным аргументам системных вызовов read(2) и write(2). Аргумент flags функции t_snd(3N) может содержать следующие флаги:
T_EXPEDITED Указывает на отправление экстренных данных T_MORE Указывает, что данные составляют логическую запись, продолжение которой будет передано последующими вызовами t_snd(3N). Напомним, что TCP обеспечивает неструктурированный поток и, следовательно, не поддерживает данной возможностиЭту информацию принимающий узел получает с помощью t_rcv(3N) также через аргумент flags.
Для протоколов без предварительного установления соединения используются функции t_rcvdata(3N) и t_snddata(3N) для получения и передачи датаграмм соответственно. Функции имеют следующий вид:
#include <tiuser.h>
int t_rcvudata(int fildes, struct t_unitdata *unitdata,
int* flags);
int t_sndudata(int fildes, struct t_unitdata *unitdata);
Для передачи данных используется структура unitdata, имеющая следующие поля:
struct netbuf addr Адрес удаленного транспортного узла struct netbuf opt Опции протокола struct netbuf udata Прикладные данныеСозданный транспортный узел может быть закрыт с помощью функции t_close(3N). Заметим, что при этом соединение, или виртуальный канал, с которым ассоциирован данный узел, в ряде случаев не будет закрыт. Функция t_close(3N) имеет вид:
#include <tiuser.h>
int t_close(int fd);
где fd определяет транспортный узел. Вызов этой функции приведет к освобождению ресурсов, связанных с транспортным узлом, а последующий системный вызов close(2) освободит и файловый дескриптор. Судьба виртуального канала (если таковой существует) зависит от того, является ли транспортный узел, адресующий данный канал, единственным. Если это так, соединение немедленно разрывается. В противном случае, например, когда несколько файловых дескрипторов адресуют один и тот же транспортный узел, виртуальный канал продолжает существовать.
Завершая разговор о программном интерфейсе TLI, необходимо упомянуть об обработке ошибок. Для большинства функций TLI свидетельством ошибки является получение -1 в качестве возвращаемого значения. Напротив, в случае нормального завершения эти функции возвращают 0. Как правило, при неудачном завершении функции TLI код ошибки сохраняется в переменной t_errno, подобно тому, как переменная errno хранит код ошибки системного вызова. Для вывода сообщения, расшифровывающего причину ошибки, используется функция t_error(3N):
#include <tiuser.h>
void t_error(const char *errmsg);
При вызове t_error(3N) после неудачного завершения какой-либо функции TLI будет выведено сообщение errmsg, определенное разработчиком программы, за которым последует расшифровка ошибки, связанной с кодом t_errno. Если значение t_errno равно TSYSERR, то расшифровка представляет собой стандартное сообщение о системной ошибке, связанной с переменной errno.
В заключение в качестве иллюстрации программного интерфейса TLI приведем пример приложения клиент-сервер. Как и в предыдущих примерах, сервер принимает сообщения от клиента и отправляет их обратно. Клиент, в свою очередь, выводит полученное сообщение на экран. В качестве сообщения, как и прежде, выступает жизнерадостное приветствие "Здравствуй, мир!".
Сервер#include <sys/types.h>
#include <sys/socket.h>
#include <tiuser.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
/* Номер порта, известный клиентам */
#define PORTNUM 1500
main(argc, argv)
int argc;
char *argv[];
{
/* Дескрипторы транспортных узлов сервера */
int tn, ntn;
int pid, flags;
int nport;
/* Адреса транспортных узлов сервера и клиента */
struct sockaddr_in serv_addr, *clnt_addr;
struct hostent *hp;
char buf[80], hname[80];
struct t_bind req;
struct t_call *call;
/* Создадим транспортный узел. В качестве поставщика
транспортных услуг выберем модуль TCP */
if ((tn = t_open("/dev/tcp", O_RDWR, NULL)) == -1) {
t_error("Ошибка вызова t_open()");
exit(1);
}
/* Зададим адрес транспортного узла — он должен быть
известен клиенту */
nport = PORTNUM;
/* Приведем в соответствие порядок следования байтов для хоста
и сети */
nport = htons((u_short)nport);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = nport;
req.addr.maxlen = sizeof(serv_addr);
req.addr.len = sizeof(serv_addr);
req.addr.buf = (char*)&serv_addr;
/* Максимальное число запросов, ожидающих обработки,
установим равным 5 */
req.qlen = 5;
/* Свяжем узел с запросом */
if (t_bind(tn, &req, (struct t_bind*)0) < 0) {
t_error("Ошибка вызова t_bind();
exit(1);
}
fprintf(stderr, "Адрес сервера: %sn",
inet_ntoa(serv_addr.sin_addr));
/* Поскольку в структуре t_call нам понадобится только буфер
для хранения адреса клиента, разместим ее динамически */
if ((call =
(struct t_call*)t_alloc(tn, T_CALL, T_ADDR)) == NULL) {
t_error("Ошибка вызова t_alloc()");
exit(2);
}
call->addr.maxlen = sizeof(serv_addr);
call->addr.len = sizeof(srv_addr);
call->opt.len = 0;
call->update.len = 0;
/* Бесконечный цикл получения и обработки запросов */
while (1) {
/* Ждем поступления запроса на установление соединения */
if (t_listen(s, call) < 0) {
t_error("Ошибка вызова t_listen()");
exit(1);
}
/* Выведем информацию о клиенте, сделавшем запрос */
clnt_addr = (struct sockaddr_in*)call->addr.buf;
printf("Клиент: %sn", inet_ntoa(clnt_addr->sin_addr));
/* Создадим транспортный узел для обслуживания запроса */
if (ntn = t_open("/dev/tcp", O_RDWR, (struct t_info*)0)) < 0) {
t_error("Ошибка вызова t_open()");
exit(1);
}
/* Пусть система сама свяжет его с подходящим адресом */
if (t_bind(ntn, (struct t_bind*)0), (struct t_bind*)0) < 0) {
t_error("Ошибка вызова t_accept()");
exit(1);
}
/* Примем запрос и переведем его обслуживание на новый
транспортный узел */
if (t_accept(tn, ntn, call) < 0) {
t_error("Ошибка вызова t_accept()");
exit(1);
}
/* Создадим новый процесс для обслуживания запроса.
При этом родительский процесс продолжает принимать
запросы от клиентов */
if ((pid = fork()) == -1) {
t_error("Ошибка вызова fork()");
exit(1);
}
if (pid == 0) {
int nbytes;
/* Дочерний процесс: этот транспортный узел уже не нужен,