《Unity网络游戏实战》终章: 坦克大战小游戏

1、介绍

在《Unity网络游戏实战》书的最后五个章节是制作一个小的多人对战游戏,坦克大战。这里就把东西都写在一起。做一个总结。

最后的五个章节是实现一个坦克大战游戏。游戏功能大致如下:
1、玩家注册游戏账号,登陆游戏。
2、进入游戏大厅,玩家可以创建房间,等待其他玩家加入房间。
3、玩家进入房间之后分配阵营,两边阵营人数相同并且最少有一人就可以开始游戏。
4、游戏过程很简单。地方坦克全灭就取得胜利。

因为章节太多,代码太长了。就不一一记录了。而且小游戏实现的功能确实比较简单。就只拿一下比较有用代码说一下。

2、大厅系统

大厅系统是玩家登录游戏之后进入游戏大厅,然后创建房间或加入已经存在的房间的操作。

客户端实现:
界面制作有大厅界面以及房间界面。
玩家登陆游戏之后,会向服务器发送MsgGetRoomList协议,获取当前存在的房间。客户端收到回应之后,就清空当前的所有RoomItem,根据服务器的回送创建新的RoomItem。

玩家点击进入房间,会向服务器发送MsgGetRoomInfo协议,获取当前进入的房间中所有玩家的信息,然后生成PlayerInfoItem。退出房间就向服务器发送MsgGetRoomList协议,显示房间列表。

客户端代码:
主要代码有:RoomListPanel和RoomPanel
RoomListPanel.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


public class RoomListPanel : BasePanel {
    //账号文本
    private Text idText;
    //战绩文本
    private Text scoreText;
    //创建房间按钮
    private Button createButton;
    //刷新列表按钮
    private Button reflashButton;
    //列表容器
    private Transform content;
    //房间物体
    private GameObject roomObj;


    //初始化
    public override void OnInit() {
        skinPath = "RoomListPanel";
        layer = PanelManager.Layer.Panel;
    }

    //显示
    public override void OnShow(params object[] args) {
        //寻找组件
        idText = skin.transform.Find("InfoPanel/IdText").GetComponent<Text>();
        scoreText = skin.transform.Find("InfoPanel/ScoreText").GetComponent<Text>();
        createButton = skin.transform.Find("CtrlPanel/CreateButton").GetComponent<Button>();
        reflashButton = skin.transform.Find("CtrlPanel/ReflashButton").GetComponent<Button>();
        content = skin.transform.Find("ListPanel/Scroll View/Viewport/Content");
        roomObj = skin.transform.Find("Room").gameObject;
        //不激活房间
        roomObj.SetActive(false);
        //显示id
        idText.text = GameMain.id;
        //按钮事件
        createButton.onClick.AddListener(OnCreateClick);
        reflashButton.onClick.AddListener(OnReflashClick);
        //协议监听
        NetManager.AddMsgListener("MsgGetAchieve", OnMsgGetAchieve);
        NetManager.AddMsgListener("MsgGetRoomList", OnMsgGetRoomList);
        NetManager.AddMsgListener("MsgCreateRoom", OnMsgCreateRoom);
        NetManager.AddMsgListener("MsgEnterRoom", OnMsgEnterRoom);
        //发送查询
        MsgGetAchieve msgGetAchieve = new MsgGetAchieve();
        NetManager.Send(msgGetAchieve);
        MsgGetRoomList msgGetRoomList = new MsgGetRoomList();
        NetManager.Send(msgGetRoomList);
    }


    //关闭
    public override void OnClose() {
        //协议监听
        NetManager.RemoveMsgListener("MsgGetAchieve", OnMsgGetAchieve);
        NetManager.RemoveMsgListener("MsgGetRoomList", OnMsgGetRoomList);
        NetManager.RemoveMsgListener("MsgCreateRoom", OnMsgCreateRoom);
        NetManager.RemoveMsgListener("MsgEnterRoom", OnMsgEnterRoom);
    }

    //收到成绩查询协议
    public void OnMsgGetAchieve (MsgBase msgBase) {
        MsgGetAchieve msg = (MsgGetAchieve)msgBase;
        scoreText.text = msg.win + "胜 " + msg.lost + "负";
    }

