【2019-10-16】 07-Socket服务端的编写--研发部内部分享

一.在我们讲解服务端编写之前,同样来看看该图。我们编写步骤,依此图为准。

07-01.png

根据我们之前的说明Socket是一个网络编程接口,那么我们就得根据此特性去使用它,Socket的作者也是依据网络通信的协议进行的接口编写,所以使用思路也是做的一个继承吧。

二. 开始上干货 我们来编写吧

思路大纲:

1.1 首先我们需要创建一个负责监听的Socket,该Socket任务是开启一个Tcp/Udp服务器,设置监听队列长度,启动监听客户端服务。----这段描述,对比上图,按照顺序走过来socket()-->bind()-->listen()-->accept()-->等待客户机连接请求。
1.2 开启接收消息服务,该服务所使用的Socket对象是监听客户端成功后得到的用于和客户端通信的Socket。----这段描述,对比上图,按照顺序走过来 recv()
1.3 发送消息服务开启。这里消息发送的Socket也是用的监听客户端成功后得到的用于和客户端通信的Socket。----这段描述,对比上图,按照顺序走过来 send()

  到此大家体会一下。我们开始编写。

2.1创建一个Socket负责监听的Socket对象---Socket()

07-02.png

2.2给该Socket对象绑定IP和端口,同时Tcp服务也开启了,并且设置该TCP服务器最大可被 客户端连接数量为10--bind()-->listen();

07-03.png

2.3 开启一个线程来执行监听客户端操作。--accept()

07-04.png

如下是监听的一个完整步骤。

07-05.png

2.4 接收客户端消息服务--recv()

07-06.png
07-07.png
07-08.png

2.5 服务端发送消息服务--send()

关键代码逻辑:消息编码,调用Send方法;

07-09.png

三.上述代码的界面:

这个界面做的逻辑只是为了不让我们服务器界面排版不那么杂乱而存在,这个界面就仅仅是传递地址信息到服务页面(服务端IP填写作为服务端的电脑ip,本机测试可以直接写127.0.0.1,端口号自定义不与其他端口冲突ip)

07-10.png

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

namespace Server

{

/// <summary>

/// MainWindow.xaml 的交互逻辑

/// </summary>

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

private void Window_MouseLeftButtonDown(object sender, RoutedEventArgs e)

{

this.DragMove();

}

/// <summary>

/// 启动服务器

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void Button_Click(object sender, RoutedEventArgs e)

{

//获取用户输入

string port = TxtboxPort.Text.Trim(),//测试账号 123456

ip = TxtboxIp.Text.Trim();//密码 pwd

//校验

if (string.IsNullOrEmpty(port) || string.IsNullOrEmpty(ip))

if (string.IsNullOrEmpty(port))

{

MessageBox.Show("Ip不能为空");

return;

}

else

{

MessageBox.Show("端口号不能为空");

return;

}

//传递Ip和端口到服务端界面

this.Hide();

ServerWindow server = new ServerWindow(port,ip);

server.ShowDialog();

this.Close();

}

}

}

07-11.png

完整代码:(不带太多解释)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

namespace Server

