利用如下的几个知识点,完成一份代码
1.使用异步设计的方法
2.异步网络IO
3.专用序列化,反序列化方法
4.良好的传输协议
5.双工通信
我们使用Go来实现整份代码
package main
import ( “encoding/binary” “fmt” “io” “net” “sync” “time” ) var zRecvCount = uint32(0) // 张大爷听到了多少句话 var lRecvCount = uint32(0) // 李大爷听到了多少句话 var total = uint32(100000) // 总共需要遇见多少次 var z0 = “吃了没,您吶?” var z3 = “嗨!吃饱了溜溜弯儿。” var z5 = “回头去给老太太请安!” var l1 = “刚吃。” var l2 = “您这,嘛去?” var l4 = “有空家里坐坐啊。” var liWriteLock sync.Mutex // 李大爷的写锁 var zhangWriteLock sync.Mutex // 张大爷的写锁 type RequestResponse struct { Serial uint32 // 序号 Payload string // 内容 } // 序列化RequestResponse,并发送 // 序列化后的结构如下: // 长度 4字节 // Serial 4字节 // PayLoad 变长 func writeTo(r *RequestResponse, conn *net.TCPConn, lock *sync.Mutex) { lock.Lock() defer lock.Unlock() payloadBytes := []byte(r.Payload) serialBytes := make([]byte, 4) binary.BigEndian.PutUint32(serialBytes, r.Serial) length := uint32(len(payloadBytes) + len(serialBytes)) lengthByte := make([]byte, 4) binary.BigEndian.PutUint32(lengthByte, length) conn.Write(lengthByte) conn.Write(serialBytes) conn.Write(payloadBytes) // fmt.Println(“发送: ” + r.Payload) } // 接收数据,反序列化成RequestResponse func readFrom(conn *net.TCPConn) (*RequestResponse, error) { ret := &RequestResponse{} buf := make([]byte, 4) if _, err := io.ReadFull(conn, buf); err != nil { return nil, fmt.Errorf(“读长度故障:%s”, err.Error()) } length := binary.BigEndian.Uint32(buf) if _, err := io.ReadFull(conn, buf); err != nil { return nil, fmt.Errorf(“读Serial故障:%s”, err.Error()) } ret.Serial = binary.BigEndian.Uint32(buf) payloadBytes := make([]byte, length-4) if _, err := io.ReadFull(conn, payloadBytes); err != nil { return nil, fmt.Errorf(“读Payload故障:%s”, err.Error()) } ret.Payload = string(payloadBytes) return ret, nil } // 张大爷的耳朵 func zhangDaYeListen(conn *net.TCPConn, wg *sync.WaitGroup) { defer wg.Done() for zRecvCount < total*3 { r, err := readFrom(conn) if err != nil { fmt.Println(err.Error()) break } // fmt.Println(“张大爷收到:” + r.Payload) if r.Payload == l2 { // 如果收到:您这,嘛去? go writeTo(&RequestResponse{r.Serial, z3}, conn, &zhangWriteLock) // 回复:嗨!吃饱了溜溜弯儿。 } else if r.Payload == l4 { // 如果收到:有空家里坐坐啊。 go writeTo(&RequestResponse{r.Serial, z5}, conn, &zhangWriteLock) // 回复:回头去给老太太请安! } else if r.Payload == l1 { // 如果收到:刚吃。 // 不用回复 } else { fmt.Println(“张大爷听不懂:” + r.Payload) break } zRecvCount++ } } // 张大爷的嘴 func zhangDaYeSay(conn *net.TCPConn) { nextSerial := uint32(0) for i := uint32(0); i < total; i++ { writeTo(&RequestResponse{nextSerial, z0}, conn, &zhangWriteLock) nextSerial++ } } // 李大爷的耳朵,实现是和张大爷类似的 func liDaYeListen(conn *net.TCPConn, wg *sync.WaitGroup) { defer wg.Done() for lRecvCount < total*3 { r, err := readFrom(conn) if err != nil { fmt.Println(err.Error()) break } // fmt.Println(“李大爷收到:” + r.Payload) if r.Payload == z0 { // 如果收到:吃了没,您吶? writeTo(&RequestResponse{r.Serial, l1}, conn, &liWriteLock) // 回复:刚吃。 } else if r.Payload == z3 { // do nothing } else if r.Payload == z5 { // do nothing } else { fmt.Println(“李大爷听不懂:” + r.Payload) break } lRecvCount++ } } // 李大爷的嘴 func liDaYeSay(conn *net.TCPConn) { nextSerial := uint32(0) for i := uint32(0); i < total; i++ { writeTo(&RequestResponse{nextSerial, l2}, conn, &liWriteLock) nextSerial++ writeTo(&RequestResponse{nextSerial, l4}, conn, &liWriteLock) nextSerial++ } } func startServer(wg *sync.WaitGroup) { tcpAddr, _ := net.ResolveTCPAddr(“tcp”, “127.0.0.1:9999”) tcpListener, _ := net.ListenTCP(“tcp”, tcpAddr) defer tcpListener.Close() fmt.Println(“张大爷在胡同口等着 …”) for { conn, err := tcpListener.AcceptTCP() if err != nil { fmt.Println(err) break } fmt.Println(“碰见一个李大爷:” + conn.RemoteAddr().String()) go zhangDaYeListen(conn, wg) go zhangDaYeSay(conn) } } func startClient(wg *sync.WaitGroup) *net.TCPConn { var tcpAddr *net.TCPAddr tcpAddr, _ = net.ResolveTCPAddr(“tcp”, “127.0.0.1:9999”) conn, _ := net.DialTCP(“tcp”, nil, tcpAddr) go liDaYeListen(conn, wg) go liDaYeSay(conn) return conn } func main() { var wg sync.WaitGroup wg.Add(2) go startServer(&wg) time.Sleep(time.Second) conn := startClient(&wg) t1 := time.Now() wg.Wait() elapsed := time.Since(t1) conn.Close() fmt.Println(“耗时: “, elapsed) } |
定义了一个结构体
RequestResponse
type RequestResponse struct {
Serial uint32 // 序号 Payload string // 内容 } |
然后自定了一个传输的协议
4个字节标明请求的长度,4个字节保存序号
最后是实际的内容,采用了前置长度的方式来进行断句
然后是分别给出了Listen和Say的方法
都是分别对方的请求,给出对应的相应,而且是不停的说着各自的请求,不关心对方的相应
然后4个方法,在不同的协程中运行,实现了双工的通信
而且为了避免同时向Socket写入数据导致混乱,分别定义了一个写锁,确保了每个大爷能有一个协程写入
完成了这10万次数据