还记得刚刚开始接触编程开发时,傻傻的将网站开发和网络编程混为一谈,常常因分不清楚而引为笑柄。后来勉强分清楚,又因为各种各样的协议端口之类的名词而倍感神秘,所以为了揭开网络编程的神秘面纱,本文尝试以一个简单的小例子,简述在网络编程开发中涉及到的相关知识点,仅供学习分享使用,如有不足之处,还请指正。

概述

在TCP/IP协议族中,传输层主要包括TCP和UDP两种通信协议,它们以不同的方式实现两台主机中的不同应用程序之间的数据传输,即数据的端到端传输。由于它们的实现方式不同,因此各有一套属于自己的端口号,且相互独立。采用五元组(协议,信源机IP地址,信源应用进程端口,信宿机IP地址,信宿应用进程端口)来描述两个应用进程之间的通信关联,这也是进行网络程序设计最基本的概念。传输控制协议(Transmission Control Protocol,TCP)提供一种面向连接的、可靠的数据传输服务,保证了端到端数据传输的可靠性。

涉及知识点

本例中涉及知识点如下所示:

  1. TcpClient : TcpClient类为TCP网络服务提供客户端连接,它构建于Socket类之上,以提供较高级别的TCP服务,提供了通过网络连接、发送和接收数据的简单方法。
  2. TcpListener:构建于Socket之上,提供了更高抽象级别的TCP服务,使得程序员能更方便地编写服务器端应用程序。通常情况下,服务器端应用程序在启动时将首先绑定本地网络接口的IP地址和端口号,然后进入侦听客户请求的状态,以便于客户端应用程序提出显式请求。
  3. NetworkStream:提供网络访问的基础数据流。一旦侦听到有客户端应用程序请求连接侦听端口,服务器端应用将接受请求,并建立一个负责与客户端应用程序通信的信道。

网络聊天示意图

如下图所示:看似两个在不同网络上的人聊天,实际上都是通过服务端进行接收转发的。

TCP网络通信示意图

如下图所示:首先是服务端进行监听,当有客户端进行连接时,则建立通讯通道进行通信。

示例截图

服务端截图,如下所示:

客户端截图,如下所示:开启两个客户端,开始美猴王和二师兄的对话。

核心代码

发送信息类,如下所示:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Common
{
/// <summary>
/// 定义一个类,所有要发送的内容,都按照这个来
/// </summary>
public class ChatMessage
{
/// <summary>
/// 头部信息
/// </summary>
public ChatHeader header { get; set; } /// <summary>
/// 信息类型,默认为文本
/// </summary>
public ChatType chatType { get; set; } /// <summary>
/// 内容信息
/// </summary>
public string info { get; set; } } /// <summary>
/// 头部信息
/// </summary>
public class ChatHeader
{
/// <summary>
/// id唯一标识
/// </summary>
public string id { get; set; } /// <summary>
/// 源:发送方
/// </summary>
public string source { get; set; } /// <summary>
/// 目标:接收方
/// </summary>
public string dest { get; set; } } /// <summary>
/// 内容标识
/// </summary>
public enum ChatMark
{
BEGIN = 0x0000,
END = 0xFFFF
} public enum ChatType {
TEXT=,
IMAGE=
}
}