{

/// <summary>

/// ServerWindow.xaml 的交互逻辑

/// </summary>

public partial class ServerWindow : Window

{

public ServerWindow()

{

InitializeComponent();

}

public ServerWindow(string port,string ip)

{

InitializeComponent();

MessageShow.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;

ListenSocket(port, ip);

}

region

public Socket listenSocket = null;//负责监听的Socket在整个服务中是唯一的存在,把它放在类成员,使它可用范围最大化。

public Thread threadListen = null;//专门用于监听客户端程序的线程

region 1.创建一个负责监听的Socket。

/// <summary>

/// 开启一个监听的方法

/// </summary>

/// <param name="port"></param>

/// <param name="ip"></param>

public void ListenSocket(string port, string ip)

{

/*

//说明点1:创建负责监听的套接字(Socket),注意其中传递的参数说明:1.IPV4(是目前最广泛使用的网络通信协议第四版) 2.流式套接字 3.TCP

我们在创建前根据我们学习到的原理知识,我们创建的该Socket是Ipv4协议的(目前在开始ipv6建设,但是我们目前接触的web项目都是ipv4的,如果有幸接触到ipv6项目,那么一定在大厂了);

我们使用的是流式套接字,双工通信。

我们最后一个参数填写就是我们通信协议TCP了。

*/

listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

/*

说明点二:下面两部都是为了给该Socket进行IP端口绑定所做的操作,因为

执行Bind(),方法需要传递一个参数,类型为EndPoint类型,这是对Ip和端口进行说明的类型,而该类型的数据的创建,我们就new IPEndPoint,去传递进.

EndPoint类型是抽象类,而IPEndPoint是继承于它,所以我们就用它。

我们在用它是发现,需要传递一个IPAddress类型的参数,这个类是对IP地址的封装。所以我们下面这逻辑其实是根据Bind推导出来的。

*/

IPAddress address = IPAddress.Parse(ip);

//根据IPAddress以及端口号创建IPE对象

IPEndPoint endpoint = new IPEndPoint(address, int.Parse(port));

try

{

//给该Socket绑定一个Ip和端口,进而开启TCP服务

listenSocket.Bind(endpoint);

this.MessageShow.AppendText("开启服务成功!" + Environment.NewLine);

MessageBox.Show("开启服务成功!", "打开服务");

}

catch (Exception ex)

{

MessageBox.Show("开启服务失败" + ex.Message, "打开服务");

return;

}

//谁知监听客户端数量为10

listenSocket.Listen(10);

/*

因为我们监听方法是在客户端未开始连接服务端时,会一直停在Accept中,代码不会往下面执行,除非有客户端连接上来,为了保证主线程能够独立做自己该做的逻辑,我们就开启一个线程来专门做监听这个操作。

*/

//开启一个线程来创建一个不断监听的服务

threadListen = new Thread(ListenConnection);

threadListen.IsBackground = true;

threadListen.Start();

}

endregion 1.创建一个负责监听的Socket。

region 2.调用Accept方法不断去监听是否有客户端连接上来

//创建一个键值对集合,用于存放客户端 键存放该客户端的地址信息,值存放于该客户端通信的Socket

public Dictionary<string, Socket> DicSocket = new Dictionary<string, Socket>();

/// <summary>

/// 2.用于监听连接的方法

/// </summary>

public void ListenConnection()

{

while (true)

{

//一旦监听到一个客户端的连接,将会创建一个与该客户端连接的套接字

Socket sockClient = listenSocket.Accept();//如果没有客户端连接将会卡在这里

string client = sockClient.RemoteEndPoint.ToString();//获取客户端的Ip和端口

//

DicSocket.Add(client, sockClient);

this.ListBoxTxt.Dispatcher.Invoke(new Action(()=> { this.ListBoxTxt.Items.Add(client); }));//列表中添加

this.MessageShow.Dispatcher.Invoke(new Action(()=> { this.MessageShow.AppendText(client + "上线了!" + Environment.NewLine); }));

//开启一个新线程用于客户端于服务端进行通信

Thread thr = new Thread(ReceiveMsg);

thr.IsBackground = true;

thr.Start(sockClient);

}

}

endregion 2.调用Accept方法不断去监听是否有客户端连接上来

region 3.接收客户端消息

/// <summary>

/// 用于接收客户端传递过来的消息

/// </summary>

/// <param name="sockClient"></param>

public void ReceiveMsg(object sockClient)

{

//获取和客户端通信的Socket

Socket messageSocket = sockClient as Socket;

while (true)

{

//定义一个2M缓冲区 1024byte登录1kb,1024kb等于,1mb

//用于存放客户端传递过来的消息,因为网络通信进行传递的是二进制,所以我们需要byte。

byte[] arrMsgRec = new byte[1024 * 1024 * 2];

//定义消息长度,-1是未接受消息(我自己定义的),原因下面会讲

//0表示客户端停止了与服务端连接

//大于0表示客户端发送了消息

int length = -1;

try

{

//解释原因 : 如果客户端未发消息,代码不会往下面执行,会停在这里,所以只要往下面执行我们length就要么为0,要么为大于0,我们只是需要一个表示没有值的数据而已,所以给一个-1,只是一个含义。

length = messageSocket.Receive(arrMsgRec);

}

catch (Exception) //服务端下线客户端

{

//获取客户端地址信息

string str = messageSocket.RemoteEndPoint.ToString();

//给我们的界面进行消息展示

this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(str + "下线了!" + Environment.NewLine); }));

//从列表中移除URL

this.ListBoxTxt.Dispatcher.Invoke(new Action(() => { ListBoxTxt.Items.Remove(str); }));

DicSocket.Remove(str);

break;

}

if (length == 0)

{

string str = messageSocket.RemoteEndPoint.ToString();

this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(str + "下线了!" + Environment.NewLine); }));

