-
Notifications
You must be signed in to change notification settings - Fork 0
/
ep1.c
187 lines (162 loc) · 6.56 KB
/
ep1.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/* Por Prof. Daniel Batista <batista@ime.usp.br>
* Em 28/8/2022
*
* Um código simples de um servidor de eco a ser usado como base para
* o EP1. Ele recebe uma linha de um cliente e devolve a mesma linha.
* Teste ele assim depois de compilar:
*
* ./mac5910-servidor-exemplo-ep1 8000
*
* Com este comando o servidor ficará escutando por conexões na porta
* 8000 TCP (Se você quiser fazer o servidor escutar em uma porta
* menor que 1024 você precisará ser root ou ter as permissões
* necessáfias para rodar o código com 'sudo').
*
* Depois conecte no servidor via telnet. Rode em outro terminal:
*
* telnet 127.0.0.1 8000
*
* Escreva sequências de caracteres seguidas de ENTER. Você verá que o
* telnet exibe a mesma linha em seguida. Esta repetição da linha é
* enviada pelo servidor. O servidor também exibe no terminal onde ele
* estiver rodando as linhas enviadas pelos clientes.
*
* Obs.: Você pode conectar no servidor remotamente também. Basta
* saber o endereço IP remoto da máquina onde o servidor está rodando
* e não pode haver nenhum firewall no meio do caminho bloqueando
* conexões na porta escolhida.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include "config.h"
#include "log.h"
#include "state_machine.h"
#define LISTENQ 1
typedef struct {
int active;
pthread_t thread;
int thread_id;
int connfd;
queue_pool *q_pool;
} connection_thread;
connection_thread threads[MAX_CONNECTIONS];
pthread_mutex_t thread_allocation_mutex;
void *connection_thread_main(void *_thread) {
connection_thread *thread = (connection_thread *) _thread;
log_connection_accept(thread->thread_id, thread->connfd);
state_machine_main(thread->connfd, thread->thread_id, thread->q_pool);
close(thread->connfd);
log_connection_close(thread->thread_id, thread->connfd);
pthread_mutex_lock(&thread_allocation_mutex);
threads[thread->thread_id].active = 0;
pthread_mutex_unlock(&thread_allocation_mutex);
return NULL;
}
int main (int argc, char **argv) {
/* Os sockets. Um que será o socket que vai escutar pelas conexões
* e o outro que vai ser o socket específico de cada conexão */
int listenfd, connfd;
/* Informações sobre o socket (endereço e porta) ficam nesta struct */
struct sockaddr_in servaddr;
queue_pool q_pool;
init_logging();
init_queue_pool(&q_pool);
pthread_mutex_init(&thread_allocation_mutex, NULL);
bzero(threads, MAX_CONNECTIONS * sizeof(connection_thread));
if (argc != 2) {
fprintf(stderr,"Uso: %s <Porta>\n",argv[0]);
fprintf(stderr,"Vai rodar um servidor de echo na porta <Porta> TCP\n");
exit(1);
}
/* Criação de um socket. É como se fosse um descritor de arquivo.
* É possível fazer operações como read, write e close. Neste caso o
* socket criado é um socket IPv4 (por causa do AF_INET), que vai
* usar TCP (por causa do SOCK_STREAM), já que o AMQP funciona sobre
* TCP, e será usado para uma aplicação convencional sobre a Internet
* (por causa do número 0) */
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket :(\n");
exit(2);
}
/* Agora é necessário informar os endereços associados a este
* socket. É necessário informar o endereço / interface e a porta,
* pois mais adiante o socket ficará esperando conexões nesta porta
* e neste(s) endereços. Para isso é necessário preencher a struct
* servaddr. É necessário colocar lá o tipo de socket (No nosso
* caso AF_INET porque é IPv4), em qual endereço / interface serão
* esperadas conexões (Neste caso em qualquer uma -- INADDR_ANY) e
* qual a porta. Neste caso será a porta que foi passada como
* argumento no shell (atoi(argv[1]))
*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
perror("bind :(\n");
exit(3);
}
/* Como este código é o código de um servidor, o socket será um
* socket passivo. Para isto é necessário chamar a função listen
* que define que este é um socket de servidor que ficará esperando
* por conexões nos endereços definidos na função bind. */
if (listen(listenfd, LISTENQ) == -1) {
perror("listen :(\n");
exit(4);
}
printf("[Servidor no ar. Aguardando conexões na porta %s]\n",argv[1]);
printf("[Para finalizar, pressione CTRL+c ou rode um kill ou killall]\n");
/* O servidor no final das contas é um loop infinito de espera por
* conexões e processamento de cada uma individualmente */
for (int thread_count = 0;;
thread_count = (thread_count + 1) % MAX_CONNECTIONS) {
connection_thread *thread;
pthread_mutex_lock(&thread_allocation_mutex);
for (int i = 0; i <= MAX_CONNECTIONS; i++) {
if (i == MAX_CONNECTIONS) {
log_max_thread_reached();
sleep(1);
i = 0;
}
if (!threads[thread_count + i % MAX_CONNECTIONS].active) {
thread_count = thread_count + i % MAX_CONNECTIONS;
thread = &threads[thread_count];
thread->active = 1;
break;
}
}
pthread_mutex_unlock(&thread_allocation_mutex);
/* O socket inicial que foi criado é o socket que vai aguardar
* pela conexão na porta especificada. Mas pode ser que existam
* diversos clientes conectando no servidor. Por isso deve-se
* utilizar a função accept. Esta função vai retirar uma conexão
* da fila de conexões que foram aceitas no socket listenfd e
* vai criar um socket específico para esta conexão. O descritor
* deste novo socket é o retorno da função accept. */
if ((connfd = accept(listenfd, (struct sockaddr *) NULL, NULL)) == -1 ) {
perror("accept :(\n");
exit(5);
}
thread->connfd = connfd;
thread->q_pool = &q_pool;
thread->thread_id = thread_count;
pthread_create(
&thread->thread,
NULL,
connection_thread_main,
thread
);
}
exit(0);
}