打包帮助类,如下所示:所有需要发送的信息,都要进行封装,打包,编码成固定格式,方便解析。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Common
{
/// <summary>
/// 包帮助类
/// </summary>
public class PackHelper
{
/// <summary>
/// 获取待发送的信息
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static byte[] GetSendMsgBytes(string text, string source, string dest)
{
ChatHeader header = new ChatHeader()
{
source = source,
dest = dest,
id = Guid.NewGuid().ToString()
};
ChatMessage msg = new ChatMessage()
{
chatType = ChatType.TEXT,
header = header,
info = text
};
string msg01 = GeneratePack<ChatMessage>(msg);
byte[] buffer = Encoding.UTF8.GetBytes(msg01);
return buffer;
} /// <summary>
/// 生成要发送的包
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static string GeneratePack<T>(T t) {
string send = SerializerHelper.JsonSerialize<T>(t);
string res = string.Format("{0}|{1}|{2}",ChatMark.BEGIN.ToString("X").PadLeft(, ''), send, ChatMark.END.ToString("X").PadLeft(, ''));
int length = res.Length; return string.Format("{0}|{1}", length.ToString().PadLeft(, ''), res);
} /// <summary>
/// 解析包
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="receive">原始接收数据包</param>
/// <returns></returns>
public static T ParsePack<T>(string msg, out string error)
{
error = string.Empty;
int len = int.Parse(msg.Substring(, ));//传输内容的长度
string msg2 = msg.Substring(msg.IndexOf("|") + );
string[] array = msg2.Split('|');
if (msg2.Length == len)
{
string receive = array[];
string begin = array[];
string end = array[];
if (begin == ChatMark.BEGIN.ToString("X").PadLeft(, '') && end == ChatMark.END.ToString("X").PadLeft(, ''))
{
T t = SerializerHelper.JsonDeserialize<T>(receive);
if (t != null)
{
return t; }
else {
error = string.Format("接收的数据有误,无法进行解析");
return default(T);
}
}
else {
error = string.Format("接收的数据格式有误,无法进行解析");
return default(T);
}
}
else {
error = string.Format("接收数据失败,长度不匹配,定义长度{0},实际长度{1}", len, msg2.Length);
return default(T);
}
}
}
}

服务端类,如下所示:服务端开启时,需要进行端口监听,等待链接。

 using Common;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks; /// <summary>
/// 描述:MeChat服务端,用于接收数据
/// </summary>
namespace MeChatServer
{
public class Program
{
/// <summary>
/// 服务端IP
/// </summary>
private static string IP; /// <summary>
/// 服务端口
/// </summary>
private static int PORT; /// <summary>
/// 服务端监听
/// </summary>
private static TcpListener tcpListener; public static void Main(string[] args)
{
//初始化信息
InitInfo();
IPAddress ipAddr = IPAddress.Parse(IP);
tcpListener = new TcpListener(ipAddr, PORT);
tcpListener.Start(); Console.WriteLine("等待连接");
tcpListener.BeginAcceptTcpClient(new AsyncCallback(AsyncTcpCallback), "async");
//如果用户按下Esc键,则结束
while (Console.ReadKey().Key != ConsoleKey.Escape)
{
Thread.Sleep();
}
tcpListener.Stop();
} /// <summary>
/// 初始化信息
/// </summary>
private static void InitInfo() {
//初始化服务IP和端口
IP = ConfigurationManager.AppSettings["ip"];
PORT = int.Parse(ConfigurationManager.AppSettings["port"]);
//初始化数据池
PackPool.ToSendList = new List<ChatMessage>();
PackPool.HaveSendList = new List<ChatMessage>();
PackPool.obj = new object();
} /// <summary>
/// Tcp异步接收函数
/// </summary>
/// <param name="ar"></param>
public static void AsyncTcpCallback(IAsyncResult ar) {
Console.WriteLine("已经连接");
ChatLinker linker = new ChatLinker(tcpListener.EndAcceptTcpClient(ar));
linker.BeginRead();
//继续下一个连接
Console.WriteLine("等待连接");
tcpListener.BeginAcceptTcpClient(new AsyncCallback(AsyncTcpCallback), "async");
}
}
}

