C# Socket和protoBuf新手村教程

Stella981
• 阅读 1043

Boss->Socket

此教程纯属Socket初级应用篇,因为网上全是理论篇(实践才是王道)

1级->Client创建

  1. 首先创建一个C#命令行工程(别告诉这个不会)

  2. 创建Socket实例,别忘了引用System.Net和System.Net.Sockets

     Socket client = new Socket(SocketType.Stream, ProtocolType.Tcp); // TCP链接
    
  3. 设置要链接的服务器ip地址,IPAddress是C#提供的ip封装类

     IPAddress ip = IPAddress.Parse("127.0.0.1"); // 本地地址127.0.0.1(别说你不知道)
    
  4. 设置要链接的服务器ip和端口,IPEndPoint是C#提供的ip和端口的封装类

     IPEndPoint point = new IPEndPoint(ip, 2333); // 端口为2333,ip为上一段代码的ip
    
  5. 链接

     client.Connect(point);
     // client.Connect("127.0.0.1", 2333); // 等同于3,4
    
  6. 开启线程接收服务器消息,别忘了引用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);
         }
     }
    
  7. 发送自定义数据给服务器

     while (true)
     {
         string s = Console.ReadLine();
         byte[] buffer = Encoding.ASCII.GetBytes(s); // 将数据转换为ASCII编码的二进制数组形式
         socketClient.Send(buffer); // 发送消息
         Console.WriteLine("Send Message");
     }
    
  8. 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创建

  1. 首先创建一个C#命令行工程(别告诉这个不会)

  2. 创建Socket实例(同client)

  3. 设置服务器的ip地址

     IPAddress ip = IPAddress.Parse("127.0.0.1");
     IPEndPoint point = new IPEndPoint(ip, 2333);
     server.Bind(point);
    
  4. 设置服务器的最大监听数

     server.Listen(10);
    
  5. 开启线程接收客户端连接和数据(※注意是连接)

     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函数,同客户端
    
  6. 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#中就得集齐各种神器

  1. ProtoBuf源码:其中有C#的示例代码
  2. ProtoBuf编译器:编译.proto文件
  3. ProtoBuf的C#工具集(你可能会需要下载nuget):提供工程中的dll引用文件
  4. 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;
}

注释:

  1. 可以看到由.proto转换成.cs的文件的父接口为IMessage
  2. ToByteArray()是protoBuf自带的类转二进制的函数(所谓的序列化)
  3. 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);
    }
}

完结撒花~

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这