一、前言

  • 最近老师要求做课设,实现一个 “炸飞机” 游戏,我是负责UI界面实现和Socket通信实现的,在这里想总结一下我实现Socket的具体过程,对其中的产生的问题和实现的方法进行进行分析。由于我是第一次具体实现Socket通信,所以走了不少弯路,请教了许多人,其中尤其是我的舍友,对我帮助很大。

二、实现思路

我采用的模式是C/S模式(客户端-服务器模式),并且是TCP模式
  • 首先是单例化对象,对客户端和服务器都进行了单例化,确保炸飞机时只有一个客户端和一个服务器(因为这个游戏是1V1嘛);
  • 然后对客户端的和服务器端 send()receive() 函数进行编写,要注意的一点是:这里不能盲目照搬网络上的代码,其代码使用场景简单,通常是发送一次接收一次(或者是发送一次一直接收),总之对本项目而言是不能适用的;
  • 再然后是封装类,封装好之后在其他命名空间中调用接口

三、具体代码

客户类代码

1. 主体代码部分
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading; namespace TestBoom
{
class Client //这是封装好的客户端类
{
public String receivestr = null;
private static Client client;
private Socket ClientSocket;
private Client(string ip1, int port1)
{
ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Init(ip1, port1);
}
public static Client clientsocket(string ip1,int port1)
{
if (client == null)
{
client = new Client(ip1,port1);
}
return client;
}
private void Init(string ip1, int port1)
{
IPEndPoint iPEnd = new IPEndPoint(IPAddress.Parse(ip1), port1);
ClientSocket.Connect(iPEnd);
Thread reciveThread = new Thread(Recive);
reciveThread.IsBackground = true;
reciveThread.Start();
}
public void Recive()
{
while (true)
{
byte[] Btye = new byte[1024];
ClientSocket.Receive(Btye);
receivestr = Encoding.UTF8.GetString(Btye,0,3);
if (receivestr[0] == '0')
{
Console.WriteLine($"接受对方了轰炸位置{receivestr}");
}
else if(receivestr[0]=='1')
{
Console.WriteLine($"接受轰炸位置结果{receivestr}");
}
}
}
public void Send(int i,int x,int y)
{
string str =Convert.ToString(i)+Convert.ToString(x) + Convert.ToString(y);
byte[] Btye = Encoding.UTF8.GetBytes(str);
ClientSocket.Send(Btye);
if (str[0] == '0')
{
Console.WriteLine($"已发送轰炸位置 {str}");
}
else if (str[0] == '1')
{
Console.WriteLine($"已发送对方轰炸位置结果{str}");
}
}
}
}
2. 具体分析:

1. 首先这个游戏我们必须要知道的一点是我们想要实现两台电脑之间的交互,就必须使用ip和端口进行连接,而想要进行连接就必须使用一个实例化的对象(在这里我没有体现出来,因为实例化对象在另一个from中,在按钮事务响应的函数中进行实例化),而且在这个游戏中,实例化对象必须是单例模型,原因之前提到过,那么实例化对象就必须包含单例化的过程;

  public String receivestr = null;  //receivestr是接受函数中接收到对方的传输过来的信息,后面用到
private static Client client; //单例化对象所需要的对象
private Socket ClientSocket; //Socket类的一个实例化对象
private Client(string ip1, int port1) //Client()客户端构造函数
{
ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Init(ip1, port1);
}
public static Client clientsocket(string ip1,int port1) //单例化实现函数
{
if (client == null) //如果实例化对象不存在,则创建一个
{
client = new Client(ip1,port1);
}
return client; //如果存在,则直接返回存在的那个对象,这样便实现了单例化
}
private void Init(string ip1, int port1) //初始化,用于进行客户端和服务器端的连接
{
IPEndPoint iPEnd = new IPEndPoint(IPAddress.Parse(ip1), port1);
ClientSocket.Connect(iPEnd);
Thread reciveThread = new Thread(Recive);
reciveThread.IsBackground = true;
reciveThread.Start();
}