    //收到房间列表协议
    public void OnMsgGetRoomList (MsgBase msgBase) {
        MsgGetRoomList msg = (MsgGetRoomList)msgBase;
        //清除房间列表
        for(int i = content.childCount-1; i >= 0 ; i--){
            GameObject o = content.GetChild(i).gameObject;
            Destroy(o);
        }
        //重新生成列表
        if(msg.rooms == null){
            return;
        }
        for(int i = 0; i < msg.rooms.Length; i++){
            GenerateRoom(msg.rooms[i]);
        }
    }

    //创建一个房间单元
    public void GenerateRoom(RoomInfo roomInfo){
        //创建物体
        GameObject o = Instantiate(roomObj);
        o.transform.SetParent(content);
        o.SetActive(true);
        o.transform.localScale = Vector3.one; 
        //获取组件
        Transform trans = o.transform;
        Text idText = trans.Find("IdText").GetComponent<Text>();
        Text countText = trans.Find("CountText").GetComponent<Text>();
        Text statusText = trans.Find("StatusText").GetComponent<Text>();
        Button btn = trans.Find("JoinButton").GetComponent<Button>();
        //填充信息
        idText.text = roomInfo.id.ToString();
        countText.text = roomInfo.count.ToString();
        if(roomInfo.status == 0){
            statusText.text = "准备中";
        }
        else{
            statusText.text = "战斗中";
        }
        //按钮事件
        btn.name = idText.text;
        btn.onClick.AddListener(delegate(){
            OnJoinClick(btn.name);
        });
    }

    //点击刷新按钮
    public void OnReflashClick(){
        MsgGetRoomList msg = new MsgGetRoomList();
        NetManager.Send(msg);
    }

    //点击加入房间按钮
    public void OnJoinClick(string idString) {
        MsgEnterRoom msg = new MsgEnterRoom();
        msg.id = int.Parse(idString);
        NetManager.Send(msg);
    }

    //收到进入房间协议
    public void OnMsgEnterRoom (MsgBase msgBase) {
        MsgEnterRoom msg = (MsgEnterRoom)msgBase;
        //成功进入房间
        if(msg.result == 0){
            PanelManager.Open<RoomPanel>();
            Close();
        }
        //进入房间失败
        else{
            PanelManager.Open<TipPanel>("进入房间失败");
        }
    }

    //点击新建房间按钮
    public void OnCreateClick() {
        MsgCreateRoom msg = new MsgCreateRoom();
        NetManager.Send(msg);
    }

    //收到新建房间协议
    public void OnMsgCreateRoom (MsgBase msgBase) {
        MsgCreateRoom msg = (MsgCreateRoom)msgBase;
        //成功创建房间
        if(msg.result == 0){
            PanelManager.Open<TipPanel>("创建成功");
            PanelManager.Open<RoomPanel>();
            Close();
        }
        //创建房间失败
        else{
            PanelManager.Open<TipPanel>("创建房间失败");
        }
    }
}

