前言

最近在学习C# Socket相关的知识,学习之余,动手做了一个简单的局域网聊天器。有萌生做这个的想法,主要是由于之前家里两台电脑之间想要传输文件十分麻烦,需要借助QQ,微信或者其他第三方应用,基本都要登录,而且可能传输的文件还有大小限制,压缩问题。所以本聊天器的首要目标就是解决这两个问题,做到使用方便(双击启动即用),传文件无限制。

废话不多说,先上图。S-Chat是服务端,C-Chat是客户端,两者除了客户端首次启动后需要设置一下连接的IP地址外,无其他区别。操作与界面都完全相同,对于用户来说,基本不用在意谁是服务端谁是客户端。

编码

服务端监听接口

服务端主要负责开启监听线程,等待客户端接入

public void StartListen()
{
// 创建Socket对象 new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
Socket socket = GetSocket();
// 将套接字与IPEndPoint绑定
socket.Bind(this.GetIPEndPoint());
// 开启监听 仅支持一个连接
socket.Listen(1);
// 开启线程等待客户端接入,避免堵塞
Thread acceptThread = new Thread(new ThreadStart(TryAccept));
acceptThread.IsBackground = true;
acceptThread.Start();
} public void TryAccept()
{
Socket socket = GetSocket();
while (true)
{
try
{
Socket connectedSocket = socket.Accept()
this.ConnectedSocket = connectedSocket;
OnConnect(); // 连接成功回调
this.StartReceive(); // 开始接收线程
break;
}
catch (Exception e)
{
}
}
}

客户端连接接口

客户端主要负责开启连接线程,每隔2秒,自动尝试连接服务端

public void StartConnect()
{
Thread connectThread = new Thread(new ThreadStart(TryConnect));
connectThread.IsBackground = true;
connectThread.Start();
} public void TryConnect()
{
Socket socket = GetSocket();
while (true)
{
try
{
socket.Connect(this.GetIPEndPoint());
this.ConnectedSocket = socket;
OnConnect(); // 连接成功回调
this.StartReceive();
break;
}
catch (Exception e)
{
Thread.Sleep(TryConnectInterval); // 指定间隔后重新尝试连接
}
}
}

文字发送,文件发送,接收文字,接收文件等通用接口主要实现在ChatBase类中,是服务端与客户端的共同父类。

文字发送接口

发送数据的第一位表示发送信息的类型,0表示字符串文字,1表示文件

然后获取待发送字符串的长度,使用long类型表示,占用8个字节

共发送的字节数据可以表示为头部(类型 + 字符串字节长度,共9个字节)+ 实际字符串字节数据

public bool Send(string msg)
{
if (ConnectedSocket != null && ConnectedSocket.Connected)
{
byte[] buffer = UTF8.GetBytes(msg);
byte[] len = BitConverter.GetBytes((long)buffer.Length);
byte[] content = new byte[1 + len.Length + buffer.Length];
content[0] = (byte)ChatType.Str; // 发送信息类型,字符串
Array.Copy(len, 0, content, 1, len.Length); // 字符串字节长度
Array.Copy(buffer, 0, content, 1 + len.Length, buffer.Length); // 实际字符串字节数据
try
{
ConnectedSocket.Send(content);
return true;
}
catch (Exception e)
{
}
}
return false;
}

文件发送接口

与字符串发送相同的头部可以表示为(类型 + 文件长度,共9个字节)

还需要再加上待发送的文件名的长度,与文件名字节数据

共发送的字节数据可以表示为头部(类型 + 文件长度,共9个字节)+ 文件名头部(文件名长度 + 文件名字节数据)+ 实际文件数据

