12篇学通C#网络编程——第四篇 TCP应用编程

 年底了,人浮躁多了,沉不下去心研究技术了,不过昨天终于抢到了回家的票,很开心。

    言归正卷,在WCF出来之后,可能我们玩这些原始的TCP越来越少了,我们知道WCF对TCP进行了再一次的包装,第一反应给我们的或许是

同构系统用TCP,异构系统用HTTP,那么问题来了,异构系统到底可不可以用TCP呢?至少WCF是玩不了的,因为其他语言没有针对.net的“服务

引用”,也没有什么ChannelFactory给你去玩,如果你是一定要追求性能的话,原始的TCP会助你一臂之力的。

   我们知道最最原始的是玩Socket,由于Socket比较复杂,但是最灵活,C#里面提供了两个简化的包装类:TcpListener和TcpClient。

一:TcpListener

   这个是作为服务器端程序而存在的,我们来看看如何使用这服务端。

1: 开启监听地址

1 TcpListener listener = new TcpListener(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }), 2222);
2
3 listener.Start();

 

2:好了,已经开启了,服务端程序开始监听该端口的客户端请求了,那么如何获取该请求呢?简单,listener的AcceptTcpClient属性搞定。

1 var myclient = listener.AcceptTcpClient();

 3:我们知道TCP传的是字节流,通过myclient.GetStream()就可以获取一个NetworkStream,利用这个Stream就可以进行收发信息了。

 <1> 收操作:

1 BinaryReader sr = new BinaryReader(client.GetStream());
2
3  //客户端数据
4  var data = sr.ReadString();

<2>发操作:也就是所谓的返回值

1 //处理完了之后要返回数据给客户端
2 BinaryWriter sw = new BinaryWriter(client.GetStream());
3
4 sw.Write(string.Format("当前时间:{0},服务端已经处理完毕!", DateTime.Now));

 

  这里要注意的地方就是AcceptTcpClient是阻塞线程的,直到收到客户端请求才算建立了一个TCP连接,在服务端处理的过程中,后续的客户端的请求将

会处理等待直到前一个请求处理完,说了这么多,就是每一个请求我们都建议开一个线程专门为其服务,类似这样。

//接受客户端的连接请求
var myclient = listener.AcceptTcpClient();

//用工作线程执行用户的请求
Task.Factory.StartNew((obj) =>
{
    var client = obj as TcpClient;

    client.Close();

}, myclient);

好了,服务器端大概就是这个样子,再有的就是一些相关属性配置了,像wcf那样什么opentime,sendtime啥的。

 

二:TcpClient

     客户端也很简单,只要我们Connect一下端口,然后通过NetworkStream再Send一些数据就OK了。

TcpClient client = new TcpClient();

client.Connect(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }), 2222);

BinaryWriter bw = new BinaryWriter(client.GetStream());

bw.Write(string.Format("你好,我来请求你! {0},当前线程:{1}", j, Thread.CurrentThread.ManagedThreadId));

三:模拟

     最后我们模拟下,客户端开启100个线程,每个线程请求100次,服务器端对每个线程都用工作线程去处理,是不是找到了netTcpBinding的

感觉,最后一个大家都懂的道理就是线程多了不是好事情。

服务端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System.Net;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpListener listener = new TcpListener(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }), 2222);

            listener.Start();

            //用专门的线程来接受请求
            Task.Factory.StartNew(() =>
            {
                //不间断的接受客户端请求
                while (true)
                {
                    //接受客户端的连接请求
                    var myclient = listener.AcceptTcpClient();

                    //用工作线程执行用户的请求
                    Task.Factory.StartNew((obj) =>
                    {
                        var client = obj as TcpClient;

                        BinaryReader sr = new BinaryReader(client.GetStream());

                        //客户端数据
                        var data = sr.ReadString();

                        //客户端ip
                        var ip = (IPEndPoint)client.Client.RemoteEndPoint;

                        Console.WriteLine("当前时间:{0},接受到了来自IP:{1}:{2},的请求,发来的数据为:{3}", DateTime.Now,
                                                                                                ip.Address, ip.Port, data);

                        Thread.Sleep(1000 * 5);

                        //处理完了之后要返回数据给客户端
                        BinaryWriter sw = new BinaryWriter(client.GetStream());

                        sw.Write(string.Format("当前时间:{0},服务端已经处理完毕!", DateTime.Now));

                        client.Close();

                    }, myclient);
                }
            });

            Console.WriteLine("服务端已经启动...");

            Console.Read();
        }
    }
}

客户端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    for (int j = 0; j < 100; j++)
                    {
                        TcpClient client = new TcpClient();

                        client.Connect(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }), 2222);

                        BinaryWriter bw = new BinaryWriter(client.GetStream());

                        bw.Write(string.Format("你好,我来请求你! {0},当前线程:{1}", j, Thread.CurrentThread.ManagedThreadId));

                        BinaryReader sr = new BinaryReader(client.GetStream());

                        var s = sr.ReadString();

                        Console.WriteLine("接受到数据:{0}", s);
                    }
                });
            }

            Console.Read();
        }
    }
}

时间: 2024-09-18 13:02:49

12篇学通C#网络编程——第四篇 TCP应用编程的相关文章