RoomPanel.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RoomPanel : BasePanel {
    //开战按钮
    private Button startButton;
    //退出按钮
    private Button closeButton;
    //列表容器
    private Transform content;
    //玩家信息物体
    private GameObject playerObj;

    //初始化
    public override void OnInit() {
        skinPath = "RoomPanel";
        layer = PanelManager.Layer.Panel;
    }

    //显示
    public override void OnShow(params object[] args) {
        //寻找组件
        startButton = skin.transform.Find("CtrlPanel/StartButton").GetComponent<Button>();
        closeButton = skin.transform.Find("CtrlPanel/CloseButton").GetComponent<Button>();
        content = skin.transform.Find("ListPanel/Scroll View/Viewport/Content");
        playerObj =  skin.transform.Find("Player").gameObject;
        //不激活玩家信息
        playerObj.SetActive(false);
        //按钮事件
        startButton.onClick.AddListener(OnStartClick);
        closeButton.onClick.AddListener(OnCloseClick);
        //协议监听
        NetManager.AddMsgListener("MsgGetRoomInfo", OnMsgGetRoomInfo);
        NetManager.AddMsgListener("MsgLeaveRoom", OnMsgLeaveRoom);
        NetManager.AddMsgListener("MsgStartBattle", OnMsgStartBattle);
        //发送查询
        MsgGetRoomInfo msg = new MsgGetRoomInfo();
        NetManager.Send(msg);
    }

    //关闭
    public override void OnClose() {
        //协议监听
        NetManager.RemoveMsgListener("MsgGetRoomInfo", OnMsgGetRoomInfo);
        NetManager.RemoveMsgListener("MsgLeaveRoom", OnMsgLeaveRoom);
        NetManager.RemoveMsgListener("MsgStartBattle", OnMsgStartBattle);
    }

    //收到玩家列表协议
    public void OnMsgGetRoomInfo (MsgBase msgBase) {
        MsgGetRoomInfo msg = (MsgGetRoomInfo)msgBase;
        //清除玩家列表
        for(int i = content.childCount-1; i >= 0 ; i--){
            GameObject o = content.GetChild(i).gameObject;
            Destroy(o);
        }
        //重新生成列表
        if(msg.players == null){
            return;
        }
        for(int i = 0; i < msg.players.Length; i++){
            GeneratePlayerInfo(msg.players[i]);
        }
    }

    //创建一个玩家信息单元
    public void GeneratePlayerInfo(PlayerInfo playerInfo){
        //创建物体
        GameObject o = Instantiate(playerObj);
        o.transform.SetParent(content);
        o.SetActive(true);
        o.transform.localScale = Vector3.one; 
        //获取组件
        Transform trans = o.transform;
        Text idText = trans.Find("IdText").GetComponent<Text>();
        Text campText = trans.Find("CampText").GetComponent<Text>();
        Text scoreText = trans.Find("ScoreText").GetComponent<Text>();
        //填充信息
        idText.text = playerInfo.id;
        if(playerInfo.camp == 1){
            campText.text = "红";
        }
        else{
            campText.text = "蓝";
        }
        if(playerInfo.isOwner == 1){
            campText.text = campText.text + " !";
        }
        scoreText.text = playerInfo.win + "胜 " + playerInfo.lost + "负";
    }

    //点击退出按钮
    public void OnCloseClick(){
        MsgLeaveRoom msg = new MsgLeaveRoom();
        NetManager.Send(msg);
    }

    //收到退出房间协议
    public void OnMsgLeaveRoom (MsgBase msgBase) {
        MsgLeaveRoom msg = (MsgLeaveRoom)msgBase;
        //成功退出房间
        if(msg.result == 0){
            PanelManager.Open<TipPanel>("退出房间");
            PanelManager.Open<RoomListPanel>();
            Close();
        }
        //退出房间失败
        else{
            PanelManager.Open<TipPanel>("退出房间失败");
        }
    }

    //点击开战按钮
    public void OnStartClick(){
        MsgStartBattle msg = new MsgStartBattle();
        NetManager.Send(msg);
    }

    //收到开战返回
    public void OnMsgStartBattle (MsgBase msgBase) {
        MsgLeaveRoom msg = (MsgLeaveRoom)msgBase;
        //开战
        if(msg.result == 0){
            //等待战斗推送的协议
        }
        //开战失败
        else{
            PanelManager.Open<TipPanel>("开战失败!两队至少都需要一名玩家,只有队长可以开始战斗!");
        }
    }
}

服务器主要是实现Room和RoomManager。这两个类在名字上就很清楚它们的功能是什么,比较简单。
Room.cs

using System;
using System.Collections.Generic;

public class Room {
    //id
    public int id = 0;
    //最大玩家数
    public int maxPlayer = 6;
    //玩家列表
    public Dictionary<string, bool> playerIds = new Dictionary<string, bool>();
    //房主id
    public string ownerId = "";
    //状态
    public enum Status {
        PREPARE = 0,
        FIGHT = 1 ,
    }
    public Status status = Status.PREPARE;


    //添加玩家
    public bool AddPlayer(string id){
        //获取玩家
        Player player = PlayerManager.GetPlayer(id);
        if(player == null){
            Console.WriteLine("room.AddPlayer fail, player is null");
            return false;
        }
        //房间人数
        if(playerIds.Count >= maxPlayer){
            Console.WriteLine("room.AddPlayer fail, reach maxPlayer");
            return false;
        }
        //准备状态才能加人
        if(status != Status.PREPARE){
            Console.WriteLine("room.AddPlayer fail, not PREPARE");
            return false;
        }
        //已经在房间里
        if(playerIds.ContainsKey(id)){
            Console.WriteLine("room.AddPlayer fail, already in this room");
            return false;
        }
        //加入列表
        playerIds[id] = true;
        //设置玩家数据
        player.camp = SwitchCamp();
        player.roomId = this.id;
        //设置房主
        if(ownerId == ""){
            ownerId = player.id;
        }
        //广播
        Broadcast(ToMsg());
        return true;
    }