public bool SendFile(string path)
{
if (ConnectedSocket != null && ConnectedSocket.Connected)
{
try
{
FileInfo fi = new FileInfo(path);
byte[] len = BitConverter.GetBytes(fi.Length);
byte[] name = UTF8.GetBytes(fi.Name);
byte[] nameLen = BitConverter.GetBytes(name.Length);
byte[] head = new byte[1 + len.Length + nameLen.Length + name.Length];
head[0] = (byte)ChatType.File; // 加上信息发送类型
Array.Copy(len, 0, head, 1, len.Length); // 加上文件长度
Array.Copy(nameLen, 0, head, 1 + len.Length, nameLen.Length); // 加上文件名长度
Array.Copy(name, 0, head, 1 + len.Length + nameLen.Length, name.Length); // 加上文件名字节数据
ConnectedSocket.SendFile(
path,
head,
null,
TransmitFileOptions.UseDefaultWorkerThread
);
return true;
}
catch(Exception e)
{
}
}
return false;
}

信息接收接口(文字与文件)

主要是解析接收到的字节数据,根据字符串或文件的类型进行处理

public void Receive()
{
if (ConnectedSocket != null)
{
while (true)
{
try
{
// 读取公共头部
byte[] head = new byte[9];
ConnectedSocket.Receive(head, head.Length, SocketFlags.None);
int len = BitConverter.ToInt32(head, 1);
if (head[0] == (byte) ChatType.Str)
{
// 接收字符串
byte[] buffer = new byte[len];
ConnectedSocket.Receive(buffer, len, SocketFlags.None);
OnReceive(ChatType.Str, UTF8.GetString(buffer));
}
else if(head[0] == (byte)ChatType.File)
{
// 接收文件
if (!Directory.Exists(dirName))
{
Directory.CreateDirectory(dirName);
}
// 读取文件名信息
byte[] nameLen = new byte[4];
ConnectedSocket.Receive(nameLen, nameLen.Length, SocketFlags.None);
byte[] name = new byte[BitConverter.ToInt32(nameLen, 0)];
ConnectedSocket.Receive(name, name.Length, SocketFlags.None);
string fileName = UTF8.GetString(name);
// 读取文件内容并写入
int readByte = 0;
int count = 0;
byte[] buffer = new byte[1024 * 8];
string filePath = Path.Combine(dirName, fileName);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write))
{
while (count != len)
{
int readLength = buffer.Length;
if(len - count < readLength)
{
readLength = len - count;
}
readByte = ConnectedSocket.Receive(buffer, readLength, SocketFlags.None);
fs.Write(buffer, 0, readByte);
count += readByte;
}
}
OnReceive(ChatType.File, fileName);
}
else
{
// 未知类型
}
}
catch (Exception e)
{
}
}
}
}

使用

  • 第一次使用,客户端需要设置待连接的IP地址。之后再启动会自动连接

    1. 双击服务端exe启动,点击设置,查看IP地址项

    2. 双击客户端exe启动,点击设置,在IP地址项,输入服务端查看到的IP地址

  • 设置成功后,等待大约一两秒,应用cion变成绿色,即表示连接成功,可以正常发送文字和文件了

  • 可以点击选择文件(支持选择多个文件),发送文件

  • 支持直接拖拽文件到输入框,发送文件

  • 支持Ctrl+Enter快捷键发送

  • 接收到的文件自动存放在exe所在目录的ChatFiles文件夹下

注意事项

  • 客户端服务端需要在同一个局域网下才能实现连接
  • 服务端IP地址是不支持修改的,自动读取本机的IP地址

源码

  • 完整代码放在GitHub上,点击查看
  • 预编译好的可运行exe程序,在仓库的Release目录,也可以直接通过百度云下载,提取码v4pe

