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
-
鏈接
client.Connect(point); // client.Connect("127.0.0.1", 2333); // 等同於3,4
-
開啟線程接收服務器消息,別忘了引用System.Threading
Thread thread = new Thread(Recive); thread.IsBackground = true; // 后台執行線程 thread.Start(client); // 傳入客戶端的Socket
// Recive函數 static void Recive(object o) { var client = o as Socket; while (true) { byte[] buffer = new byte[1024 * 1024 * 2]; int effective = client.Receive(buffer); //二進制數據存儲在buffer中,數據長度為effective if (effective == 0) { break; } var str = Encoding.UTF8.GetString(buffer, 0, effective); // 將二進制數據轉換為UTF8格式的String Console.WriteLine(str); } }
-
發送自定義數據給服務器
while (true) { string s = Console.ReadLine(); byte[] buffer = Encoding.ASCII.GetBytes(s); // 將數據轉換為ASCII編碼的二進制數組形式 socketClient.Send(buffer); // 發送消息 Console.WriteLine("Send Message"); }
-
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);
Thread thread = new Thread(Recive);
thread.IsBackground = true;
thread.Start(client);
while (true)
{
string s = Console.ReadLine();
byte[] buffer = Encoding.ASCII.GetBytes(s);
client.Send(buffer);
Console.WriteLine("Send Message");
}
}
static void Recive(object o)
{
var client = o as Socket;
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 2];
var effective = client.Receive(buffer);
if (effective == 0)
{
break;
}
var str = Encoding.UTF8.GetString(buffer, 0, effective);
Console.WriteLine(str);
}
}
}
}
2級->Server創建
-
首先創建一個C#命令行工程(別告訴這個不會)
-
創建Socket實例(同client)
-
設置服務器的ip地址
IPAddress ip = IPAddress.Parse("127.0.0.1"); IPEndPoint point = new IPEndPoint(ip, 2333); server.Bind(point);
-
設置服務器的最大監聽數
server.Listen(10);
-
開啟線程接收客戶端連接和數據(※注意是連接)
Thread thread = new Thread(Listen); thread.IsBackground = true; thread.Start(serverSocket);
// Listen函數,等待客戶端連接 static void Listen(object o) { var serverSocket = o as Socket; while (true) { client = serverSocket.Accept(); // 等待客戶端連接,返回客戶端的Socket,之前的10限制就是在這里,最多有10個客戶端可以建立連接 var sendIpoint = client.RemoteEndPoint.ToString(); // 客戶端的ip和端口 Console.WriteLine($"{sendIpoint}Connection"); // 連接成功則開啟一個接收線程接收客戶端發來的消息 Thread thread = new Thread(Recive); thread.IsBackground = true; thread.Start(client); } }
// 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);
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start(server);
Console.Read();
}
static void Listen(object o)
{
var server = o as Socket;
while (true)
{
client = server.Accept();
var clientIpoint = client.RemoteEndPoint.ToString();
Console.WriteLine($"{clientIpoint}Connection");
Thread thread = new Thread(Recive);
thread.IsBackground = true;
thread.Start(client);
}
}
static void Recive(object o)
{
var client = o as Socket;
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 2];
var effective = client.Receive(buffer);
if (effective == 0)
{
break;
}
var str = Encoding.UTF8.GetString(buffer, 0, effective);
Console.WriteLine(str);
}
}
}
}
Boss->ProtoBuf
如果我們要用ProtoBuf用在C#中就得集齊各種神器
- ProtoBuf源碼:其中有C#的示例代碼
- ProtoBuf編譯器:編譯
.proto
文件 - ProtoBuf的C#工具集(你可能會需要下載nuget):提供工程中的dll引用文件
- ProtoBuf官方教程(蜜汁上網)
1級->編寫proto文件
都說ProtoBuf不依賴於任何語言是一個跨語言的神器,然而他的語言格式是scheme(Lisp的方言),並且編譯器就是把.proto
文件翻譯成各個不同語言的編譯器。
proto文件示例(教程中示例的文件)
// [START declaration]
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
// [END declaration]
// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]
// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]
// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]
2級->編譯proto文件
將上面的文件用proto.exe編譯
格式:proto -I=當前目錄 -out_csharp=當前目錄 目錄/文件名.擴展名
例:
文件名為:address.proto
目錄為:D:/Work下
proto -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 才支持)
public static byte[] Serialize<T>(T obj) where T : IMessage
{
return obj.ToByteArray();
}
public static T Deserialize<T>(byte[] data) where T : class, IMessage, new()
{
T obj = new T();
IMessage message = obj.Descriptor.Parser.ParseFrom(data);
return message as T;
}
注釋:
- 可以看到由
.proto
轉換成.cs
的文件的父接口為IMessage - ToByteArray()是protoBuf自帶的類轉二進制的函數(所謂的序列化)
- obj.Descriptor.Parser.ParseFrom是是protoBuf自帶的二進制轉類的函數(所謂的反序列化)
知道這些之后就可以傳輸二進制數據啦,所以我們的客戶端代碼的發送數據部分改為
// 建立數據
Person john = new Person
{
Id = 1234,
Name = "John Doe",
Email = "jdoe@example.com",
Phones = { new PhoneNumber { Number = "555-4321", Type = PhoneType.Home } }
};
var message = Serialize(john); // 得到byte[]的message
while (true)
{
string s = Console.ReadLine();
socketClient.Send(message); // 直接傳輸message
Console.WriteLine("Send Message");
}
服務端的接受部分改一下,這部分注意反序列化素組的長度必須和序列化后的一致,所以這邊新建了一個正確長度的b2數組把數據復制過去
static void Recive(object o)
{
var send = o as Socket;
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 2];
var effective = send.Receive(buffer);
byte[] b2 = new byte[effective];
Array.Copy(buffer, 0, b2, 0, effective); // 把數據拷貝給b2
if (effective == 0)
{
break;
}
var message = Deserialize<Person>(b2); // 解析時候必須為正確的長度
Console.WriteLine("ID = {0}", message.Id);
Console.WriteLine("Name = {0}", message.Name);
Console.WriteLine("Email = {0}", message.Email);
Console.WriteLine("Phone Number = {0}", message.Phones[0].Number);
Console.WriteLine("Phone Type = {0}", message.Phones[0].Type);
}
}
完結撒花~