简述

我们做软件工作的虽然每天都离不开网络,可网络协议细节却不是每个人都会接触和深入了解。我今天就来和大家一起学习下Socket,并写一个简单的聊天程序。

一些基础类

首先我们每天打开浏览器访问网页信息都是使用的HTTP/HTTPS协议,而HTTP是通过的TCP建立的连接。TCP底层又是通过的Socket套接字进行的通信。所以他们之间的抽象关系是:

我们在学习Socket编程的时候可能会需要用到IPEndPoint、Dns、IPAddress等类,再往上TCP相关有TcpListener、TcpClient、NetworkStream,再往上就是大家熟悉的HttpClient等。

IPEndPoint、Dns、IPAddress基础作用如下:

//根据DNS获取域名绑定的IP
foreach (var address in Dns.GetHostEntry("www.baidu.com").AddressList)
{
Console.WriteLine($"百度IP:{address}");
} //字符串转IP地址
IPAddress ipAddress = IPAddress.Parse("192.168.1.101"); //通过IP和端口构造IPEndPoint对象,用于远程连接
//通过IP可以确定一台电脑,通过端口可以确定电脑上的一个程序
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 80);

利用Socket编写聊天程序

我们首先从Socket开始讲起。

要实现Socket通信,先得有个服务端的监听,再有个客户端的连接,然后客户端和服务端就可以通信了。如下:



服务端代码如下:

//1 创建Socket对象
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2 绑定ip和端口
IPAddress ip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ip, 50001);
socketServer.Bind(ipEndPoint); //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
socketServer.Listen(10); //4、【阻塞】,等待客户端连接
Socket newSocket = socketServer.Accept(); //5、【阻塞】,等待读取客户端发送过来的数据
byte[] data = new byte[1024 * 1024];
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None); //6、读取数据
var msg = Encoding.UTF8.GetString(data, 0, readLeng);

客户端代码如下:

//1 创建Socket对象
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2 连接到服务端
IPAddress ip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ip, 50001);
socketClient.Connect(ipEndPoint); //3 发送消息到服务端
socketClient.Send(Encoding.UTF8.GetBytes("你好,农码一生"));

到此,我们就可以开启服务端的服务,并接受客户端的发来的消息了。

不过,这里有个很大的问题,服务端只能建立一个客户端连接和接受一次客户端发来的消息。如果想要连接更多的客户端和接受无数次的消息,服务端代码两处阻塞的地方需要另外开一个线程然后包到循环里面去。

修改后的服务端代码如下:

void .... ()
{
//1 创建Socket对象
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2 绑定ip和端口
IPAddress ip = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPoint = new IPEndPoint(ip, 50001);
socketServer.Bind(ipEndPoint); //3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
socketServer.Listen(10); //开启新的线程,循环等待新的客户端连接
Task.Run(() => { Accept(socketServer); });
} void Accept(Socket socket)
{
while (true)
{
//4、【阻塞】,等待客户端连接
Socket newSocket = socket.Accept(); //开启新的线程,循环等待接收新的数据
Task.Run(() => { Receive(newSocket); });
}
} void Receive(Socket newSocket)
{
while (true)
{
//5、【阻塞】,等待读取客户端发送过来的数据
byte[] data = new byte[1024 * 1024];
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None); //6、读取数据
var msg = Encoding.UTF8.GetString(data, 0, readLeng);
}
}

到此,服务端就可以接受多个客户端的连接和接收多次客户端发来的消息了。不过我们可能还需客服端能接收服务端发来的消息,这个你可以自己尝试下。文末会提供完整的代码参考。

注意:
用Socket来编写聊天软件是长连接,有状态的。不确定服务端什么时候会发送消息过来,我们也可以连续发送消息而不响应。所以,对于消息的接收就需要开一个新的线程循环接收。
而对于HTTP来说,虽然它是也是通过TCP建立的通信,但在数据请求完毕后会马上关闭连接,这个过程很短。每次访问都会建立一个新的连接,是无状态的。
对于浏览器来说是一问一答的形式,先发送请求(Send),然后接收响应(Receive)所以就可以做到不开启新的线程,直接有序的同步的完成。这个在下一篇《模拟浏览器的请求和服务端的响应》会具体分析。

利用TCP编写聊天程序

虽然上面我们利用Socket类实现了一个简单的聊天程序,但是微软觉得Socket太复杂。为了让你们早点干完活,早点下班,于是又在Socket的基础上有封装了两个相关的类TcpListener、TcpClient。

利用TcpListener、TcpClient来实现同上面相同的功能。

服务端代码:

void ...()
{
//1 开启监听
TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9999);
tcpListener.Start(10); //最多同时接收10个用户连接 //开启一个线程,循环等待客户端的连接
Task.Run(() => { Accept(); });
} //等待客户端的连接
void Accept()
{
while (true)
{
//2 【阻塞】等待客户端的连接
TcpClient tcpClient = tcpListener.AcceptTcpClient();
NetworkStream networkStream = tcpClient.GetStream(); //开启一个新的线程 等待新的消息
Task.Run(() => { Read(networkStream, tcpClient); });
}
} //接收消息
void Read(NetworkStream networkStream)
{
while (true)
{
byte[] buffer = new byte[1024 * 1024];
//3 【阻塞】等待接收新的消息
var readLen = networkStream.Read(buffer, 0, buffer.Length);
var msg = Encoding.UTF8.GetString(buffer, 0, readLen);
}
}

