俺の報告

RoomClipを運営するエンジニアの日報(多分)です。

TCPシェイクハンドとKeepAliveについて - 日報 #152

3ヶ月間ブログ休んでましたが、
まぁそういうこともあるよね。
大事なことは続けることだと思いますんで、グッと面の皮を厚くして今まで通りいきますね。

さて、

色々な求人媒体様ならびにFBでもさかんに言ってますが、
RoomClipのエンジニア大募集中でございます。
iOSAndroid、サーバサイド、インフラ、UI/UX、
全方位に人材不足なので、どの方面から始めても、どの方面にも成長できます。
そして「俺ならもっとすごいのつくれるのに!」と思っいながらも、 日々決められたフレームワークで開発せざるを得ない敏腕エンジニアの方。
ぜひウチで腕試しにいらっしゃってください。
バチバチしましょう。

とにかく、少しでも興味ある人は、このブログへのコメントでもなんでも結構ですので、
ご連絡下さいませ。「ちょっと話してみたいんだけど」的なノリも大歓迎です。
Wantedlyさまからも募集要項だしております!

www.wantedly.com
www.wantedly.com

----ここまで宣伝。

そんで今日の話。
今日はTCPシェイクハンドとKeepAliveについて。

込み入った話になるので結論めいたところから説明します。
リバースプロキシを使っているとき、 クライアントとの間だけでなく、プロキシの向こう側の、要は本体サーバとの接続にもちゃんとKeepAliveしておかないと、
TIME_WAITでポートが枯渇したり、TCP接続に必要なメモリがオーバーフローして、大変なことになるよ。
という話です。

結論を書くと極めてシンプルなのですが、
これが中々気づきづらい。
だって、CPUもメモリ(全体)もさしたる圧迫がないのに、
なぜか通信が止まる、という奇っ怪な様子にみえてしまうからです。
(俗にいうライブロックというやつですかね)
この状態、色々と疑い先は列挙できますが、 「TCPコネクション周りが怪しい」 という目線があるかないかで、解決にかかる時間も大分変わってきます。
普段使っている「意識しない技術」に対してきちんと理解しておくことは、とっても重要ですね。

さて、今回の事件の中心であるTCP
TCPなんて「そうだね、TCPだね」以上のことを知ってる必要がないと思うのですが、
それがサービス停止の原因になりうる箇所だというのなら、 改めて順を追ってこの「プロトコル」を確認してみましょう。

TCPは3ウェイ・ハンドシェイク方式と言われる方法で接続を確立し、
(多くの場合)4ウェイ・ハンドシェイクという方式で接続を終了させる。
今回のような「ポートとかメモリとかの枯渇事件」の落とし穴は、この確立と終了の両者に1つずつ存在しているようです。

まずコネクション確立、3ウェイ・ハンドシェイクをみてみる。

  1. クライアントがサーバに対してSYNシグナルを送る
  2. サーバがクライアントへSYN ACKを返す
  3. クライアントがACKを返す

これで終わりだ。

  1. 「起きてる?いま電話して大丈夫?」とメッセしたら、
  2. 「起きてるよ。電話いいよ、待ってるね。」と返信がきたので
  3. 電話した

こんなイメージだ。
さて、実はこの2番から3番への状態遷移において、落とし穴が存在する。
サーバはSYNを受け取ったとき、
今後の通信に必要なクライアント情報(最低でも16バイト程度)をメモリに保存する。
この状態のまま、クライアントが3番の行為をしてくれないと、長い間その確保したメモリを開放できなくなる。
つまり、
「起きてるよ。電話いいよ、待ってるね。」
とメッセ返してからずっと電話してこねーなあいつなんなんだよマジで、別れたくせに思わせぶりなことしやがってなめてんのかフジコフジコとなってしまうわけですね。
奇特な性癖の豚野郎なら耐えられるでしょうが、これを短時間に大量にやられたら人はパンクしてしまうわけです。
サーバも同様に瞬殺されてしまい、結果としてライブロックといわれるような、 「動作はしているが返ってこない」という人騒がせな状態に追い込まれてしまうのですね。
これをSYN FLOOD攻撃とよび、これはいわゆるDDoS攻撃の一種です。

もちろんLinuxのサーバアプリケーションは大体それに対するワクチンをもっており、
SYN FLOODを受けた!と判定したら、メモリに情報を書き出すのをやめて、
ステートレスに相手を同定するシステムに移行しようとします。
cookieと同じような技術を使うので、set_cookiesとかと呼ばれてます)