12篇学通C#网络编程——第三篇 HTTP应用编程(下)

    第三篇来的好晚啊,上一篇说了如何向服务器推送信息,这一篇我们看看如何"快好准"的从服务器下拉信息.     网络上有很多大资源文件,比如供人下载的zip包,电影(你懂的),那么我们如何快速的进行下载,大家第一反应肯定就是多线程下载, 那么这些东西是如何做的呢?首先我们可以从"QQ的中转站里面拉一个rar下来". 然后用fiddler监视一下,我们会发现一个有趣的现象: 第一:7.62*1024*1024≈7990914  千真万确是此文件 第二:我明明是一个

12篇学通C#网络编程——第一篇 基础之进程线程

       在C#的网络编程中,进程和线程是必备的基础知识,同时也是一个重点,所以我们要好好的掌握一下. 一:概念           首先我们要知道什么是"进程",什么是"线程",好,查一下baike.   进程:是一个具有一定独立功能的程序关于某个数据集合的一次活动.它是操作系统动态执行的基本单元,            在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元.   线程:是"进程"中某个单一顺序的控制流.    关

教大家8天学通MongoDB——第一天 基础入门篇_MongoDB

关于mongodb的好处,优点之类的这里就不说了,唯一要讲的一点就是mongodb中有三元素:数据库,集合,文档,其中"集合" 就是对应关系数据库中的"表","文档"对应"行". 一: 下载       上MongoDB官网 ,我们发现有32bit和64bit,这个就要看你系统了,不过这里有两点注意:           ①:根据业界规则,偶数为"稳定版"(如:1.6.X,1.8.X),奇数为"开

Windows界面编程第七篇 文件拖拽(文件拖放)

本文配套程序下载地址为:http://download.csdn.net/detail/morewindows/5128654 转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8634451 欢迎关注微博:http://weibo.com/MoreWindows   前面已经有六篇文章介绍了Windows界面编程,目录如下: 1. <Windows界面编程第一篇位图背景与位图画刷> 2. <Windows界面编程

《21天学通C++(第7版)》——12.2 单目运算符

12.2 单目运算符 21天学通C++(第7版) 顾名思义,单目运算符只对一个操作数进行操作.实现为全局函数或静态成员函数的单目运算符的典型定义如下: 作为类成员的单目运算符的定义如下: 12.2.1 单目运算符的类型 可重载(或重新定义)的单目运算符如表12.1所示. 表12.1 单目运算符 12.2.2 单目递增与单目递减运算符 要在类声明中编写单目前缀递增运算符(++),可采用如下语法: 而后缀递增运算符(++)的返回值不同,且有一个输入参数(但并非总是使用它): 前缀和后缀递减运算符的声

《21天学通C++(第7版)》——12.4 函数运算符operator()

12.4 函数运算符operator() 21天学通C++(第7版)operator()让对象像函数,被称为函数运算符.函数运算符用于标准模板库(STL)中,通常是STL算法中.其用途包括决策.根据使用的操作数数量,这样的函数对象通常称为单目谓词或双目谓词.下面分析一个非简单的函数对象,如程序清单12.11所示,以便理解使用如此有意思的名称的原因! 程序清单12.11 一个使用operator()实现的函数对象 输出: 分析:第8-11行实现了operator(),然后在main()函数的第18

《21天学通C++(第7版)》——第12章 运算符类型与运算符重载

第12章 运算符类型与运算符重载 21天学通C++(第7版) 关键字class让您不仅能够封装数据和方法,还能封装运算符,以简化对对象执行的操作.通过使用这些运算符,可以像第5章处理整数那样,对对象执行赋值或加法运算.与函数一样,运算符也可以重载. 在本章中,您将学习: 使用关键字operator: 单目运算符与双目运算符: 转换运算符: C++11新增的移动复制运算符: 不能重新定义的运算符. 本文仅用于学习和交流目的,不代表异步社区观点.非商业转载请注明作译者.出处,并保留本文的原始链接.

《21天学通Java(第6版)》—— 第 2 章 Java编程基础

第 2 章 Java编程基础 21天学通Java(第6版)Java程序是由类和对象组成的,而对象和类又是由方法和变量组成的.方法是由语句和表达式组成的,表达式又由运算符组成. 至此,您可能担心Java就像俄罗斯套娃.每个洋娃娃里边都有一个更小的洋娃娃,而后者同前者一样错综复杂. 本章将消除大洋娃娃的困扰,揭示Java编程的最小元素.本章暂时撇开类.对象和方法,介绍单行Java代码中的基本元素. 这包括以下内容: Java语句和表达式:变量和基本数据类型:常量:注释:字面量:算术运算符:比较运算符

《21天学通C语言(第6版•修订版)》一1.3 编程前的准备工作

1.3 编程前的准备工作 21天学通C语言(第6版•修订版)解决问题时,应采取一些特定的步骤.首先必须定义问题.如果不知道问题是什么,将无法找到解决方案.知道问题是什么之后便可以设计解决它的方案.有了方案后,您通常能够实现它.方案实现后,必须对结果进行测试,以确定问题是否得到解决.这种逻辑也适用于包含编程在内的许多其他领域. 创建C语言程序(或其他语言的计算机程序)时,应遵循下面类似的步骤: 1.确定程序的目标: 2.确定要使用什么样的方法来编写程序: 3.创建程序,以解决问题: 4.运行程序,