客户端代码:

//1 连接服务端
TcpClient tcpClient = new TcpClient();
tcpClient.Connect(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text)); //2 发送消息到服务端
byte[] buffer = Encoding.UTF8.GetBytes("你好,农码一生");
networkStream.Write(buffer, 0, buffer.Length);

用TcpListener、TcpClient的实现也算ok了,TcpListener代码写的服务端和Socket通信也是完成没问题的,因为他们最后都是Socket。

对此你有觉得比Socket简单和容易理解?其实我更习惯Socket。

注意:

// 1、断开连接使用
socketClient?.Shutdown(SocketShutdown.Both);
socketClient?.Close(); // 2、服务端需要判断
int readLeng = newSocket.Receive(data, 0, data.Length, SocketFlags.None);
if (readLeng == 0)//客户端断开连接
{
//停止会话(禁用Socket上的发送和接收,该方法允许Socket对象一直等待,直到将内部缓冲区的数据发送完为止)
newSocket.Shutdown(SocketShutdown.Both);
//关闭连接
newSocket.Close();
//跳出循环
return;
} 3、具体请参考文末提供的完整demo

效果图

结束

你也可以写聊天程序 - C# Socket学习1的更多相关文章

  1. 你也可以写个聊天程序 - C# Socket学习1

    原文:你也可以写个聊天程序 - C# Socket学习1 简述 我们做软件工作的虽然每天都离不开网络,可网络协议细节却不是每个人都会接触和深入了解.我今天就来和大家一起学习下Socket,并写一个简单 ...

  2. 你也可以写个服务器 - C# Socket学习2

    前言 这里说的服务器是Web服务器,是类似IIS.Tomcat之类的,用来响应浏览器请求的服务. Socket模拟浏览器的Url Get请求 首先浏览器的请求是HTTP协议.我们上一篇说过,HTTP是 ...

  3. 聊天程序——基于Socket、Thread (二)

    聊天程序简述 1.目的:主要是为了阐述Socket,以及应用多线程,本文侧重Socket相关网路编程的阐述.如果您对多线程不了解,大家可以看下我的上一篇博文浅解多线程 . 2.功能:此聊天程序功能实现 ...

  4. java Socket多线程聊天程序

    参考JAVA 通过 Socket 实现 TCP 编程 参考java Socket多线程聊天程序(适合初学者) 以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包 ...

  5. Socket网络编程--聊天程序(9)

    这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...

  6. Socket聊天程序——Common

    写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...

  7. Socket聊天程序——客户端

    写在前面: 上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细 ...

  8. Socket聊天程序——服务端

    写在前面: 昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图: ...

  9. Socket聊天程序——初始设计

    写在前面: 可能是临近期末了,各种课程设计接踵而来,最近在csdn上看到2个一样问答(问题A,问题B),那就是编写一个基于socket的聊天程序,正好最近刚用socket做了一些事,出于兴趣,自己抽了 ...

随机推荐

  1. CF992B Nastya Studies Informatics 数学(因子) 暴力求解 第三道

    Nastya Studies Informatics time limit per test 1 second memory limit per test 256 megabytes input st ...

  2. poj 2115 求线性同余方程 C Looooops(好理解欧几里德扩展定理怎么应用)

    C Looooops Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 29061   Accepted: 8360 Descr ...

  3. Java集合中List、Set以及Map

    概述: List , Set, Map都是接口:List , Set继承至Collection接口,Map为独立接口 Set下有HashSet,LinkedHashSet,TreeSet List下有 ...

  4. Erlang中的RSA签名

    RSA签名校验 -spec check_rsa_sign(DataBin, Sign, RSAPublicKeyBin, DigestType) -> boolean when DataBin ...

  5. java第一次测验

    package kaoshi; import java.util.Scanner; public class ScoreManagement { static int t=0; static int ...

  6. 007 Python程序语法元素分析

    目录 一.概述 二.程序的格式框架 2.1 代码高亮 2.2 缩进 2.3 注释 2.4 缩进.注释 三.命名与保留字 3.1 变量 3.2 命名 3.3 保留字 3.4 变量.命名.保留字 四.数据 ...

  7. Spring Boot 配置文件和命令行配置

    Spring Boot 属于约定大于配置,就是说 Spring Boot 推荐不做配置,很多都是默认配置,但如果想要配置系统,使得软件符合业务定义,Spring Boot 可以通过多种方式进行配置. ...

  8. Django ORM 知识点总结

    Query是如何工作的 Django QuerySet是懒执行的,只有访问到对应数据的时候,才会去访问数据库.另外如果你再次读取查询到的数据,将不会触发数据库的访问,而是直接从缓存获取. 比如 # 这 ...

  9. Volatile与synchronize的区别

    仅靠Volatile不能保证线程的安全性(原子性) 1.Volatile轻量级的,只能修饰变量.synchronize重量级的,还可以修饰方法 2.Volatile只保证数据的可见性,不能用来同步,因 ...

  10. ios 把数组对象转成json字符串存起来

    1第一步是我们获取数据源 一般我们都是从接口请求数据 NSArray *subColumnsArray = nil; NSDictionary *dict = [NSJSONSerialization ...