一.TcpClient与TcpServe。
首先我们需要知道服务器的IP地址,在服务器端建立监听,当监听到客户端的连接请求后,连接到客户端。
而客户端则需要连接到指定的IP服务器地址,建立网络流,则可以实现通信。
接下来给出一个服务器端与客户端的实例:
服务器端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace fuwuqi { class Program { static void Main(string[] args) { try { IPAddress Ip = IPAddress.Parse("127.0.0.1"); //IPAddress提供网际协议地址的类,Parse将IP地址字符串转换为IPAddress的实例 TcpListener TcpList = new TcpListener(Ip, 8888); //TcpListener网络侦听类,从TCP网络客户端侦听连接,TcpListener (参数1,参数2),参数1表示本地IP地址,参数2表示用来侦听传入的端口连接 TcpList.Start(); //启动对挂起连接数的传入链接请求的侦听 Console.WriteLine("Server start!"); Console.WriteLine("Ip address:" +TcpList.LocalEndpoint); //LocalEndpoint获取服务端(即本地)地址与端口等信息 Console.WriteLine("Wait"); Socket Soc = TcpList.AcceptSocket();//相当于启动客户端后,Socket被挂起 //AcceptSocket表示接受挂起的连接请求,Socket为套接字接口的类 Console.WriteLine("Received Connection:" + Soc.RemoteEndPoint); //RemoteEndPoint获取客户端地址与端口等信息 byte[] b = new byte[100]; int k = Soc.Receive(b); //Soc.Receive(b)从socket接收数据,将数据存入接收缓冲区列表中,k的值为该数据的长度 Console.WriteLine("Received data from client:"); for (int i = 0; i < k; i++) Console.Write(Convert.ToChar(b[i])); //Convert.ToChar(b[i])将数组b转换基本数据类型为char的类型并输出 ASCIIEncoding AS = new ASCIIEncoding(); //ASCIIEncoding表示Unicode字符的ASCII字符编码类 Soc.Send(AS.GetBytes("Received data!")); //Soc.Send向客户端发送数据,AS.GetBytes()获得括号中字符串的bytes值 Soc.Close(); //关闭连接并释放所有关联的资源 TcpList.Stop(); //关闭侦听 Console.ReadLine(); //等待输入,起到暂停的作用 } catch (Exception e) { Console.WriteLine("Error!" + e.StackTrace); //获取当前异常发生时调用堆栈上的帧的字符串 } } } }
此时服务器端应用的是Socket类发送数据流,Socket类用于客户端和服务器都可,其发送机制是以一种包的形式发送,在做项目时,发现Socket接收到的数据包与发送到的数据包长度不一定吻合,有阻塞,所以使用Socket数据包发送时,接收和发送的数据组要指定长度,太长也会丢数。
客户端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Net.Sockets; namespace kehuduan { class Program { static void Main(string[] args) { try { TcpClient TcpClient = new TcpClient(); //TcpClient为TCP网络服务提供客户端连接的类 TcpClient.Connect("127.0.0.1", 8888); //Connect方法使用指定的IP地址和端口号将客户端连接到远程TCP主机 Console.WriteLine("Connect Complete"); Console.WriteLine("Input string:"); String str = Console.ReadLine(); //定义字符串str变量,保存输入的字符 Stream stm = TcpClient.GetStream(); //定义数据流,用于发送和接收数据 ASCIIEncoding AS = new ASCIIEncoding(); byte[] b = AS.GetBytes(str); //将字符串转换为byte类型 stm.Write(b, 0, b.Length); //Write(参数1,参数2,参数3)表示向服务端发送字符串,参数1指将此数组复制到当前流,参数2指从零开始的字节偏移量,参数3指要写入当前流的字节数(即字符串长度) Console.WriteLine("Send Complete"); byte[] bb = new byte[100]; int k = stm.Read(bb, 0, 100); //stm.Read在当前流中读入服务端发来的响应信息,其参数与Write方法参数一致,k值为读入字符串的长度 for (int i = 0; i < k; i++) Console.Write(Convert.ToChar(bb[i])); TcpClient.Close(); //释放TcpClient实例,并不关闭基础连接 Console.ReadLine(); } catch (Exception e) { Console.WriteLine("Error!" + e.StackTrace); } } } }
上面展示了客户端与服务器端的简单通信。作为例子程序学习,接下来对自己做的项目中连接摄像头服务器做一个记录。
首先,把摄像头作为服务器端,定义客户端:
TcpClient client = new TcpClient("IP",8080);
获取网络流:
NetworkStream networkstream = client.GetStream();//获取网络流
此时可以建立与服务器端的网络流连接,如果需要从网络流中读取数据或者写入数据,使用了BinaryReader/BinaryWriter或者StreamReader/StreamWriter,实例化后,就可实现对流的读写
BinaryReaderbr = new BinaryReader(networkstream);
BinaryWriter bw = new BinaryWriter(networkstream);
我对这两个的理解是StreamReder读取函数ReadToEnd,ReadLine等,读取出来是字符串类型。而BinaryReader可以按字节读取。根据需求选取。
(笔记StreamReader的方法: sr.BaseStream.Seek(0, SeekOrigin.Begin);//从流中当前开始的位置(SeekOrigin.begin)偏移0位读取)
如果需要对服务器发送数据直接写入流,使用Write等函数就可以了。
这里对自己找的资料也做一个总结。
MemoryStream类:
Memmory类相当于一个缓存流,首先需要对这个流开辟一个空间,然后将数据放入流中对其进行操作。该流的优势在于可以将数据先放入流中,然后使用指针对流中的任一个字节进行操作。
学习例子:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //属性测试 MemoryStream ms = new MemoryStream(); Console.WriteLine(ms.CanRead); //True 内存流可读 Console.WriteLine(ms.CanSeek); //True 内存流支持查找,指针移来移去的查找 Console.WriteLine(ms.CanTimeout); //False 内存流不支持超时 Console.WriteLine(ms.CanWrite); //True 内存流可写 Console.WriteLine(ms.Capacity); //0 分配给该流的字节数 byte[] bytes = Encoding.UTF8.GetBytes("abcdedcba");//字符转为ASCII ms.Write(bytes, 0, bytes.Length); //已将一段文本写入内存 int l = bytes.Length; Console.WriteLine(ms.Capacity); //256 再次读取为文本流分配的字节数已经变成了256,看来内存流是根据需要的多少来分配的 Console.WriteLine(ms.Length); //9 这个是流长度,通常与英文的字符数一样,真正占用的字节数。 Console.WriteLine(ms.Position); //9 流当前的位置,该属性可读可设置 //Console.WriteLine(ms.ReadTimeout); 由于流不支持超时,此属性如果读取或者设置的话会报错 //Console.WriteLine(ms.WriteTimeout); 由于流不支持超时,此属性如果读取或者设置的话会报错 //方法测试 byte[] byte1 = ms.GetBuffer(); //返回无符号字节数组 差点被忽悠了,无符号字节数组 其实就是byte(0~255),有符号字节sbyte(-128~127) string str1 = Encoding.UTF8.GetString(byte1); Console.WriteLine(str1); //输出 abcdedcba long p = ms.Position;//指针当前位置 ms.Position = 0; ms.Seek(0, SeekOrigin.Current); //设置当前流正在读取的位置 为开始位置即从0开始 //从内存中读取一个字节 int i = ms.ReadByte(); Console.WriteLine(i); //输出99 byte[] bytes3 = ms.ToArray(); foreach (byte b in bytes3) { Console.Write(b + "-");//用于对比 输出 97-98-99-100-101-100-99-98-97- 可以看到 0,1,2第二位刚好是99 } MemoryStream ms2 = new MemoryStream(); byte[] bytes6 = Encoding.UTF8.GetBytes("abcde"); ms2.Write(bytes6, 0, bytes6.Length); Console.WriteLine(ms2.Position); //输出5 写完之后流的位置就到了最后,因此想用read读取必须加下面这一行代码。 //ms2.Seek(0, SeekOrigin.Begin); //想要用Read方法读取完整的流,必须设置当前位置,Read是从Position的位置开始读。 ms2.Position = 0; //Read是从当前位置开始读,这行代码和上面一行意义一样。 byte[] byteArray = new byte[5] { 0, 0,0, 0, 0 }; //数组附初值全部是0 ms2.Read(byteArray, 2, 1); //读取一个字节,byteArray的第一个元素中,(注意从0开始)读取数据流里第二个位子开始读取一个 Console.WriteLine(Encoding.UTF8.GetString(byteArray)); //nnann ms2.Read(byteArray, 2, 2); Console.WriteLine(Encoding.UTF8.GetString(byteArray)); //nnabn //当超出接收数组总长度的时候,后面的元素会被移开 //设置当前流的长度 Console.WriteLine(ms.Length); //输出9 当前流的长度是9 ms.SetLength(20); Console.WriteLine(ms.Length); //输出20 foreach (byte b in ms.ToArray()) //将流的内容也就是内存中的内容转换字节数组 { Console.Write(b + "-"); //输出 97-98-99-100-101-100-99-98-97-0-0-0-0-0-0-0-0-0 由于设置了长度,因此空的自动补0 } Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray())); //输出 abcdedcba 虽然长度变长了,但是没影响读取数据 MemoryStream ms1 = new MemoryStream(); byte[] bytes4 = ms1.ToArray(); Console.WriteLine("此内存流并没有写入数据(Write)" + Encoding.UTF8.GetString(bytes4));//输出 此内存流并没有写入数据(Write) 因为内存为空 //下面来一个指定位置的写入 MemoryStream ms3 = new MemoryStream(); byte[] bytesArr = Encoding.ASCII.GetBytes("abcdefg"); ms3.Write(bytesArr, 0, bytesArr.Length); ms3.Position = 2; ms3.WriteByte(97); //97代表的是a 这段代码的意思是,将原先第二个的c替换为a string str = Encoding.ASCII.GetString(ms3.ToArray()); Console.WriteLine(str); //输出 abacdefg byte[] byteArr1 = Encoding.ASCII.GetBytes("kk"); ms3.Position = 4; ms3.Write(byteArr1, 0, byteArr1.Length); Console.WriteLine(Encoding.UTF8.GetString(ms3.ToArray())); //abadkkg //从第4位替换掉了两个字节为KK Console.ReadKey(); } } }