C# Socket网络编程实例

2021-04-23 13:53

阅读:377

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;

namespace Socket通信

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

this.txtPort.Text = “5000”;

this.txtIp.Text = “192.168.137.1”;

}

private void btnStart_Click(object sender, EventArgs e)

{

//当点击开始监听的时候,在服务器端创建一个负责监听IP地址跟端口号的Socket

Socket socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

//Any:提供一个 IP 地址,指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。

IPAddress ip = IPAddress.Any;

//创建端口号对象;将txtPort.Text控件的值设为服务端的端口号

IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));

//监听

socketWatch.Bind(point);

ShowMsg(“监听成功”);

socketWatch.Listen(10);//连接队列的最大长度 ;即:一个时间点内最大能让几个客户端连接进来,超过长度就进行排队

//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket

Thread th = new Thread(Listen); //被线程执行的方法如果有参数的话,参数必须是object类型

Control.CheckForIllegalCrossThreadCalls = false; //因为.net不允许跨线程访问的,所以这里取消跨线程的检查。.net不检查是否有跨线程访问了,所有就不会报: “从不是创建控件“txtLog”的线程访问它” 这个错误了,从而实现了跨线程访问

th.IsBackground = true; //将th这个线程设为后台线程。

//Start(object parameter); parameter:一个对象,包含线程执行的方法要使用的数据,即线程执行Listen方法,Listen的参数

th.Start(socketWatch);  //这个括号里的参数其实是Listen()方法的参数。因为Thread th = new Thread(Listen)这个括号里只能写方法名(函数名) 但是Listen()方法是有参数的,所有就要用Start()方法将它的参数添加进来

}

///

/// 等待客户端连接,如果监控到有客户端连接进来就创建一个与之通信的Socket

///

///

void Listen(object o) //这里为什么不直接传递Socket类型的参数呢? 原因是:被线程执行的方法如果有参数的话,参数必须是object类型

{

Socket socketWatch = o as Socket;

while (true) //为什么这里要有个while循环?因为当一个人连接进来的时候创建了与之通信的Socket后就程序就会往下执行了,就不会再回来为第二个人的连接创建负责通信的Socket了。(应该是每个人(每个客户端)创建一个与之通信的Socket)所以要写在循环里。

{

//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket

Socket socketSend = socketWatch.Accept();

dic.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //(根据客户端的IP地址和端口号找负责通信的Socket,每个客户端对应一个负责通信的Socket),ip地址及端口号作为键,将负责通信的Socket作为值填充到dic键值对中。

//我们通过负责通信的这个socketSend对象的一个RemoteEndPoint属性,能够拿到远程连过来的客户端的Ip地址跟端口号

ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + “连接成功”);//效果:192.168.1.32:连接成功

comboBox1.Items.Add(socketSend.RemoteEndPoint.ToString()); //将连接过来的每个客户端都添加到combBox控件中。

//客户端连接成功后,服务器应该接收客户端发来的消息。

Thread getdata = new Thread(GetData);

getdata.IsBackground = true;

getdata.Start(socketSend);

}

}

Dictionary dic = new Dictionary();

///

/// 不停的接收客户端发送过来的消息

///

///

void GetData(object o)

{

while (true)

{

Socket socketSend = o as Socket;

//将客户端发过来的数据先放到一个字节数组里面去

byte[] buffer = new byte[1024 * 1024 * 2]; //创建一个字节数组,字节数组的长度为2M

//实际接收到的有效字节数; (利用Receive方法接收客户端传过来的数据,然后把数据保存到buffer字节数组中,返回一个接收到的数据的长度)

int r = socketSend.Receive(buffer);

if (r == 0) //如果接收到的有效字节数为0 说明客户端已经关闭了。这时候就跳出循环了。

{

//只有客户端给用户发消息,不可能是发0个长度的字节。即便发空消息,空消息也是有过个长度的。所有接收到的有效字节长度为0就代表客户端已经关闭了

break;

}

//将buffer这个字节数组里面的数据按照UTF8的编码,解码成我们能够读懂的的string类型,因为buffer这个数组的实际存储数据的长度是r个 ,所以从索引为0的字节开始解码,总共解码r个字节长度。

string str = Encoding.UTF8.GetString(buffer, 0, r);

ShowMsg(socketSend.RemoteEndPoint.ToString() + “:” + str);

}

}

private void ShowMsg(string str)

{

txtLog.AppendText(str + “\r\n”); //将str这个字符串添加到txtLog这个文本框中。

}

///

/// 服务端给客户端发信息

///

///

///

private void btnSend_Click_1(object sender, EventArgs e)

{

if (comboBox1.SelectedItem == null) //如果comboBox控件没有选中值。就提示用户选择客户端

{

MessageBox.Show(“请选择客户端”);

return;

}

string str = txtMes.Text; //获取用户输入的内容 (服务端要给客户端发的信息)

byte[] strByte = Encoding.Default.GetBytes(str); //将信息转换成二进制字节数组

string getIp = comboBox1.SelectedItem as string; //comboBox存储的是客户端的(ip+端口号)

Socket socketSend = dic[getIp] as Socket; //根据这个(ip及端口号)去dic键值对中找对应 赋值与客户端通信的Socket【每个客户端都有一个负责与之通信的Socket】

socketSend.Send(strByte); //将信息发送到客户端

}

}

}


评论


亲,登录后才可以留言!