質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

HTTP

HTTP(Hypertext Transfer Protocol)とはweb上でHTML等のコンテンツを交換するために使われるアプリケーション層の通信プロトコルです。

Q&A

1回答

1968閲覧

C言語のHTTP通信、recv()関数が最後まで動作しない

orange0808

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

HTTP

HTTP(Hypertext Transfer Protocol)とはweb上でHTML等のコンテンツを交換するために使われるアプリケーション層の通信プロトコルです。

0グッド

0クリップ

投稿2021/06/07 13:17

前提・実現したいこと

C言語でHTTPサーバー・クライアントを作成しています。サーバーはhtmlファイルをボディに書いてレスポンスし、クライアントはGETメソッドで受け取ったメッセージのボディ部をファイルとして保存します。

発生している問題・エラーメッセージ

クライアント側でrecv()関数の実行が終了せずにレスポンスメッセージの最後の行だけ読めません。(printf()で確認しようとしても出てこない)

該当のソースコード

クライアント側のソースコード

c

1#include <sys/socket.h> 2#include <sys/types.h> 3#include <netinet/in.h> 4#include <arpa/inet.h> 5#include <stdlib.h> 6#include <stdio.h> 7#include <string.h> 8#include <unistd.h> 9#include <netdb.h> 10 11#include <errno.h> 12 13#define CONNECT_PORT 8080 14#define MAX_RESPONSE_SIZE (1024*1024*5) 15#define MAX_REQUEST_SIZE (1024) 16#define MAX_HOSTNAME_SIZE 1024 17#define MAX_PATH_SIZE 1024 18#define MAX_SIZE 128 19#define MAX_URL 256 20 21/* 22 ホスト名はIPアドレスを取得する 23 hostname:ホスト名 24 戻り値:IPアドレスが格納されたバッファへのアドレス(失敗時はNULL) 25*/ 26char *getIpAddress(char *hostname){ 27 struct hostent *host; 28 29 // ホスト名からホスト情報を取得 30 host = gethostbyname(hostname); 31 if(host == NULL){ 32 printf("gethostbyname error\n"); 33 return NULL; 34 } 35 36 // IPv4以外はエラー 37 if(host->h_length != 4){ 38 printf("Not ipv4\n"); 39 return NULL; 40 } 41 42 // ホスト情報のアドレス群の1つ目をIPアドレスとする 43 return host->h_addr_list[0]; 44} 45 46/* 47 URLからホスト名とパスを取得する 48 hostname:ホスト名を格納するバッファへのアドレス 49 path:パスを格納するバッファへのアドレス 50 url:URLが格納されたバッファへのアドレス 51 戻り値:0 52*/ 53int getHostnameAndPath(char *hostname, char *path, char *url){ 54 unsigned int i; 55 char hostname_path[MAX_HOSTNAME_SIZE + MAX_PATH_SIZE]; 56 57 // URLの最初が"http://"の場合は"http://"を取り除く 58 if(strncmp(url, "http://", strlen("http://")) == 0){ 59 sscanf(url, "http://%s", hostname_path); 60 }else if(strncmp(url, "https://", strlen("https://")) == 0){ 61 sscanf(url, "https://%s", hostname_path); 62 }else{ 63 strcpy(hostname_path, url); 64 } 65 66 // 最初の'/'までの文字数をカウント 67 for(i=0; i < strlen(hostname_path); i++){ 68 if(hostname_path[i] == '/'){ 69 break; 70 } 71 } 72 73 if(i == strlen(hostname_path)){ 74 // '/'がhostname_pathに含まれていなかった場合hostname_path全体をhostnameとする 75 strcpy(hostname, hostname_path); 76 77 // pathは'/'とする 78 strcpy(path, "/"); 79 }else{ 80 // '/'がhostname_pathに含まれていた場合'/'の直前までをhostnameとする 81 strncpy(hostname, hostname_path, i); 82 83 // '/'以降をpathとする 84 strcpy(path, &hostname_path[i]); 85 } 86 87 return 0; 88} 89 90/* 91 リクエストメッセージを作成する 92 request_message:リクエストメッセージを格納するバッファへのアドレス 93 target:リクエストターゲット(リクエストするファイル) 94 host:リクエスト先のホスト名 95 戻り値:メッセージのサイズ 96*/ 97int createRequestMessage(char *request_message, char *path, char *hostname){ 98 char request[MAX_SIZE]; 99 char header[MAX_SIZE]; 100 101 sprintf(request, "GET %s HTTP/1.1", path); 102 sprintf(header, "Host: %s\r\nConnection: close", hostname); 103 sprintf(request_message, "%s\r\n%s\r\n\r\n", request, header); 104 105 return strlen(request_message); 106} 107 108/* 109 リクエストメッセージを送信する 110 sock:接続済みのソケット 111 request_message:送信するリクエストメッセージへのアドレス 112 message_size:送信するメッセージのサイズ 113 戻り値:送信したデータサイズ(バイト長) 114*/ 115int sendRequestMessage(int sock, char *request_message, unsigned int message_size){ 116 int send_size; 117 send_size = send(sock, request_message, message_size, 0); 118 119 return send_size; 120} 121 122/* 123 レスポンスメッセージを受信する 124 sock:接続済みのソケット 125 response_message:rレスポンスメッセージを格納するバッファへのアドレス 126 buffer_size:↑のバッファのサイズ 127 戻り値:受信したデータサイズ(バイト長) 128*/ 129int recvResponseMessage(int sock, char *response_message, unsigned int buffer_size){ 130 int total_recv_size = 0; 131 int i; 132 133 while(1){ 134 int recv_size = 0; 135 recv_size = recv(sock, &response_message[total_recv_size], sizeof(response_message), 0); 136 137 printf("%d", recv_size); 138 139 if(recv_size == -1){ 140 printf("recv error\n"); 141 return -1; 142 } 143 144 if(recv_size <= 0){ 145 printf("connection ended\n"); 146 break; 147 } 148 149 for(i = 0; i < recv_size; i++){ 150 printf("%c", response_message[total_recv_size + i]); 151 } 152 153 total_recv_size += recv_size; 154 } 155 156 return total_recv_size; 157} 158 159/* 160 ボディをファイル保存する 161 file_path:保存先のファイルパス 162 respons_message:レスポンスメッセージを格納するバッファへのアドレス 163 response_size:レスポンスメッセージのサイズ 164 戻り値:成功時 0、失敗時 -1 165*/ 166int saveBody(const char *file_path, char *response_message, unsigned int response_size){ 167 FILE *fo; 168 char *tmp; 169 unsigned int skip_size = 0; 170 171 // レスポンスメッセージを空行まで読み飛ばし 172 tmp = strtok(response_message, "\n"); 173 while(tmp != NULL && (strcmp(tmp, "\r") != 0 && strcmp(tmp, "") != 0)){ 174 skip_size += strlen(tmp) + strlen("\n"); 175 176 tmp = strtok(NULL, "\n"); 177 printf("%s\n", tmp); 178 } 179 180 if(tmp ==NULL){ 181 printf("body is not found\n"); 182 return -1; 183 } 184 185 // 空行の次の行からボディとみなしてファイル保存 186 skip_size += strlen(tmp) + strlen("\n"); 187 188 fo = fopen(file_path, "wb"); 189 if(fo == NULL){ 190 printf("Open error (%s)\n", file_path); 191 return -1; 192 } 193 194 fwrite(&response_message[skip_size], 1, response_size - skip_size, fo); 195 196 fclose(fo); 197 198 return 0; 199} 200 201/* 202 HTTP通信でサーバーとデータのやり取りを行う 203 sock:サーバーと接続済みのソケット 204 hostname:ホスト名が格納されたバッファへのアドレス 205 path:パスが格納されたバッファへのアドレス 206 戻り値:成功時 0、失敗時 -1 207*/ 208int httpClient(int sock, char *hostname, char *path){ 209 int request_size, response_size; 210 char request_message[MAX_REQUEST_SIZE]; 211 char response_message[MAX_RESPONSE_SIZE]; 212 213 // リクエストメッセージを作成 214 request_size = createRequestMessage(request_message, path, hostname); 215 if(request_size == -1){ 216 printf("createRequestMessage error\n"); 217 return -1; 218 } 219 220 // リクエストメッセージを送信 221 if(sendRequestMessage(sock, request_message, request_size) == -1){ 222 printf("sendRequestMessage error\n"); 223 return -1; 224 } 225 226 // レスポンスメッセージを受信 227 response_size = recvResponseMessage(sock, response_message, MAX_RESPONSE_SIZE); 228 if(response_size == -1){ 229 printf("recvResponseMessage error\n"); 230 return -1; 231 } 232 233 // レスポンスメッセージに応じた処理(ボディのファイル保存のみ) 234 if(saveBody("test.html", response_message, response_size) == -1){ 235 printf("saveBody error\n"); 236 return -1; 237 } 238 239 return 0; 240} 241 242int main(int argc, char *argv[]){ 243 int sock = -1; 244 struct sockaddr_in addr; 245 unsigned short port = CONNECT_PORT; 246 char url[MAX_URL]; 247 char *ip_address; 248 249 char hostname[MAX_HOSTNAME_SIZE]; 250 char path[MAX_PATH_SIZE]; 251 252 if(argc == 2){ 253 strcpy(url, argv[1]); 254 }else{ 255 printf("set url!!\n"); 256 return -1; 257 } 258 259 // ホスト名とパス名を取得 260 getHostnameAndPath(hostname, path, url); 261 262 // IPアドレスを取得 263 ip_address = getIpAddress(hostname); 264 if(ip_address == NULL){ 265 printf("getIPAdress error\n"); 266 return -1; 267 } 268 269 // ソケットを作成 270 sock = socket(AF_INET, SOCK_STREAM, 0); 271 if(sock == -1){ 272 printf("socket error\n"); 273 return -1; 274 } 275 276 // 構造体をすべて0にセット 277 memset(&addr, 0, sizeof(struct sockaddr_in)); 278 279 // 接続先の情報を設定 280 addr.sin_family = AF_INET; 281 addr.sin_port = htons(port); 282 283 // ip_addressは数値の配列なのでそのままコピー(inet_addrは不要) 284 memcpy(&(addr.sin_addr.s_addr), ip_address, 4); 285 286 // サーバーに接続 287 printf("Connect to %u.%u.%u.%u\n", 288 (unsigned char)ip_address[0], 289 (unsigned char)ip_address[1], 290 (unsigned char)ip_address[2], 291 (unsigned char)ip_address[3] 292 ); 293 294 if(connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1){ 295 printf("connect error\n"); 296 close(sock); 297 return -1; 298 } 299 300 // HTTP通信でデータのやり取り 301 httpClient(sock, hostname, path); 302 303 // ソケット通信をクローズ 304 if(close(sock) == -1){ 305 printf("close error\n"); 306 return -1; 307 } 308 309 return 0; 310}

試したこと

printf()で変数の中身など確認してみましたが解決には至りませんでした。

補足情報(FW/ツールのバージョンなど)

最近勉強し始めてわからないことが多く躓いてしまっています。長くて読みにくいソースコードかもしれませんがよろしくお願いします。

実行環境はWindows10でwslを入れてます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/06/07 14:10

サーバー側に問題はない、ということでしょうか。 現状見える範囲で言えば、「recvResponseMessage()」内で、「recv()」の引数にポインタのサイズを渡しています、 引数にバッファサイズ渡してるのだからそれ使ったらどうでしょう、程度の指摘しかできませんが。
guest

回答1

0

recv()関数はブロッキングなので、データが来るまで待ち続けます。
サーバーが適切なレスポンスを返していないと考えられます。

投稿2021/06/07 14:30

fukatani

総合スコア626

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問