客户端类,如下所示:客户端主要进行数据的封装发送,接收解析等操作,并在页面关闭时,关闭连接。

 using Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; namespace MeChatClient
{
/// <summary>
/// 聊天页面
/// </summary>
public partial class FrmMain : Form
{
/// <summary>
/// 链接客户端
/// </summary>
private TcpClient tcpClient; /// <summary>
/// 基础访问的数据流
/// </summary>
private NetworkStream stream; /// <summary>
/// 读取的缓冲数组
/// </summary>
private byte[] bufferRead; /// <summary>
/// 昵称信息
/// </summary>
private Dictionary<string, string> dicNickInfo; public FrmMain()
{
InitializeComponent();
} private void MainForm_Load(object sender, EventArgs e)
{
//获取昵称
dicNickInfo = ChatInfo.GetNickInfo();
//设置标题
string title = string.Format(":{0}-->{1} 的对话",dicNickInfo[ChatInfo.Source], dicNickInfo[ChatInfo.Dest]);
this.Text = string.Format("{0}:{1}", this.Text, title);
//初始化客户端连接
this.tcpClient = new TcpClient(AddressFamily.InterNetwork);
bufferRead = new byte[this.tcpClient.ReceiveBufferSize];
this.tcpClient.BeginConnect(ChatInfo.IP, ChatInfo.PORT, new AsyncCallback(RequestCallback), null); } /// <summary>
/// 异步请求链接函数
/// </summary>
/// <param name="ar"></param>
private void RequestCallback(IAsyncResult ar) {
this.tcpClient.EndConnect(ar);
this.lblStatus.Text = "连接服务器成功";
//获取流
stream = this.tcpClient.GetStream();
//先发送一个连接信息
string text = CommonVar.LOGIN;
byte[] buffer = PackHelper.GetSendMsgBytes(text,ChatInfo.Source,ChatInfo.Source);
stream.BeginWrite(buffer, , buffer.Length, new AsyncCallback(WriteMessage), null);
//只有stream不为空的时候才可以读
stream.BeginRead(bufferRead, , bufferRead.Length, new AsyncCallback(ReadMessage), null);
} /// <summary>
/// 发送信息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
string text = this.txtMsg.Text.Trim();
if( string.IsNullOrEmpty(text)){
MessageBox.Show("要发送的信息为空");
return;
}
byte[] buffer = ChatInfo.GetSendMsgBytes(text);
stream.BeginWrite(buffer, , buffer.Length, new AsyncCallback(WriteMessage), null);
this.rtAllMsg.AppendText(string.Format("\r\n[{0}]", dicNickInfo[ChatInfo.Source]));
this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Right;
this.rtAllMsg.AppendText(string.Format("\r\n{0}", text));
this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Right;
} /// <summary>
/// 异步读取信息
/// </summary>
/// <param name="ar"></param>
private void ReadMessage(IAsyncResult ar)
{
if (stream.CanRead)
{
int length = stream.EndRead(ar);
if (length >= )
{ string msg = string.Empty;
msg = string.Concat(msg, Encoding.UTF8.GetString(bufferRead, , length));
//处理接收的数据
string error = string.Empty;
ChatMessage t = PackHelper.ParsePack<ChatMessage>(msg, out error);
if (string.IsNullOrEmpty(error))
{
this.rtAllMsg.Invoke(new Action(() =>
{
this.rtAllMsg.AppendText(string.Format("\r\n[{0}]", dicNickInfo[t.header.source]));
this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Left;
this.rtAllMsg.AppendText(string.Format("\r\n{0}", t.info));
this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Left;
this.lblStatus.Text = "接收数据成功!";
}));
}
else {
this.lblStatus.Text = "接收数据失败:"+error;
}
}
//继续读数据
stream.BeginRead(bufferRead, , bufferRead.Length, new AsyncCallback(ReadMessage), null);
}
} /// <summary>
/// 发送成功
/// </summary>
/// <param name="ar"></param>
private void WriteMessage(IAsyncResult ar)
{
this.stream.EndWrite(ar);
//发送成功
} /// <summary>
/// 页面关闭,断开连接
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (MessageBox.Show("正在通话中,确定要关闭吗?", "关闭", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
e.Cancel = false;
string text = CommonVar.QUIT;
byte[] buffer = ChatInfo.GetSendMsgBytes(text);
stream.Write(buffer, , buffer.Length);
//发送完成后,关闭连接
this.tcpClient.Close(); }
else {
e.Cancel = true;
}
}
}
}

备注:本示例中,所有的建立连接,数据接收,发送等都是采用异步方式,防止页面卡顿。

源码下载链接

备注

每一次的努力,都是幸运的伏笔。