    //分配阵营
    public int SwitchCamp() {
        //计数
        int count1 = 0;
        int count2 = 0;
        foreach(string id in playerIds.Keys) {
            Player player = PlayerManager.GetPlayer(id);
            if(player.camp == 1) {count1++;}
            if(player.camp == 2) {count2++;}
        }
        //选择
        if (count1 <= count2){
            return 1;
        }
        else{
            return 2;
        }
    }

    //是不是房主
    public bool isOwner(Player player){
        return player.id == ownerId;
    }

    //删除玩家
    public bool RemovePlayer(string id) {
        //获取玩家
        Player player = PlayerManager.GetPlayer(id);
        if(player == null){
            Console.WriteLine("room.RemovePlayer fail, player is null");
            return false;
        }
        //没有在房间里
        if(!playerIds.ContainsKey(id)){
            Console.WriteLine("room.RemovePlayer fail, not in this room");
            return false;
        }
        //删除列表
        playerIds.Remove(id);
        //设置玩家数据
        player.camp = 0;
        player.roomId = -1;
        //设置房主
        if(ownerId == player.id){
            ownerId = SwitchOwner();
        }
        //房间为空
        if(playerIds.Count == 0){
            RoomManager.RemoveRoom(this.id);
        }
        //广播
        Broadcast(ToMsg());
        return true;
    }

    //选择房主
    public string SwitchOwner() {
        //选择第一个玩家
        foreach(string id in playerIds.Keys) {
            return id;
        }
        //房间没人
        return "";
    }


    //广播消息
    public void Broadcast(MsgBase msg){
        foreach(string id in playerIds.Keys) {
            Player player = PlayerManager.GetPlayer(id);
            player.Send(msg);
        }
    }

    //生成MsgGetRoomInfo协议
    public MsgBase ToMsg(){
        MsgGetRoomInfo msg = new MsgGetRoomInfo();
        int count = playerIds.Count;
        msg.players = new PlayerInfo[count];
        //players
        int i = 0;
        foreach(string id in playerIds.Keys){
            Player player = PlayerManager.GetPlayer(id);
            PlayerInfo playerInfo = new PlayerInfo();
            //赋值
            playerInfo.id = player.id;
            playerInfo.camp = player.camp;
            playerInfo.win = player.data.win;
            playerInfo.lost = player.data.lost;
            playerInfo.isOwner = 0;
            if(isOwner(player)){
                playerInfo.isOwner = 1;
            }

            msg.players[i] = playerInfo;
            i++;
        }
        return msg;
    }
}


RoomManager.cs

using System;
using System.Collections.Generic;

public class RoomManager
{
    //最大id
    private static int maxId = 1;
    //房间列表
    public static Dictionary<int, Room> rooms = new Dictionary<int, Room>();

    //创建房间
    public static Room AddRoom(){
        maxId++;
        Room room = new Room();
        room.id = maxId;
        rooms.Add(room.id, room);
        return room;
    }

    //删除房间
    public static bool RemoveRoom(int id) {
        rooms.Remove(id);
        return true;
    }

    //获取房间
    public static Room GetRoom(int id) {
        if(rooms.ContainsKey(id)){
            return rooms[id];
        }
        return null;
    }

    //生成MsgGetRoomList协议
    public static MsgBase ToMsg(){
        MsgGetRoomList msg = new MsgGetRoomList();
        int count = rooms.Count;
        msg.rooms = new RoomInfo[count];
        //rooms
        int i = 0;
        foreach(Room room in rooms.Values){
            RoomInfo roomInfo = new RoomInfo();
            //赋值
            roomInfo.id = room.id;
            roomInfo.count = room.playerIds.Count;
            roomInfo.status = (int)room.status;

            msg.rooms[i] = roomInfo;
            i++;
        }
        return msg;
    }
}

RoomMsgHandle.cs

using System;


public partial class MsgHandler {
    
