Skip to content

Commit

Permalink
Add a simple-tcp game server to use for testing. (#1071)
Browse files Browse the repository at this point in the history
  • Loading branch information
roberthbailey authored and markmandel committed Sep 25, 2019
1 parent 4f9f701 commit 188bd12
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 0 deletions.
32 changes: 32 additions & 0 deletions examples/simple-tcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright 2019 Google LLC All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# build
FROM golang:1.11.5 as builder
WORKDIR /go/src/simple-tcp

COPY examples/simple-tcp/main.go .
COPY . /go/src/agones.dev/agones
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .

# final image
FROM alpine:3.9

RUN adduser -D server
COPY --from=builder /go/src/simple-tcp/server /home/server/server
RUN chown -R server /home/server && \
chmod o+x /home/server/server

USER server
ENTRYPOINT ["/home/server/server"]
42 changes: 42 additions & 0 deletions examples/simple-tcp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2019 Google LLC All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Makefile for building a simple tcp server
#

# __ __ _ _ _
# \ \ / /_ _ _ __(_) __ _| |__ | | ___ ___
# \ \ / / _` | '__| |/ _` | '_ \| |/ _ \ __|
# \ V / (_| | | | | (_| | |_) | | __\__ \
# \_/ \__,_|_| |_|\__,_|_.__/|_|\___|___/
#

REPOSITORY = gcr.io/agones-images

mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
project_path := $(dir $(mkfile_path))
server_tag = $(REPOSITORY)/tcp-server:0.1
root_path = $(realpath $(project_path)/../..)

# _____ _
# |_ _|_ _ _ __ __ _ ___| |_ ___
# | |/ _` | '__/ _` |/ _ \ __/ __|
# | | (_| | | | (_| | __/ |_\__ \
# |_|\__,_|_| \__, |\___|\__|___/
# |___/

# Build a docker image for the server, and tag it
build:
cd $(root_path) && docker build -f $(project_path)/Dockerfile --tag=$(server_tag) .
26 changes: 26 additions & 0 deletions examples/simple-tcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Simple TCP Server

A very simple game server, for the purposes of testing a TCP based server on Agones.

## Server
Starts a server on port `7654` by default. Can be overwritten by `PORT` env var or `port` flag.

When it receives a text message ending with a newline, it will send back "ACK:<text content>" as an echo.

If it receives the text "EXIT", then it will `sys.Exit(0)`

## Firewalls

If you plan to access your server remotely, you may need to open up a hole in your
firewall.

For example, if you created a cluster running on Google Kubernetes Engine following
the installation guide, you can create a firewall rule to allow TCP traffic to nodes
tagged as game-server via ports 7000-8000.

```bash
gcloud compute firewall-rules create game-server-firewall-tcp \
--allow tcp:7000-8000 \
--target-tags game-server \
--description "Firewall to allow game server udp traffic"
```
36 changes: 36 additions & 0 deletions examples/simple-tcp/gameserver.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2019 Google LLC All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: "agones.dev/v1"
kind: GameServer
metadata:
generateName: "simple-tcp-"
spec:
ports:
- name: default
portPolicy: Dynamic
containerPort: 7654
protocol: TCP
template:
spec:
containers:
- name: simple-tcp
image: gcr.io/agones-images/tcp-server:0.1
resources:
requests:
memory: "32Mi"
cpu: "20m"
limits:
memory: "32Mi"
cpu: "20m"
144 changes: 144 additions & 0 deletions examples/simple-tcp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2019 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package main is a very simple echo TCP server
package main

import (
"bufio"
"flag"
"log"
"net"
"os"
"strings"
"time"

"agones.dev/agones/pkg/util/signals"
sdk "agones.dev/agones/sdks/go"
)

// main starts a TCP server that receives a message at a time
// (newline delineated), and echos the output.
func main() {
go doSignal()

port := flag.String("port", "7654", "The port to listen to tcp traffic on")
flag.Parse()
if ep := os.Getenv("PORT"); ep != "" {
port = &ep
}

log.Printf("Starting TCP server, listening on port %s", *port)
ln, err := net.Listen("tcp", ":"+*port)
if err != nil {
log.Fatalf("Could not start tcp server: %v", err)
}
defer ln.Close() // nolint: errcheck

log.Print("Creating SDK instance")
s, err := sdk.NewSDK()
if err != nil {
log.Fatalf("Could not connect to sdk: %v", err)
}

log.Print("Starting Health Ping")
stop := make(chan struct{})
go doHealth(s, stop)

log.Print("Marking this server as ready")
if err := s.Ready(); err != nil {
log.Fatalf("Could not send ready message")
}

for {
conn, err := ln.Accept()
if err != nil {
log.Printf("Unable to accept incoming tcp connection: %v", err)
}
go handleConnection(conn, stop, s)
}
}

// doSignal shutsdown on SIGTERM/SIGKILL
func doSignal() {
stop := signals.NewStopChannel()
<-stop
log.Println("Exit signal received. Shutting down.")
os.Exit(0)
}

// handleConnection services a single tcp connection to the server
func handleConnection(conn net.Conn, stop chan struct{}, s *sdk.SDK) {
log.Printf("Client %s connected", conn.RemoteAddr().String())
scanner := bufio.NewScanner(conn)
for {
if ok := scanner.Scan(); !ok {
log.Printf("Client %s disconnected", conn.RemoteAddr().String())
return
}
handleCommand(conn, scanner.Text(), stop, s)
}
}

// respond responds to a given sender.
func respond(conn net.Conn, txt string) {
log.Printf("Responding with %q", txt)
if _, err := conn.Write([]byte(txt+"\n")); err != nil {
log.Fatalf("Could not write to tcp stream: %v", err)
}
}

func handleCommand(conn net.Conn, txt string, stop chan struct{}, s *sdk.SDK) {
parts := strings.Split(strings.TrimSpace(txt), " ")

log.Printf("parts: %v", parts)
switch parts[0] {
// shuts down the gameserver
case "EXIT":
respond(conn, "ACK: "+txt)
exit(s)

// turns off the health pings
case "UNHEALTHY":
close(stop)
}

respond(conn, "ACK: "+txt+"\n")
}

// exit shutdowns the server
func exit(s *sdk.SDK) {
log.Printf("Received EXIT command. Exiting.")
// This tells Agones to shutdown this Game Server
if err := s.Shutdown(); err != nil {
log.Printf("Could not call shutdown: %v", err)
}
os.Exit(0)
}

// doHealth sends the regular Health Pings
func doHealth(sdk *sdk.SDK, stop <-chan struct{}) {
tick := time.Tick(2 * time.Second)
for {
if err := sdk.Health(); err != nil {
log.Fatalf("Could not send health ping: %v", err)
}
select {
case <-stop:
log.Print("Stopped health pings")
return
case <-tick:
}
}
}
3 changes: 3 additions & 0 deletions site/content/en/docs/Examples/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ These are full examples for each of the resource types of Agones
These are all examples of simple game server implementations, that integrate the Agones game server SDK.

- {{< ghlink href="examples/simple-udp" >}}Simple UDP{{< /ghlink >}} (Go) - simple server and client that send UDP packets back and forth.
{{% feature publishVersion="1.1.0" %}}
- {{< ghlink href="examples/simple-tcp" >}}Simple TCP{{< /ghlink >}} (Go) - simple server that responds to new-line delimited messages sent over a TCP connection.
{{% /feature %}}
- {{< ghlink href="examples/cpp-simple" >}}CPP Simple{{< /ghlink >}} (C++) - C++ example that starts up, stays healthy and then shuts down after 60 seconds.
- {{< ghlink href="examples/nodejs-simple" >}}Node.js Simple{{< /ghlink >}} (Node.js) -
A simple Node.js example that marks itself as ready, sets some labels and then shutsdown.
Expand Down

0 comments on commit 188bd12

Please sign in to comment.