C# 网络编程之简易聊天示例的更多相关文章

  1. 使用Android网络编程实现简易聊天室

    在Java中我们可以利用socket编程实现聊天室,在Android中也一样,因为Android完全支持JDK本身的TCP.UDP网络通信API.我们可以使用ServerSocket.Socket来建 ...

  2. 网络编程TCP协议-聊天室

    网络编程TCP协议-聊天室(客户端与服务端的交互); <span style="font-size:18px;">1.客户端发数据到服务端.</span> ...

  3. java网络编程实现两端聊天

    网络编程的三要素: ip地址:唯一标识网络上的每一台计算机 端口号:计算机中应用的标号(代表一个应用程序),0-1024系统使用或者保留端口,有效端口0-65535(short) 通信协议:通信的规则 ...

  4. python学习(九) 网络编程学习--简易网站服务器

    python `网络编程`和其他语言都是一样的,服务器这块步骤为:`1. 创建套接字``2. 绑定地址``3. 监听该描述符的所有请求``4. 有新的请求到了调用accept处理请求` Python ...

  5. 网络编程-基于Websocket聊天室(IM)系统

    目录 一.HTML5 - Websocket协议 二.聊天室(IM)系统的设计 2.1.使用者眼中的聊天系统 2.2.开发者眼中的聊天系统 2.3.IM系统的特性 2.4.心跳机制:解决网络的不确定性 ...

  6. 【Python网络编程】多线程聊天软件程序

    课程设计的时候制作的多线程聊天软件程序 基于python3.4.3 import socket import pickle import threading import tkinter import ...

  7. 基于C语言的Socket网络编程搭建简易的Web服务器(socket实现的内部原理)

    首先编写我们服务器上需要的c文件WebServer.c 涉及到的函数API: int copy(FILE *read_f, FILE * write_f) ----- 文件内容复制的方法 int Do ...

  8. Ubuntu上Qt+Tcp网络编程之简单聊天对话框

    首先看一下实现结果: >>功能: (1)服务器和客户端之间进行聊天通信: (2)一个服务器可同时给多个客户端发送消息:(全部连接时)   也可以只给特定的客户端发送消息:(连接特定IP) ...

  9. Linux C 网络编程——多线程的聊天室实现(server端)

    server端的主要功能: 实现多用户群体聊天功能(此程序最多设定为10人.可进行更改),每一个人所发送的消息其它用户均能够收到.用户能够任意的增加或退出(推出以字符串"bye"实 ...

随机推荐

  1. PAT-2019年秋季考试-甲级

    7-1 Forever (20 分) #include <bits/stdc++.h> using namespace std; int N,K,m,number[10]; multima ...

  2. 洛谷 题解 P4613 【[COCI2017-2018#5] Olivander】

    我又双叒叕被包菜辣! P4613 [COCI2017-2018#5] Olivander 首先,不知道为什么这题无法提交翻译: 所以,我先放个翻译: 哈利波特在与伏地魔的战斗中损坏了他的魔杖.他决定在 ...

  3. Java修炼——异常的概念以及处理方式(捕获异常)

    异常概念分类 异常( Exception 也称例外)就是在程序的运行过程中 所发生的不正常的事件,它会中断正在运行的程序  所需文件找不到  网络连接不通或中断  算术运算错 (被零除-)  ...

  4. HDU-1698-----Just Hook

    In the game of DotA, Pudge's meat hook is actually the most horrible thing for most of the heroes. T ...

  5. 正则replace 回调函数里接收的参数是什么?

    前言 我们都知道 replace 在做替换处理方面会很常用,通常也是第一个会想到的方法.replace 第一个参数可以传入 string 或 RegExp,第二个参数可以传入 string 或 一个回 ...

  6. Android Studio 3.0下创建menu布局文件的图例

    当开始一个android app的时候,android studio项目中没有看到menu文件夹:如下所示: 当要添加一个按钮时,很多文档上都会说,通过在项目的 res/menu 目录中新增一个 XM ...

  7. 关于OV7670摄像头的分辨率设置

    关于OV7670摄像头的分辨率设置最近一直在纠结如何把OV7670输出的图片分辨率缩小,以减少数据量来适应数据的传输,在网上看了好多也没有关于寄存器的具体设置,最终又读了几遍数据手册,加上网友们写的帖 ...

  8. Python3 常用模块1

    目录 os模块 对文件夹操作 对文件进行操作 sys模块 json 和pickle模块 logging模块 日志等级 longging模块的四大组件 自定义配置 os模块 通过os模块我们可以与操作系 ...

  9. 基于 .NET Core 的简单文件服务器

    Netnr.FileServer 基于 .NET Core 的简单文件服务器,数据库为SQLite 源码 https://github.com/netnr/blog https://gitee.com ...

  10. 深入学习CSS3-flexbox布局

    学习博客:https://css-tricks.com/snippets/css/a-guide-to-flexbox/ 学习博客:http://caibaojian.com/demo/flexbox ...