さてもう一つ、コネクションの終了の4ウェイ・シェイクハンドをみてみます。
(クライアントから接続を終わらすストーリーでみてみます)
1. クライアントがFINシグナルを送る
2. サーバがACKを送る
3. サーバがFINを送る
4. クライアントがACKを送る
それで終わり、となるところですが、実はこれには隠された5がありまして、、、
5. 指定された秒数サーバはTIME_WAIT状態で待機
というのがあります。
これは使っていたポートが遅れてきたパケットを受信してしまい、 別のコネクションで利用されてしまう、という危険を回避する大事な機能です。
60秒程度は待つ、というのが一般的のようです。

さて、

もうおわかりだと思いますが、このTIME_WAITが中々やっかいです。
そもそもTCP接続のために利用されるポートはエフェメラルポートが利用されます。
CentOSでは 32,768 から 61,000 の28,233個程度あるようです。
( sudo cat /proc/sys/net/ipv4/ip_local_port_range で確認 )
またTIME_WAITの時間は、60秒。
( sudo cat /proc/sys/net/ipv4/tcp_fin_timeout で確認 )
つまり、28,233/60 = 470 req/sec 程度でポートは枯渇します。
もっというと、
もしこのサーバが画像のCDNサーバとかで、
実体のサービスへの1リクエストごとに10個の画像リクエストが飛ぶとしたら、
47req/sec程度で瞬殺されるわけです。
(まぁ極端な話ですが。TCPも同じリクエスト元ならKeepAliveなくして再利用するので)

これを対策するためには、ポートを増やすとかTIME_WAITを短くするとかがありますが、
そもそもエフェメラルポートやらTIME_WAITやらは「別の事情」できまった数値なわけで、
今回のような理由で下げていいわけではないはずです。
なのでKeepAliveをつかう、という発想がでてくるわけです。
僕はこのKeepAliveの設定をクライアント⇔リバースプロキシのみに設定して満足してしまい、
リバースプロキシ⇔本体サーバにおいて設定しておらず、このような地雷を踏んでしまったわけです。
怖いですね。

以上に長くなりましたが、一言でTCPとかいってますが、
設定や場合によってはreq/secのボトルネックになりうるレベルの「知らねぇなんて許されねぇぞ」レベルの話なんですね。
まぁTCPなんて基礎の基礎の基礎だろ!なんでてめぇ今までよく知らずにやってたんだ!
という声も聞こえてきますが、
そーゆーこと言うような凄腕エンジニアの手助けは大歓迎ですので、
ぜひ一度お会いしてガチで叱って下さい。
よろしくお願いいたします!

エフェメラルポート出現に見るGoogle検索ってすげぇなって思うこと - 日報 - #151

ネットワークACLの設定をしていたエンジニアから、
「ふえぇぇ…wgetできないよう…TCP80も開けているのにぃ…」
との報告を受けて、なんだとう!となったわけです。

とにかくAWS公式を読みましょう。
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_ACLs.html#VPC_ACLs_Ephemeral_Ports
HTTPだけなら80だけっども、やれcurlだ、やれwgetだと使うのであれば、
ダイナミックにいろんなポート使うんだよと。
80番だけあけて調子に乗ってっとTCPだからって簡単にゃ通さんぞゴラと。

ホントかいな、ってことで、
watch netstat -tanp
これでもってポートを毎秒監視してみます。
えいって叩くと、見事に30000番台くらいのポートががっつり開きます。
ほっほーなるほどなるほど。

ということで、調べてみましょう。
TCP先生は有名な80番以外をどう使い、
一時ポートとはどの番数のことをいうのか。

でてきたのはIANAという団体です。
http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
要はポートやらIPやらの使い方の標準化を目指す団体様ですね。
彼らによるとTCPは3つの領域を使え、とのことだそうです。
http://www.infraexpert.com/study/tea5.htm
こちらがわかりやすいですが、
well knownとregisteredとdynamicの3つの領域があるそうです。
ほうほうなるほど。