    //查询战绩
    public static void MsgGetAchieve(ClientState c, MsgBase msgBase){
        MsgGetAchieve msg = (MsgGetAchieve)msgBase;
        Player player = c.player;
        if(player == null) return;

        msg.win = player.data.win;
        msg.lost = player.data.lost;

        player.Send(msg);
    }


    //请求房间列表
    public static void MsgGetRoomList(ClientState c, MsgBase msgBase){
        MsgGetRoomList msg = (MsgGetRoomList)msgBase;
        Player player = c.player;
        if(player == null) return;

        player.Send(RoomManager.ToMsg());
    }

    //创建房间
    public static void MsgCreateRoom(ClientState c, MsgBase msgBase){
        MsgCreateRoom msg = (MsgCreateRoom)msgBase;
        Player player = c.player;
        if(player == null) return;
        //已经在房间里
        if(player.roomId >=0 ){
            msg.result = 1;
            player.Send(msg);
            return;
        }
        //创建
        Room room = RoomManager.AddRoom();
        room.AddPlayer(player.id);

        msg.result = 0;
        player.Send(msg);
    }

    //进入房间
    public static void MsgEnterRoom(ClientState c, MsgBase msgBase){
        MsgEnterRoom msg = (MsgEnterRoom)msgBase;
        Player player = c.player;
        if(player == null) return;
        //已经在房间里
        if(player.roomId >=0 ){
            msg.result = 1;
            player.Send(msg);
            return;
        }
        //获取房间
        Room room = RoomManager.GetRoom(msg.id);
        if(room == null){
            msg.result = 1;
            player.Send(msg);
            return;
        }
        //进入
        if(!room.AddPlayer(player.id)){
            msg.result = 1;
            player.Send(msg);
            return;
        }
        //返回协议  
        msg.result = 0;
        player.Send(msg);
    }


    //获取房间信息
    public static void MsgGetRoomInfo(ClientState c, MsgBase msgBase){
        MsgGetRoomInfo msg = (MsgGetRoomInfo)msgBase;
        Player player = c.player;
        if(player == null) return;

        Room room = RoomManager.GetRoom(player.roomId);
        if(room == null){
            player.Send(msg);
            return;
        }

        player.Send(room.ToMsg());
    }

    //离开房间
    public static void MsgLeaveRoom(ClientState c, MsgBase msgBase){
        MsgLeaveRoom msg = (MsgLeaveRoom)msgBase;
        Player player = c.player;
        if(player == null) return;

        Room room = RoomManager.GetRoom(player.roomId);
        if(room == null){
            msg.result = 1;
            player.Send(msg);
            return;
        }

        room.RemovePlayer(player.id);
        //返回协议
        msg.result = 0;
        player.Send(msg);
    }
}

如果是从头到尾实现一遍书上的内容的话,上面的代码还是比较简单的。

3、预测同步算法

在最后一章中作者介绍了服务器的同步算法。原理如下:
1、每个客户端每隔一段时间发送同步“帧”到服务器。
2、服务器更细玩家的位置、旋转信息并转发到各个客户端。
3、客户端收到同步信息之后,使用预测算法计算同步玩家的位置。

预测的方法也很简单。因为坦克是匀速运动的。因此预测位置只需要根据s = v * t就行了。
这里直接看代码就行,比较简单。
SyncMsg.cs

//同步坦克信息
public class MsgSyncTank:MsgBase {
    public MsgSyncTank() {protoName = "MsgSyncTank";}
    //位置、旋转、炮塔旋转
    public float x = 0f;        
    public float y = 0f;
    public float z = 0f;
    public float ex = 0f;       
    public float ey = 0f;
    public float ez = 0f;
    public float turretY = 0f;  
    //服务端补充
    public string id = "";      //哪个坦克
}

//开火
public class MsgFire:MsgBase {
    public MsgFire() {protoName = "MsgFire";}
    //炮弹初始位置、旋转
    public float x = 0f;        
    public float y = 0f;
    public float z = 0f;
    public float ex = 0f;
    public float ey = 0f;
    public float ez = 0f;
    //服务端补充
    public string id = "";      //哪个坦克
}

//击中
public class MsgHit:MsgBase {
    public MsgHit() {protoName = "MsgHit";}
    //击中谁
    public string targetId = "";
    //击中点   
    public float x = 0f;        
    public float y = 0f;
    public float z = 0f;
    //服务端补充
    public string id = "";      //哪个坦克
    public int hp = 0;          //被击中坦克血量
    public int damage = 0;      //受到的伤害
}

