1. 程式人生 > >CMU15-440 project-0:用Go實現一個廣播回顯的Server

CMU15-440 project-0:用Go實現一個廣播回顯的Server

最近在學習分散式系統,特定跟上了CMU15-440這門課。
寫完了Project 0
Project 0 : 是讓寫一個能夠回顯的Echo Server
要求是每一個Client傳送給Server訊息,用\n來進行分割,並將收到的資訊都返回給所有的使用者:
下面是具體的實驗要求(直接上英文,為了方便):
Your multi-client echo server must have the following characteristics:

  1. The server must manage and interact with its clients concurrently using goroutines and channels. Multiple clients should be able to connect/disconnect to the server simultaneously.

  2. The server should assume that all messages are line-oriented, separated by newline (\n) characters. When the server reads a newline-terminated message from a client, it must respond by writing that exact message (up to and including the newline character) to all connected clients, including the client that sent the message.

  3. The server must be responsive to slow-reading clients. To better understand what this means, consider a scenario in which a client does not call Read for an extended period of time. If during this time the server continues to write messages to the client’s TCP connection, eventually the TCP connection’s output buffer will reach maximum capacity and subsequent calls to Write made by the server will block.

To handle these cases, your server should keep a queue of at most 100 outgoing messages to be written to the client at a later time. Messages sent to a slow-reading client whose outgoing message buffer has reached the maximum capacity of 100 should simply be dropped. If the slow-reading client starts reading again later on, the server should make sure to write any buffered messages in its queue back to the client. (Hint: use a buffered channel to implement this property).

這個實驗的難度不大,重點是為了熟悉Go語言以及go和chan

需要注意的點:
一個啟動了4個協程:

  1. Server端需要啟動協程來處理client的請求。
  2. 啟動一個廣播協程,向所有存活的使用者傳送訊息
  3. 讀取client請求協程,讀取Client所讀取的所有資訊(用空格來進行分割,一個空格一條資訊)。
  4. 向client寫協程,單獨起一個協程,for迴圈來讀取服務端傳送的資訊,並寫向client。

這裡,要注意熟悉Go語言的chan的阻塞特性,
利用chan來作為鎖,即簡單點表示就是

 <- chan

只有當chan管道收到訊息的時候,才會繼續執行,所以可以將chan作為阻塞鎖來用。
通過傳送訊息來對協程進行喚醒。
同時,要熟悉select的chan選擇特性,select本身也是阻塞的。

select {
   case <- chan:
}

具體的程式碼如下:

// Implementation of a MultiEchoServer. Students should write their code in this file.

package p0

import (
	"bufio"
	"net";
//	"time";
	"strconv";
	"fmt";
)


const (
	MaxQueue=100  //max buffer size
)


type clientinfo struct {
	conn net.Conn
	WriteBuffChan chan string
	live bool
}


type multiEchoServer struct {
	// TODO: implement this!
	// 結構體儲存指標,不能儲存整個結構體的空間
	client []*clientinfo	//用來表示所有使用者
	closechan chan byte  //用來確認關閉的管道
	readchan chan string	//建立一個管道,專門用來讀取資訊
}


//make返回一個型別為T的變數,但是new返回的是一個指標

// New creates and returns (but does not start) a new MultiEchoServer.
func New() MultiEchoServer {
	// TODO: implement this!
	server:= multiEchoServer {
		client:make([]*clientinfo,0,0),		//make的第二個引數是申請的數量,第三個引數是預留的空間
		closechan:make(chan byte),
		readchan:make(chan string),
	}
	go server.TcpClientBroadcase()
	return &server
}

func (mes *multiEchoServer) Start(port int) error {
	// TODO: implement this!
	//繫結埠
	service:="localhost:"+strconv.Itoa(port)
	tcpAddr,err:=net.ResolveTCPAddr("tcp",service)
	//連線
	ln,err:=net.ListenTCP("tcp",tcpAddr)
	if err!=nil {
		return err
	}
	//開啟監聽狀態
	//必須要直接開一個攜程,不然無法進行下面的測試程式
	go func() {
		for {
			select {
				//接收到結束程序的訊息
			case <- mes.closechan:
				ln.Close()
				return ;
			default:
			}
//			ln.SetDeadline(time.Now().Add(time.Millisecond));	//設定超時時長
			//啟動監聽的訊息
			c,err:=ln.Accept()
			if err!=nil {
				fmt.Printf("The Accept is something wrong! And the Err is %s\n",err)
				return
			}
			//建立一個clientinfo
			NewClient:=&clientinfo {
				conn:c,
				WriteBuffChan:make(chan string,MaxQueue),
				live:true,
			}
			//:=只用在做初始化的時候,但是初始化過後,需要修改變數的時候應該用=號
			mes.client=append(mes.client,NewClient)

			//為每個TCP連線建立兩個新的read與write的內容
			go mes.TcpClientRead(NewClient)
			go mes.TcpClientWrite(NewClient)
		}
	}()
	return nil
}

func (mes *multiEchoServer) Close() {
	// TODO: implement this!
	// 將closechan進行關閉,先關閉server的監聽狀態,然後再依次關閉所連線的客戶端
	close(mes.closechan);
	for _,client:=range mes.client {
		client.live=false
		client.conn.Close()
	}
}

func (mes *multiEchoServer) Count() int {
	// TODO: implement this!
	Number:=0
	//for 迴圈,前面一個引數是索引,後面一個引數是實體
	for _,client:=range mes.client {
		if client.live==true {
			Number++
		}
	}
	return Number
}

// TODO: add additional methods/functions below!

//進行廣播的函式
func (mes *multiEchoServer) TcpClientBroadcase() {
	for {
		select {
		case message:= <- mes.readchan :
			//將接收到的訊息廣播到所有連線使用者
			for _,client:=range mes.client {
				if client.live==true && len(client.WriteBuffChan)<MaxQueue {
					client.WriteBuffChan <-message
				}
			}
		case <-mes.closechan :
			return ;
		}
	}
}

//讀取客戶端的函式
func (mes *multiEchoServer) TcpClientRead(client * clientinfo) {
//	fmt.Printf("A new Client Comming\n")
	Reader:=bufio.NewReader(client.conn)
	for {
		message,err:=Reader.ReadBytes('\n')
		if err!=nil {
			client.live=false
//			fmt.Printf("The Accept is something wrong! And the Err is %s\n",err)
			return ;
		}
//		fmt.Printf("%s\n",string(message))
		mes.readchan <- string(message)
	}
}

//向客戶端寫函式
//主要負責把資訊寫回
func (mes *multiEchoServer) TcpClientWrite(client * clientinfo) {
	for {
		select {
		case message:= <- client.WriteBuffChan :
			if client.live==true {
				client.conn.Write([]byte(message))
			}
		//如果關閉就直接關閉
		case <- mes.closechan :
			return;
		}
	}
}

結果沒有全部pass,大多是因為超時的原因,應該與機器效能有關。具體的功能實現了,也就沒有多關注不同機器、系統的效能問題了。