最近学习了基于C#的Scoket网络编程,简单整理如下:
1、关于Socket
SOCKET,通常也称作“套接字”,实际是在计算机中提供了一个通信端口,用于描述IP地址和端口。可以通过这个端口与任何一个具有SOCKET接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个SOCKET接口来实现。在应用开发中就像使用文件句柄一样,可以对SOCKET句柄进行读,写操作。
Socket常见方法如下:
public Socket Accept ():为新建连接创建新的 Socket。
public void Bind (EndPoint localEP):使 Socket 与一个本地终结点相关联。
public void Close ():关闭 Socket 连接并释放所有关联的资源。注意这个方法有冲载方法。
public void Connect (EndPoint remoteEP):建立与远程主机的连接。注意这个方法有重载方法。
public void Disconnect (bool reuseSocket):关闭套接字连接并是否允许重用套接字。
public void Listen (int backlog):将 Socket 置于侦听状态。
public int Receive (byte[] buffer):接收来自绑定的 Socket 的数据。注意这个方法有重载方法。
public int ReceiveFrom (byte[] buffer,ref EndPoint remoteEP):接收数据报并存储源终结点。注意这个方法有重载方法。
public int Send (byte[] buffer):将数据发送到连接的 Socket。注意这个方法有重载方法。
public void SendFile (string fileName):将文件和可选数据异步发送到连接的 Socket。注意这个方法有重载方法。
public int SendTo (byte[] buffer,EndPoint remoteEP):将数据发送到特定终结点。注意这个方法有重载方法。
public void Shutdown (SocketShutdown how):禁用某 Socket 上的发送和接收。
2、Socket网络编程思路
通俗来讲,可以将Socket通信比作是打电话,电话双方相当于两个正在交互的应用程序,电话号码相当于IP地址(包括端口号)。双方交互时,客户端向服务器端发送请求的前提是,服务器端需要有监听到客户端发送消息的监听机制,即创建Socket对象,同时绑定可以被监听到的端口号,并设置监听队列,规定同一时刻可接受的请求客户端的数量,此时要使用多线程思想。当客户端向服务器端发送请求时,客户端创建了一个Socket用于两者之间的通信,当服务器端接受到客户端的请求时,做出相应的响应,从而实现客户端和服务器端的交互。Socket通信机制如下。
注意:因为在网络传输时传输的数据都是二进制形式的(表现为字节数组),所以如果要传输类似于中文这样的双字节字符就需要在传输之前用合适的编码转换成字节数组,然后接收方按照发送方的编码将接收到字节数组转换成字符串。另外,注意接收数据的时候是先声明了一个字节数组,然后将接收到的数据保存到字节数组中,这个方法有个返回值表示实际接收了多少字节数据。这是因为数组是不可变的,假如声明了一个1024字节大小的数组,而发送方仅发送1字节数据,而接收方并不直到发送方发送的数据量把整个1024字节当作发送发送的数据的话,最终会发生错误。
3、实现网络聊天小程序
在本机实现了一个简单的网络聊天小程序,完成了服务器和客户端的基本交互功能,包括客户端响应服务器实现消息传输、文件传输以及震动等功能。注意:在本机调试时,请使用启动新实例调试功能,以便在同一台机器实现客户端和服务器两端的同时启动。
客户端代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
namespace Client
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Socket socketSend;
private void button1_Click(object sender, EventArgs e)
{
try
{
//创建负责通信的Socket
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(textServer.Text);
//创建端口号
IPEndPoint endPoint = new IPEndPoint(ip, Convert.ToInt32(textEndPoint.Text));
//获得远程连接客户端应用程序的IP地址和端口号
socketSend.Connect(endPoint);
ShowMsg("连接成功");
Thread thr = new Thread(Receive);
thr.IsBackground = true;
thr.Start();
}
catch
{ }
}
/// <summary>
/// 不停的接受服务器发来的消息
/// </summary>
void Receive()
{
while (true)
{
try
{
byte[] buffer = new byte[1024 * 1024 * 3];
//实际接收的有效字节数
int r = socketSend.Receive(buffer);
if (r == 0)
{
break;
}
//判断接收到的是消息
if (buffer[0] == 0)
{
string str = Encoding.UTF8.GetString(buffer, 1, r-1);
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + str);
}
//判断接受到的是文件
else if(buffer[0] == 1)
{
SaveFileDialog saveFD = new SaveFileDialog();
saveFD.InitialDirectory = @"C:\Users\zhuanfeng\Desktop";
saveFD.Title = "请选择要保存的文件";
saveFD.Filter = "所有文件(*.*)|*.*";
saveFD.ShowDialog(this);
using(FileStream fsWrite= new FileStream(saveFD.FileName,FileMode.OpenOrCreate,FileAccess.Write))
{
fsWrite.Write(buffer,1,r-1);
}
ShowMsg(socketSend.RemoteEndPoint.ToString() + " : 文件接受成功");
MessageBox.Show("文件保存成功");
}
else if(buffer[0] == 2)
{
ZD();
}
}
catch
{ }
}
}
void ShowMsg(string str)
{
textLog.AppendText(str + "\r\n");
}
private void btnSendMessage_Click(object sender, EventArgs e)
{
string str = textMessage.Text.Trim();
byte[] buffer = Encoding.UTF8.GetBytes(str);
socketSend.Send(buffer);
}
void ZD()
{
Point point = this.Location;
for (int i = 0; i < 1000; i++)
{
this.Location = new Point(200,200);
this.Location = point;
}
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
}
}
服务器端代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
namespace Scoket网络编程
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//当点击开始监听时,在服务器端创建一个负责监听IP地址和端口号的socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;
//创建端口号
IPEndPoint endPoint = new IPEndPoint(ip,50000);
//绑定监听端口
socketWatch.Bind(endPoint);
ShowMsg("监听成功!");
//设置监听队列(在某个时间点内)
socketWatch.Listen(10);
//创建一个新线程
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start(socketWatch);
}
Socket socketSend;
//创建一个键值对,存IP地址端口号和socket
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
/// <summary>
/// 等待客户端的连接,并创建一个负责通信的Socket
/// </summary>
void Listen(object obj)
{
Socket socketWatch = obj as Socket;
//等待客户端连接,并创建一个新的Socket负责通信
while (true)
{
//创建负责通信的Socket
socketSend = socketWatch.Accept();
//将远程连接的客户端的IP地址和端口号及其对应的socket存入集合中
dicSocket.Add(socketSend.RemoteEndPoint.ToString(),socketSend);
//将远程连接的客户端的IP地址和端口号存入到下拉列表中
cmbEndpoint.Items.Add(socketSend.RemoteEndPoint.ToString());
cmbEndpoint.SelectedIndex = 0;
//192.168.3.44:连接成功
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功");
//开启一个新线程不停地接收客户端传来的消息
Thread th = new Thread(ReceiveMessage);
th.IsBackground = true;
th.Start(socketSend);
}
}
void ReceiveMessage(object obj)
{
try
{
while (true)
{
Socket socketSend = obj as Socket;
byte[] buffer = new byte[1024 * 1024 * 2];
//实际接收的有效字节数
int r = socketSend.Receive(buffer);
if (r == 0)
{
break;
}
string str = Encoding.UTF8.GetString(buffer,0,r);
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + str);
}
}
catch
{ }
}
public void ShowMsg(string str)
{
textLog.AppendText(str + "\r\n");
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
/// <summary>
/// 服务器给客户端发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void sendMsg_Click(object sender, EventArgs e)
{
try
{
string str = textMsg.Text;
byte[] buffer = Encoding.UTF8.GetBytes(str);
//将泛型集合转化为数组
List<byte> list = new List<byte>();
//将集合首位作为标记位,用来判断向客户端发送文件类型(文本消息,文件,震动)
list.Add(0);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
string s = cmbEndpoint.SelectedItem.ToString();
dicSocket[s].Send(newBuffer);
ShowMsg(socketSend.RemoteEndPoint.ToString() + " : 文件传输成功");
//socketSend.Send(buffer);
}
catch
{ }
}
/// <summary>
/// 发送文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSendFile_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog openFD = new OpenFileDialog();
openFD.InitialDirectory = @"C:\Users\zhuanfeng\Desktop";
openFD.Title = "请选择您要传送的文件";
openFD.Filter = "所有文件(*.*)|*.*";
//openFD.ShowDialog(this);
if (openFD.ShowDialog() == DialogResult.OK)
{
string fileName = openFD.FileName;
using (FileStream fsRead = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
//实际读取的有效字节数
int r = fsRead.Read(buffer, 0, buffer.Length);
List<byte> list = new List<byte>();
list.Add(1);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
dicSocket[cmbEndpoint.SelectedItem.ToString()].Send(newBuffer, 0, r + 1, SocketFlags.None);
}
}
}
catch
{ }
}
private void btnZD_Click(object sender, EventArgs e)
{
try
{
byte[] buffer = new byte[1];
buffer[0] = 2;
dicSocket[cmbEndpoint.SelectedItem.ToString()].Send(buffer);
}
catch
{ }
}
}
}
运行结果如下: