Boss->Socket
此教程纯属Socket初级应用篇,因为网上全是理论篇(实践才是王道)
1级->Client创建
-
首先创建一个C#命令行工程(别告诉这个不会)
-
创建Socket实例,别忘了引用System.Net和System.Net.Sockets
Socket client = new Socket(SocketType.Stream, ProtocolType.Tcp); // TCP链接 -
设置要链接的服务器ip地址,IPAddress是C#提供的ip封装类
IPAddress ip = IPAddress.Parse("127.0.0.1"); // 本地地址127.0.0.1(别说你不知道) -
设置要链接的服务器ip和端口,IPEndPoint是C#提供的ip和端口的封装类
IPEndPoint point = new IPEndPoint(ip, 2333); // 端口为2333,ip为上一段代码的ip -
链接
1 client.Connect(point); 2 // client.Connect("127.0.0.1", 2333); // 等同于3,4 -
开启线程接收服务器消息,别忘了引用System.Threading
1 Thread thread = new Thread(Recive); 2 thread.IsBackground = true; // 后台执行线程 3 thread.Start(client); // 传入客户端的Socket 4 5 6 // Recive函数 7 static void Recive(object o) 8 { 9 var client = o as Socket; 10 while (true) 11 { 12 byte[] buffer = new byte[1024 * 1024 * 2]; 13 int effective = client.Receive(buffer); //二进制数据存储在buffer中,数据长度为effective 14 if (effective == 0) 15 { 16 break; 17 } 18 var str = Encoding.UTF8.GetString(buffer, 0, effective); // 将二进制数据转换为UTF8格式的String 19 Console.WriteLine(str); 20 } 21 } -
发送自定义数据给服务器
1 while (true) 2 { 3 string s = Console.ReadLine(); 4 byte[] buffer = Encoding.ASCII.GetBytes(s); // 将数据转换为ASCII编码的二进制数组形式 5 socketClient.Send(buffer); // 发送消息 6 Console.WriteLine("Send Message"); 7 } -
client完整代码
using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading;
namespace SocketTest { class Program { static void Main(string[] args) { Socket client = new Socket(SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse("127.0.0.1"); IPEndPoint point = new IPEndPoint(ip, 2333); client.Connect("127.0.0.1", 2333);
1 Thread thread = new Thread(Recive); 2 thread.IsBackground = true; 3 thread.Start(client); 4 5 while (true) 6 { 7 string s = Console.ReadLine(); 8 byte[] buffer = Encoding.ASCII.GetBytes(s); 9 client.Send(buffer); 10 Console.WriteLine("Send Message"); 11 } 12 } 13 14 static void Recive(object o) 15 { 16 var client = o as Socket; 17 while (true) 18 { 19 byte[] buffer = new byte[1024 * 1024 * 2]; 20 var effective = client.Receive(buffer); 21 if (effective == 0) 22 { 23 break; 24 } 25 var str = Encoding.UTF8.GetString(buffer, 0, effective); 26 Console.WriteLine(str); 27 } 28 } 29}}
2级->Server创建
-
首先创建一个C#命令行工程(别告诉这个不会)
-
创建Socket实例(同client)
-
设置服务器的ip地址
1 IPAddress ip = IPAddress.Parse("127.0.0.1"); 2 IPEndPoint point = new IPEndPoint(ip, 2333); 3 server.Bind(point); -
设置服务器的最大监听数
server.Listen(10); -
开启线程接收客户端连接和数据(※注意是连接)
1 Thread thread = new Thread(Listen); 2 thread.IsBackground = true; 3 thread.Start(serverSocket); 4 5 6 // Listen函数,等待客户端连接 7 static void Listen(object o) 8 { 9 var serverSocket = o as Socket; 10 while (true) 11 { 12 client = serverSocket.Accept(); // 等待客户端连接,返回客户端的Socket,之前的10限制就是在这里,最多有10个客户端可以建立连接 13 var sendIpoint = client.RemoteEndPoint.ToString(); // 客户端的ip和端口 14 Console.WriteLine($"{sendIpoint}Connection"); 15 // 连接成功则开启一个接收线程接收客户端发来的消息 16 Thread thread = new Thread(Recive); 17 thread.IsBackground = true; 18 thread.Start(client); 19 } 20 } 21 22 23 // Recive函数,同客户端 -
server完整代码
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading;
namespace SocketServer { class Program { static void Main(string[] args) { Socket server = new Socket(SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse("127.0.0.1"); IPEndPoint point = new IPEndPoint(ip, 2333); server.Bind(point); server.Listen(10);
1 Thread thread = new Thread(Listen); 2 thread.IsBackground = true; 3 thread.Start(server); 4 5 Console.Read(); 6 } 7 8 static void Listen(object o) 9 { 10 var server = o as Socket; 11 while (true) 12 { 13 client = server.Accept(); 14 var clientIpoint = client.RemoteEndPoint.ToString(); 15 Console.WriteLine($"{clientIpoint}Connection"); 16 Thread thread = new Thread(Recive); 17 thread.IsBackground = true; 18 thread.Start(client); 19 } 20 } 21 22 static void Recive(object o) 23 { 24 var client = o as Socket; 25 while (true) 26 { 27 byte[] buffer = new byte[1024 * 1024 * 2]; 28 var effective = client.Receive(buffer); 29 if (effective == 0) 30 { 31 break; 32 } 33 var str = Encoding.UTF8.GetString(buffer, 0, effective); 34 Console.WriteLine(str); 35 } 36 } 37}}
Boss->ProtoBuf
如果我们要用ProtoBuf用在C#中就得集齐各种神器
- ProtoBuf源码:其中有C#的示例代码
- ProtoBuf编译器:编译
.proto文件 - ProtoBuf的C#工具集(你可能会需要下载nuget):提供工程中的dll引用文件
- ProtoBuf官方教程(蜜汁上网)
1级->编写proto文件
都说ProtoBuf不依赖于任何语言是一个跨语言的神器,然而他的语言格式是scheme(Lisp的方言),并且编译器就是把.proto文件翻译成各个不同语言的编译器。
proto文件示例(教程中示例的文件)
1// [START declaration] 2syntax = "proto3"; 3package tutorial; 4 5import "google/protobuf/timestamp.proto"; 6// [END declaration] 7 8// [START java_declaration] 9option java_package = "com.example.tutorial"; 10option java_outer_classname = "AddressBookProtos"; 11// [END java_declaration] 12 13// [START csharp_declaration] 14option csharp_namespace = "Google.Protobuf.Examples.AddressBook"; 15// [END csharp_declaration] 16 17// [START messages] 18message Person { 19 string name = 1; 20 int32 id = 2; // Unique ID number for this person. 21 string email = 3; 22 23 enum PhoneType { 24 MOBILE = 0; 25 HOME = 1; 26 WORK = 2; 27 } 28 29 message PhoneNumber { 30 string number = 1; 31 PhoneType type = 2; 32 } 33 34 repeated PhoneNumber phones = 4; 35 36 google.protobuf.Timestamp last_updated = 5; 37} 38 39// Our address book file is just one of these. 40message AddressBook { 41 repeated Person people = 1; 42} 43// [END messages]
2级->编译proto文件
将上面的文件用proto.exe编译
1格式:proto -I=当前目录 -out_csharp=当前目录 目录/文件名.扩展名 2例: 3文件名为:address.proto 4目录为:D:/Work下 5proto -I=D:/Work -out_csharp=D:/Work D:/Wrok/address.proto
编译完后会生成一个.cs文件,文件很长我就不展示了
3级->nuget下载dll
nuget install Google.Protobuf -Version 3.8.0 // 版本随意
下载下来找到dll
Google.Protobuf.3.8.0/bin/net45/Google.Protobuf.dll
引用到客户端和服务器中,然后把生成的.cs文件也复制到项目中
4级->编写序列化和反序列化代码
在客户端和服务器的Program(上面的代码)中加入序列化和反序列化的函数,需要引用Google.Protobuf、Google.Protobuf.Examples.AddressBook和static Google.Protobuf.Examples.AddressBook.Person.Types(C#6 才支持)
1public static byte[] Serialize<T>(T obj) where T : IMessage 2{ 3 return obj.ToByteArray(); 4} 5 6public static T Deserialize<T>(byte[] data) where T : class, IMessage, new() 7{ 8 T obj = new T(); 9 IMessage message = obj.Descriptor.Parser.ParseFrom(data); 10 return message as T; 11}
注释:
- 可以看到由
.proto转换成.cs的文件的父接口为IMessage - ToByteArray()是protoBuf自带的类转二进制的函数(所谓的序列化)
- obj.Descriptor.Parser.ParseFrom是是protoBuf自带的二进制转类的函数(所谓的反序列化)
知道这些之后就可以传输二进制数据啦,所以我们的客户端代码的发送数据部分改为
1// 建立数据 2Person john = new Person 3{ 4 Id = 1234, 5 Name = "John Doe", 6 Email = "jdoe@example.com", 7 Phones = { new PhoneNumber { Number = "555-4321", Type = PhoneType.Home } } 8}; 9var message = Serialize(john); // 得到byte[]的message 10 11while (true) 12{ 13 string s = Console.ReadLine(); 14 socketClient.Send(message); // 直接传输message 15 Console.WriteLine("Send Message"); 16}
服务端的接受部分改一下,这部分注意反序列化素组的长度必须和序列化后的一致,所以这边新建了一个正确长度的b2数组把数据复制过去
1static void Recive(object o) 2{ 3 var send = o as Socket; 4 while (true) 5 { 6 byte[] buffer = new byte[1024 * 1024 * 2]; 7 var effective = send.Receive(buffer); 8 byte[] b2 = new byte[effective]; 9 Array.Copy(buffer, 0, b2, 0, effective); // 把数据拷贝给b2 10 if (effective == 0) 11 { 12 break; 13 } 14 var message = Deserialize<Person>(b2); // 解析时候必须为正确的长度 15 Console.WriteLine("ID = {0}", message.Id); 16 Console.WriteLine("Name = {0}", message.Name); 17 Console.WriteLine("Email = {0}", message.Email); 18 Console.WriteLine("Phone Number = {0}", message.Phones[0].Number); 19 Console.WriteLine("Phone Type = {0}", message.Phones[0].Type); 20 } 21}
完结撒花~