ん?
30000番台はregisteredなんだけど、、、
とくに予約登録されているポートではない番号が使われているようです。
はて、そういうダイナミックな使い方は49152番〜なのではないか?
と、おもって「ubuntu 一時ポート」で検索してみたら、
一番上に「エフェメラルポート」のwikipediaが登場。
俺の検索したワードが一文字も含まれていないのに驚愕しつつ開くとビンゴでした。
https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%95%E3%82%A7%E3%83%A1%E3%83%A9%E3%83%AB%E3%83%9D%E3%83%BC%E3%83%88
一時ポートはエフェメラルポートと言って、
OSごとに異なるようです。
超バラバラみたいです。困りますよねこれって。。。
そりゃIANAなる団体が標準化をしたがるのも分かりますね。
んで、Ubuntuはというと、
sudo cat /proc/sys/net/ipv4/ip_local_port_range
というファイルに、
10240 65535
とかいう心の底から雑な形式で規定されていました。
IANAが推奨する49152から65535や、多くのLinuxが採用している32768から61000のどちらでもないという大オチでした。
これって、、、もしUbuntuのバージョンアップなどで変更されたらどうすんだよって話ですよね。ACL書き換えろってことなんすかね。まぁいいんですけど、OSバージョンアップがまた1つ辛くなりました。

日々勉強になりますね。

Arduinoでエアコンのリモコンを再現す。 - 日報 #150

梅雨入りしたそうですが、
日々暑いです。
今日はもう疲れたので、趣味のお話。

弊社オフィスはペッカペカで至極快適なのですが、
1個だけ不満がございます。
タイトルですこし触れましたが、、、
弊社は合計4つほどエアコンがあるのですが、
リモコンが1つしかないのです。
コレではいけません。

ということで、自作しましょう。

発想としては、PICだかなんだかのマイコンを通して、
赤外線受光器でもってリモコンの信号を解析して、
赤外線LEDにその通りの動作をさせればよいわけです。

受光器からの信号解析のためにシリアル通信が必要になるので、
ここはUSBインターフェースを使いやすい18F14K50を使います。
この神サイトがあるかぎり、なんだってできそうです。

ですが、
解析してテストするまでならもっと簡単にやりたいものです。
てことで、みんなだいすきArduinoを使いましょう。

macArduinoが初めてだったのですが、
IDEは$3程度で手に入るようです。
http://www.arduino.cc/en/Main/Software

そいで、赤外線受光器は手元にあったOSRB38C9AA(100円なり)をつかい、
LEDはまぁ、そのへんの適当な奴をつかいます。
型番とか忘れた知らん。

あぁなんか書き出して面倒臭くなってきた。
要は赤外線受光器に向けてリモコンの電源ボタンとかを押して、
その点滅情報をマイクロ秒で取得して、
そのままLEDを光らせればよいわけですよ。

あとは赤外線リモコンの仕様ですが、
このへんを参考にすれば、フレームとかデータとかの仕切りがわかります。
ってか、ぶっちゃけ解析は面倒なんであんまいらんくて、
「ON、OFF」だけできりゃいいので、その情報をそのままプログラムに書き込んじゃいます。

さて、面倒な読み取りスクリプトですが、
色んなサンプルがありますが、僕はこうしましたってのを後ろにくっつけておきます。
だいたい下記のような感じですごーーい簡単にとりあえずリモコン動作は確認できました。

f:id:tom_rc:20150609212257j:plain

さて、続いてはこれをPICで組んで、
あとはかっこいいボタンでもつけてやればいいですね。
楽しみです。

#define DETECTOR_PIN 7
#define SERIAL_BPS 57600

// Button Interval 1sec
#define INTERVAL 1000000 // usec

// NEC 562, AEHA 350-500 (425)
#define FRAME_SEC 500 // usec

// 1: state process, 2: frame count
#define DATA_MODE 1

// 0: debugging, 1: data only
#define STDOUT_MODE 1

// Separator
#define SEPARATOR ","

unsigned long interval_start = 0;
unsigned long window_start = 0;
unsigned long state_start = 0;
int frame_cnt = 0;
int state = HIGH;

void setup() {
  // set pinMode
  pinMode(DETECTOR_PIN, INPUT);

  // put your setup code here, to run once:
  Serial.begin(SERIAL_BPS);
  Serial.println("Start!");
}

