2010/12/19 22時頃追記
『通信を手動で終了した場合に、謎のiframeが残ってしまい再接続ができない』
というGAE側の?バグが確認されました。
このページで公開されているソースは、このバグ対策用コードも含みます。
詳細はこのページをご覧になってください。
2010-12-19 - 風柳メモ「Google App Engine/PythonのChannel APIを使ってみた」

Google App Engine Channel APIのサーバ側のサンプルコードです。

クライアント側のHTML、JavaScriptのコードはこのページです。
『Channel APIを使うプログラムのクライアント側のコード』

上記のURLのソースコードで作ったChannel APIアプリケーションはこちらです。
ソースにコメントをたくさん書いておいたので、JavaScriptについてあまり詳しくなくても読めるはず?です。
『実際に動作しているChannel API アプリケーションページ』

元のページはこちらです。
『Google App Engine Channel APIの使い方』

サーバとクライアントのサンプルコードはそのままデプロイすればGoogle App Engine上で動きます。
もし分からないことがあったら、@ts_3156に何でもリプライして聞いてください。

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.channel.ChannelMessage;
import com.google.appengine.api.channel.ChannelService;
import com.google.appengine.api.channel.ChannelServiceFactory;

@SuppressWarnings("serial")
public class ChannelAPITestServlet extends HttpServlet {

	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

		// ゲームに割り当てられるIdとか、ログインしたユーザのIdとか自分で決めましょう
		// ここではとりあえず時間を表す文字列にしてます
		String userId = Long.toString(System.currentTimeMillis());

		String htmlSource = "http://channel3156.appspot.com/channel_api_test.htmlのHTMLをそのまま代入";

		StringBuilder strBuilder = new StringBuilder();
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("channel_api_test.html"), "utf-8"));
		String line = null;
		while((line = br.readLine()) != null)
			strBuilder.append(line + "\n");
		htmlSource = strBuilder.toString();

		htmlSource = htmlSource.replaceAll("--token--", getToken(userId));
		htmlSource = htmlSource.replaceAll("--userId--", userId);

		resp.setContentType("text/html; charset=utf-8");
		resp.getWriter().write(htmlSource);
	}

	public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

		String getToken = req.getParameter("getToken");

		// Token再取得要求
		if(getToken != null && getToken.equals("true")){
			String userId = Long.toString(System.currentTimeMillis());
			String token = getToken(userId);
			String response = "{\"userId\": \"" + userId + "\", \"token\": \"" + token + "\"}";
			resp.setContentType("text/html; charset=utf-8");
			resp.getWriter().write(response);
			return;
		}

		String userId = req.getParameter("userId");
		String message = req.getParameter("message");
		String sendTime = req.getParameter("time");

		long curTime = System.currentTimeMillis();
		String sendTimeStr = "" + (curTime - Long.parseLong(sendTime));

		// 受け取ったデータをそのまま返します
		String response = "{\"text\": \"" + message + "を受け取ったよ! 送信: " + sendTimeStr + "ミリ秒" + "\", \"time\": \"" + curTime + "\"}";
		sendUpdateToUser(userId, response);
	}

	private void sendUpdateToUser(String userId, String message) {
	      ChannelService channelService = ChannelServiceFactory.getChannelService();
	      channelService.sendMessage(new ChannelMessage(userId, message));
	}

	private String getToken(String userId){
		ChannelService channelService = ChannelServiceFactory.getChannelService();

		// ゲームのパラメータとかユーザIdを元にして一意なIdを作りましょう
		// ここでは簡易化のために、userIdをそのまま使っています
		String token = channelService.createChannel(userId);
		return token;
	}

}