详细步骤描述:
服务器端:
第一步:创建一个用于监听连接的Socket对像;
第二步:用指定的端口号和服务器的ip建立一个EndPoint对像;
第三步:用socket对像的Bind()方法绑定EndPoint;
第四步:用socket对像的Listen()方法开始监听;
第五步:接收到客户端的连接,用socket对像的Accept()方法创建一个新的用于和客户端进行通信的socket对像;
第六步:通信结束后一定记得关闭socket;
客户端:
第一步:建立一个Socket对像;
第二步:用指定的端口号和服务器的ip建立一个EndPoint对像;
第三步:用socket对像的Connect()方法以上面建立的EndPoint对像做为参数,向服务器发出连接请求;
第四步:如果连接成功,就用socket对像的Send()方法向服务器发送信息;
第五步:用socket对像的Receive()方法接受服务器发来的信息 ;
第六步:通信结束后一定记得关闭socket;
Note: 同步和异步通信的区别—同步工作方式是指利用TCP编写的程序执行到监听或接收语句时,在未完成工作(侦听到连接请求或收到对方发来的数据)前不再继续往下执行,线程处于阻塞状态,直到该语句完成相应的工作后才继续执行下一条语句。
异步工作方式是指程序执行到监听或接收语句时,不论工作是否完成,都会继续往下执行。
视图代码:
<Window x:Class="ClientCode.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="4*"/>
<RowDefinition Height="4*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" BorderBrush="Black" Margin="5" HorizontalContentAlignment="Left"
Text="{Binding Ip, Mode=TwoWay}"/>
<Border Margin="5" Grid.Column="1" Grid.Row="0" CornerRadius="10" Background="CadetBlue">
<TextBox BorderBrush="Black" HorizontalContentAlignment="Left" Text="{Binding Port, Mode=TwoWay}"/>
</Border>
<Border Margin="5" Grid.Column="2" Grid.Row="0" CornerRadius="10" Background="CadetBlue">
<Button BorderBrush="Black" Content="连接服务器" Command="{Binding ConnectedServer}" />
</Border>
<Border Margin="5" Grid.Column="3" Grid.Row="0" CornerRadius="10" Background="CadetBlue">
<Button BorderBrush="Black" Content="断开服务器" Command="{Binding DisconnectedServer}"/>
</Border>
<TextBox Grid.Row="1" Grid.ColumnSpan="5" Background="LightSlateGray"
BorderBrush="Black" Margin="5" Text="{Binding LogText, Mode=TwoWay}"
VerticalScrollBarVisibility="Visible"/>
<TextBox Grid.Row="2" Grid.ColumnSpan="5" Background="LightSlateGray"
BorderBrush="Black" Margin="5" Text="{Binding SendText, Mode=TwoWay}"
VerticalScrollBarVisibility="Visible"/>
<Border Grid.Column="2" Grid.ColumnSpan="4" Grid.Row="3" CornerRadius="5" Background="White">
<DockPanel Dock="Right" >
<Button Content="发送消息" Margin="5" Grid.Column="1" MaxWidth="200"
MinWidth="150" HorizontalAlignment="Right" Command="{Binding SendMessage}"/>
</DockPanel>
</Border>
</Grid>
</Window>
后台逻辑代码:
using Microsoft.Win32;
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows;
namespace ClientCode.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "客户端";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _ip= "192.168.147.127";
public string Ip
{
get { return _ip; }
set { SetProperty(ref _ip, value); }
}
private string _port="8970";
public string Port
{
get { return _port; }
set { SetProperty(ref _port, value); }
}
private string _logText;
public string LogText
{
get { return _logText; }
set { SetProperty(ref _logText, value); }
}
private string _sendText;
public string SendText
{
get { return _sendText; }
set { SetProperty(ref _sendText, value); }
}
public bool CloseClientThread; // 用于关闭客户端接收消息线程
public Socket ClientSocketSend; //创建连接的Socket
private Thread CLientThreadReceive; // 创建客户端接收服务器的线程变量
public DelegateCommand ConnectedServer { get; private set; }
public DelegateCommand DisconnectedServer { get; private set; }
public DelegateCommand SendMessage { get; private set; }
public MainWindowViewModel()
{
ConnectedServer = new DelegateCommand(Client2Server);
DisconnectedServer = new DelegateCommand(CloseClientFun);
SendMessage = new DelegateCommand(SendMessageFun);
}
///客户端连接到服务器 >
private void Client2Server()
{
CloseClientThread = true;
if (Ip != null && Port != null)
{
try
{
ClientSocketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(Ip.Trim());
ClientSocketSend.Connect(ip, Convert.ToInt32(Port.Trim()));
LogText += "连接成功......" + "\r\n";
// 开启循环接收服务器消息的线程
CLientThreadReceive = new Thread(new ThreadStart(Receive));
CLientThreadReceive.IsBackground = true;
CLientThreadReceive.Start();
}
catch (Exception ex)
{
LogText += "连接到服务器失败:" + ex.ToString() + "\r\n";
}
}
else
{
LogText += "请输入IP/Port:" + "\r\n";
}
}
private void Receive()
{
try
{
while (CloseClientThread)
{
byte[] buffer = new byte[2048];
//实际接收到的字节数
int r = ClientSocketSend.Receive(buffer);
if (r == 0)
{
LogText += "服务端断开连接........." + "\r\n";
break;
}
else
{
//判断发送的数据的类型
if (buffer[0] == 0) //表示发送的是文字消息
{
string str = Encoding.Default.GetString(buffer, 1, r - 1);
LogText += "接收远程服务器:" + ClientSocketSend.RemoteEndPoint + "发送的消息:" + str + "\r\n";
}
//表示发送的是文件
if (buffer[0] == 1) // 这里后期改进为WPF中专用的
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.InitialDirectory = @"";
sfd.Title = "请选择要保存的文件";
sfd.Filter = "所有文件|*.*";
sfd.ShowDialog();
string strPath = sfd.FileName;
using (FileStream fsWrite = new FileStream(strPath, FileMode.OpenOrCreate, FileAccess.Write))
{
fsWrite.Write(buffer, 1, r - 1);
}
LogText += "保存文件成功" + "\r\n";
}
}
}
}
catch (Exception ex)
{
LogText += "接收服务端发送的消息出错/与服务器断开连接:" + "\r\n";
}
}
private void SendMessageFun()
{
if (ClientSocketSend != null)
{
if (SendText != null)
try
{
string strMsg = SendText.Trim();
byte[] buffer = new byte[2048];
buffer = Encoding.Default.GetBytes(strMsg);
int receive = ClientSocketSend.Send(buffer);
}
catch (Exception ex)
{
LogText += "发送消息失败:" + ex.Message + "\r\n";
}
else
{
LogText += "请先输入要发送的信息:" + "\r\n";
}
}
else
{
LogText += "请先连接到服务端:" + "\r\n";
}
}
private void SendMessageDisconnect()
{
try
{
string strMsg = "disconnectNetwork";
byte[] buffer = new byte[2048];
buffer = Encoding.Default.GetBytes(strMsg);
int receive = ClientSocketSend.Send(buffer);
}
catch (Exception ex)
{
LogText += "发送断开连接消息出错:" + ex.Message + "\r\n";
}
}
private void CloseClientFun()
{
// 发送消息告诉服务端,我将断开连接;如果客户端主动断开与服务端的连接,
// 最好实现让服务端接收消息的线程函数退出循环阻塞状态,不然会引发服务端异常:
// System.Net.Sockets.SocketException: 您的主机中的软件中止了一个已建立的连接。
//An established connection was aborted by the software in your host machine
SendMessageDisconnect();
if (ClientSocketSend != null)
{
//终止线程
CloseClientThread = false;
//关闭socket
ClientSocketSend.Close();
}
else
{
LogText += "客户端未开启" + "\r\n";
}
}
}
}
1、 视图代码
<Window x:Class="ServerCode.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="542" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="4*"/>
<RowDefinition Height="4*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" BorderBrush="Black" Margin="5" Text="{Binding Ip}" HorizontalContentAlignment="Left"/>
<Border Margin="5" Grid.Column="1" Grid.Row="0" CornerRadius="10" Background="CadetBlue">
<TextBox BorderBrush="Black" Text="{Binding Port,Mode=TwoWay}" HorizontalContentAlignment="Left"/>
</Border>
<Border Margin="5" Grid.Column="2" Grid.Row="0" CornerRadius="10" Background="CadetBlue">
<Button BorderBrush="Black" Content="开始监听" Command="{Binding StartListen}"/>
</Border>
<Border Margin="5" Grid.Column="3" Grid.Row="0" CornerRadius="10" Background="CadetBlue">
<Button BorderBrush="Black" Content="停止监听" Command="{Binding StopListen}"/>
</Border>
<Border Margin="5" Grid.Column="4" Grid.Row="0" CornerRadius="10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="连接的客户:" VerticalAlignment="Center" TextAlignment="Center"/>
<ComboBox Width="80" ItemsSource="{Binding DicSocket,Mode=TwoWay}" SelectedValuePath="ItemSocket" DisplayMemberPath="ip"
SelectedValue="{Binding SelectSocket,Mode=TwoWay}" />
</StackPanel>
</Border>
<TextBox Grid.Row="1" Grid.ColumnSpan="5" Background="LightSlateGray"
BorderBrush="Black" Margin="5" Text="{Binding LogReceive}" VerticalScrollBarVisibility="Visible"/>
<TextBox Grid.Row="2" Grid.ColumnSpan="5" Background="LightSlateGray"
BorderBrush="Black" Margin="5" Text="{Binding Sendtext}" VerticalScrollBarVisibility="Visible"/>
<Border Margin="5" Grid.ColumnSpan="2" Grid.Row="3" CornerRadius="10" Background="CadetBlue">
<TextBox BorderBrush="Black" Text="{Binding SelectFile,Mode=TwoWay}"/>
</Border>
<Border Grid.Column="2" Grid.ColumnSpan="4" Grid.Row="3" CornerRadius="5" Background="White">
<StackPanel Orientation="Horizontal" >
<Grid Width="323">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Content="选择文件" Margin="5" Grid.Column="0" Command="{Binding SelectCommand}"/>
<Button Content="发送文件" Margin="5" Grid.Column="1" Command="{Binding SendFileCommand}"/>
<Button Content="发送消息" Margin="5" Grid.Column="2" Command="{Binding SendMessageCommand}"/>
</Grid>
</StackPanel>
</Border>
</Grid>
</Window>
2、 服务端业务代码
using Microsoft.Win32;
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCode.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "服务端";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _ip="192.168.178.208";
public string Ip
{
get { return _ip; }
set { SetProperty(ref _ip, value); }
}
private string _port = "8970";
public string Port
{
get { return _port; }
set { SetProperty(ref _port, value); }
}
private string _selectFile;
public string SelectFile
{
get { return _selectFile; }
set { SetProperty(ref _selectFile, value); }
}
private string _logReceive;
public string LogReceive
{
get { return _logReceive; }
set { SetProperty(ref _logReceive, value); }
}
private string _sendtext;
public string Sendtext
{
get { return _sendtext; }
set { SetProperty(ref _sendtext, value); }
}
private SynchronizedCollection<IpSocket> _dicSocket; // 一个集合关于连接到服务端的客户端套接字(IP地址和SOCKET)
public SynchronizedCollection<IpSocket> DicSocket
{
get { return _dicSocket; }
set { SetProperty(ref _dicSocket, value);
RaisePropertyChanged();
}
}
private Socket _selectSocket;
public Socket SelectSocket
{
get { return _selectSocket; }
set { SetProperty(ref _selectSocket, value); }
}
public DelegateCommand StartListen { get; private set; } // 开始监听命令
public DelegateCommand StopListen { get; private set; } // 停止监听命令
public DelegateCommand SelectCommand { get; private set; } // 选择文件命令
public DelegateCommand SendFileCommand { get; private set; } // 发送文件命令
public DelegateCommand SendMessageCommand { get; private set; } // 发送消息命令
public Socket socketWatch; // 用于监听的Socket
public Socket socketSend; // 用于通信的Socket
Thread AcceptSocketThread; // 创建监听连接得线程,接收消息的线程
Thread threadReceive; // 接收客户端发送消息的线程
public bool AcceptSocketBool;
public bool ThreadReceiveBool;
public MainWindowViewModel()
{
DicSocket = new SynchronizedCollection<IpSocket>();
StartListen = new DelegateCommand(startListen);
StopListen = new DelegateCommand(StopListenFun);
SelectCommand = new DelegateCommand(SelectFileFun);
SendFileCommand = new DelegateCommand(SendFilesFun);
SendMessageCommand = new DelegateCommand(Server2Client);
}
// 开始监听并接收客户端发送的消息
public void startListen()
{
ThreadReceiveBool = true;
AcceptSocketBool = true;
if (socketWatch == null)
{
try
{
// step1: 在服务器端创建一个负责监听IP地址和端口号的Socket
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(Ip.Trim()); // step2: 获取IP地址
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(Port.Trim())); // step3: 获取Port
socketWatch.Bind(point); // step4: 给服务器端的SOCKET绑定IP和Port
socketWatch.Listen(10); // step5: 开始监听,并指定最大监听客户端的个数为10
LogReceive += "服务端:监听开启,等待客户端的连接........" + "\r\n";
// 等待客户端的连接,如果连接成功,就将客户端的Socket和对应的IP地址加入到监听集合中,并开启消息接收线程
AcceptSocketThread = new Thread(new ParameterizedThreadStart(StartListenFunc));
AcceptSocketThread.IsBackground = true;
AcceptSocketThread.Start(socketWatch);
}
catch (Exception ex)
{
LogReceive += ex.ToString() + "\r\n";
}
}
else
{
LogReceive += "服务器端已开启监听:" + "\r\n";
}
}
///获取远程连接得Socket和IP地址,同时实现客户端消息的接收
private void StartListenFunc(object obj)
{
Socket socketWatch = obj as Socket;
while (AcceptSocketBool)
{
try
{
// 等待客户端的连接,并且创建一个用于通信的Socket,这里只能是一个全局唯一变量的Socket类型
socketSend = socketWatch.Accept();
if (socketSend != null)
{
// 获取远程主机的ip地址和端口号
string strIp = socketSend.RemoteEndPoint.ToString();
if (JudgeHave(strIp))
{
IpSocket perClient = new IpSocket();
perClient.ip = strIp;
perClient.ItemSocket = socketSend;
// 这里要加一个判断条件,如果已经存在的客户端不能够添加进来
DicSocket.Add(perClient);
LogReceive += "远程主机:" + socketSend.RemoteEndPoint + "连接成功" + "\r\n";
// 定义接收客户端消息接收的线程
threadReceive = new Thread(new ParameterizedThreadStart(ReceiveMessage));
threadReceive.IsBackground = true;
threadReceive.Start(socketSend);
}
}
else
{
LogReceive = "客户端断开连接/服务端停止监听" + "\r\n";
}
}
catch
{
LogReceive = "客户端断开连接/服务端停止监听" + "\r\n";
}
}
}
///
/// 用于判断最新监听到的客户端连接请求,在服务端是否已近为其创建了接收消息线程,若存在则不再为其创建接收消息线程
///
/// 服务端最新监听到的客户端请求连接得ip地址
/// true表示不存在,可以创建接收线程。false表示存在,不为其创建接收线程
private bool JudgeHave(string ip)
{
if (DicSocket.Count > 0)
{
foreach (IpSocket PerClient in DicSocket)
{
if (PerClient.ip == ip)
{
return false;
}
else
{
return true;
}
}
}
return true;
}
/// 服务器端不停的接收客户端发送的消息
private void ReceiveMessage(object obj)
{
Socket socketSendLocal = obj as Socket;
while (ThreadReceiveBool)
{
//客户端连接成功后,服务器接收客户端发送的消息
byte[] buffer = new byte[2048];
//实际接收到的有效字节数
int count = socketSendLocal.Receive(buffer);
//count 表示客户端关闭,要退出循环
if (count == 0)
{
LogReceive += "客户端:" + socketSendLocal.RemoteEndPoint + "断开连接...." +"\r\n";
break;
}
else
{
string str = Encoding.Default.GetString(buffer, 0, count);
string strReceiveMsg = "接收:" + socketSendLocal.RemoteEndPoint + "发送的消息:" + str;
LogReceive += strReceiveMsg + "\r\n";
if (str == "disconnectNetwork")
{
// 当客户端与服务器断开连接后,需要更新DicSocket列表数据,下面代码可以更改为条件查询语句进行优化
for(int i=0; i < DicSocket.Count; i++)
{
if (DicSocket[i].ip == socketSendLocal.RemoteEndPoint.ToString())
{
DicSocket.RemoveAt(i);
}
}
LogReceive += socketSendLocal.RemoteEndPoint.ToString() + "断开连接" + DicSocket.Count + "\r\n";
break;
}
}
}
}
///服务器给客户端发送消息 >
private void Server2Client()
{
if (Sendtext != null && SelectSocket!=null)
{
string strMessage = Sendtext.Trim();
byte[] buffer = Encoding.Default.GetBytes(strMessage);
List<byte> list = new List<byte>();
list.Add(0);
list.AddRange(buffer);
//将泛型集合转换为数组
byte[] newBuffer = list.ToArray();
//获得用户选择的IP地址 ? 如何获取itemControl的选项
try
{
//string ip = SelectIp.ToString();
//DicSocket[SelectIp.ToString()].Send(newBuffer);
SelectSocket.Send(newBuffer);
}
catch (Exception ex)
{
LogReceive += "向客户端发送消息发生异常:" + ex.ToString() + "\r\n";
}
}
else
{
LogReceive += "请输入发送的消息/选择发送客户端对象...." + "\r\n";
}
}
///选择要发送的文件
private void SelectFileFun()
{
OpenFileDialog dia = new OpenFileDialog();
//设置初始目录
dia.InitialDirectory = @"";
dia.Title = "请选择要发送的文件";
//过滤文件类型
dia.Filter = "所有文件|*.*";
dia.ShowDialog();
//将选择的文件的全路径赋值给文本框
SelectFile = dia.FileName;
}
///发送文件
private void SendFilesFun()
{
List<byte> list = new List<byte>();
//获取要发送的文件的路径
string strPath = SelectFile.Trim();
using (FileStream sw = new FileStream(strPath, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[2048];
int r = sw.Read(buffer, 0, buffer.Length);
list.Add(1);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
try
{
SelectSocket.Send(newBuffer, 0, r + 1, SocketFlags.None);
}
catch (Exception ex)
{
LogReceive += "文件发送异常:" + ex.ToString() + "\r\n";
}
}
}
///停止监听 >
private void StopListenFun()
{
if (ThreadReceiveBool && AcceptSocketBool)
{
if (socketWatch==null)
{
LogReceive += "服务器还未启动......" + "\r\n";
}
else
{
ThreadReceiveBool = false;
AcceptSocketBool = false;
if (socketSend != null) socketSend.Close();
if (socketWatch!=null) socketWatch.Close();
}
}
else
{
LogReceive += "服务器还未启动:" + "\r\n";
}
}
}
}
基础版本代码与概念参考地址: https://www.cnblogs.com/dotnet261010/p/6211900.html
代码调试参考地址: https://www.cnblogs.com/chucklu/p/5421246.html
源代码压缩包地址: https://download.csdn.net/download/xiaochuideai/86398577