void loop() {
    if (digitalRead(DETECTOR_PIN) == LOW) {
      interval_start = micros();
      window_start = interval_start;
      state_start = interval_start;
      state = LOW;

      Serial.println("Catch the signal.");

      while ((micros() - interval_start) <= INTERVAL) {
        if (state != digitalRead(DETECTOR_PIN)) {
          if (STDOUT_MODE == 0) {
            if (state) {
              Serial.print("OFF: ");
            } else {
              Serial.print("ON: ");
            }
          }

          /*
          // total process time
          Serial.print("Total Process Time: ");
          Serial.println(micros() - interval_start);
          // window process time
          Serial.print("Window Process Time: ");
          Serial.println(micros() - window_start);
          */

          if (DATA_MODE == 2) {
            // frame count
            if (STDOUT_MODE == 0) {
              Serial.print("Frame count: ");
            }
            Serial.print(frame_cnt);
          }

          if (DATA_MODE == 1) {
            // state process time
            if (STDOUT_MODE == 0) {
              Serial.print("State Process Time: ");
            }
            Serial.print(micros() - state_start);
          }

          // separate
          Serial.print(SEPARATOR);

          // init frame state
          window_start = micros();
          state_start = window_start;
          frame_cnt = 0;
          state = !state;
        }
        if (micros() - window_start >= FRAME_SEC) {
          window_start = micros();
          frame_cnt = 0;
        } else {
          frame_cnt++;
        }
      }
      Serial.println("\nEnd the signal.");
    }
}

TCPソケットをつかって簡単なサーバクライアントプロセスを作る - 日報 #149

WebRTCが巷で流行っているそうです。
まだほとんど触っていないので詳細は分かりませんが、
リアルタイムコネクションとおっしゃっているのだから、
きっとなんかリアルタイムなコネクションがあるのでしょう。
そうなるときっとsocket系の話になるのでしょう。

ということで、TCPソケットについて少し調べました。

そもそもこのTCPソケットについては昔から「得体の知れないすごいやつ」感がすごくて、
ApacheやらNginxみたいな、「多分C系で書かれてるんだろうなぁ…」みたいなアプリケーションだけができる特殊な世界観だと思ってました。
ですが、Node.jsみたいなものがではじめてからというもの、
そしてBSDソケットについて知見が深まってからというもの、
「サーバって意外と単純な仕組みなんじゃないか?」
というふうな考えになりつつあります。
Apache httpの開発者にぶっ殺されるような発言ですが、
勇気をだして言ってみました。

正確に言うと、
「得体が知れないほど複雑怪奇な世界」というわけではなく、 あくまで、「むちゃくちゃ複雑というわけではなく、理解はできそうな世界」なんじゃないかなと。
そう思い始めております。

ということで、
なにをさて置いても、自作してみるのが一番、
ってことでTCPソケットを作って「むっっっちゃ簡単なサーバ・クライアントソフト」を作ってみました。

以下は簡単なコード。
gccコンパイルできました。

まずはサーバ側。
server.cについて。ソケット作って、バインドして、リッスンして、アクセプトしたら、書き出す。
そんな簡単なサーバ。

// まずは server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main()
{
 int socket_conn;
 struct sockaddr_in addr;
 struct sockaddr_in client;
 int len_sockaddr_client;
 int socket_accept;

// ソケット初期化
// ストリーム型で接続するので SOCK_STREAM。UDPとかのダイアグラム型ならSCOK_DGRAMかな?
 socket_conn = socket(AF_INET, SOCK_STREAM, 0);
 if (socket_conn < 0) printf("Cannnot connect.\n");
 addr.sin_family = AF_INET; // ちょー一般的なアドレスファミリ
 addr.sin_port = htons(89898); // ポート指定
 addr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0からの接続を受け付けるという意味

// 満を持してバインド
if (bind(socket_conn, (struct sockaddr*)addr, sizeof(addr)) < 0) printf("Cannnot bind.\n");

// そしてリッスン
 listen(socket_conn, 5); // サーバー側でacceptされるまでのキュー数がこの5という数値

 // アクセプト準備
 len_sockaddr_client = sizeof(client);
 socket_accept = accept(socket_conn, (struct sockaddr *)&client, &len_sockaddr_client);

// とりあえず、文字列だけをソケットに書き出す
 write(socket_accept, "hogehoge", 8); // メッセージとバイト数

// ごみ処理
 close(socket_accept);
 close(socket_conn);
 return 0;
}

そして、クライアント側。
要はブラウザ的な。
127.0.0.1localhostでサーバを待機させるので、アドレスはそちらに設定。

// そして client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main()
{
 int sock_conn;
 struct sockaddr_in server_addr;
 char res[5];
 memset(res, '\0', sizeof(res));

 // ソケット初期化 
 // server.cと同じアドレス構造を使う
 sock_conn = socket(AF_INET, SOCK_STREAM, 0);
 if (socket_conn < 0) printf("Cannnot connect.\n");
 server_addr.sin_family = AF_INET;
 server_addr.sin_port = htons(89898);
 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

 // 自分で作ったソケットを先方のソケットにつなげる
 connect(sock_conn, (struct sockaddr *)&server_addr, sizeof(server_addr));

 // ソケットをリードする
 read(sock_conn, res, sizeof(res));

 // 結果出力
 printf("%s\n", buf);

 // ごみ処理
 close(sock_conn);
 return 0;
}