SyncTank.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SyncTank : BaseTank {
    //预测信息,哪个时间到达哪个位置
    private Vector3 lastPos;
    private Vector3 lastRot;
    private Vector3 forecastPos;
    private Vector3 forecastRot;
    private float forecastTime;

    //重写Init
    public new void Init(string skinPath){
        base.Init(skinPath);
        //不受物理运动影响
        rigidBody.constraints = RigidbodyConstraints.FreezeAll;
        rigidBody.useGravity = false;
        //初始化预测信息
        lastPos = transform.position;
        lastRot = transform.eulerAngles;
        forecastPos = transform.position;
        forecastRot = transform.eulerAngles;
        forecastTime = Time.time;
    }

    new void Update(){
        base.Update();
        //更新位置
        ForecastUpdate();
    }

    //移动同步
    public void SyncPos(MsgSyncTank msg){
        //预测位置
        Vector3 pos = new Vector3(msg.x, msg.y, msg.z);
        Vector3 rot = new Vector3(msg.ex, msg.ey, msg.ez);
        forecastPos = pos + 2*(pos - lastPos);
        forecastRot = rot + 2*(rot - lastRot);
        //更新
        lastPos = pos;
        lastRot = rot;
        forecastTime = Time.time;
        //炮塔
        Vector3 le = turret.localEulerAngles;
        le.y = msg.turretY;
        turret.localEulerAngles = le;
    }


    //更新位置
    public void ForecastUpdate(){
        //时间
        float t =  (Time.time - forecastTime)/CtrlTank.syncInterval;
        t = Mathf.Clamp(t, 0f, 1f);
        //位置
        Vector3 pos = transform.position;
        pos = Vector3.Lerp(pos, forecastPos, t);
        transform.position = pos;
        //旋转
        Quaternion quat = transform.rotation;
        Quaternion forcastQuat = Quaternion.Euler(forecastRot);
        quat = Quaternion.Lerp(quat, forcastQuat, t) ;
        transform.rotation = quat;
    }

    //开火
    public void SyncFire(MsgFire msg){
        Bullet bullet = Fire();
        //更新坐标
        Vector3 pos = new Vector3(msg.x, msg.y, msg.z);
        Vector3 rot = new Vector3(msg.ex, msg.ey, msg.ez);
        bullet.transform.position = pos;
        bullet.transform.eulerAngles = rot;
    }
}

战斗管理类中加入:

    //收到同步协议
    public static void OnMsgSyncTank(MsgBase msgBase){
        MsgSyncTank msg = (MsgSyncTank)msgBase;
        //不同步自己
        if(msg.id == GameMain.id){
            return;
        }
        //查找坦克
        SyncTank tank = (SyncTank)GetTank(msg.id);
        if(tank == null){
            return;
        }
        //移动同步
        tank.SyncPos(msg);
    }

    //收到开火协议
    public static void OnMsgFire(MsgBase msgBase){
        MsgFire msg = (MsgFire)msgBase;
        //不同步自己
        if(msg.id == GameMain.id){
            return;
        }
        //查找坦克
        SyncTank tank = (SyncTank)GetTank(msg.id);
        if(tank == null){
            return;
        }
        //开火
        tank.SyncFire(msg);
    }

    //收到击中协议
    public static void OnMsgHit(MsgBase msgBase){
        MsgHit msg = (MsgHit)msgBase;
        //查找坦克
        BaseTank tank = GetTank(msg.targetId);
        if(tank == null){
            return;
        }
        //被击中
        tank.Attacked(msg.damage);
    }

服务器主要做的就是转发功能。并没有做校验。相对简单。就不赘述了。

4、结语

原本打算花个十来天把这本书看完的。但是没想到花了差不多一个多月。《Unity3D网络游戏实战》这本书对于接触u3d的初学者还是很友好的,非常简单容易上手,代码也简单。最有用的应该是通用客户端网络框架和通用服务器框架那两章。对于网络通讯写的很好,清晰易懂。以及最后一章的同步方法。在书上学习的代码也能用于工作中,让自己上手更快了点。收获还是很大的。以后也要坚持看书,学习!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343