一、前言

  • 最近老师要求做课设,实现一个 “炸飞机” 游戏,我是负责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】学习路径64-补充-编写一个会抛异常的方法

    有一些方法,在调用的时候有可能会出错,所以我们使用这些方法的时候会使用try catch. 比如InputStream里面的read()方法等等,那么这些方法是怎么实现抛异常的效果的呢? 能抛异常的方 ...

  2. 开发个RTMP播放器居然这么难?RTMP播放器对标和考察指标

    好多开发者提到,RTMP播放器,不知道有哪些对标和考察指标,以下大概聊聊我们的一点经验,感兴趣的,可以关注 github: 1. 低延迟:大多数RTMP的播放都面向直播场景,如果延迟过大,严重影响体验 ...

  3. Trigger Before 与 After 区别

    用户在使用trigger时,经常会面临before or after的选择问题.二者有什么区别?从字面理解,before trigger 是在触发操作完成之前完成,而after 是在触发操作完成之后完 ...

  4. 从零开始搭建gitea代码管理平台

    Gitea,一款极易搭建的Git自助服务.如其名,Git with a cup of tea.跨平台的开源服务,支持Linux.Windows.macOS和ARM平台.配置要求低,甚至可以运行在树莓派 ...

  5. 操作系统学习笔记10 | I/O、显示器与键盘

    从这一部分开始介绍操作系统的设备驱动,操作系统通过文件系统的抽象驱动设备让用户能够使用显示器.键盘等交互工具.并讲解printf和scanf是如何实现敲下键盘将字符显示到屏幕上的. 参考资料: 课程: ...

  6. 聊聊两个Go即将过时的GC优化策略

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 这篇文章本来是要讲 Go Memory Ballast 以及 Go GC Tuner 来 ...

  7. 使用C#编写一个.NET分析器(一)

    译者注 这是在Datadog公司任职的Kevin Gosse大佬使用C#编写.NET分析器的系列文章之一,在国内只有很少很少的人了解和研究.NET分析器,它常被用于APM(应用性能诊断).IDE.诊断 ...

  8. Windows 2012 R2 计划任务发送邮件

     这两天把域控制器升级到了2012 R2,忽然发现原本用的系统自动发邮件提示用户账户锁定的计划任务配置起来有点麻烦了.原因是微软把自动发送邮件和提示消息的功能从计划任务中去除了. 首先用wevtu ...

  9. stm32fxx_hal_i2c.c解读之HAL_I2C_Mem_Write

    HAL_I2C_Mem_Write()函数位于stm32fxx_hal_i2c.c文件的2432行,源代码对该函数的解释如下图 HAL_StatusTypeDef HAL_I2C_Mem_Write( ...

  10. Activiti 7 源码学习

    1.  启动分析 源码是 7.1.0.M6 首先从 ProcessEngineAutoConfiguration 开始 ProcessEngineAutoConfiguration 是activiti ...