こんな感じで、あとはコンパイルして、serverをたたき、
clientを叩けばhogeを受信できます。
その昔、ターミナルでttyに対して標準出力を投げ合うことでチャットのようなことをやったことがありますが、
その感覚にすごく似ていますね。
つまるところ、BSDソケットが極めて優秀なAPIのために、
ファイルのリードライト感覚でTCPの通信ができるだなんて、
本当にすばらしい世界ですね。

サービスには全く関係ないけどね!

innodb_stats_persistent_sample_pages と innodb_stats_transient_sample_pages の違い - 日報 #148

思えば「Windows 95」という地の果てのようなOSから、
色々なバージョンのOSを使ってきましたが、
最近では
「バージョンアップってなんかワクワクするよね」 という感覚がやや薄れてしまったように思えます。
あくまで個人的な話ですが。
今日はMySQL5.5から5.6へ移行した際に、
注意が必要なパラメータについて少し。

MySQLのインデックス運用で少し触れた、
innodb_stats_sample_pages
についてです。
MySQL5.5 innodbエンジンでは、インデックス統計計算にこの値の数だけサンプリングして推計します。
つこって、インデックス統計値は毎回「ちょこちょこっと」変わります。
ですが、これはクエリオプティマイザからすればちょっと困るわけです。
MySQL 5.6ではこのパラメータは廃止されて、下記の2つにわかれています。

  • innodb_stats_persistent_sample_pages
  • innodb_stats_transient_sample_pages

persistentとtransient、
直訳すると「永続的」と「一時的」ということなので、
一目瞭然です。
要はきっちり選択できるようになったんですね。
インデックスの統計値計算は、transientは都度計算、
persistentは「あるタイミング」で統計値計算をしたものを再利用する、
という2つに分かれているようです。
persistentかtransientのどちらにするかはinnodb_stats_persistentというパラメータでスイッチできるようです。
で、ちなみにMySQLではpersistent推奨のようです。
下記参照です。
http://dev.mysql.com/doc/refman/5.6/en/innodb-persistent-stats.html
英語で分かりづらいので、重要な部分だけ抜粋します。

まず、統計値の更新のタイミングは?

The configuration option innodb_stats_auto_recalc determines whether the statistics are calculated
automatically whenever a table undergoes substantial changes (to more than 10% of the rows).

対象テーブル行数の10%以上の変化があった時に、バックグラウンドで計算されるようです。

If innodb_stats_auto_recalc is disabled, ensure the accuracy of optimizer statistics
by issuing the ANALYZE TABLE statement for each applicable table after making substantial changes to indexed columns.
You might run this statement in your setup scripts after representative data has been loaded into the table, and run it periodically after DML operations significantly change the contents of indexed columns, or on a schedule at times of low activity. When a new index is added to an existing table, index statistics are calculated and added to the innodb_index_stats table regardless of the value of innodb_stats_auto_recalc.

でも innodb_stats_auto_recalc がonになってないと、手作業でやんないとだめみたいですよ。
MySQL 5.5のときはデフォルトサンプリングページ数が8でしたが、
innodeb_stats_persistent_sample_pagesは20と高めの数値になっています。
妥当なサンプリング数は母比率の区間推定の時の妥当サンプリング数を用いるといいでしょう。
母比率xで、精度σ、信頼率1−αなら、

n ≧ x(1-x) ∑|α/2 /σ2
(ただし ∑|α/2 は 標準正規分布の確率表から抜粋)

信頼率5%で、母比率0.5で±10%程度の精度なら、大体

0.5 x 0.5 x 1.96 / 0.01 = 49

ということなので、50個くらいを指定すればいいのじゃないでしょうか。
偏りが激しい場合はまぁ色々考え方があると思いますが。

つぅ感じで、
5.5 to 5.6でも色々と変化があって楽しいですね!

本郷で働くエンジニアの一日 - 日報 #147

地震こわいですね。
地震も怖いですが、CloudWatchからのアラートも個人的には結構怖いです。

今日は基本に立ち返り、「日報」をしてみようと思います。