C# Socket编程实现简单的局域网聊天器的更多相关文章

  1. socket编程,简单多线程服务端测试程序

    socket编程,简单多线程服务端测试程序 前些天重温了MSDN关于socket编程的WSAStartup.WSACleanup.socket.closesocket.bind.listen.acce ...

  2. Socket编程的简单实现

    关于socket编程的简单实现,主要分成客户端.服务端两个部分.实现如下: 1.服务端代码如下,注意:server端要优先于client端启动 2.client端代码,以及启动后客户端和服务端之间的简 ...

  3. [JavaWeb基础] 024.Socket编程之简单的聊天程序

    1.Socket的简介 1)什么是Socket 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket.Socket通常用来实现客户方和服务方的连接.Socket ...

  4. java socket编程开发简单例子 与 nio非阻塞通道

    基本socket编程 1.以下只是简单例子,没有用多线程处理,只能一发一收(由于scan.nextLine()线程会进入等待状态),使用时可以根据具体项目功能进行优化处理 2.以下代码使用了1.8新特 ...

  5. [转] 3个学习Socket编程的简单例子:TCP Server/Client, Select

    以前都是采用ACE的编写网络应用,最近由于工作需要,需要直接只用socket接口编写CS的代码,重新学习这方面的知识,给出自己所用到的3个简单例子,都是拷贝别人的程序.如果你能完全理解这3个例子,估计 ...

  6. socket编程——一个简单的样例

    从一个简单的使用TCP样例開始socket编程,其基本过程例如以下: server                                                  client ++ ...

  7. socket编程——一个简单的例子

    从一个简单的使用TCP例子开始socket编程,其基本步骤如下: server                                                  client ++++ ...

  8. 5.1 socket编程、简单并发服务器

    什么是socket? socket可以看成是用户进程与内核网络协议栈的编程接口.是一套api函数. socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机间的进程间通信. 工业上使用的为t ...

  9. python socket编程 实现简单p2p聊天程序

    目标是写一个python的p2p聊天的项目,这里先说一下python socket的基础课程 一.Python Socket 基础课程 Socket就是套接字,作为BSD UNIX的进程通信机制,取后 ...

随机推荐

  1. Google DevTools Explanation

    Evaluating network performance The Network panel records information about each network operation in ...

  2. JavaScript秒转换成天-小时-分钟-秒

    根据时间秒转换成天-小时-分钟-秒 // 秒转换成day.hour.minutes.seconds formatSecond(second: number) { const days = Math.f ...

  3. web中间件之nginx

    web中间件之nginx https://www.jianshu.com/p/d8bd75c0fb1b   对nginx正向代理和反向代理理解特别好的一篇文章. 一.nginx nginx缺点,负载均 ...

  4. <SCOI2008>奖励关

    emmm第一道期望dp+个状压 真有趣.. #include<cstdio> #include<iostream> #include<cstring> #inclu ...

  5. 实战:CentOS 7.2 / Zabbix3.4安装graphtrees

    众所周知的 Zabbix图形显示问题,决定使用graphtrees 插件. 环境:CentOS7.2 + Zabbix 3.4 1)首先切换到root用户以获得足够的权限将资源下载到 /usr/sha ...

  6. Word目录生成

    之所以写这篇文章,是因为每次写报告都需要生成相应目录,但常常只记得个大概,最终还得要重新百度,十分头疼,故在此记录一下. 大概分为3个步骤 步骤1 设置标题级数 进入大纲模式 选择相应级数,这里选的是 ...

  7. 关于(Building tool)的认识以及当下流行的Building tool有哪些?

    1.Building tool是什么? (Building tool)构建工具是一种工具,它负责构建流程的所有内容,并自动化与构建项目相关的所有内容.它致力于以下任务: 生成源代码(如果在软件项目中使 ...

  8. 前端学习之路CSS基础学习二

    CSS属性相关 样式操作: (1)width:为元素设置宽度 (2)height:为元素设置高度 ps:块儿级标签才能设置长宽行内标签设置长宽没有任何影响 p{ width: 30px; height ...

  9. js join()

    在本例中,我们将创建一个数组,然后把它的所有元素放入一个字符串: <script type="text/javascript"> var arr = new Array ...

  10. fastcgi_param详解

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;#脚本文件请求的路径 fastcgi_param QUERY_STRI ...