//从列表中移除URL

this.ListBoxTxt.Dispatcher.Invoke(new Action(() => { ListBoxTxt.Items.Remove(str); }));

DicSocket.Remove(str);

break;

}

else

{

//因为我们客户端发送过来的是二进制,所以我们需要,进行编码准换

//我们把我们想要传递给服务端的数据,需要先编译为二进制,在这个步骤我们就需要先确定我们编码的格式,因为中文的二进制如果被Ascll格式编码那是不行的,因为该编码格式不认中文,我们就选择识别度最广的编码UTF8编码,来编译为二进制数据,我们服务端解码也同样如此。

//小插曲:乱码的由来,编码格式和解码格式不一致就导致乱码。

string strMsg = Encoding.UTF8.GetString(arrMsgRec,0, length);

string Msg = "[接收] " + messageSocket.RemoteEndPoint.ToString() + " " + strMsg;

this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(Msg + Environment.NewLine); }));

}

}

}

endregion

region 单独发送消息

/// <summary>

/// 发送消息

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void SendOne_Click_1(object sender, RoutedEventArgs e)

{

string StrMsg = this.SendTxt.Text.Trim();

byte[] arrMsg = Encoding.Default.GetBytes(StrMsg);

byte[] arrSend = new byte[arrMsg.Length + 1];

arrSend[0] = 0;

Buffer.BlockCopy(arrMsg, 0, arrSend, 1, arrMsg.Length);

if (this.ListBoxTxt.SelectedItems.Count == 0)

{

MessageBox.Show("请选择你要发送的对象!", "发送提示");

return;

}

else

{

foreach (string item in this.ListBoxTxt.SelectedItems)

{

DicSocket[item].Send(arrSend);

string Msg = "[发送] " + item + " " + StrMsg;

this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(Msg + Environment.NewLine); }));

}

}

}

endregion

region 群发消息

/// <summary>

/// 群发消息

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void SendAll_Click(object sender, RoutedEventArgs e)

{

string StrMsg = this.SendTxt.Text.Trim();

byte[] arrMsg = Encoding.UTF8.GetBytes(StrMsg);

byte[] arrSend = new byte[arrMsg.Length + 1];

arrSend[0] = 0;

Buffer.BlockCopy(arrMsg, 0, arrSend, 1, arrMsg.Length);

foreach (string item in this.DicSocket.Keys)

{

DicSocket[item].Send(arrSend);

string Msg = "[发送] " + item + " " + StrMsg;

this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(Msg + Environment.NewLine); }));

}

this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText("[群发] 群发完毕!" + Environment.NewLine); }));

}

endregion

endregion

}

}

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

推荐阅读更多精彩内容