2. 其次在连接妥当之后,必须进行信息传输,如果而现在假定客户端时先手,则要进行 send() 函数的调用,在函数中你可以发送任意的数据,但必须时btye数组(因为在物理层传输数据是发送的是比特,发送到对方物理层会进行解析还原,但是这些东西C#的Socket类已经封装好了,我们调用接口即可),需要注意的是在使用 send() 的时候必须调用(这个之后再详细说);

  public void Send(int i,int x,int y) //这里面的参数 i,x,y 的含义分别是 模式0/1,x坐标, y坐标,可以根据需求改变
{
string str =Convert.ToString(i)+Convert.ToString(x) + Convert.ToString(y); //将数字转化为string类型字符串
byte[] Btye = Encoding.UTF8.GetBytes(str); //将刚刚转化好的string类型字符串转化为byte类型数组
ClientSocket.Send(Btye); //调用Socket类中的Send()函数发送数据
if (str[0] == '0') //判断模式0/1,在己方控制台显示己方发送过去的内容,方便自己查看
{
Console.WriteLine($"已发送轰炸位置 {str}");
}
else if (str[0] == '1')
{
Console.WriteLine($"已发送对方轰炸位置结果{str}");
}
}

3. 最后阐述一下 receive() 函数,再对方(服务器端)接收到你发送的信息之后,一定会返回一个信息(因为下棋是交互的嘛),这时候你便需要一个接收函数 receive() ,这个函数是用来接受对方发送的信息的,但是需要注意的是这个函数会随着你的进程一直运行,在from中是不需要调用的。

    public void Recive()
{
while (true) //因为是一直在另一个进程中运行,所以给一个死循环
{
byte[] Btye = new byte[1024]; //接收也是byte数组的
ClientSocket.Receive(Btye);
receivestr = Encoding.UTF8.GetString(Btye,0,3); //转化为string类型
if (receivestr[0] == '0') //判断模式0/1,在己方控制台显示对方发送过来的内容,方便查看对方信息
{
Console.WriteLine($"接受对方了轰炸位置{receivestr}");
}
else if(receivestr[0]=='1')
{
Console.WriteLine($"接受轰炸位置结果{receivestr}");
}
}
}

服务器类代码

1.主体代码部分
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading; namespace TestBoom
{
class Server
{
public String receivestr = null;
private Socket SocketWatch;
private Socket SocketSend;
private static Server server = null;
private Server(string ip1, int port1)
{
SocketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Init(ip1,port1);
}
public static Server serversocket(string ip1, int port1)
{
if (server == null)
{
server = new Server(ip1, port1);
}
return server;
} private void Init(string ip1, int port1)
{
SocketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iPEnd = new IPEndPoint(IPAddress.Parse(ip1), port1);
SocketWatch.Bind(iPEnd);
SocketWatch.Listen(1);
System.Windows.Forms.MessageBox.Show("开始监听...");
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start();
}
void Listen()
{
while (SocketSend==null)
{
SocketSend = SocketWatch.Accept();
}
System.Windows.Forms.MessageBox.Show("连接成功..." + SocketSend.RemoteEndPoint.ToString());
Thread reciveThread = new Thread(Recive);
reciveThread.IsBackground = true;
reciveThread.Start();
} public void Recive()
{
while (true)
{
byte[] buffer = new byte[1024];
SocketSend.Receive(buffer);
receivestr = Encoding.UTF8.GetString(buffer, 0, 3);
if (receivestr[0] == '0')
{
Console.WriteLine($"接受对方了轰炸位置{receivestr}");
}
else if (receivestr[0] == '1')
{
Console.WriteLine($"接受我方轰炸位置结果{receivestr}");
}
}
} public void Send(int i,int x,int y)
{
string str = Convert.ToString(i) + Convert.ToString(x) + Convert.ToString(y);
byte[] buffer = Encoding.UTF8.GetBytes(str);
SocketSend.Send(buffer);
if (str[0] == '0')
{
Console.WriteLine($"已发送轰炸位置 {str}");
}
else if (str[0] == '1')
{
Console.WriteLine($"已发送对方轰炸位置结果{str}");
}
}
}
}
2.具体分析:
  1. 对于 send()receive() 函数就不过多赘述,主要分析一下 listen() 函数,listen() 函数其实是一个监听函数,只有监听成功之后才能够连接,才可以实例化一个发送Socket对象和一个接收Thread对象,而服务器端也是单例模式的,与客户端结构基本相同。

四、对服务器类和客户类的具体使用

1.代码部分(部分代码,不能直接使用,注释部分即内容)
private void button2_Click(object sender, EventArgs e)
{
if (Plane_Sum < 3)
{
label4.Text = "请先放置坤坤";
}
else
{
if(ipok && portok)
{
label4.Text = "坤坤已放置好";
label3.Text = "你的回合";
client = Client.clientsocket(ip,port); //实例化客户端
serorcli = true;
}
else
{
label4.Text = "没有输入ip或者端口";
}
}
} private void button1_Click(object sender, EventArgs e)
{
if (Plane_Sum < 3)
{
label4.Text = "请先放置坤坤";
}
else
{
if(ipok && portok)
{
label4.Text = "坤坤已放置好";
label3.Text = "对手回合";
server = Server.serversocket(ip, port); //实例化服务器端
serorcli = false;
}
else
{
label4.Text = "没有输入ip或者端口";
}
}
} private void button3_Click(object sender, EventArgs e)
{
if (Plane_Sum < 3)
{
label4.Text = "请先放置坤坤";
}
else
{
if (serorcli == false&&ipok&&portok)
{
while (server.receivestr == null) { } //判断有没有接收,直到有接收才可以跳出循环
if (server.receivestr[0] == '0')//接收直接使用,由于接收是处于一直接收的状态
{
PutKunKun2(server.receivestr[1] - '0', server.receivestr[2] - '0');
server.Send(1, Board1[server.receivestr[1] - '0', server.receivestr[2] - '0'], 0); //发送调用
server.receivestr = null; //赋值为null,为下一次接收做准备
}
}
}
}
private void textBox1_ipChanged(object sender, EventArgs e)
{
ip = textBox1.Text; //输入ip
ipok = true;
}
private void textBox2_portChanged(object sender, EventArgs e)
{
int.TryParse(textBox2.Text,out port); //输入port
portok = true;
}

五、问题分析与总结

1.问题分析:
  1. 在本项目中单例化对象中,对单例化思想并不清楚, 求助于舍友,在他的帮助下明白了,对象没有就创建,有的话就直接返回已经创建的对象。
  2. 在引用实例化对象的函数时,搞不清楚使用的位置,经过多次试错,多次调整,才明白使用逻辑
  3. 开始使用 receive() 这一函数时,以为其必须进行调用才行,后来才知道其在对象线程中一直存在,根本不需要调用。
2.总结:

在这个项目中,我对计算机网络中学习的内容有了更深的理解,对Socket通信有了更深的认识,对TCP和UDP也有了不同于书本单薄的理解。

C#实践炸飞机socket通信的更多相关文章

  1. AgileEAS.NET SOA 中间件平台.Net Socket通信框架-介绍

    一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...

  2. 我看不下去鸟。。。。Java和C#的socket通信真的简单吗?

    这几天在博客园上看到好几个写Java和C#的socket通信的帖子.但是都为指出其中关键点. C# socket通信组件有很多,在vs 使用nuget搜索socket组件有很多类似的.本人使用的是自己 ...

  3. php简单实现socket通信

    socket通信的原理在这里就不说了,它的用途还是比较广泛的,我们可以使用socket来做一个API接口出来,也可以使用socket来实现两个程序之间的通信,我们来研究一下在php里面如何实现sock ...

  4. Socket通信类

    package com.imooc; import java.io.BufferedReader; import java.io.IOException; import java.io.InputSt ...

  5. socket通信

    socket通信 一:socket基于Tcp连接,数据传输有保证 二:socket连接的建立过程: 1:服务器监听 2:客户端发出请求 3:建立连接 4:通信 三:一个简单的例子:服务器端每隔一段时间 ...

  6. Android之Socket通信、List加载更多、Spinner下拉列表

    Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户端向服务器发送请求后,服务 ...

  7. .NET开源高性能Socket通信中间件Helios介绍及演示

    一:Helios是什么 Helios是一套高性能的Socket通信中间件,使用C#编写.Helios的开发受到Netty的启发,使用非阻塞的事件驱动模型架构来实现高并发高吞吐量.Helios为我们大大 ...

  8. iOS开发之Socket通信实战--Request请求数据包编码模块

    实际上在iOS很多应用开发中,大部分用的网络通信都是http/https协议,除非有特殊的需求会用到Socket网络协议进行网络数 据传输,这时候在iOS客户端就需要很好的第三方CocoaAsyncS ...

  9. AgileEAS.NET SOA 中间件平台.Net Socket通信框架-简单例子-实现简单的服务端客户端消息应答

    一.AgileEAS.NET SOA中间件Socket/Tcp框架介绍 在文章AgileEAS.NET SOA 中间件平台Socket/Tcp通信框架介绍一文之中我们对AgileEAS.NET SOA ...

随机推荐

  1. 【Java】学习路径54-使用UDP协议开发发送、接收端

    UDP协议,简单的说就是,发信息. 不管对方有没有收到. 发送端: import java.net.*; public class UDP_Send { public static void main ...

  2. React报错之Property 'value' does not exist on type EventTarget

    正文从这开始~ 总览 当event参数的类型不正确时,会产生"Property 'value' does not exist on type EventTarget"错误.为了解决 ...

  3. Android平台摄像头/屏幕/外部数据采集及RTMP推送接口设计描述

    好多开发者提到,为什么大牛直播SDK的Android平台RTMP推送接口怎么这么多?不像一些开源或者商业RTMP推送一样,就几个接口,简单明了. 不解释,以Android平台RTMP推送模块常用接口, ...

  4. cmake 入门笔记

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/p/16667896.html 1. cmake 是什么? 这些 ...

  5. Python实践项目——LSB隐写术

    此为北京理工大学某专业某学期某课程的某次作业 一.项目背景 1.隐写术 隐写术是一门关于信息隐藏的技巧与科学,所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容. 2.L ...

  6. 2020年12月-第01阶段-前端基础-HTML CSS 项目阶段(二)

    品优购项目(二) 1. 品优购首页布局 命名集合: 名称 说明 快捷导航栏 shortcut 头部 header 标志 logo 购物车 shopcar 搜索 search 热点词 hotwrods ...

  7. G&GH05 删除文件和.gitignore

    注意事项与声明 平台: Windows 10 作者: JamesNULLiu 邮箱: jamesnulliu@outlook.com 博客: https://www.cnblogs.com/james ...

  8. CPU密集型和IO密集型(判断最大核心线程的最大线程数)

    CPU密集型和IO密集型(判断最大核心线程的最大线程数) CPU密集型 1.CPU密集型获取电脑CPU的最大核数,几核,最大线程数就是几Runtime.getRuntime().availablePr ...

  9. flutter系列之:Material中的3D组件Card

    目录 简介 Card详解 Card的使用 总结 简介 除了通用的组件之外,flutter还提供了两种风格的特殊组件,其中在Material风格中,有一个Card组件,可以很方便的绘制出卡片风格的界面, ...

  10. Prometheus高可用部署

    Prometheus的本地存储给Prometheus带来了简单高效的使用体验,可以让Promthues在单节点的情况下满足大部分用户的监控需求.但是本地存储也同时限制了Prometheus的可扩展性, ...