朝。
見るべき数値をそーっと確認。
開発陣から「Fluentdとまってね?」と言われて絶望的な気持ちになりながら対応。
1日1回リブートしくされこんちくしょう設定を導入。
(これってオートスケーリングで大量のインスタンスのIPを受けたせいかなとか思いつつ一旦放置)
処理の後、おもむろにタスク管理ツールをひらきチケット処理開始。
「期日的にはコレが先なんだけど、のらねぇからこっち」的なノリでブランチ切り。

昼。
調子に乗ってやり過ぎたせいで、
本来優先すべきタスクに対する不安が募る。
「ボチボチやるか、、、」といっておもむろに現作業分をコミット。
ブランチを切り直す。
とはいえ、DBにデータを打ち込む作業が待っているためコード変更をほぼせずにエクセルを立ち上げる。
即座に閉じて、Googleスプレッドシートに切り替えて作業開始。
CSVにしてローカルDBに打ち込むあたりで極端に腹が減る。
フルーツグラノーラをもっさもっさ食う。

昼過ぎ。
作業の続き。
「あれ?これ終わんねぇな」的な色合いが濃くなったあたりで、全体会議開始。
気を引き締め直す。
なぜなら終わらないから。

夕方。
会議の後の掃除を終えて、調子に乗って洗い物なんぞをしていたら、 コーヒーのサーバーを落として割る。
サービスのサーバーじゃなくてよかったとかほざきながら、
手でガラスを拾おうとしてエンジニアに怒られる。
「おまえアホか」と。
腹が減ったのでグラノーラをもしゃもしゃ食う。

夕方過ぎ。
不審なエラーをキャッチ。
CDNを落としてインフラエンジニアと原因調査。
パケットキャプチャしようが、サーバのログを読もうが、
全く意味不明な状態に腹を立てる。
「そこまでいうなら戦争(Zabbix導入)だ!」
という結論に落ち着いた頃にはターミナル疲れが全身に。
いそいそと帰る支度を始める。

そして帰宅。
インコがうるさい。
そんな一日。

PHPのincludeとrequireの違いと使いドコロ - 日報 #146

fluentdのno nodes are availableがなんかよく分からんタイミングで出現する時は、 ハートビートのためのUDPが(なんかしらんけど)ドロップするのが原因の1つみたいなので、
UDPでのハートビート用のデータを増やすか、
TCPでハートビートするよう設定する(hheartbeat_type tcp)か、
したほうがいいですぜ。
正攻法は後者だと思いますぜ。

さて、
飼ってるインコが春日和で発情し始めて困っていますが、
ボチボチいきましょう。
今日はすごく下らない話ですが、
誰ぞかのコードをレビューすることになったとき、
「includeとrequire」のどちらを使うべきかについて一瞬悩んだのでそれについて。

前にどっかで、 includeの読み込み方は「その場にコードを挿入する」ということなので、
例えばincludeされるファイルで/*のようなコメントアウトの開始があれば、
以下全てがコメントアウトされる、みたいな話を聞きましたが、
gccコンパイラがそもそも通らないので、これは一体何の話だったのだろうかという感じです。
脱線しましたが、
じゃぁPHPでのincludeとrequireの違いは何かというと、
公式の解説によれば、

include は、ファイルを見つけられない場合に warning を発行します。一方 require の場合は、同じ場合に fatal error を発行する点が異なります。

だそうです。
まぁつまるところ

require =「そのファイルがなきゃそのプログラム全体がFatalな状態だ!」
include =「そのファイルが無ければ警告は出すけど、プログラムは止めなくていいぜ」

の違いですね。
なんかincludeは差し込みで、requireはプログラム的なロードだ、
みたいな話をよく聞きますが、そんな違いはあんまないんじゃないでしょうか。
公式には上記の違いだけみたいですしおすし。
あと重要なのはこの一文ですね。

includeもしくはrequireが実行された 行の変数スコープを継承します。
呼び出し側の行で利用可能である全ての変数は、読み込まれたファイル内で利用可能です。 しかし、読み込まれたファイル内で定義されている関数やクラスはすべて グローバルスコープとなります。

ということだそうです。
つまりfunctionやclass内でrequireやらincludeはできますが、
そこで読み込まれたfunctionはグローバルだとおっしゃっとるわけです。
まぁでもこれはclassのメソッド内部でfunctionを作った時も、
functionでfunctionを作った時も同じような動作なので、
まぁそうか、ってかんじです。

あ!眠い!じゃぁね!